diff options
author | alyssaf16 <alyssa_feinberg@brown.edu> | 2024-03-30 00:06:27 -0400 |
---|---|---|
committer | alyssaf16 <alyssa_feinberg@brown.edu> | 2024-03-30 00:06:27 -0400 |
commit | e95d25eb8159bb7c753fa27e74e9baa8d3bffea6 (patch) | |
tree | 570d27aff92be8b50b98f07687829fe0d0eb6432 /src | |
parent | 416e00b5481e4835a674683c2e8213b536cab74c (diff) |
working on making flashcards from pdf with ai
Diffstat (limited to 'src')
-rw-r--r-- | src/.DS_Store | bin | 10244 -> 10244 bytes | |||
-rw-r--r-- | src/client/apis/gpt/GPT.ts | 8 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 4 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 5 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCarouselView.scss | 18 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCarouselView.tsx | 76 | ||||
-rw-r--r-- | src/client/views/collections/CollectionView.tsx | 9 | ||||
-rw-r--r-- | src/client/views/nodes/ComparisonBox.tsx | 181 | ||||
-rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 23 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.tsx | 39 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 4 |
11 files changed, 315 insertions, 52 deletions
diff --git a/src/.DS_Store b/src/.DS_Store Binary files differindex f8d745dbf..f1f08fbb5 100644 --- a/src/.DS_Store +++ b/src/.DS_Store diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index cea862330..968b45273 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -4,6 +4,7 @@ enum GPTCallType { SUMMARY = 'summary', COMPLETION = 'completion', EDIT = 'edit', + FLASHCARD = 'flashcard', } type GPTCallOpts = { @@ -14,9 +15,10 @@ type GPTCallOpts = { }; const callTypeMap: { [type: string]: GPTCallOpts } = { - summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, - edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, - completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' }, + summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, + edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, + flashcard: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Make flashcards out of this text with questions on the front and answers on the back: ' }, + completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' }, }; /** diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d2d13519..bf3d14560 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1054,8 +1054,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), '', options); } - export function ComparisonDocument(options: DocumentOptions = { title: 'Comparison Box' }) { - return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), undefined, options); + export function ComparisonDocument(text: string, options: DocumentOptions = { title: 'Comparison Box' }) { + return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), text, options); } export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 958240f94..ab0315ba1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -341,14 +341,15 @@ pie title Minerals in my tap water creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, - {key: "Flashcard", creator: Docs.Create.ComparisonDocument, opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }}, + {key: "Flashcard", creator: opts => Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300}}, + // {key: "Flashcard", creator: Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }}, //{key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }}, - {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, + {key: "Comparison", creator: opts => Docs.Create.ComparisonDocument("",opts), opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index 130b31325..f115bb40a 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -13,7 +13,10 @@ } } .carouselView-back, -.carouselView-fwd { +.carouselView-fwd, +.carouselView-star, +.carouselView-remove, +.carouselView-check { position: absolute; display: flex; top: 42.5%; @@ -34,6 +37,19 @@ .carouselView-back { left: 20; } +.carouselView-star { + top: 0; + right: 20; +} +.carouselView-remove { + top: 80%; + left: 52%; +} +.carouselView-check { + top: 80%; + right: 52%; +} + .carouselView-back:hover, .carouselView-fwd:hover { background: lightgray; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index dae16bafb..7f5176123 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -36,11 +36,67 @@ export class CollectionCarouselView extends CollectionSubView() { 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 + 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 the practice filter is selected + if (this.layoutDoc[`_${this._props.fieldKey}_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; + } }; goback = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % 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 + 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 the practice filter is selected + if (this.layoutDoc[`_${this._props.fieldKey}_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; + } + }; + + 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.layout[`${this.fieldKey}_star`] == undefined) curDoc.layout[`${this.fieldKey}_star`] = true; + else curDoc.layout[`${this.fieldKey}_star`] = !curDoc.layout[`${this.fieldKey}_star`]; + }; + + 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; }; + captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => { // first look for properties on the document in the carousel, then fallback to properties on the container const childValue = doc?.['caption-' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined; @@ -106,6 +162,15 @@ export class CollectionCarouselView extends CollectionSubView() { <div key="fwd" className="carouselView-fwd" onClick={this.advance}> <FontAwesomeIcon icon={'chevron-right'} size={'2x'} /> </div> + <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' }}> + <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' }}> + <FontAwesomeIcon icon={'check'} color={'green'} size={'1x'} /> + </div> </> ); } @@ -120,6 +185,17 @@ export class CollectionCarouselView extends CollectionSubView() { color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), }}> {this.content} + <p + style={{ + color: 'red', + zIndex: '999', + position: 'relative', + left: '10px', + top: '10px', + visibility: this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] == 'missed' ? 'visible' : 'hidden', + }}> + Recently missed! + </p> {this.Document._chromeHidden ? null : this.buttons} </div> ); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 18eb4dd1f..168176edf 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -176,6 +176,15 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr return newRendition; }); + 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: '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' }); + const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null; diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 81f624b20..ce862cd22 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -4,13 +4,14 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { DocUtils, Docs } from '../../documents/Documents'; import { DragManager, dropActionType } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { SnappingManager } from '../../util/SnappingManager'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; +import { Tooltip } from '@mui/material'; import { CSSTransition } from 'react-transition-group'; import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; @@ -18,6 +19,8 @@ import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { RichTextField } from '../../../fields/RichTextField'; +import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; +import { DocData } from '../../../fields/DocSymbols'; @observer export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface { @@ -88,7 +91,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() if (this._isAnyChildContentActive) 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.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth(); + this.layoutDoc[this.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight(); setTimeout( action(() => (this._animating = '')), @@ -128,7 +132,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() clearDoc = (fieldKey: string) => { delete this.dataDoc[fieldKey]; this.dataDoc[fieldKey] = 'empty'; - // this.dataDoc[this.fieldKey + '_1'] = 'empty'; }; // clearDoc = (fieldKey: string) => delete this.dataDoc[fieldKey]; @@ -182,47 +185,72 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined; }; + hoverFlip = (side: string | undefined) => { + if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side; + }; @computed get overlayAlternateIcon() { const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; return ( - <div - className="formattedTextBox-alternateButton" - onPointerDown={e => - setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => { - console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]); - if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') { - this.flipFlashcard(); - } - }) - } - style={{ - //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none', - background: usepath === 'alternate' ? 'white' : 'black', - color: usepath === 'alternate' ? 'black' : 'white', - }}> - <FontAwesomeIcon icon="turn-up" size="sm" /> - </div> + <Tooltip title={<div className="dash-tooltip">flip</div>}> + <div + className="formattedTextBox-alternateButton" + onPointerDown={e => + setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => { + console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]); + if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') { + this.flipFlashcard(); + } + }) + } + style={{ + //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none', + background: usepath === 'alternate' ? 'white' : 'black', + color: usepath === 'alternate' ? 'black' : 'white', + }}> + <FontAwesomeIcon icon="turn-up" size="sm" /> + </div> + </Tooltip> ); } render() { const clearButton = (which: string) => { return ( - <div - ref={this._closeRef} - className={`clear-button ${which}`} - onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding - > - <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" /> - </div> + <Tooltip title={<div className="dash-tooltip">remove</div>}> + <div + ref={this._closeRef} + className={`clear-button ${which}`} + onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding + > + <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" /> + </div> + </Tooltip> ); }; const displayDoc = (which: string) => { const whichDoc = DocCast(this.dataDoc[which]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot - const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined; + //const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined; + const subjectText = RTFCast(this.Document[this.fieldKey])?.Text; + const layoutTemplateString = !targetDoc + ? which.endsWith('0') && subjectText !== undefined + ? FormattedTextBox.LayoutString(this.fieldKey) + : which.endsWith('1') && (this.Document[which] instanceof RichTextField || typeof this.Document[which] === 'string') + ? FormattedTextBox.LayoutString(which) + : undefined + : undefined; + + if (which.endsWith('1') && !layoutTemplateString && targetDoc) { + const queryText = RTFCast(this.Document[this.fieldKey + '_1'])?.Text; + console.log('QUER' + queryText); + if (queryText?.includes('--TEXT--') && subjectText) { + this.Document[DocData][this.fieldKey + '_1'] = ''; + gptAPICall(queryText?.replace('--TEXT--', subjectText), GPTCallType.COMPLETION).then(value => (this.Document[DocData][this.fieldKey + '_1'] = value.trim())); + } + } + return targetDoc || layoutTemplateString ? ( <> <DocumentView @@ -234,7 +262,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2} removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2} NativeWidth={() => NumCast(this.layoutDoc.width, 200)} - NativeHeight={() => NumCast(this.layoutDoc.height, 200)} + NativeHeight={(): number => { + return NumCast(this.layoutDoc.height, 200); + }} isContentActive={emptyFunction} isDocumentActive={returnFalse} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} @@ -251,6 +281,8 @@ 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)} @@ -258,26 +290,89 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() ); }; + const displayBoxReveal = (which: string, which2: string, index: number, cover: number) => { + return ( + <div style={{ display: 'flex', flexDirection: 'column' }}> + <div + className={`beforeBox-cont`} + key={which} + style={{ width: this._props.PanelWidth(), height: NumCast(this.layoutDoc.height, 200) / 2 }} + onPointerDown={e => this.registerSliding(e, cover)} + ref={ele => this.createDropTarget(ele, which, 0)}> + {displayDoc(which)} + </div> + <div + className={`afterBox-cont`} + key={which2} + style={{ width: this._props.PanelWidth(), height: NumCast(this.layoutDoc.height, 200) / 2 }} + onPointerDown={e => this.registerSliding(e, cover)} + ref={ele => this.createDropTarget(ele, which2, 1)}> + {displayDoc(which2)} + </div> + </div> + ); + }; + if (this.Document._layout_isFlashcard) { const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0; - if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) this.dataDoc[this.fieldKey + '_0'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); + // add text box when first created + if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) { + console.log('TEXT HERE' + this.dataDoc.data); + const newDoc = Docs.Create.TextDocument(StrCast(this.dataDoc.data)); + this.addDoc(newDoc, this.fieldKey + '_0'); + } + // if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) this.dataDoc[this.fieldKey + '_0'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); if (!(this.dataDoc[this.fieldKey + '_1'] || this.dataDoc[this.fieldKey + '_1'] == 'empty')) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); - return ( - <div - className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} /* change className to easily disable/enable pointer events in CSS */ - onMouseEnter={() => { - if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate'; - }} - onMouseLeave={() => { - if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = undefined; - }}> - {displayBox(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)} + 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)} + </div> + ); + // return ( + // <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */} style={{ display: 'flex', flexDirection: 'column' }}> + // {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelHeight() - 3)} + // <div className="clip-div" style={{ height: this.clipHeight + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}> + // {displayBox(`${this.fieldKey}_1`, 0, 0)} + // </div> + // </div> + // ); + // return ( + // <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}> + // {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelHeight() / 2)} + // {displayBox(`${this.fieldKey}_${side === 0 ? 0 : 1}`, 1, this._props.PanelHeight() / 2)} - {this.overlayAlternateIcon} - </div> - ); + // </div> + // ); + } + } 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' }} + onMouseEnter={() => { + this.hoverFlip('alternate'); + }} + onMouseLeave={() => { + this.hoverFlip(undefined); + }}> + {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)} + {/* {displayBoxReveal(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)} + {displayBoxReveal(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side === 0 ? 1 : 0, this._props.PanelWidth() - 3)} */} + {/* <div>{displayBoxReveal(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)}</div> */} + {/* <div style={{ display: this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal' ? 'block' : 'none' }} className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`}> + {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side === 0 ? 1 : 0, this._props.PanelWidth() - 3)} + </div> */} + + {this.overlayAlternateIcon} + </div> + ); + } } else { return ( <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}> diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index d0688c338..844c1e36d 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import { ColorResult } from 'react-color'; import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; +import { DocUtils, Docs } from '../../documents/Documents'; import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; import { SelectionManager } from '../../util/SelectionManager'; @@ -87,6 +88,22 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { GPTPopup.Instance.setLoading(false); }; + 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); + + try { + const res = await gptAPICall(this.selectedText, GPTCallType.FLASHCARD); + GPTPopup.Instance.setText(res || 'Something went wrong.'); + GPTPopup.Instance.transferToFlashcard(); + } catch (err) { + console.error(err); + } + GPTPopup.Instance.setLoading(false); + }; + pointerDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -176,6 +193,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { color={SettingsManager.userColor} /> )} + <IconButton + tooltip="Create flashcards" // + onPointerDown={this.gptFlashcards} + icon={<FontAwesomeIcon icon="id-card" size="lg" />} + color={SettingsManager.userColor} + /> {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( <IconButton tooltip="Click to Record Annotation" // diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index da8a88803..dbcdd4e3a 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -15,11 +15,14 @@ import { DocUtils, Docs } from '../../../documents/Documents'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { AnchorMenu } from '../AnchorMenu'; import './GPTPopup.scss'; +import { ComparisonBox } from '../../nodes/ComparisonBox'; +import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../../Utils'; export enum GPTPopupMode { SUMMARY, EDIT, IMAGE, + FLASHCARD, } interface GPTPopupProps {} @@ -149,6 +152,22 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { } }; + transferToFlashcard = () => { + const senArr = this.text.split('.'); + for (var i = 0; i < senArr.length; i += 2) { + console.log('SEN: ' + senArr[i]); + const newDoc = Docs.Create.ComparisonDocument(senArr[i], { _layout_isFlashcard: true, _width: 300, _height: 300 }); + newDoc.text = senArr[i]; + this.addDoc(newDoc, this.sidebarId); + const anchor = AnchorMenu.Instance?.GetAnchor(undefined, false); + if (anchor) { + DocUtils.MakeLink(newDoc, anchor, { + link_relationship: 'GPT Summary', + }); + } + } + }; + /** * Transfers the image urls to actual image docs */ @@ -213,6 +232,23 @@ 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' }}> @@ -268,6 +304,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { <> <IconButton tooltip="Generate Again" onClick={this.callSummaryApi} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} /> <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} /> + {/* <Button tooltip="Transfer to flashcard" text="flashcard" onClick={this.transferToFlashcard} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} /> */} </> ) : ( <div className="summarizing"> @@ -308,7 +345,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { render() { return ( <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}> - {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>} + {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.mode == GPTPopupMode.FLASHCARD ? this.summaryBox() : <></>} </div> ); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0d4cfda88..7d8529a1c 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -20,11 +20,13 @@ import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView'; import { LinkInfo } from '../nodes/LinkDocPreview'; import { PDFBox } from '../nodes/PDFBox'; +import { ComparisonBox } from '../nodes/ComparisonBox'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { StyleProp } from '../StyleProvider'; import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import { GPTPopup } from './GPTPopup/GPTPopup'; +import { Docs } from '../../documents/Documents'; import './PDFViewer.scss'; const _global = (window /* browser */ || global) /* node */ as any; @@ -415,6 +417,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { // Changing which document to add the annotation to (the currently selected PDF) GPTPopup.Instance.setSidebarId('data_sidebar'); GPTPopup.Instance.addDoc = this._props.sidebarAddDoc; + // const newDoc = Docs.Create.ComparisonDocument({ _layout_isFlashcard: true, _width: 300, _height: 300 }); + // this.props.addDocument?.(newDoc); }; @action |