From ed94cc8fb516455dbeea223d0fab25e4fb1c1d78 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Mon, 27 Feb 2023 16:56:02 -0500
Subject: feat: added summarization functionality to WebBox and cleaned up code

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 1 -
 1 file changed, 1 deletion(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 683ccb9c4..b9327db0d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -888,7 +888,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         
     }
 
-
     breakupDictation = () => {
         if (this._editorView && this._recording) {
             this.stopDictation(true);
-- 
cgit v1.2.3-70-g09d2


From f189ce6f25b91fcd402b7e81ba8ed378e39e6142 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Wed, 1 Mar 2023 23:33:01 -0500
Subject: Added text completion

---
 src/client/apis/gpt/Summarization.ts               |  45 ++++---
 .../nodes/formattedText/FormattedTextBox.scss      |   4 +
 .../views/nodes/formattedText/FormattedTextBox.tsx | 129 ++++++++++++++++-----
 src/client/views/pdf/AnchorMenu.tsx                |  26 ++++-
 src/client/views/pdf/PDFViewer.tsx                 |   1 -
 5 files changed, 154 insertions(+), 51 deletions(-)

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

diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts
index ba98ad591..b65736237 100644
--- a/src/client/apis/gpt/Summarization.ts
+++ b/src/client/apis/gpt/Summarization.ts
@@ -1,27 +1,41 @@
 import { Configuration, OpenAIApi } from 'openai';
 
+enum GPTCallType {
+    SUMMARY = 'summary',
+    COMPLETION = 'completion',
+}
+
+type GPTCallOpts = {
+    model: string;
+    maxTokens: number;
+    temp: number;
+    prompt: string;
+};
+
+const callTypeMap: { [type: string]: GPTCallOpts } = {
+    summary: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: 'Summarize this text: ' },
+    completion: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: '' },
+};
+
 /**
- * Summarizes the inputted text with OpenAI.
- * 
- * @param text Text to summarize
- * @returns Summary of text
+ * Calls the OpenAI API.
+ *
+ * @param inputText Text to process
+ * @returns AI Output
  */
-const gptSummarize = async (text: string) => {
-    text += '.';
-    const model = 'text-curie-001'; // Most advanced: text-davinci-003
-    const maxTokens = 200;
-    const temp = 0.5; 
-
+const gptAPICall = async (inputText: string, callType: GPTCallType) => {
+    if (callType === GPTCallType.SUMMARY) inputText += '.';
+    const opts: GPTCallOpts = callTypeMap[callType];
     try {
         const configuration = new Configuration({
             apiKey: process.env.OPENAI_KEY,
         });
         const openai = new OpenAIApi(configuration);
         const response = await openai.createCompletion({
-            model: model,
-            max_tokens: maxTokens,
-            temperature: temp,
-            prompt: `Summarize this text: ${text}`,
+            model: opts.model,
+            max_tokens: opts.maxTokens,
+            temperature: opts.temp,
+            prompt: `${opts.prompt}${inputText}`,
         });
         return response.data.choices[0].text;
     } catch (err) {
@@ -30,4 +44,5 @@ const gptSummarize = async (text: string) => {
     }
 };
 
-export { gptSummarize };
+
+export { gptAPICall, GPTCallType};
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index cbe0a465d..fd7fbb333 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -149,6 +149,10 @@ audiotag:hover {
     }
 }
 
+.gpt-typing-wrapper {
+    padding: 10px;
+}
+
 // .menuicon {
 //     display: inline-block;
 //     border-right: 1px solid rgba(0, 0, 0, 0.2);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b9327db0d..35c845deb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -64,12 +64,21 @@ import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
 import { Configuration, OpenAIApi } from 'openai';
+import { gptAPICall, GPTCallType } from '../../../apis/gpt/Summarization';
+import ReactLoading from 'react-loading';
+import Typist from 'react-typist';
 const translateGoogleApi = require('translate-google-api');
 
 export const GoogleRef = 'googleDocId';
 
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
+enum GPTStatus {
+    LOADING,
+    TYPING,
+    NONE,
+}
+
 @observer
 export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldStr: string) {
@@ -172,6 +181,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         };
     }
 
+    // State for GPT Typing animation
+    @observable
+    private gptStatus: GPTStatus = GPTStatus.NONE;
+    @observable
+    private gptPrompt: string = '';
+    @observable
+    private gptRes: string = '';
+
+    @action
+    private setGPTStatus = (status: GPTStatus) => {
+        this.gptStatus = status;
+    };
+
     public static PasteOnLoad: ClipboardEvent | undefined;
     public static SelectOnLoad = '';
     public static DontSelectInitialText = false; // whether initial text should be selected or not
@@ -840,14 +862,48 @@ 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: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' });
         optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
         this._downX = this._downY = Number.NaN;
     };
 
+    mockGPT = async (): Promise<string> => {
+        return new Promise((resolve, reject) => {
+            setTimeout(() => {
+                resolve('Mock GPT Call.');
+            }, 2000);
+        });
+    };
+
+    askGPT = action(async () => {
+        try {
+            this.gptPrompt = (this.dataDoc.text as RichTextField)?.Text;
+            this.setGPTStatus(GPTStatus.LOADING);
+            let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+            // let res = await this.mockGPT();
+            if (res) {
+                this.gptRes = res;
+                this.setGPTStatus(GPTStatus.TYPING);
+            } else {
+                this.setGPTStatus(GPTStatus.NONE);
+            }
+        } catch (err) {
+            console.log(err);
+            this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong';
+            this.setGPTStatus(GPTStatus.NONE);
+        }
+    });
+
+    setGPTText = action(() => {
+        this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes;
+        this.gptRes = '';
+        this.setGPTStatus(GPTStatus.NONE);
+    });
+
     generateImage = async () => {
-        console.log("Generate image from text: ", (this.dataDoc.text as RichTextField)?.Text);
+        console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
         try {
             const configuration = new Configuration({
                 apiKey: process.env.OPENAI_KEY,
@@ -856,17 +912,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             const response = await openai.createImage({
                 prompt: (this.dataDoc.text as RichTextField)?.Text,
                 n: 1,
-                size: "1024x1024",
+                size: '1024x1024',
             });
             let image_url = response.data.data[0].url;
             console.log(image_url);
             if (image_url) {
                 const newDoc = Docs.Create.ImageDocument(image_url, {
-                    x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, 
+                    x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10,
                     y: NumCast(this.rootDoc.y),
                     _height: 200,
-                    _width: 200
-                })
+                    _width: 200,
+                });
                 if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) {
                     newDoc.overlayX = this.rootDoc.x;
                     newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
@@ -878,15 +934,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     newDoc.data = image_url;
                     newDoc._height = 200;
                     newDoc._width = 200;
-                    DocUtils.MakeLink({doc: this.rootDoc}, {doc: newDoc}, "Dall-E");
-                }, 500)
+                    DocUtils.MakeLink({ doc: this.rootDoc }, { doc: newDoc }, 'Dall-E');
+                }, 500);
             }
         } catch (err) {
             console.log(err);
             return '';
         }
-        
-    }
+    };
 
     breakupDictation = () => {
         if (this._editorView && this._recording) {
@@ -1942,29 +1997,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     onPointerUp={this.onPointerUp}
                     onPointerDown={this.onPointerDown}
                     onDoubleClick={this.onDoubleClick}>
-                    <div
-                        className={`formattedTextBox-outer${selected ? '-selected' : ''}`}
-                        ref={this._scrollRef}
-                        style={{
-                            width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
-                            pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined,
-                            overflow: this.layoutDoc._singleLine ? 'hidden' : this.layoutDoc._autoHeight ? 'visible' : undefined,
-                        }}
-                        onScroll={this.onScroll}
-                        onDrop={this.ondrop}>
+                    {this.gptStatus === GPTStatus.NONE || this.gptStatus === GPTStatus.LOADING ? (
                         <div
-                            className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`}
-                            ref={this.createDropTarget}
+                            className={`formattedTextBox-outer${selected ? '-selected' : ''}`}
+                            ref={this._scrollRef}
                             style={{
-                                padding: StrCast(this.layoutDoc._textBoxPadding),
-                                paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
-                                paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
-                                paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,
+                                width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
+                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined,
+                                overflow: this.layoutDoc._singleLine ? 'hidden' : this.layoutDoc._autoHeight ? 'visible' : undefined,
                             }}
-                        />
-                    </div>
+                            onScroll={this.onScroll}
+                            onDrop={this.ondrop}>
+                            <div
+                                className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`}
+                                ref={this.createDropTarget}
+                                style={{
+                                    padding: StrCast(this.layoutDoc._textBoxPadding),
+                                    paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
+                                    paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
+                                    paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
+                                    paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
+                                    pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,
+                                }}
+                            />
+                        </div>
+                    ) : (
+                        <div className="gpt-typing-wrapper">
+                            <div>{this.gptPrompt}</div>
+                            <br />
+                            <Typist
+                                key={this.gptRes}
+                                avgTypingDelay={15}
+                                cursor={{ hideWhenDone: true }}
+                                onTypingDone={() => {
+                                    this.setGPTText();
+                                }}>
+                                <div>{this.gptRes}</div>
+                            </Typist>
+                        </div>
+                    )}
                     {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
                     {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
                     {this.audioHandle}
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 8f9261614..04904b3b1 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -10,7 +10,7 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
 import { LinkPopup } from '../linking/LinkPopup';
 import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu';
-import { gptSummarize } from '../../apis/gpt/Summarization';
+import { gptAPICall, GPTCallType } from '../../apis/gpt/Summarization';
 import { GPTPopup } from './GPTPopup';
 import './AnchorMenu.scss';
 
@@ -136,16 +136,17 @@ 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
      */
-    invokeGPT = async (e: React.PointerEvent) => {
+    gptSummarize = async (e: React.PointerEvent) => {
         this.setGPTPopupVis(true);
         this.setLoading(true);
-        const res = await gptSummarize(this.selectedText);
+        const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
         // const res = await this.mockSummarize();
         if (res) {
             this.setSummarizedText(res);
         } else {
             this.setSummarizedText('Something went wrong.');
         }
+
         this.setLoading(false);
     };
 
@@ -243,6 +244,19 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         this.highlightColor = Utils.colorString(col);
     };
 
+    /**
+     * Returns whether the selected text can be summarized. The goal is to have
+     * all selected text available to summarize but its only supported for pdf and web ATM.
+     * @returns Whether the GPT icon for summarization should appear
+     */
+    canSummarize = (): boolean => {
+        const docs = SelectionManager.Docs();
+        if (docs.length > 0) {
+            return docs[0].type === 'pdf' || docs[0].type === 'web';
+        }
+        return false;
+    };
+
     render() {
         const buttons =
             this.Status === 'marquee' ? (
@@ -254,14 +268,14 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         </button>
                     </Tooltip>
                     {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
-                    {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && (
+                    {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
                         <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with AI</div>}>
-                            <button className="antimodeMenu-button annotate" onPointerDown={this.invokeGPT} style={{ cursor: 'grab' }}>
+                            <button className="antimodeMenu-button annotate" onPointerDown={this.gptSummarize} style={{ cursor: 'grab' }}>
                                 <FontAwesomeIcon icon="comment-dots" size="lg" />
                             </button>
                         </Tooltip>
                     )}
-                    <GPTPopup key="gptpopup" visible={this.showGPTPopup} text={this.summarizedText} loadingSummary={this.loadingSummary} callApi={this.invokeGPT} />
+                    <GPTPopup key="gptpopup" visible={this.showGPTPopup} text={this.summarizedText} loadingSummary={this.loadingSummary} callApi={this.gptSummarize} />
                     {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
                         <Tooltip key="annoaudiotate" title={<div className="dash-tooltip">Click to Record Annotation</div>}>
                             <button className="antimodeMenu-button annotate" onPointerDown={this.audioDown} style={{ cursor: 'grab' }}>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6e268c561..88854debe 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -24,7 +24,6 @@ import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import './PDFViewer.scss';
 import React = require('react');
-import { gptSummarize } from '../../apis/gpt/Summarization';
 import { GPTPopup } from './GPTPopup';
 const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer');
 const pdfjsLib = require('pdfjs-dist');
-- 
cgit v1.2.3-70-g09d2


From d34d212ea550a3c1ca16747fadeb9e69c936cb5d Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Wed, 1 Mar 2023 23:49:11 -0500
Subject: feat: simple typing animation

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 97 +++++++---------------
 1 file changed, 31 insertions(+), 66 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 35c845deb..65b96c281 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -65,20 +65,12 @@ import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
 import { Configuration, OpenAIApi } from 'openai';
 import { gptAPICall, GPTCallType } from '../../../apis/gpt/Summarization';
-import ReactLoading from 'react-loading';
-import Typist from 'react-typist';
 const translateGoogleApi = require('translate-google-api');
 
 export const GoogleRef = 'googleDocId';
 
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
-enum GPTStatus {
-    LOADING,
-    TYPING,
-    NONE,
-}
-
 @observer
 export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldStr: string) {
@@ -181,19 +173,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         };
     }
 
-    // State for GPT Typing animation
-    @observable
-    private gptStatus: GPTStatus = GPTStatus.NONE;
-    @observable
-    private gptPrompt: string = '';
+    // State for GPT
     @observable
     private gptRes: string = '';
 
-    @action
-    private setGPTStatus = (status: GPTStatus) => {
-        this.gptStatus = status;
-    };
-
     public static PasteOnLoad: ClipboardEvent | undefined;
     public static SelectOnLoad = '';
     public static DontSelectInitialText = false; // whether initial text should be selected or not
@@ -877,31 +860,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         });
     };
 
+    animateRes = (resIndex: number) => {
+        if (resIndex < this.gptRes.length) {
+            this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex];
+            setTimeout(() => {
+                this.animateRes(resIndex + 1);
+            }, 20);
+        }
+    };
+
     askGPT = action(async () => {
         try {
-            this.gptPrompt = (this.dataDoc.text as RichTextField)?.Text;
-            this.setGPTStatus(GPTStatus.LOADING);
             let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
             // let res = await this.mockGPT();
             if (res) {
                 this.gptRes = res;
-                this.setGPTStatus(GPTStatus.TYPING);
-            } else {
-                this.setGPTStatus(GPTStatus.NONE);
+                this.animateRes(0);
             }
         } catch (err) {
             console.log(err);
             this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong';
-            this.setGPTStatus(GPTStatus.NONE);
         }
     });
 
-    setGPTText = action(() => {
-        this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes;
-        this.gptRes = '';
-        this.setGPTStatus(GPTStatus.NONE);
-    });
-
     generateImage = async () => {
         console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
         try {
@@ -1997,45 +1978,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     onPointerUp={this.onPointerUp}
                     onPointerDown={this.onPointerDown}
                     onDoubleClick={this.onDoubleClick}>
-                    {this.gptStatus === GPTStatus.NONE || this.gptStatus === GPTStatus.LOADING ? (
+                    <div
+                        className={`formattedTextBox-outer${selected ? '-selected' : ''}`}
+                        ref={this._scrollRef}
+                        style={{
+                            width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
+                            pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined,
+                            overflow: this.layoutDoc._singleLine ? 'hidden' : this.layoutDoc._autoHeight ? 'visible' : undefined,
+                        }}
+                        onScroll={this.onScroll}
+                        onDrop={this.ondrop}>
                         <div
-                            className={`formattedTextBox-outer${selected ? '-selected' : ''}`}
-                            ref={this._scrollRef}
+                            className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`}
+                            ref={this.createDropTarget}
                             style={{
-                                width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
-                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined,
-                                overflow: this.layoutDoc._singleLine ? 'hidden' : this.layoutDoc._autoHeight ? 'visible' : undefined,
+                                padding: StrCast(this.layoutDoc._textBoxPadding),
+                                paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
+                                paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
+                                paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
+                                paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
+                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,
                             }}
-                            onScroll={this.onScroll}
-                            onDrop={this.ondrop}>
-                            <div
-                                className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`}
-                                ref={this.createDropTarget}
-                                style={{
-                                    padding: StrCast(this.layoutDoc._textBoxPadding),
-                                    paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
-                                    paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
-                                    paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                    paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                    pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,
-                                }}
-                            />
-                        </div>
-                    ) : (
-                        <div className="gpt-typing-wrapper">
-                            <div>{this.gptPrompt}</div>
-                            <br />
-                            <Typist
-                                key={this.gptRes}
-                                avgTypingDelay={15}
-                                cursor={{ hideWhenDone: true }}
-                                onTypingDone={() => {
-                                    this.setGPTText();
-                                }}>
-                                <div>{this.gptRes}</div>
-                            </Typist>
-                        </div>
-                    )}
+                        />
+                    </div>
                     {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
                     {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
                     {this.audioHandle}
-- 
cgit v1.2.3-70-g09d2

