import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { MathJax, MathJaxContext } from 'better-react-mathjax'; import { Tooltip } from '@mui/material'; import { action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnFalse, returnNone, returnTrue, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { RichTextField } from '../../../fields/RichTextField'; import { DocCast, NumCast, RTFCast, StrCast, toList } from '../../../fields/Types'; import { GPTCallType, gptAPICall, gptImageLabel } from '../../apis/gpt/GPT'; import '../pdf/GPTPopup/GPTPopup.scss'; import { DocUtils } from '../../documents/DocUtils'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { undoable } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import ReactLoading from 'react-loading'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { tickStep } from 'd3'; import { CollectionCarouselView } from '../collections/CollectionCarouselView'; import { FollowLinkScript } from '../../documents/DocUtils'; import { schema } from '../nodes/formattedText/schema_rts'; import { Id } from '../../../fields/FieldSymbols'; import axios from 'axios'; import ReactMarkdown from 'react-markdown'; import { WebField, nullAudio } from '../../../fields/URLField'; const API_URL = 'https://api.unsplash.com/search/photos'; @observer export class ComparisonBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); } private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined]; constructor(props: FieldViewProps) { super(props); makeObservable(this); this.setListening(); } @observable private _inputValue = ''; @observable private _outputValue = ''; @observable private _loading = false; @observable private _isEmpty = false; @observable private _audio: Doc = Docs.Create.TextDocument(''); @observable childActive = false; @observable _yRelativeToTop: boolean = true; @observable _animating = ''; @observable mathJaxConfig = { loader: { load: ['input/asciimath'] }, }; private _ref = React.createRef(); get revealOp() { return this.layoutDoc[`_${this._props.fieldKey}_revealOp`]; } get clipHeightKey() { return '_' + this._props.fieldKey + '_clipHeight'; } get clipWidthKey() { return '_' + this._props.fieldKey + '_clipWidth'; } @computed get clipWidth() { return NumCast(this.layoutDoc[this.clipWidthKey], 50); } @computed get clipHeight() { return NumCast(this.layoutDoc[this.clipHeightKey], 200); } @computed get overlayAlternateIcon() { const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; return ( flip}>
setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => { if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] === 'flip') { this.flipFlashcard(); // console.log('Print Front of cards: ' + (RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text ?? '')); // console.log('Print Back of cards: ' + (RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text ?? '')); } }) } style={{ background: this.revealOp === 'hover' ? 'gray' : usepath === 'alternate' ? 'white' : 'black', color: this.revealOp === 'hover' ? 'black' : usepath === 'alternate' ? 'black' : 'white', display: 'inline-block', }}>
); } @computed get flashcardMenu() { return (
Flip to front side to use GPT
) : (
Ask GPT to create an answer on the back side of the flashcard based on your question on the front
) }>
(!this.layoutDoc[`_${this._props.fieldKey}_usePath`] ? this.findImageTags() : null)}>
{DocCast(this.Document.embedContainer).type_collection === 'carousel' ? null : (
Create a flashcard pile
}>
this.createFlashcardPile([this.Document], false)}>
Create new flashcard stack based on text}>
this.gptFlashcardPile()}>
)} Hover to reveal}>
this.handleHover()}>
{/* Remove this side of the flashcard}>
this.closeDown(e, this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? this._props.fieldKey + '_1' : this._props.fieldKey + '_0')}>
*/} {/* {this.overlayAlternateIcon} */} ); } componentDidMount() { this._props.setContentViewBox?.(this); reaction( () => this._props.isSelected(), // when this reaction should update selected => !selected && (this.childActive = false) // what it should update to ); } protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => { this._disposers[disposerId]?.(); // this.childActive = true; if (ele) { this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.internalDrop(e, dropEvent, fieldKey), this.layoutDoc); } }; private internalDrop = undoable((e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => { if (dropEvent.complete.docDragData) { const { droppedDocuments } = dropEvent.complete.docDragData; const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocuments, this.Document, (doc: Doc | Doc[]) => this.addDoc(toList(doc).lastElement(), fieldKey)); Doc.SetContainer(droppedDocuments.lastElement(), this.dataDoc); !added && e.preventDefault(); e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place // this.childActive = false; return added; } return undefined; }, 'internal drop'); private registerSliding = (e: React.PointerEvent, targetWidth: number) => { if (e.button !== 2) { setupMoveUpEvents( this, e, this.onPointerMove, emptyFunction, action((moveEv, doubleTap) => { if (doubleTap) { this.childActive = true; if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); if (!this.dataDoc[this.fieldKey + '_2'] && !this.dataDoc[this.fieldKey + '_alternate']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); } }), false, undefined, action(() => { if (this.childActive) return; this._animating = 'all 200ms'; // on click, animate slider movement to the targetWidth this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth(); // this.layoutDoc[this.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight(); setTimeout( action(() => { this._animating = ''; }), 200 ); }) ); } }; // private onClick(e: React.PointerEvent) { // setupMoveUpEvents( // this, e, this.onPointerMOve, emptyFunction(), () => {this._isAnyChildContentActive = true;}, emptyFunction(), emptyFunction() // ) // } @action private onPointerMove = ({ movementX }: PointerEvent) => { const width = movementX * this.ScreenToLocalBoxXf().Scale + (this.clipWidth / 100) * this._props.PanelWidth(); if (width && width > 5 && width < this._props.PanelWidth()) { this.layoutDoc[this.clipWidthKey] = (width * 100) / this._props.PanelWidth(); } return false; }; @action handleInputChange = (e: React.ChangeEvent) => { this._inputValue = e.target.value; console.log(this._inputValue); }; // this.closeDown(e, this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? this.fieldKey + '_0' : this.fieldKey + '_1')} @action activateContent = () => { this.childActive = true; }; @action handleRenderGPTClick = async () => { // Call the GPT model and get the output // await this.pushInfo(); // console.log('PHONETIC TRANSCRIPTION: ' + DocCast(this._audio)[DocData]); // this.Document.audio = this._audio; console.log('Phonetic transcription: ' + DocCast(this.Document.audio).phoneticTranscription); const phonTrans = DocCast(this.Document.audio).phoneticTranscription; if (phonTrans) { this._inputValue = StrCast(phonTrans); console.log('INPUT:' + this._inputValue); this.askGPTPhonemes(this._inputValue); } else if (this._inputValue) this.askGPT(GPTCallType.QUIZ); this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate'; this._outputValue = ''; }; askGPTPhonemes = async (phonemes: string) => { const phon = 'w ʌ t ɪ z j ɔː ɹ n e ɪ m '; const phon2 = 'h aʊ ɛ r j u t ʌ d eɪ'; const question = 'Consider all possible phonetic transcriptions of "she sells seashells by the sea shore" that is standard in speech without showing the user. Align the phonemes with each word based on what is similar; if it seems like a part of the word is missing, check the phonemes beforehand and after to see if they should be part of that word and reevaluate to see if those phonemes should be a part of that word. Note if a word or sound missing, including missing vowels and consonants. Compare the phonemes in each word with those phonetic transcriptions without displaying anything to the user: "' + phonemes + '". If there is an additional word that does not match with the provided sentence, say so. For each word, if any letters mismatch and would sound weird in American speech and they are not allophones of the same phoneme and they are far away from each on the ipa vowel chat and that pronunciation is not normal for the meaning of the word, note this difference. If there is a sound missing, note that. If nothing is wrong, say "good job" for the word. Just so you know, "i" sounds like "ee" as in "bee", not "ih" as an "lick". Interpret "ɹ" as the same as "r". Interpret "ʌ" as the same as "ə". If "ɚ", "ɔː", and "ɔ" are options for pronunciation, do not choose "ɚ".'; const question5 = 'Consider all possible phonetic transcriptions of "how are you today" that is standard in speech without showing the user. Compare these phonemes with those phonetic transcriptions without displaying anything to the user: "' + phonemes + '". If there is an additional word that does not match with the provided sentence, say so. For each word, if any letters mismatch and would sound weird in American speech and they are not allophones of the same phoneme and they are far away from each on the ipa vowel chat, note this difference. If not, say "good job" for the word. Just so you know, "i" sounds like "ee" as in "bee", not "ih" as an "lick". Interpret "ɹ" as the same as "r".'; const question4 = 'Match the following phonemes as words with each word in "what is your name" without displaying this to the user: "w ɛ t ɪ z j i ɹ n eɪ m ". If everything is correct, return only "good job" with no other notes. Note if a letter is added or missing if that letter changes the meaning. If a letter does not match the real phonetic transcription of the phrase, note this only if the letters are not allophones of the same phoneme and if they are far away from each other on the vowel chart.'; const question0 = 'These phonemes should match "what is your name": ' + phon + 'Use the structure of this response as guidance: "Your pronunciation of the vowel in "what" is not front enough. It should be pronounced like /uh/."'; const question3 = 'Match the following phonemes as words with each word in "what is your name" without displaying this to the user and there will be spaces between diphthongs and colons so treat it like they are together: ' + phon + '. If everything is correct, return only "good job" with no other notes. Note if a letter is added or missing if that letter changes the meaning. If mismatching sounds are not allophones of the same phoneme and they are far away from each other on the vowel chart, describe the difference. For the mismatches, use the structure of this response as guidance: "Your pronunciation of the vowel in "what" is not pronounced correctly. It should be pronounced like /uh/." Do not list anything that is correct.'; const question1 = 'Consider all phonetic transcriptions of "what is your name" with different vowel pronunications. Compares these phonemes with that phonetic transcription: ' + phonemes + '. If the differences are not allophones of the same phoneme and they are far away from each other on the vowel chart, list the difference. If it is missing or added a letter, say that.'; //Only describe sound changes that will change the meaning drastically. Provide two sentences describing this. Do not list differences that do not change the meaning.'; const question2 = 'Is this a valid phonetic transcription of the phrase "what is your name": ' + phonemes + '.'; // If the difference found will definitely make the word be not understood and change the meaning, then list it. If the difference is minimal or the sound matches, do not list it.'; //These phonemes are supposed to match the pronunciation of ' + //'hello: ' + //phonemes + //'. If there is a difference in sound that would change the meaning of the word or sentence, such as "pen" vs. "pin", describe that. Otherwise say "good job."'; // Identify any differences in pronunciation that would change the meaning of the intended word or sentence and only list differences that would change the meaning. If there are no major differences, say "Good job." If there are differences, describe it in terms of sounds in sentences.'; // const question = // 'These phonemes are supposed to match the pronunciation of ' + // StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text) + // '. Identify any differences in pronunciation that would change the meaning of the intended word or sentence.'; console.log(question); const res = await gptAPICall(question, GPTCallType.PRONUNCIATION); console.log('GPT: ' + res); if (!res) { console.error('GPT call failed'); return; } // const questionText = 'Question: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text); // const rubricText = ' Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); // const queryText = questionText + ' UserAnswer: ' + this._inputValue + '. ' + rubricText; // this._loading = true; }; pushInfo = async () => { const formData = new FormData(); console.log(DocCast(this._audio).dataDoc); const audio = { file: this._audio.url, }; const response = await axios.post('http://localhost:105/recognize/', audio, { headers: { 'Content-Type': 'application/json', }, }); this.Document.phoneticTranscription = response.data['transcription']; console.log('RESPONSE: ' + response.data['transcription']); }; @action handleHover = () => { if (this.revealOp === 'hover') { this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip'; this.Document.forceActive = false; } else { this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover'; this.Document.forceActive = true; } //this.revealOp === 'hover' ? (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip') : (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover'); }; @action handleRenderClick = () => { // Call the GPT model and get the output this.layoutDoc[`_${this._props.fieldKey}_usePath`] = undefined; }; getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ title: 'CompareAnchor:' + this.Document.title, // set presentation timing properties for restoring view presentation_transition: 1000, annotationOn: this.Document, }); if (anchor) { if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; /* addAsAnnotation && */ this.addDocument(anchor); PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document); return anchor; } return this.Document; }; clearDoc = undoable((fieldKey: string) => { // delete this.dataDoc[fieldKey]; this.dataDoc[fieldKey] = undefined; this._isEmpty = true; // this.dataDoc[fieldKey] = 'empty'; console.log('HERE' + fieldKey + ';'); }, 'clear doc'); // clearDoc = (fieldKey: string) => delete this.dataDoc[fieldKey]; moveDoc = (doc: Doc, addDocument: (document: Doc | Doc[]) => boolean, which: string) => this.remDoc(doc, which) && addDocument(doc); addDoc = (doc: Doc, which: string) => { if (this.dataDoc[which] && !this._isEmpty) return false; this.dataDoc[which] = doc; return true; }; remDoc = (doc: Doc, which: string) => { if (this.dataDoc[which] === doc) { this._isEmpty = true; // this.dataDoc[which] = 'empty'; console.log('HEREEEE'); this.dataDoc[which] = undefined; return true; } console.log('FALSE'); return false; }; closeDown = (e: React.PointerEvent, which: string) => { setupMoveUpEvents( this, e, moveEv => { const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move); de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => addDocument(doc); de.canEmbed = true; DragManager.StartDocumentDrag([this._closeRef.current!], de, moveEv.clientX, moveEv.clientY); return true; }, emptyFunction, () => this.clearDoc(which) ); }; docStyleProvider = (doc: Opt, props: Opt, property: string): any => { if (property === StyleProp.PointerEvents) return 'none'; return this._props.styleProvider?.(doc, props, property); }; moveDoc1 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => toList(docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true); moveDoc2 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => toList(docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true); remDoc1 = (docs: Doc | Doc[]) => toList(docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true); remDoc2 = (docs: Doc | Doc[]) => toList(docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true); /** * Tests for whether a comparison box slot (ie, before or after) has renderable text content. * If it does, render a FormattedTextBox for that slot that references the comparisonBox's slot field * @param whichSlot field key for start or end slot * @returns a JSX layout string if a text field is found, othwerise undefined */ testForTextFields = (whichSlot: string) => { const slotData = Doc.Get(this.dataDoc, whichSlot, true); const slotHasText = slotData instanceof RichTextField || typeof slotData === 'string'; const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim(); const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim(); const layoutTemplateString = slotHasText ? FormattedTextBox.LayoutString(whichSlot): whichSlot.endsWith('1') ? (subjectText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey) : undefined) : altText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey + '_alternate'): undefined; // prettier-ignore // A bit hacky to try out the concept of using GPT to fill in flashcards // If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string) // and the fieldKey + "_alternate" has text that includes a GPT query (indicated by (( && )) ) that is parameterized (optionally) by the fieldKey text (this) or other metadata (this.). // eg., this.text_alternate is // "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))" // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field // The GPT call will put the "answer" in the second slot of the comparison (eg., text_2) if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) { const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in Doc.setField but it doesn't know about the fieldKey ... if (queryText?.match(/\(\(.*\)\)/)) { Doc.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt } } return layoutTemplateString; }; _closeRef = React.createRef(); 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, }); newCol['x'] = this.layoutDoc['x']; newCol['y'] = NumCast(this.layoutDoc['y']) + 50; newCol.type_collection = 'carousel'; // console.log(newCol.data); if (gpt) { this._props.DocumentView?.()._props.addDocument?.(newCol); this._props.removeDocument?.(this.Document); } else { this._props.addDocument?.(newCol); this._props.removeDocument?.(this.Document); this.Document.embedContainer = newCol; } } gptFlashcardPile = async () => { var text = await this.askGPT(GPTCallType.STACK); console.log(text); var senArr = text?.split('Question: '); var collectionArr: Doc[] = []; for (let i = 1; i < senArr?.length!; i++) { const newDoc = Docs.Create.ComparisonDocument(senArr![i], { _layout_isFlashcard: true, _width: 300, _height: 300 }); if (StrCast(senArr![i]).includes('Keyword: ')) { const question = StrCast(senArr![i]).split('Keyword: '); const img = await this.fetchImages(question[1]); // newDoc['image'] = img; // const newDoc = Docs.Create.TextDocument(dataSplit[1]); const textSide1 = question[0].includes('Answer: ') ? question[0].split('Answer: ')[0] : question[0]; const textDoc1 = Docs.Create.TextDocument(question[0]); const rtfiel = new RichTextField( JSON.stringify({ doc: { type: 'doc', content: [ { type: 'paragraph', attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, content: [ { type: 'text', text: question[0].includes('Answer: ') ? question[0].split('Answer: ')[0] : question[0] }, { type: 'dashDoc', attrs: { width: '200px', height: '200px', title: 'dashDoc', float: 'unset', hidden: false, docId: img![Id] } }, ], }, ], }, selection: { type: 'text', anchor: 2, head: 2 }, }), textSide1 ); textDoc1[DocData].text = rtfiel; DocCast(newDoc)[DocData][this.fieldKey + '_1'] = textDoc1; DocCast(newDoc)[DocData][this.fieldKey + '_0'] = Docs.Create.TextDocument(question[0].includes('Answer: ') ? question[0].split('Answer: ')[1] : question[1]); // Doc.AddToMyOverlay(img!); } collectionArr.push(newDoc); } this.createFlashcardPile(collectionArr, true); }; /** * Flips a flashcard to the alternate side for the user to view. */ flipFlashcard = () => { const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined; }; /** * Changes the view option to hover for a flashcard. */ hoverFlip = (side: string | undefined) => { if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] === 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side; }; animateRes = (resIndex: number, newText: string, callType: GPTCallType) => { if (resIndex < newText.length) { // const marks = this._editorView?.state.storedMarks ?? []; switch (callType) { case GPTCallType.CHATCARD: DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text += newText[resIndex]; break; case GPTCallType.QUIZ: this._outputValue += newText[resIndex]; break; default: return; } // this._editorView?.dispatch(this._editorView?.state.tr.insertText(newText[resIndex]).setStoredMarks(this._outputValue)); setTimeout(() => this.animateRes(resIndex + 1, newText, callType), 20); } }; /** * Calls the GPT model to create QuizCards. Evaluates how similar the user's response is to the alternate * side of the flashcard. */ askGPT = async (callType: GPTCallType): Promise => { const questionText = 'Question: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text); const rubricText = ' Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); const queryText = questionText + ' UserAnswer: ' + this._inputValue + '. ' + rubricText; this._loading = true; const doc = DocCast(this.dataDoc[this.props.fieldKey + '_0']); if (callType == GPTCallType.CHATCARD) { if (StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text) === '') { this._loading = false; return; } this.flipFlashcard(); } try { const res = await gptAPICall(callType == GPTCallType.QUIZ ? queryText : questionText, callType); if (!res) { console.error('GPT call failed'); return; } // this.animateRes(0, res, callType); if (callType == GPTCallType.CHATCARD) { DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = res; // this.flipFlashcard(); } else if (callType == GPTCallType.QUIZ) { console.log(this._inputValue); this._outputValue = res.replace(/UserAnswer/g, "user's answer").replace(/Rubric/g, 'rubric'); } // DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = res; // this._outputValue = res; else if (callType === GPTCallType.FLASHCARD) { // console.log(res); this._loading = false; return res; } else if (callType === GPTCallType.STACK) { } this._loading = false; return res; // console.log(res); } catch (err) { console.error('GPT call failed'); } this._loading = false; }; layoutWidth = () => NumCast(this.layoutDoc.width, 200); layoutHeight = () => NumCast(this.layoutDoc.height, 200); // specificMenu = (): void => { // const cm = ContextMenu.Instance; // cm.addItem({ description: 'Create an Answer on the Back', event: () => this.askGPT(GPTCallType.CHATCARD), icon: 'pencil' }); // }; findImageTags = async () => { // const d = DocCast(this.dataDoc[this.props.fieldKey + '_0']); // const copy = Doc.MakeCopy(this.Document, true); const c = this.DocumentView?.().ContentDiv!.getElementsByTagName('img'); // this.ProseRef?.getElementsByTagName('img'); if (c?.length === 0) await this.askGPT(GPTCallType.CHATCARD); if (c) { this._loading = true; for (let i of c) { console.log(i); if (i.className !== 'ProseMirror-separator') await this.getImageDesc(i.src); } this._loading = false; // this.flipFlashcard(); } // console.log('HI' + this.ProseRef?.getElementsByTagName('img')); }; static imageUrlToBase64 = async (imageUrl: string): Promise => { try { const response = await fetch(imageUrl); const blob = await response.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => resolve(reader.result as string); reader.onerror = error => reject(error); }); } catch (error) { console.error('Error:', error); throw error; } }; getImageDesc = async (u: string) => { try { const hrefBase64 = await ComparisonBox.imageUrlToBase64(u); const response = await gptImageLabel(hrefBase64, 'Answer the following question as a short flashcard response. Do not include a label.' + (this.dataDoc.text as RichTextField)?.Text); DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = response; } catch (error) { console.log('Error'); } }; fetchImages = async (selection: string) => { try { const { data } = await axios.get(`${API_URL}?query=${selection}&page=1&per_page=${1}&client_id=Q4zruu6k6lum2kExiGhLNBJIgXDxD6NNj0SRHH_XXU0`); console.log(data.results); const imageSnapshot = Docs.Create.ImageDocument(data.results[0].urls.small, { _nativeWidth: Doc.NativeWidth(this.layoutDoc), _nativeHeight: Doc.NativeHeight(this.layoutDoc), x: NumCast(this.layoutDoc.x), y: NumCast(this.layoutDoc.y), onClick: FollowLinkScript(), _width: 150, _height: 150, title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-', }); imageSnapshot['x'] = this.layoutDoc['x']; imageSnapshot['y'] = this.layoutDoc['y']; return imageSnapshot; } catch (error) { console.log(error); } }; // handleSelection = async (selection: string, newDoc: Doc) => { // const images = await this.fetchImages(selection); // return images; // // Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true); // images!.embedContainer = newDoc; // Doc.AddEmbedding(newDoc, images!); // const c = this.DocumentView?.().ContentDiv!.getElementsByClassName('afterBox-cont'); // for (let i in c) { // console.log('HERE' + i); // } // this.addDoc(images!, this.fieldKey + '_0'); // Doc.AddEmbedding(newDoc, images!); // this._props. // Doc.AddToMyOverlay(images!); // const node = schema.nodes.dashDoc.create({ // width: NumCast(images?._width), // height: NumCast(images?._height), // title: 'dashDoc', // docId: images![Id], // float: 'unset', // }); // }; @observable private _listening = false; @observable transcriptElement = ''; SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; @observable recognition = new this.SpeechRecognition(); handleResult = (e: SpeechRecognitionEvent) => { let interimTranscript = ''; let finalTranscript = ''; for (let i = e.resultIndex; i < e.results.length; i++) { const transcript = e.results[i][0].transcript; if (e.results[i].isFinal) { finalTranscript += transcript; } else { interimTranscript += transcript; } } console.log(interimTranscript); this._inputValue += finalTranscript; }; setListening = () => { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (SpeechRecognition) { this.recognition.continuous = true; this.recognition.interimResults = true; this.recognition.lang = 'en-US'; this.recognition.onresult = this.handleResult.bind(this); } ContextMenu.Instance.setLangIndex(0); }; setLanguage = (e: React.MouseEvent, language: string, ind: number) => { this.recognition.lang = language; ContextMenu.Instance.setLangIndex(ind); }; startListening = () => { this.recognition.start(); this._listening = true; }; stopListening = () => { this.recognition.stop(); this._listening = false; }; openContextMenu = (x: number, y: number) => { ContextMenu.Instance.clearItems(); ContextMenu.Instance.addItem({ description: 'English', event: e => this.setLanguage(e, 'en-US', 0) }); //prettier-ignore ContextMenu.Instance.addItem({ description: 'Spanish', event: e => this.setLanguage(e, 'es-ES', 1 )}); //prettier-ignore ContextMenu.Instance.addItem({ description: 'French', event: e => this.setLanguage(e, 'fr-FR', 2) }); //prettier-ignore ContextMenu.Instance.addItem({ description: 'Italian', event: e => this.setLanguage(e, 'it-IT', 3) }); //prettier-ignore ContextMenu.Instance.addItem({ description: 'Mandarin Chinese', event: e => this.setLanguage(e, 'zh-CH', 4) }); //prettier-ignore ContextMenu.Instance.addItem({ description: 'Japanese', event: e => this.setLanguage(e, 'ja', 5) }); //prettier-ignore ContextMenu.Instance.addItem({ description: 'Korean', event: e => this.setLanguage(e, 'ko', 6) }); //prettier-ignore ContextMenu.Instance.displayMenu(x, y); }; evaluatePronunciation = () => { const newAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100 }); this.Document.audio = newAudio[DocData]; // DocCast(this.Document.embedContainer)()._props.addDocument?.(newAudio); this._props.DocumentView?.()._props.addDocument?.(newAudio); // Doc.AddToMyOverlay(newAudio); }; render() { const clearButton = (which: string) => ( remove}>
this.closeDown(e, which)} // prevent triggering slider movement in registerSliding >
); const displayDoc = (whichSlot: string) => { const whichDoc = DocCast(this.dataDoc[whichSlot]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); const layoutString = targetDoc ? '' : this.testForTextFields(whichSlot); // whichDoc['backgroundColor'] = this.layoutDoc['backgroundColor']; return targetDoc || layoutString ? ( // // <> this.childActive} isDocumentActive={returnFalse} dontSelect={returnTrue} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} styleProvider={this.childActive ? this._props.styleProvider : this.docStyleProvider} hideLinkButton pointerEvents={this.childActive ? undefined : returnNone} /> {/* */} {/*
{layoutString ? null : clearButton(whichSlot)}
*/} {/*
// placeholder image if doc is missingleft: `${NumCast(this.layoutDoc.width, 200) - 33}px` */} ) : (
); }; const displayBox = (which: string, index: number, cover: number) => (
{ this.registerSliding(e, cover); this.activateContent(); }} ref={ele => this.createDropTarget(ele, which, index)}> {!this._isEmpty ? displayDoc(which) : null} {/* {this.dataDoc[this.fieldKey + '_0'] !== 'empty' ? displayDoc(which) : null} */}
); if (this.Document._layout_isFlashcard) { const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0; // add text box to each side when comparison box is first created // (!this.dataDoc[this.fieldKey + '_0'] && this.dataDoc[this._props.fieldKey + '_0'] !== 'empty') if (!this.dataDoc[this.fieldKey + '_0'] && !this._isEmpty) { const dataSplit = StrCast(this.dataDoc.data).includes('Keyword: ') ? StrCast(this.dataDoc.data).split('Keyword: ') : StrCast(this.dataDoc.data).split('Answer: '); const newDoc = Docs.Create.TextDocument(dataSplit[1]); this.addDoc(newDoc, this.fieldKey + '_0'); } if (!this.dataDoc[this.fieldKey + '_1'] && !this._isEmpty) { const dataSplit = StrCast(this.dataDoc.data).includes('Keyword: ') ? StrCast(this.dataDoc.data).split('Keyword: ') : StrCast(this.dataDoc.data).split('Answer: '); const newDoc = Docs.Create.TextDocument(dataSplit[0]); this.addDoc(newDoc, this.fieldKey + '_1'); // if (this.Document.image) { // console.log('ID: ' + DocCast(this.Document.image)[Id]); // const rtfiel = new RichTextField( // JSON.stringify({ // doc: { // type: 'doc', // content: [ // { // type: 'paragraph', // attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, // content: [ // { type: 'text', text: dataSplit[0] }, // { type: 'dashDoc', attrs: { width: '200px', height: '200px', title: 'dashDoc', float: 'unset', hidden: false, docId: DocCast(this.Document.image)[Id] } }, // ], // }, // ], // }, // selection: { type: 'text', anchor: 2, head: 2 }, // }) // ); // newDoc[DocData].text = rtfiel; // } } // render the QuizCards // console.log('GERE' + DocCast(this.Document.embedContainer).filterOp); if (DocCast(this.Document.embedContainer) && DocCast(this.Document.embedContainer).practiceMode === 'quiz') { const text = StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text); return (

{text}

Return to all flashcards and add text to both sides.

{this._loading ? (
) : null}
this.openContextMenu(e.clientX, e.clientY)} style={{ position: 'absolute', top: '5px', left: '11px', zIndex: '100', width: '5px', height: '5px', cursor: 'pointer' }}>
{this.layoutDoc[`_${this._props.fieldKey}_usePath`] !== 'alternate' ? ( ) : ( )}
); } //console.log('HEREEE2' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)); // render a normal flashcard when not a QuizCard return (
{ this.hoverFlip('alternate'); }} onMouseLeave={() => { this.hoverFlip(undefined); }} // onPointerUp={() => (this._isAnyChildContentActive = true)} > {/* {!this.layoutDoc[`_${this._props.fieldKey}_usePath`] && StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text) === '' && !this.childActive ?

Enter text in the flashcard.

: null} */} {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)} {this._loading ? (
) : null} {this._props.isContentActive() ? this.flashcardMenu : null} {this.overlayAlternateIcon}
); } // render a comparison box that compares items side by side return (
{displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
{displayBox(`${this.fieldKey}_1`, 0, 0)}
(this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined, }} onPointerDown={e => !this.childActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ >
); } } Docs.Prototypes.TemplateMap.set(DocumentType.COMPARISON, { data: '', layout: { view: ComparisonBox, dataField: 'data' }, options: { acl: '', backgroundColor: 'gray', dropAction: dropActionType.move, waitForDoubleClickToClick: 'always', _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit', }, });