diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/nodes/ComparisonBox.tsx | 98 | ||||
-rw-r--r-- | src/fields/RichTextField.ts | 23 |
2 files changed, 68 insertions, 53 deletions
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 111fabca3..672008968 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -34,6 +34,22 @@ import { FormattedTextBox } from './formattedText/FormattedTextBox'; const API_URL = 'https://api.unsplash.com/search/photos'; +/** + * This view serves two distinct functions depending on the metadata field layout_isFlashcard + * 1) it provides a before/after animated sliding transition between two Docs + * 2) it provides a question/answer switch between two Docs (flashcard) + * + * In either case, the two docs are stored in the <fieldKey>_front and <fieldKey>_back fields + * + * In the case of the flashcard, there is an icon that allows the user to choose between a + * hover and a flip action to switch between cards. The transition is stored in the 'revealOp' field. + * In addition, if a flashcard is created without data in the front/back fields, this will + * create Text documents with placeholder text indicating to the user how to fill in the cards. + * One option is to allow the user to enter a topic and, by clicking on the flashcard stack button, + * convert the comparision box into a stack of comparison boxes filled in by GPT about the topic. + * + */ + @observer export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { @@ -129,7 +145,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() )} {DocCast(this.Document.embedContainer)?.type_collection !== CollectionViewType.Freeform || this._renderSide === this.backKey ? null : ( <Tooltip title={<div className="dash-tooltip">Create new flashcard stack based on text</div>}> - <div className="comparisonBox-button" onClick={this.gptFlashcardPile}> + <div className="comparisonBox-button" onClick={() => this.askGPT(GPTCallType.STACK).then(this.createFlashcardDeck)}> <FontAwesomeIcon icon="layer-group" size="xl" /> </div> </Tooltip> @@ -404,50 +420,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() return response.data.transcription; }; - createFlashcardPile(collectionArr: Doc[], gpt: boolean) { - const newCol = Docs.Create.CarouselDocument(collectionArr, { - _width: NumCast(this.layoutDoc['_' + this._props.fieldKey + '_width'], 250) + 50, - _height: NumCast(this.layoutDoc['_' + this._props.fieldKey + '_width'], 200) + 50, - _layout_fitWidth: false, - _layout_autoHeight: true, - _xMargin: 5, - _yMargin: 5, - x: NumCast(this.layoutDoc.x), - y: NumCast(this.layoutDoc.y) + 50, - }); - - if (gpt) { - this._props.DocumentView?.()._props.addDocument?.(newCol); - this._props.removeDocument?.(this.Document); - } else { - this._props.addDocument?.(newCol); - this._props.removeDocument?.(this.Document); - Doc.SetContainer(this.Document, newCol); - } - } - - textToRtf = (text: string, img?: Doc) => - new RichTextField( - JSON.stringify({ - // this is a RichText json that has the question text placed above a related image - doc: { - type: 'doc', - content: [ - { - type: 'paragraph', - attrs: { align: 'center', color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, - content: [ - ...(text ? [{ type: 'text', text }] : []), // - ...(img ? [{ type: 'dashDoc', attrs: { width: '200px', height: '200px', title: 'dashDoc', float: 'unset', hidden: false, docId: img[Id] } }] : []), - ], - }, - ], - }, - selection: { type: 'text', anchor: 2, head: 2 }, - }), - text - ); - + /** + * Creates a flashcard (or fills in flashcard data to a specified Doc) from a control string containing a question and answer + * @param tuple string containing Question:, Answer: and optionally a Keyword: + * @param useDoc doc to fill in instead of creating a Doc + * @returns the resulting flashcard Doc + */ createFlashcard = (tuple: string, useDoc?: Doc) => { const [ktoken, atoken] = [ComparisonBox.ktoken, ComparisonBox.atoken]; const newDoc = useDoc ?? Docs.Create.ComparisonDocument('', { _layout_isFlashcard: true, _width: 300, _height: 300 }); @@ -465,22 +443,36 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() newDoc[DocData][this.backKey] = this.textCreator('answer', answer); return newDoc; }; - return keyword && keyword !== 'none' ? this.fetchImages(keyword).then(img => fillInFlashcard(img)) : fillInFlashcard(); + return keyword && keyword.toLowerCase() !== 'none' ? this.fetchImages(keyword).then(img => fillInFlashcard(img)) : fillInFlashcard(); }; + /** + * Create a carousel of flashcards from a GPT response string where questions and answers are given in a format loosely defined by: + * Question: ... Answer: ... Keyword: ... + * Note that Keyword or Answer may not be present, or their orders may be reversed. + */ createFlashcardDeck = (text: string) => { Promise.all( text .split(ComparisonBox.qtoken) .filter(t => t) .map(tuple => this.createFlashcard(tuple)) - ).then(docs => this.createFlashcardPile(docs, true)); - }; + ).then(docs => { + const newCol = Docs.Create.CarouselDocument(docs, { + _width: NumCast(this.layoutDoc._width, 250) + 50, + _height: NumCast(this.layoutDoc._height, 200) + 50, + _layout_fitWidth: false, + _layout_autoHeight: true, + _xMargin: 5, + _yMargin: 5, + x: NumCast(this.layoutDoc.x), + y: NumCast(this.layoutDoc.y), + }); - /** - * queries GPT about a topic and then creates a flashcard deck from the results. - */ - gptFlashcardPile = () => this.askGPT(GPTCallType.STACK).then(this.createFlashcardDeck); + this._props.DocumentView?.()._props.addDocument?.(newCol); + this._props.removeDocument?.(this.Document); + }); + }; /** * Calls GPT for each flashcard type. @@ -658,7 +650,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() return layoutTemplateString; }; textCreator = (title: string, text: string, img?: Doc) => { - const newDoc = Docs.Create.TextDocument(this.textToRtf(text, img), { + const newDoc = Docs.Create.TextDocument(RichTextField.textToRtf(text, img?.[Id]), { title, // _layout_autoHeight: true, _layout_centered: true, diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index 613bb0fd1..dc636031a 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -48,4 +48,27 @@ export class RichTextField extends ObjectField { '' ); } + + public static textToRtf(text: string, imgDocId?: string) { + return new RichTextField( + JSON.stringify({ + // this is a RichText json that has the question text placed above a related image + doc: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { align: 'center', color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, + content: [ + ...(text ? [{ type: 'text', text }] : []), // + ...(imgDocId ? [{ type: 'dashDoc', attrs: { width: '200px', height: '200px', title: 'dashDoc', float: 'unset', hidden: false, docId: imgDocId } }] : []), + ], + }, + ], + }, + selection: { type: 'text', anchor: 2, head: 2 }, + }), + text + ); + } } |