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 question0 = 'These phonemes should match "what is your name": ' + phonemes + '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 question = 'Match the following phonemes with each word in "what is your name": ' + phonemes + '. Note if a letter is added or missing that changes the meaning. If the mismatches are not allophones of the same phoneme and they are far away from each other on the vowel chart, list the difference. For the mismatches, 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 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', }, });