diff options
Diffstat (limited to 'src/client/views')
| -rw-r--r-- | src/client/views/collections/CollectionCarouselView.tsx | 79 | ||||
| -rw-r--r-- | src/client/views/collections/CollectionView.tsx | 15 | ||||
| -rw-r--r-- | src/client/views/nodes/ComparisonBox.scss | 143 | ||||
| -rw-r--r-- | src/client/views/nodes/ComparisonBox.tsx | 109 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 24 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 5 | ||||
| -rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 27 | ||||
| -rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.tsx | 45 | ||||
| -rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 4 |
9 files changed, 340 insertions, 111 deletions
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 7f5176123..d45b0822b 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -33,29 +33,53 @@ export class CollectionCarouselView extends CollectionSubView() { } }; + /** + * Goes to the next flashcard in the stack and filters + * based on the the currently selected option. + */ advance = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; var startInd = this.layoutDoc._carousel_index; // if the star filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') { - // go to a new index that is starred, skip the ones that aren't + if (this.layoutDoc[`filterOp`] == 'star') { + // go to a flashcard that is starred, skip the ones that aren't while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd + 1) % this.childLayoutPairs.length; } this.layoutDoc._carousel_index = startInd; + // if there aren't any starred, show all cards + if (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) { + this.layoutDoc[`filterOp`] = 'all'; + } } // if the practice filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') { + if (this.layoutDoc[`filterOp`] == 'practice') { // go to a new index that is missed, skip the ones that are correct while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd + 1) % this.childLayoutPairs.length; } this.layoutDoc._carousel_index = startInd; + + // if the user has gone through all of the cards and gotten them all correct, show all cards and exit practice mode + if (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct') { + this.layoutDoc[`filterOp`] = 'all'; + + // set all the cards to missed + for (var i = 0; i < this.childLayoutPairs.length; i++) { + const curDoc = this.childLayoutPairs?.[NumCast(i)]; + curDoc.layout[`${this.fieldKey}_missed`] = undefined; + } + } } }; + + /** + * Goes to the previous flashcard in the stack and filters + * based on the the currently selected option. + */ goback = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; @@ -63,38 +87,58 @@ export class CollectionCarouselView extends CollectionSubView() { var startInd = this.layoutDoc._carousel_index; // if the star filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') { + if (this.layoutDoc[`filterOp`] == 'star') { // go to a new index that is starred, skip the ones that aren't while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; } this.layoutDoc._carousel_index = startInd; + // if there aren't any starred, show all cards + if (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) { + this.layoutDoc[`filterOp`] = 'all'; + } } // if the practice filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') { + if (this.layoutDoc[`filterOp`] == 'practice') { // go to a new index that is missed, skip the ones that are correct while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; } + this.layoutDoc._carousel_index = startInd; + + // See all flashcards when finish going through practice mode and set all of the flashcards back to + if (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct') { + this.layoutDoc[`filterOp`] = 'all'; + + for (var i = 0; i < this.childLayoutPairs.length; i++) { + const curDoc = this.childLayoutPairs?.[NumCast(i)]; + curDoc.layout[`${this.fieldKey}_missed`] = undefined; + } + } } }; + /* + * Stars the document when the star button is pressed. + */ star = (e: React.MouseEvent) => { e.stopPropagation(); - // stars the document when the button is pressed const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]; + if (!curDoc) return; if (curDoc.layout[`${this.fieldKey}_star`] == undefined) curDoc.layout[`${this.fieldKey}_star`] = true; else curDoc.layout[`${this.fieldKey}_star`] = !curDoc.layout[`${this.fieldKey}_star`]; }; + /* + * Sets a flashcard to either missed or correct depending on if they got the question right in practice mode. + */ missed = (e: React.MouseEvent, val: string) => { e.stopPropagation(); const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]; curDoc.layout[`${this.fieldKey}_missed`] = val; - this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; - this.advance; + this.advance(e); }; captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => { @@ -154,6 +198,7 @@ export class CollectionCarouselView extends CollectionSubView() { ); } @computed get buttons() { + if (!this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]) return; return ( <> <div key="back" className="carouselView-back" onClick={this.goback}> @@ -165,10 +210,10 @@ export class CollectionCarouselView extends CollectionSubView() { <div key="star" className="carouselView-star" onClick={this.star}> <FontAwesomeIcon icon={'star'} color={this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_star`] ? 'yellow' : 'gray'} size={'1x'} /> </div> - <div key="remove" className="carouselView-remove" onClick={e => this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}> + <div key="remove" className="carouselView-remove" onClick={e => this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}> <FontAwesomeIcon icon={'xmark'} color={'red'} size={'1x'} /> </div> - <div key="check" className="carouselView-check" onClick={e => this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}> + <div key="check" className="carouselView-check" onClick={e => this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}> <FontAwesomeIcon icon={'check'} color={'green'} size={'1x'} /> </div> </> @@ -185,6 +230,18 @@ export class CollectionCarouselView extends CollectionSubView() { color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), }}> {this.content} + {/* Displays a message to the user to add more flashcards if they are in practice mode and no flashcards are there. */} + <p + style={{ + display: !this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc[`filterOp`] == 'practice' ? 'flex' : 'none', + justifyContent: 'center', + alignItems: 'center', + height: '100%', + zIndex: '-1', + }}> + Add flashcards! + </p> + {/* Displays a message to the user that a flashcard was recently missed if they had previously gotten it wrong. */} <p style={{ color: 'red', @@ -192,7 +249,7 @@ export class CollectionCarouselView extends CollectionSubView() { position: 'relative', left: '10px', top: '10px', - visibility: this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] == 'missed' ? 'visible' : 'hidden', + display: this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)] ? (this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] == 'missed' ? 'block' : 'none') : 'none', }}> Recently missed! </p> diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 168176edf..2f0f2a773 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -80,6 +80,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr super(props); makeObservable(this); this._annotationKeySuffix = returnEmptyString; + this.layoutDoc[`filterOp`] = 'all'; } componentDidMount() { @@ -176,14 +177,18 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr return newRendition; }); + // creates menu option for flashcard filters const revealOptions = cm.findByDescription('Filter Flashcards'); const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : []; - revealItems.push({ description: 'All', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'all'), icon: 'eye-slash' }); - revealItems.push({ description: 'Star', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'star'), icon: 'hand-point-up' }); - revealItems.push({ description: 'Practice Mode', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'practice'), icon: 'rotate' }); + revealItems.push({ description: 'All', event: () => (this.layoutDoc[`filterOp`] = 'all'), icon: 'layer-group' }); + revealItems.push({ description: 'Star', event: () => (this.layoutDoc[`filterOp`] = 'star'), icon: 'star' }); + revealItems.push({ description: 'Practice Mode', event: () => (this.layoutDoc[`filterOp`] = 'practice'), icon: 'check' }); + revealItems.push({ description: 'Quiz Cards', event: () => (this.layoutDoc[`filterOp`] = 'quiz'), icon: 'pencil' }); - //revealItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' }); - !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); + // only show the filter options if it is a collection of type Carousel view + if (this.Document?._type_collection === CollectionViewType.Carousel) { + !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); + } const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss index 39c864b2b..093b9c004 100644 --- a/src/client/views/nodes/ComparisonBox.scss +++ b/src/client/views/nodes/ComparisonBox.scss @@ -1,4 +1,5 @@ .comparisonBox-interactive, +.quiz-card, .comparisonBox { border-radius: inherit; width: 100%; @@ -7,6 +8,40 @@ z-index: 0; pointer-events: none; display: flex; + p { + color: rgb(0, 0, 0); + -webkit-text-stroke-color: black; + -webkit-text-stroke-width: 0.2px; + } + + .input-box { + position: relative; + padding: 10px; + width: 100%; + height: 100%; + display: flex; + } + + .submit-button { + position: relative; + padding-bottom: 10px; + padding-left: 5px; + padding-right: 5px; + width: 100%; + height: 15%; + display: flex; + + button { + flex: 1; + position: relative; + } + } + textarea { + flex: 1; + padding: 10px; + position: relative; + resize: none; + } .clip-div { position: absolute; @@ -95,4 +130,112 @@ display: flex; } } + // .input-box { + // position: relative; + // padding: 10px; + // } + // input[type='text'] { + // flex: 1; + // position: relative; + // margin-right: 10px; + // width: 100px; + // } +} + +// .quiz-card { +// position: relative; + +// input[type='text'] { +// flex: 1; +// position: relative; +// margin-right: 10px; +// width: 100px; +// } +// } +.QuizCard { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .QuizCard-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .QuizCardBox { + /* existing code */ + + .DIYNodeBox-iframe { + height: 100%; + width: 100%; + border: none; + } + } + + .search-bar { + display: flex; + justify-content: left; + align-items: left; + width: 100%; + padding: 10px; + + input[type='text'] { + flex: 1; + margin-right: 10px; + } + + button { + padding: 5px 10px; + } + } + + .content { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + .diagramBox { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + svg { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + } + } + } + + .loading-circle { + position: relative; + width: 50px; + height: 50px; + border-radius: 50%; + border: 3px solid #ccc; + border-top-color: #333; + animation: spin 1s infinite linear; + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + } } diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 19fccce8a..9fd4d696a 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents, unimplementedFunction } from '../../../Utils'; import { Doc, Opt, DocListCast } from '../../../fields/Doc'; import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { DocUtils, Docs } from '../../documents/Documents'; @@ -34,6 +34,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() makeObservable(this); } + @observable inputValue = ''; + @observable outputValue = ''; + @observable loading = false; + @observable errorMessage = ''; + @observable outputMessage = ''; + + @action handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { + this.inputValue = e.target.value; + console.log(this.inputValue); + }; + @observable _animating = ''; @computed get clipWidth() { @@ -160,7 +171,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() e => { 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 => { - //this.clearDoc(which); return addDocument(doc); }; de.canEmbed = true; @@ -181,15 +191,24 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true); _closeRef = React.createRef<HTMLDivElement>(); + /** + * 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; }; + /** + * Creates the button used to flip the flashcards. + */ @computed get overlayAlternateIcon() { const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; return ( @@ -203,21 +222,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() 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); - - //const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text; - // DocCast(this.dataDoc[this.fieldKey + '_1'])[DocData].text = 'hello'; - // const mes = gptAPICall(queryText, GPTCallType.COMPLETION).trim(); - // const res = await gptAPICall(queryText, GPTCallType.COMPLETION) - // console.log(res); - //.then(value => (DocCast(this.dataDoc[this.fieldKey + '_1']).text = value.trim())); - if (usepath !== 'alternate') { - this.askGPT(); - } } }) } style={{ - //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none', background: usepath === 'alternate' ? 'white' : 'black', color: usepath === 'alternate' ? 'black' : 'white', }}> @@ -227,15 +235,34 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() ); } + @action handleRenderGPTClick = () => { + // Call the GPT model and get the output + this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate'; + this.outputValue = ''; + if (this.inputValue) this.askGPT(); + }; + + @action handleRenderClick = () => { + // Call the GPT model and get the output + this.layoutDoc[`_${this._props.fieldKey}_usePath`] = undefined; + }; + + /** + * Calls the GPT model to create QuizCards. Evaluates how similar the user's response is to the alternate + * side of the flashcard. + */ askGPT = async (): Promise<string | undefined> => { - const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text; + 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; + try { - let res = await gptAPICall(StrCast(queryText), GPTCallType.COMPLETION); + let res = await gptAPICall(queryText, GPTCallType.QUIZ); if (!res) { console.error('GPT call failed'); return; } - DocCast(this.dataDoc[this.fieldKey + '_0'])[DocData].text = res; + this.outputValue = res; console.log(res); } catch (err) { console.error('GPT call failed'); @@ -292,8 +319,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() ); }; const displayBox = (which: string, index: number, cover: number) => { - // if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') this.layoutDoc[this.clipHeightKey] = 100; - // else this.layoutDoc.height = 300; return ( <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}> {displayDoc(which)} @@ -326,36 +351,54 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() if (this.Document._layout_isFlashcard) { const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0; - // add text box when first created + // add text box to each side when comparison box is first created if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) { const dataSplit = StrCast(this.dataDoc.data).split('Answer'); const newDoc = Docs.Create.TextDocument(dataSplit[1]); + // if there is text from the pdf ai cards, put the question on the front side. newDoc[DocData].text = dataSplit[1]; this.addDoc(newDoc, this.fieldKey + '_0'); } if (!(this.dataDoc[this.fieldKey + '_1'] || this.dataDoc[this.fieldKey + '_1'] == 'empty')) { const dataSplit = StrCast(this.dataDoc.data).split('Answer'); const newDoc = Docs.Create.TextDocument(dataSplit[0]); - newDoc[DocData].text = 'placeholder...'; + // if there is text from the pdf ai cards, put the answer on the alternate side. + newDoc[DocData].text = dataSplit[0]; this.addDoc(newDoc, this.fieldKey + '_1'); } - if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') { - { - return ( - <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} style={{ display: 'flex', flexDirection: 'column' }}> - {displayBox(`${this.fieldKey}_0`, side, this._props.PanelHeight() - 3)} - {displayBox(`${this.fieldKey}_1`, 1, this._props.PanelHeight() - 3)} + // render the QuizCards + if (DocCast(this.Document.embedContainer) && DocCast(this.Document.embedContainer)[`filterOp`] == 'quiz') { + return ( + <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} style={{ display: 'flex', flexDirection: 'column' }}> + <p style={{ color: 'white', padding: 10 }}>{StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)}</p> + {/* {StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)} */} + <div className={'input-box'}> + { + <textarea + value={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? this.outputValue : this.inputValue} + onChange={this.handleInputChange} + readOnly={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate'} + /> + } </div> - ); - } - } else { + <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'none' : 'flex' }}> + <button onClick={this.handleRenderGPTClick}>Submit</button> + </div> + <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'flex' : 'none' }}> + <button onClick={this.handleRenderClick}>Edit Your Response</button> + </div> + </div> + ); + } + + // render a normal flashcard when not a QuizCard + else { return ( <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} /* change className to easily disable/enable pointer events in CSS */ - // style={{ display: 'flex', flexDirection: 'column' }} - // style={{ position: 'absolute', top: '0px' }} + style={{ display: 'flex', flexDirection: 'column' }} onMouseEnter={() => { this.hoverFlip('alternate'); }} @@ -363,12 +406,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() this.hoverFlip(undefined); }}> {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)} - {this.overlayAlternateIcon} </div> ); } } else { + // render a comparison box that compares items side by side return ( <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}> {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 26e5ec622..e1b501c5a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -48,6 +48,9 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; import { FieldsDropdown } from '../FieldsDropdown'; +import { RTFCast } from '../../../fields/Types'; +import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT'; + interface Window { MediaRecorder: MediaRecorder; } @@ -517,6 +520,21 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document input.click(); }; + askGPT = async (): Promise<string | undefined> => { + const queryText = RTFCast(DocCast(this.dataDoc[this.props.fieldKey + '_1']).text)?.Text; + try { + let res = await gptAPICall('Question: ' + StrCast(queryText), GPTCallType.CHATCARD); + if (!res) { + console.error('GPT call failed'); + return; + } + DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = res; + console.log(res); + } catch (err) { + console.error('GPT call failed'); + } + }; + onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => { if (e && this.layoutDoc.layout_hideContextMenu && Doc.noviceMode) { e.preventDefault(); @@ -569,17 +587,19 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => LightboxView.Instance.SetLightboxDoc(this.Document), icon: 'external-link-alt' }); } appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' }); + if (this.Document._layout_isFlashcard) { + appearanceItems.push({ description: 'Create ChatCard', event: () => this.askGPT(), icon: 'id-card' }); + } !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' }); + // creates menu for the user to select how to reveal the flashcards if (this.Document._layout_isFlashcard) { const revealOptions = cm.findByDescription('Reveal Options'); const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : []; revealItems.push({ description: 'Hover', event: () => (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover'), icon: 'hand-point-up' }); revealItems.push({ description: 'Flip', event: () => (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip'), icon: 'rotate' }); - revealItems.push({ description: 'Hide & Reveal', event: () => (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hide/reveal'), icon: 'eye-slash' }); - //revealItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' }); !revealOptions && cm.addItem({ description: 'Reveal Options', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 4fce72cdc..3192ac537 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -178,6 +178,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps @observable private gptRes: string = ''; + public makeAIFlashcards: () => void = unimplementedFunction; + public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; + public static PasteOnLoad: ClipboardEvent | undefined; private static SelectOnLoad: Doc | undefined; public static SetSelectOnLoad(doc: Doc) { @@ -959,7 +962,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' }); - optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); + optionItems.push({ description: `Ask GPT-3`, event: () => this.makeAIFlashcards(), icon: 'lightbulb' }); this._props.renderDepth && optionItems.push({ description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns', diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 7cb6a20f4..a0c3cf487 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -76,7 +76,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { * @param e pointer down event */ gptSummarize = async (e: React.PointerEvent) => { - // move this logic to gptpopup, need to implement generate again GPTPopup.Instance.setVisible(true); GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY); GPTPopup.Instance.setLoading(true); @@ -90,24 +89,28 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { GPTPopup.Instance.setLoading(false); }; + /** + * Invokes the API with the selected text and stores it in the selected text. + * @param e pointer down event + */ gptFlashcards = async (e: React.PointerEvent) => { - // move this logic to gptpopup, need to implement generate again - // GPTPopup.Instance.setVisible(true); - // GPTPopup.Instance.setMode(GPTPopupMode.FLASHCARD); - // GPTPopup.Instance.setLoading(true); - + const queryText = this.selectedText; try { - const res = await gptAPICall(this.selectedText, GPTCallType.FLASHCARD); - + const res = await gptAPICall(queryText, GPTCallType.FLASHCARD); + console.log(res); GPTPopup.Instance.setText(res || 'Something went wrong.'); - this.transferToFlashcard(res); + this.transferToFlashcard(res || 'Something went wrong'); } catch (err) { console.error(err); } GPTPopup.Instance.setLoading(false); }; + /* + * Transfers the flashcard text generated by GPT on flashcards and creates a collection out them. + */ transferToFlashcard = (text: string) => { + // put each question generated by GPT on the front of the flashcard const senArr = text.split('Question'); const collectionArr: Doc[] = []; for (var i = 1; i < senArr.length; i++) { @@ -116,10 +119,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { newDoc.text = senArr[i]; collectionArr.push(newDoc); } + // create a new carousel collection of these flashcards const newCol = Docs.Create.CarouselDocument(collectionArr, { - _width: 200, + _width: 250, _height: 200, - _layout_fitWidth: true, + _layout_fitWidth: false, _layout_autoHeight: true, }); @@ -215,6 +219,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { color={SettingsManager.userColor} /> )} + {/* Adds a create flashcards option to the anchor menu, which calls the gptFlashcard method. */} <IconButton tooltip="Create flashcards" // onPointerDown={this.gptFlashcards} diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 32c1721ec..0b741c85e 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -144,15 +144,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { _layout_autoHeight: true, }); this.addDoc(newDoc, this.sidebarId); - // const arr = [newDoc]; - // const newCol = Docs.Create.CarouselDocument(arr, { - // _width: 200, - // _height: 200, - // _layout_fitWidth: true, - // _layout_autoHeight: true, - // }); - // this.addDoc(newDoc, this.sidebarId); - // this.addDoc(newCol, this.sidebarId); const anchor = AnchorMenu.Instance?.GetAnchor(undefined, false); if (anchor) { DocUtils.MakeLink(newDoc, anchor, { @@ -161,25 +152,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { } }; - // transferToFlashcard = () => { - // const senArr = this.text.split('Question'); - // const collectionArr: Doc[] = []; - // for (var i = 1; i < senArr.length; i++) { - // console.log('Arr ' + i + ': ' + senArr[i]); - // const newDoc = Docs.Create.ComparisonDocument(senArr[i], { _layout_isFlashcard: true, _width: 300, _height: 300 }); - // newDoc.text = senArr[i]; - // collectionArr.push(newDoc); - // } - // const newCol = Docs.Create.CarouselDocument(collectionArr, { - // _width: 200, - // _height: 200, - // _layout_fitWidth: true, - // _layout_autoHeight: true, - // }); - // this.addDoc(newCol, this.sidebarId); - // this.addToCollection?.(newCol); - // }; - /** * Transfers the image urls to actual image docs */ @@ -244,23 +216,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { ); }; - flashcardBox = () => { - // const textArr = this.text.split("."); - // textArr.forEach(function(sentence) { - // console.log(sentence); - - // }); - // const newDoc = Docs.Create.ComparisonDocument(); - // this.addToCollection?.(newDoc); - // // const newDoc = Docs.Create.ComparisonDocument(); - // DocUtils.copyDragFactory(Doc.UserDoc().emptyFlashcard as Doc); - // // this.addToCollection?.(newDoc); - // // return newDoc; - // <ComparisonBox/> - const newDoc = Docs.Create.TextDocument('Hello there'); - this.addDoc?.(newDoc); - }; - data = () => { return ( <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index cecaf17ff..fe1ed8159 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -414,12 +414,10 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); } - // Changing which document to add the annotation to (the currently selected PDF) GPTPopup.Instance.setSidebarId('data_sidebar'); GPTPopup.Instance.addDoc = this._props.sidebarAddDoc; + // allows for creating collection AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument; - // const newDoc = Docs.Create.ComparisonDocument({ _layout_isFlashcard: true, _width: 300, _height: 300 }); - // this.props.addDocument?.(newDoc); }; @action |
