From 73f709846365cb266c8489678d5c6713c0c43158 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Tue, 5 Mar 2024 17:23:33 -0500
Subject: difference between flashcard and comparison node

---
 src/client/views/nodes/ComparisonBox.tsx | 38 ++++++++++++++++++--------------
 1 file changed, 21 insertions(+), 17 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 116dc48a6..bdf92038f 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -206,24 +206,28 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             );
         };
 
-        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)}
-                <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
-                    {displayBox(`${this.fieldKey}_1`, 0, 0)}
-                </div>
+        if (this.Document._layout_isFlashcard) {
+            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)}</div>;
+        } else {
+            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)}
+                    <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
+                        {displayBox(`${this.fieldKey}_1`, 0, 0)}
+                    </div>
 
-                <div
-                    className="slide-bar"
-                    style={{
-                        left: `calc(${this.clipWidth + '%'} - 0.5px)`,
-                        cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined,
-                    }}
-                    onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
-                >
-                    <div className="slide-handle" />
+                    <div
+                        className="slide-bar"
+                        style={{
+                            left: `calc(${this.clipWidth + '%'} - 0.5px)`,
+                            cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined,
+                        }}
+                        onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
+                    >
+                        <div className="slide-handle" />
+                    </div>
                 </div>
-            </div>
-        );
+            );
+        }
     }
 }
-- 
cgit v1.2.3-70-g09d2


From a38f7ea40bc44e5e213dc03b687a4be32592b4a2 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Sat, 9 Mar 2024 11:39:35 -0500
Subject: flashcard flips

---
 src/client/views/nodes/ComparisonBox.tsx | 46 +++++++++++++++++++++++++++++++-
 1 file changed, 45 insertions(+), 1 deletion(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index bdf92038f..eaf7419fc 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -8,12 +8,14 @@ import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { DocUtils, Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import './ComparisonBox.scss';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
+import { IntegerType } from 'mongodb';
 
 @observer
 export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -157,6 +159,41 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
     remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
     _closeRef = React.createRef<HTMLDivElement>();
+
+    cycleAlternateText = () => {
+        const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+        this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined;
+        //: usePath === 'alternate' ? 'alternate:hover' : undefined;
+        //usePath === undefined ? 'alternate' :
+        //undefined;
+        //: usePath === 'alternate' ? 'alternate:hover' : undefined;
+        // console.log('CYCLE' + this.layoutDoc[`_${this._props.fieldKey}_usePath`]);
+    };
+
+    @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 => {
+                        //side = side === 1 ? 0 : 1;
+                        this.cycleAlternateText();
+                        console.log('HEREEEEE');
+                    })
+                }
+                style={{
+                    //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none',
+                    background: usepath === 'alternate' ? 'white' : 'black',
+                    //usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
+                    color: usepath === 'alternate' ? 'black' : 'white',
+                    //usePath === undefined ? 'black' : 'white',
+                }}>
+                <FontAwesomeIcon icon="turn-up" size="sm" />
+            </div>
+        );
+    }
+
     render() {
         const clearButton = (which: string) => {
             return (
@@ -207,7 +244,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         };
 
         if (this.Document._layout_isFlashcard) {
-            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)}</div>;
+            const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0;
+            console.log('SIDEEEE' + side);
+            return (
+                <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
+                    {displayBox(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)}
+                    {this.overlayAlternateIcon}
+                </div>
+            );
         } else {
             return (
                 <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
-- 
cgit v1.2.3-70-g09d2


From ba61f7fb706b17f5b186ac32e2f8844715899886 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Sun, 10 Mar 2024 00:02:39 -0500
Subject: flashcards

---
 src/client/views/nodes/ComparisonBox.tsx | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index eaf7419fc..fa0ab61bc 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -16,6 +16,7 @@ import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 import { IntegerType } from 'mongodb';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
 
 @observer
 export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -160,14 +161,9 @@ 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>();
 
-    cycleAlternateText = () => {
+    flipFlashcard = () => {
         const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
         this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined;
-        //: usePath === 'alternate' ? 'alternate:hover' : undefined;
-        //usePath === undefined ? 'alternate' :
-        //undefined;
-        //: usePath === 'alternate' ? 'alternate:hover' : undefined;
-        // console.log('CYCLE' + this.layoutDoc[`_${this._props.fieldKey}_usePath`]);
     };
 
     @computed get overlayAlternateIcon() {
@@ -177,17 +173,13 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 className="formattedTextBox-alternateButton"
                 onPointerDown={e =>
                     setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => {
-                        //side = side === 1 ? 0 : 1;
-                        this.cycleAlternateText();
-                        console.log('HEREEEEE');
+                        this.flipFlashcard();
                     })
                 }
                 style={{
                     //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none',
                     background: usepath === 'alternate' ? 'white' : 'black',
-                    //usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
                     color: usepath === 'alternate' ? 'black' : 'white',
-                    //usePath === undefined ? 'black' : 'white',
                 }}>
                 <FontAwesomeIcon icon="turn-up" size="sm" />
             </div>
@@ -245,7 +237,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
         if (this.Document._layout_isFlashcard) {
             const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0;
-            console.log('SIDEEEE' + side);
+
+            if (!this.dataDoc[this.fieldKey + '_0']) this.dataDoc[this.fieldKey + '_0'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+            if (!this.dataDoc[this.fieldKey + '_1']) 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 */}>
                     {displayBox(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)}
-- 
cgit v1.2.3-70-g09d2


From 32a742020243ffadfdae2cbc0a8b2853b0f06fc5 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Sun, 10 Mar 2024 14:33:08 -0400
Subject: flashcard

---
 src/client/views/nodes/ComparisonBox.tsx                  | 12 +++++++++++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  4 ----
 2 files changed, 11 insertions(+), 5 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index fa0ab61bc..86a9bcf63 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -37,6 +37,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     get clipWidthKey() {
         return '_' + this._props.fieldKey + '_clipWidth';
     }
+
+    @computed get clipHeight() {
+        return NumCast(this.layoutDoc[this.clipHeightKey], 200);
+    }
+    get clipHeightKey() {
+        return '_' + this._props.fieldKey + '_clipHeight';
+    }
+
     componentDidMount() {
         this._props.setContentViewBox?.(this);
     }
@@ -80,6 +88,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     this._animating = 'all 200ms';
                     // on click, animate slider movement to the targetWidth
                     this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
+
                     setTimeout(
                         action(() => (this._animating = '')),
                         200
@@ -201,6 +210,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const displayDoc = (which: string) => {
             const whichDoc = DocCast(this.dataDoc[which]);
             const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
+
             return targetDoc ? (
                 <>
                     <DocumentView
@@ -211,7 +221,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
-                        NativeHeight={returnZero}
+                        NativeHeight={() => this.clipHeight}
                         isContentActive={emptyFunction}
                         isDocumentActive={returnFalse}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 61d6094b9..4250b018e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1949,10 +1949,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (this.layoutDoc._layout_enableAltContentUI) {
             const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
             this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined;
-            //: usePath === 'alternate' ? 'alternate:hover' : undefined;
-            //usePath === undefined ? 'alternate' :
-            //undefined;
-            //: usePath === 'alternate' ? 'alternate:hover' : undefined;
         }
     };
     @computed get overlayAlternateIcon() {
-- 
cgit v1.2.3-70-g09d2


From ecfb0d119e08a101cd8d18e8a90d31995f71eeec Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Tue, 12 Mar 2024 16:10:37 -0400
Subject: change

---
 src/client/views/nodes/ComparisonBox.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 86a9bcf63..828c82142 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -220,8 +220,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         containerViewPath={this.DocumentView?.().docViewPath}
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
-                        NativeWidth={returnZero}
-                        NativeHeight={() => this.clipHeight}
+                        NativeWidth={() => NumCast(this.layoutDoc.width, 200)}
+                        NativeHeight={() => NumCast(this.layoutDoc.height, 200)}
                         isContentActive={emptyFunction}
                         isDocumentActive={returnFalse}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-- 
cgit v1.2.3-70-g09d2


From 490033a1de1562d5dbc6b27655ad749acc451308 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Thu, 14 Mar 2024 00:11:19 -0400
Subject: flashcard

---
 src/client/views/nodes/ComparisonBox.tsx | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 083575530..434a3c184 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -124,16 +124,22 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     };
 
     @undoBatch
-    clearDoc = (fieldKey: string) => delete this.dataDoc[fieldKey];
+    clearDoc = (fieldKey: string) => {
+        delete this.dataDoc[fieldKey];
+        this.dataDoc[fieldKey] = 'empty';
+        // this.dataDoc[this.fieldKey + '_1'] = 'empty';
+    };
+    // 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]) return false;
+        if (this.dataDoc[which] && this.dataDoc[which] !== 'empty') return false;
         this.dataDoc[which] = doc;
         return true;
     };
     remDoc = (doc: Doc, which: string) => {
         if (this.dataDoc[which] === doc) {
+            // this.dataDoc[which] = 'empty';
             this.dataDoc[which] = undefined;
             return true;
         }
@@ -149,7 +155,7 @@ 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);
+                    //this.clearDoc(which);
                     return addDocument(doc);
                 };
                 de.canEmbed = true;
@@ -250,8 +256,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         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'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
-            if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+            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 */}>
-- 
cgit v1.2.3-70-g09d2


From 1e5d21d85119335ceba5d43075fb9eb52c1ef3f5 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Thu, 14 Mar 2024 01:20:34 -0400
Subject: flashcard remove

---
 src/client/views/nodes/ComparisonBox.tsx | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 434a3c184..97b9a8744 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -11,6 +11,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
+import { CSSTransition } from 'react-transition-group';
 import './ComparisonBox.scss';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
@@ -262,6 +263,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             return (
                 <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
                     {displayBox(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)}
+
                     {this.overlayAlternateIcon}
                 </div>
             );
-- 
cgit v1.2.3-70-g09d2


From fa2b150fcccef8df17a4e199767dc6d643a9c16c Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Sun, 17 Mar 2024 14:23:08 -0400
Subject: menu working

---
 src/client/views/nodes/ComparisonBox.tsx |  6 +++++-
 src/client/views/nodes/DocumentView.tsx  | 12 ++++++++++--
 2 files changed, 15 insertions(+), 3 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 97b9a8744..9caa216a5 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -189,7 +189,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 className="formattedTextBox-alternateButton"
                 onPointerDown={e =>
                     setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => {
-                        this.flipFlashcard();
+                        if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') {
+                            this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip';
+                            console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]);
+                            this.flipFlashcard();
+                        }
                     })
                 }
                 style={{
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7e7a9d7f9..c4c5204a3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -570,11 +570,19 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             }
             appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' });
 
-            // if(this.layoutDoc.)
-            appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' });
             !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' });
 
+            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._props.pinToPres(this.Document, {}), 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' });
+            }
+
             if (this._props.bringToFront) {
                 const zorders = cm.findByDescription('ZOrder...');
                 const zorderItems: ContextMenuProps[] = zorders && 'subitems' in zorders ? zorders.subitems : [];
-- 
cgit v1.2.3-70-g09d2


From 416e00b5481e4835a674683c2e8213b536cab74c Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Mon, 18 Mar 2024 10:08:32 -0400
Subject: hover

---
 src/client/views/nodes/ComparisonBox.tsx | 13 ++++++++++---
 src/client/views/nodes/DocumentView.tsx  |  2 +-
 2 files changed, 11 insertions(+), 4 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 9caa216a5..81f624b20 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -179,6 +179,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
     flipFlashcard = () => {
         const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+
         this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined;
     };
 
@@ -189,9 +190,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 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.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip';
-                            console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]);
                             this.flipFlashcard();
                         }
                     })
@@ -265,7 +265,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             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 */}>
+                <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)}
 
                     {this.overlayAlternateIcon}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c4c5204a3..26e5ec622 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -578,7 +578,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 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._props.pinToPres(this.Document, {}), icon: 'eye-slash' });
+                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' });
             }
-- 
cgit v1.2.3-70-g09d2


From e95d25eb8159bb7c753fa27e74e9baa8d3bffea6 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Sat, 30 Mar 2024 00:06:27 -0400
Subject: working on making flashcards from pdf with ai

---
 src/.DS_Store                                      | Bin 10244 -> 10244 bytes
 src/client/apis/gpt/GPT.ts                         |   8 +-
 src/client/documents/Documents.ts                  |   4 +-
 src/client/util/CurrentUserUtils.ts                |   5 +-
 .../views/collections/CollectionCarouselView.scss  |  18 +-
 .../views/collections/CollectionCarouselView.tsx   |  76 +++++++++
 src/client/views/collections/CollectionView.tsx    |   9 +
 src/client/views/nodes/ComparisonBox.tsx           | 181 ++++++++++++++++-----
 src/client/views/pdf/AnchorMenu.tsx                |  23 +++
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  39 ++++-
 src/client/views/pdf/PDFViewer.tsx                 |   4 +
 11 files changed, 315 insertions(+), 52 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/.DS_Store b/src/.DS_Store
index f8d745dbf..f1f08fbb5 100644
Binary files a/src/.DS_Store and b/src/.DS_Store differ
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
-- 
cgit v1.2.3-70-g09d2


From c5b2912cc4f44c5e12d8b5dc123d99bf9c4d9b85 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Wed, 17 Apr 2024 13:17:51 -0400
Subject: ai flashcards

---
 src/client/apis/gpt/GPT.ts                 |  4 +-
 src/client/views/nodes/ComparisonBox.tsx   | 71 +++++++++++-------------------
 src/client/views/pdf/AnchorMenu.tsx        | 24 +++++++++-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx | 42 +++++++++++-------
 src/client/views/pdf/PDFViewer.tsx         |  1 +
 5 files changed, 79 insertions(+), 63 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 968b45273..2757fc830 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -17,7 +17,7 @@ type GPTCallOpts = {
 const callTypeMap: { [type: string]: GPTCallOpts } = {
     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: ' },
+    flashcard: { model: 'gpt-3.5-turbo-instruct', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with questions and answers: ' },
     completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' },
 };
 
@@ -28,7 +28,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
  * @returns AI Output
  */
 const gptAPICall = async (inputText: string, callType: GPTCallType) => {
-    if (callType === GPTCallType.SUMMARY) inputText += '.';
+    if (callType === GPTCallType.SUMMARY || callType == GPTCallType.FLASHCARD) inputText += '.';
     const opts: GPTCallOpts = callTypeMap[callType];
     try {
         const configuration: ClientOptions = {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index ce862cd22..76631f071 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -3,7 +3,7 @@ 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 { Doc, Opt } from '../../../fields/Doc';
+import { Doc, Opt, DocListCast } from '../../../fields/Doc';
 import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
 import { DocUtils, Docs } from '../../documents/Documents';
 import { DragManager, dropActionType } from '../../util/DragManager';
@@ -21,6 +21,7 @@ import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { RichTextField } from '../../../fields/RichTextField';
 import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
 import { DocData } from '../../../fields/DocSymbols';
+import { KeyValueBox } from './KeyValueBox';
 
 @observer
 export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -200,6 +201,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]);
                             if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') {
                                 this.flipFlashcard();
+                                console.log('Print context of cards: ' + DocCast(this.dataDoc[this.fieldKey + '_1']).text);
                             }
                         })
                     }
@@ -214,6 +216,18 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         );
     }
 
+    askGPT = async (): Promise<string | undefined> => {
+        try {
+            let res = await gptAPICall(StrCast(this.dataDoc.data), GPTCallType.COMPLETION);
+            if (!res) {
+                console.error('GPT call failed');
+                return;
+            }
+        } catch (err) {
+            console.error('GPT call failed');
+        }
+    };
+
     render() {
         const clearButton = (which: string) => {
             return (
@@ -232,24 +246,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             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 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()));
-                }
-            }
+            const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined;
 
             return targetDoc || layoutTemplateString ? (
                 <>
@@ -315,15 +312,20 @@ 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
+
             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));
+                const dataSplit = StrCast(this.dataDoc.data).split('Answer');
+                const newDoc = Docs.Create.TextDocument(dataSplit[1]);
+                newDoc.text = dataSplit[1];
                 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);
+            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.text = 'jhkhkjh';
+                this.addDoc(newDoc, this.fieldKey + '_1');
+            }
 
             if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') {
                 {
@@ -333,21 +335,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             {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)}
-
-                    //     </div>
-                    // );
                 }
             } else {
                 return (
@@ -362,12 +349,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             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>
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 844c1e36d..7cb6a20f4 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -15,6 +15,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
 import { LinkPopup } from '../linking/LinkPopup';
 import './AnchorMenu.scss';
 import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
+import { PDFViewer } from 'pdfjs-dist/web/pdf_viewer.mjs';
 
 @observer
 export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -57,6 +58,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public get Active() {
         return this._left > 0;
     }
+    public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
 
     componentWillUnmount() {
         this._disposer?.();
@@ -96,14 +98,34 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
         try {
             const res = await gptAPICall(this.selectedText, GPTCallType.FLASHCARD);
+
             GPTPopup.Instance.setText(res || 'Something went wrong.');
-            GPTPopup.Instance.transferToFlashcard();
+            this.transferToFlashcard(res);
         } catch (err) {
             console.error(err);
         }
         GPTPopup.Instance.setLoading(false);
     };
 
+    transferToFlashcard = (text: string) => {
+        const senArr = 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.addToCollection?.(newCol);
+    };
+
     pointerDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(
             this,
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index dbcdd4e3a..32c1721ec 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -144,6 +144,15 @@ 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, {
@@ -152,21 +161,24 @@ 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',
-                });
-            }
-        }
-    };
+    // 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
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 7d8529a1c..cecaf17ff 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -417,6 +417,7 @@ 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;
+        AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
         // const newDoc = Docs.Create.ComparisonDocument({ _layout_isFlashcard: true, _width: 300, _height: 300 });
         // this.props.addDocument?.(newDoc);
     };
-- 
cgit v1.2.3-70-g09d2


From 02ce102a6d53421d0c84faa21f34d0560450edf5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 Apr 2024 14:53:58 -0400
Subject: fixed access to flashcard text

---
 package-lock.json                        | 3 ++-
 src/client/views/nodes/ComparisonBox.tsx | 7 ++++---
 2 files changed, 6 insertions(+), 4 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/package-lock.json b/package-lock.json
index cf8e8ce29..f372d011c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -31609,7 +31609,8 @@
         "node_modules/text-table": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
+            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+            "dev": true
         },
         "node_modules/textarea-caret": {
             "version": "3.1.0",
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 76631f071..01494f217 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -201,7 +201,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]);
                             if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') {
                                 this.flipFlashcard();
-                                console.log('Print context of cards: ' + DocCast(this.dataDoc[this.fieldKey + '_1']).text);
+                                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);
                             }
                         })
                     }
@@ -317,13 +318,13 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             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]);
-                newDoc.text = dataSplit[1];
+                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.text = 'jhkhkjh';
+                newDoc[DocData].text = 'placeholder...';
                 this.addDoc(newDoc, this.fieldKey + '_1');
             }
 
-- 
cgit v1.2.3-70-g09d2


From 3fb9eada221670022aa575c72fb89103638c3cbd Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Wed, 17 Apr 2024 16:25:51 -0400
Subject: flip

---
 src/client/views/nodes/ComparisonBox.tsx | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 01494f217..19fccce8a 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -203,6 +203,16 @@ 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();
+                                }
                             }
                         })
                     }
@@ -218,12 +228,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     }
 
     askGPT = async (): Promise<string | undefined> => {
+        const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text;
         try {
-            let res = await gptAPICall(StrCast(this.dataDoc.data), GPTCallType.COMPLETION);
+            let res = await gptAPICall(StrCast(queryText), GPTCallType.COMPLETION);
             if (!res) {
                 console.error('GPT call failed');
                 return;
             }
+            DocCast(this.dataDoc[this.fieldKey + '_0'])[DocData].text = res;
+            console.log(res);
         } catch (err) {
             console.error('GPT call failed');
         }
-- 
cgit v1.2.3-70-g09d2


From 5ff0bef5d3c4825aa7210a26c98aae3b24f4a835 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Fri, 17 May 2024 13:18:40 -0400
Subject: chatcards, quizcards, and ai flashcards

---
 src/client/apis/gpt/GPT.ts                         |  36 ++++--
 src/client/util/CurrentUserUtils.ts                |   2 -
 .../views/collections/CollectionCarouselView.tsx   |  79 ++++++++++--
 src/client/views/collections/CollectionView.tsx    |  15 ++-
 src/client/views/nodes/ComparisonBox.scss          | 143 +++++++++++++++++++++
 src/client/views/nodes/ComparisonBox.tsx           | 109 +++++++++++-----
 src/client/views/nodes/DocumentView.tsx            |  24 +++-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   5 +-
 src/client/views/pdf/AnchorMenu.tsx                |  27 ++--
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  45 -------
 src/client/views/pdf/PDFViewer.tsx                 |   4 +-
 11 files changed, 367 insertions(+), 122 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 2757fc830..6600ddab2 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -1,10 +1,13 @@
 import { ClientOptions, OpenAI } from 'openai';
+import { ChatCompletionMessageParam } from 'openai/resources';
 
 enum GPTCallType {
     SUMMARY = 'summary',
     COMPLETION = 'completion',
     EDIT = 'edit',
+    CHATCARD = 'chatcard',
     FLASHCARD = 'flashcard',
+    QUIZ = 'quiz',
 }
 
 type GPTCallOpts = {
@@ -15,10 +18,17 @@ type GPTCallOpts = {
 };
 
 const callTypeMap: { [type: string]: GPTCallOpts } = {
-    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: 512, temp: 0.5, prompt: 'Make flashcards out of this text with questions and answers: ' },
-    completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' },
+    summary: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
+    edit: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
+    flashcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with each question and answer labeled. Do not label each flashcard and do not include asterisks: ' },
+    completion: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: '' },
+    chatcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Answer the following question as a short flashcard response. Do not include a label.' },
+    quiz: {
+        model: 'gpt-4-turbo',
+        maxTokens: 1024,
+        temp: 0,
+        prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct',
+    },
 };
 
 /**
@@ -28,7 +38,8 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
  * @returns AI Output
  */
 const gptAPICall = async (inputText: string, callType: GPTCallType) => {
-    if (callType === GPTCallType.SUMMARY || callType == GPTCallType.FLASHCARD) inputText += '.';
+    if (!inputText) return 'Please provide a response.';
+    if (callType === GPTCallType.SUMMARY || callType == GPTCallType.FLASHCARD || GPTCallType.QUIZ) inputText += '.';
     const opts: GPTCallOpts = callTypeMap[callType];
     try {
         const configuration: ClientOptions = {
@@ -36,13 +47,20 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
             dangerouslyAllowBrowser: true,
         };
         const openai = new OpenAI(configuration);
-        const response = await openai.completions.create({
+
+        let messages: ChatCompletionMessageParam[] = [
+            { role: 'system', content: opts.prompt },
+            { role: 'user', content: inputText },
+        ];
+
+        const response = await openai.chat.completions.create({
             model: opts.model,
-            max_tokens: opts.maxTokens,
+            messages: messages,
             temperature: opts.temp,
-            prompt: `${opts.prompt}${inputText}`,
+            max_tokens: opts.maxTokens,
         });
-        return response.choices[0].text;
+
+        return response.choices[0].message.content;
     } catch (err) {
         console.log(err);
         return 'Error connecting with API.';
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index ab0315ba1..b6ba3f187 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -342,8 +342,6 @@ pie title Minerals in my tap water
               }[] = [
             {key: "Note",       creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true }},
             {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, }},
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
-- 
cgit v1.2.3-70-g09d2


From bb11e8c07f86b1ba0148f1e406a070c14e9abc7c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 May 2024 15:59:52 -0400
Subject: lint cleanup and some flashcard fixes

---
 src/client/apis/gpt/setup.ts                       |   1 +
 src/client/util/CurrentUserUtils.ts                |   3 +-
 src/client/views/nodes/ComparisonBox.tsx           | 162 +++++++++------------
 src/client/views/nodes/DocumentView.tsx            |   6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 src/client/views/pdf/AnchorMenu.tsx                |  10 +-
 6 files changed, 80 insertions(+), 106 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/apis/gpt/setup.ts b/src/client/apis/gpt/setup.ts
index 831c97eaa..7084f38bf 100644
--- a/src/client/apis/gpt/setup.ts
+++ b/src/client/apis/gpt/setup.ts
@@ -5,6 +5,7 @@ export enum GPTCallType {
     SUMMARY = 'summary',
     COMPLETION = 'completion',
     EDIT = 'edit',
+    FLASHCARD = 'flashcard',
 }
 
 export type GPTCallOpts = {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b9c4d8b5f..e095bc659 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -354,8 +354,7 @@ 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: opts => 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: "Flashcard",  creator: opts => Docs.Create.ComparisonDocument("", opts),  opts: { _layout_isFlashcard: true, _width: 300, _height: 300}},
             {key: "Image",      creator: opts => Docs.Create.ImageDocument("", opts),       opts: { _width: 400, _height:400 }},
             {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}},
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 84d14d4ef..5dd3cc9b0 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -170,9 +170,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             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 => {
-                    return addDocument(doc);
-                };
+                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;
@@ -235,7 +233,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
      * 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;
+        if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] === 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side;
     };
 
     /**
@@ -248,12 +246,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 <div
                     className="formattedTextBox-alternateButton"
                     onPointerDown={e =>
-                        setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => {
+                        setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => {
                             console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]);
-                            if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') {
+                            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);
+                                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 ?? ''));
                             }
                         })
                     }
@@ -289,7 +287,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const queryText = questionText + ' UserAnswer: ' + this.inputValue + '. ' + rubricText;
 
         try {
-            let res = await gptAPICall(queryText, GPTCallType.QUIZ);
+            const res = await gptAPICall(queryText, GPTCallType.QUIZ);
             if (!res) {
                 console.error('GPT call failed');
                 return;
@@ -302,19 +300,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     };
 
     render() {
-        const clearButton = (which: string) => {
-            return (
-                <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 clearButton = (which: string) => (
+            <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 = (whichSlot: string) => {
             const whichDoc = DocCast(this.dataDoc[whichSlot]);
             const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
@@ -334,9 +330,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={() => NumCast(this.layoutDoc.width, 200)}
-                        NativeHeight={(): number => {
-                            return NumCast(this.layoutDoc.height, 200);
-                        }}
+                        NativeHeight={(): number => NumCast(this.layoutDoc.height, 200)}
                         isContentActive={emptyFunction}
                         isDocumentActive={returnFalse}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -358,112 +352,90 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             </div>
         );
 
-        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;
 
             // add text box to each side when comparison box is first created
-            if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) {
+            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.
+                // eslint-disable-next-line prefer-destructuring
                 newDoc[DocData].text = dataSplit[1];
                 this.addDoc(newDoc, this.fieldKey + '_0');
             }
-            if (!(this.dataDoc[this.fieldKey + '_1'] || this.dataDoc[this.fieldKey + '_1'] == 'empty')) {
+            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]);
                 // if there is text from the pdf ai cards, put the answer on the alternate side.
+                // eslint-disable-next-line prefer-destructuring
                 newDoc[DocData].text = dataSplit[0];
                 this.addDoc(newDoc, this.fieldKey + '_1');
             }
 
             // render the QuizCards
-            if (DocCast(this.Document.embedContainer) && DocCast(this.Document.embedContainer)[`filterOp`] == 'quiz') {
+            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 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>
                         <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'none' : 'flex' }}>
-                            <button onClick={this.handleRenderGPTClick}>Submit</button>
+                            <button type="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>
+                            <button type="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' }}
-                        onMouseEnter={() => {
-                            this.hoverFlip('alternate');
-                        }}
-                        onMouseLeave={() => {
-                            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)}
-                    <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
-                        {displayBox(`${this.fieldKey}_1`, 0, 0)}
-                    </div>
-
-                    <div
-                        className="slide-bar"
-                        style={{
-                            left: `calc(${this.clipWidth + '%'} - 0.5px)`,
-                            cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined,
-                        }}
-                        onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
-                    >
-                        <div className="slide-handle" />
-                    </div>
+                <div
+                    className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} /* change className to easily disable/enable pointer events in CSS */
+                    style={{ display: 'flex', flexDirection: 'column' }}
+                    onMouseEnter={() => {
+                        this.hoverFlip('alternate');
+                    }}
+                    onMouseLeave={() => {
+                        this.hoverFlip(undefined);
+                    }}>
+                    {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)}
+                    {this.overlayAlternateIcon}
                 </div>
             );
         }
+        // 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)}
+                <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
+                    {displayBox(`${this.fieldKey}_1`, 0, 0)}
+                </div>
+
+                <div
+                    className="slide-bar"
+                    style={{
+                        left: `calc(${this.clipWidth + '%'} - 0.5px)`,
+                        cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined,
+                    }}
+                    onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
+                >
+                    <div className="slide-handle" />
+                </div>
+            </div>
+        );
     }
 }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fca6cda81..7a1f94948 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -504,7 +504,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     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);
+            const res = await gptAPICall('Question: ' + StrCast(queryText), GPTCallType.CHATCARD);
             if (!res) {
                 console.error('GPT call failed');
                 return;
@@ -585,8 +585,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             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: 'Hover', event: () => { this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover'; }, icon: 'hand-point-up' }); // prettier-ignore
+                revealItems.push({ description: 'Flip', event: () => { this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip'; }, icon: 'rotate' }); // prettier-ignore
                 !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 9ccd95f24..9f2a9b8e1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -925,7 +925,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
             appearanceItems.push({
                 description: (this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate:hover' ? 'no hover' : 'hover') + ' to show alt content',
-                event: () => (this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usepath === 'alternate' || usepath === undefined ? 'alternate:hover' : undefined),
+                event: () => {
+                    this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usepath === 'alternate' || usepath === undefined ? 'alternate:hover' : undefined;
+                },
                 icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
             });
         }
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index b7247a034..745e809db 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -78,13 +78,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * Invokes the API with the selected text and stores it in the summarized text.
      * @param e pointer down event
      */
-    gptSummarize = async (e: React.PointerEvent) => {
+    gptSummarize = async () => {
         GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
         GPTPopup.Instance.setLoading(true);
 
         try {
-            const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
+            const res = await gptAPICall(this._selectedText, GPTCallType.SUMMARY);
             GPTPopup.Instance.setText(res || 'Something went wrong.');
         } catch (err) {
             console.error(err);
@@ -100,8 +100,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * Invokes the API with the selected text and stores it in the selected text.
      * @param e pointer down event
      */
-    gptFlashcards = async (e: React.PointerEvent) => {
-        const queryText = this.selectedText;
+    gptFlashcards = async () => {
+        const queryText = this._selectedText;
         try {
             const res = await gptAPICall(queryText, GPTCallType.FLASHCARD);
             console.log(res);
@@ -120,7 +120,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         // 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++) {
+        for (let 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];
-- 
cgit v1.2.3-70-g09d2


From 72a668d621346666c5e0bc35dd02e70f033ad8a8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 May 2024 16:38:31 -0400
Subject: restoring click to slide comparisonbox slider back and forth

---
 src/client/views/nodes/ComparisonBox.tsx | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 5dd3cc9b0..1bb6157b0 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -104,8 +104,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.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight();
+                    this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
+                    // this.layoutDoc[this.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight();
 
                     setTimeout(
                         action(() => {
@@ -298,6 +298,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             console.error('GPT call failed');
         }
     };
+    layoutWidth = () => NumCast(this.layoutDoc.width, 200);
+    layoutHeight = () => NumCast(this.layoutDoc.height, 200);
 
     render() {
         const clearButton = (which: string) => (
@@ -329,8 +331,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         containerViewPath={this.DocumentView?.().docViewPath}
                         moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2}
-                        NativeWidth={() => NumCast(this.layoutDoc.width, 200)}
-                        NativeHeight={(): number => NumCast(this.layoutDoc.height, 200)}
+                        NativeWidth={this.layoutWidth}
+                        NativeHeight={this.layoutHeight}
                         isContentActive={emptyFunction}
                         isDocumentActive={returnFalse}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-- 
cgit v1.2.3-70-g09d2


From 9caef373ab0563d4008488fabefa644360d2ecd9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 May 2024 16:47:14 -0400
Subject: restored old style of flashcards for now.

---
 src/client/views/nodes/ComparisonBox.tsx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

(limited to 'src/client/views/nodes/ComparisonBox.tsx')

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 1bb6157b0..adb380f12 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -189,7 +189,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     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
+     * 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
      */
@@ -316,8 +317,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const displayDoc = (whichSlot: string) => {
             const whichDoc = DocCast(this.dataDoc[whichSlot]);
             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 layoutString = !targetDoc && whichSlot.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined;
+            const layoutString = targetDoc ? '' : this.testForTextFields(whichSlot);
 
             return targetDoc || layoutString ? (
                 <>
-- 
cgit v1.2.3-70-g09d2

