From 88caa55967b1dbf670b156dd08efc4f559067af7 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Mon, 19 Feb 2024 16:19:05 -0500
Subject: ai (updates from sophie, then added dataviz summary)

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 32 +++++++++++++++++++++++-
 1 file changed, 31 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 66a08f13e..33b7dddfc 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -18,7 +18,7 @@ import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponen
 import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
-import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
+import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
 import { DocumentView } from '../DocumentView';
 import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { PinProps } from '../trails';
@@ -28,6 +28,8 @@ import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
 import { Checkbox } from '@mui/material';
+import { ContextMenu } from '../../ContextMenu';
+import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -43,6 +45,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [name: string]: IReactionDisposer } = {};
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+    sidebarAddDoc: ((doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean) | undefined;
     crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@@ -402,6 +405,32 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
         this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive
     }
 
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const cm = ContextMenu.Instance;
+        const options = cm.findByDescription('Options...');
+        const optionItems = options && 'subitems' in options ? options.subitems : [];
+        optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' });
+        !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
+    }
+
+
+    askGPT = action(async () => {
+        GPTPopup.Instance.setVisible(true);
+        GPTPopup.Instance.setSidebarId('data_sidebar');
+        GPTPopup.Instance.addDoc = this.sidebarAddDocument;
+        GPTPopup.Instance.setMode(GPTPopupMode.DATA);
+        GPTPopup.Instance.setLoading(true);
+        try {
+            let data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+            let input = JSON.stringify(data);
+            let res = await gptAPICall(input, GPTCallType.DATA);
+            GPTPopup.Instance.setText(res || 'Something went wrong.');
+        } catch (err) {
+            console.error(err);
+        }
+        GPTPopup.Instance.setLoading(false);
+    });
+
     render() {
         const scale = this._props.NativeDimScaling?.() || 1;
         return !this.records.length ? (
@@ -418,6 +447,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
                     transform: `scale(${scale})`,
                     position: 'absolute',
                 }}
+                onContextMenu={this.specificContextMenu}
                 onWheel={e => e.stopPropagation()}
                 ref={this._mainCont}>
                 <div className="datatype-button">
-- 
cgit v1.2.3-70-g09d2


From 54c597bb865aba15f16e0692c2f2119109d119aa Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Wed, 28 Feb 2024 19:24:22 -0500
Subject: chat with ai

---
 src/client/apis/gpt/GPT.ts                       |  5 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 16 ++----
 src/client/views/pdf/GPTPopup/GPTPopup.tsx       | 62 ++++++++++++++++++++----
 3 files changed, 59 insertions(+), 24 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 82436c42b..97a332294 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -33,7 +33,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
  * @param inputText Text to process
  * @returns AI Output
  */
-const gptAPICall = async (inputText: string, callType: GPTCallType) => {
+const gptAPICall = async (inputText: string, callType: GPTCallType, prompt?: any) => {
     if (callType === GPTCallType.SUMMARY) inputText += '.';
     const opts: GPTCallOpts = callTypeMap[callType];
     try {
@@ -43,8 +43,9 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
         };
         const openai = new OpenAI(configuration);
 
+        let usePrompt = prompt? prompt: opts.prompt;
         let messages: ChatCompletionMessageParam[] = [
-            { role: 'system', content: opts.prompt },
+            { role: 'system', content: usePrompt },
             { role: 'user', content: inputText },
         ];
 
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 33b7dddfc..0a7fe59a5 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -29,7 +29,6 @@ import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
 import { Checkbox } from '@mui/material';
 import { ContextMenu } from '../../ContextMenu';
-import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -415,20 +414,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
 
 
     askGPT = action(async () => {
-        GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.setSidebarId('data_sidebar');
         GPTPopup.Instance.addDoc = this.sidebarAddDocument;
         GPTPopup.Instance.setMode(GPTPopupMode.DATA);
-        GPTPopup.Instance.setLoading(true);
-        try {
-            let data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
-            let input = JSON.stringify(data);
-            let res = await gptAPICall(input, GPTCallType.DATA);
-            GPTPopup.Instance.setText(res || 'Something went wrong.');
-        } catch (err) {
-            console.error(err);
-        }
-        GPTPopup.Instance.setLoading(false);
+        let data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+        let input = JSON.stringify(data);
+        GPTPopup.Instance.setDataJson(input);
+        GPTPopup.Instance.generateDataAnalysis();
     });
 
     render() {
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 8c575748f..6301721e8 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, IconButton, Type } from 'browndash-components';
+import { Button, EditableText, IconButton, Size, Type } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -28,6 +28,7 @@ interface GPTPopupProps {}
 @observer
 export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     static Instance: GPTPopup;
+    @observable private chatMode: boolean = false;
 
     @observable
     public visible: boolean = false;
@@ -53,6 +54,13 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     public setSelectedText = (text: string) => {
         this.selectedText = text;
     };
+    @observable
+    public dataJson: string = '';
+    public dataChatPrompt: string | null = null;
+    @action
+    public setDataJson = (text: string) => {
+        this.dataJson = text;
+    };
 
     @observable
     public imgDesc: string = '';
@@ -86,6 +94,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     @action
     public setDone = (done: boolean) => {
         this.done = done;
+        this.chatMode = false;
     };
 
     // change what can be a ref into a ref
@@ -153,6 +162,18 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         GPTPopup.Instance.setLoading(false);
     }
 
+    generateDataAnalysis = async () => {
+        GPTPopup.Instance.setVisible(true);
+        GPTPopup.Instance.setLoading(true);
+        try {
+            let res = await gptAPICall(this.dataJson, GPTCallType.DATA, this.dataChatPrompt);
+            GPTPopup.Instance.setText(res || 'Something went wrong.');
+        } catch (err) {
+            console.error(err);
+        }
+        GPTPopup.Instance.setLoading(false);
+    }
+
     /**
      * Transfers the summarization text to a sidebar annotation text document.
      */
@@ -197,6 +218,16 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         DocUtils.MakeLink(textAnchor, newDoc, { link_relationship: 'Image Prompt' });
     };
 
+    /**
+     * Creates a chatbox for analyzing data so that users can ask specific questions.
+     */
+    private chatWithAI = () => {
+        this.chatMode = true;
+    }
+    dataPromptChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
+        this.dataChatPrompt = e.target.value;
+    });
+
     private getPreviewUrl = (source: string) => source.split('.').join('_m.');
 
     constructor(props: GPTPopupProps) {
@@ -310,22 +341,33 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             </div>
             {!this.loading && (
                 <div className="btns-wrapper">
-                    {this.done ? (
+                    {this.done? 
+                        this.chatMode?(
+                            <input
+                                defaultValue=""
+                                autoComplete="off"
+                                onChange={this.dataPromptChanged}
+                                onKeyDown={e => {
+                                    e.key === 'Enter' ? this.generateDataAnalysis() : null;
+                                    e.stopPropagation();
+                                }}
+                                type="text"
+                                placeholder="Ask GPT a question about the data..."
+                                id="search-input"
+                                className="searchBox-input"
+                                style={{width: "100%"}}
+                            />
+                            )
+                        :(
                         <>
                             <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+                            <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
                         </>
                     ) : (
                         <div className="summarizing">
                             <span>Summarizing</span>
                             <ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
-                            <Button
-                                text="Stop Animation"
-                                onClick={() => {
-                                    this.setDone(true);
-                                }}
-                                color={StrCast(Doc.UserDoc().userVariantColor)}
-                                type={Type.TERT}
-                            />
+                            <Button text="Stop Animation" onClick={() => {this.setDone(true);}} color={StrCast(Doc.UserDoc().userVariantColor)}  type={Type.TERT}/>
                         </div>
                     )}
                 </div>
-- 
cgit v1.2.3-70-g09d2


From dfef33c3f1cde9a24d734e0bd47e7f377a3ae4c9 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Tue, 5 Mar 2024 16:17:14 -0500
Subject: chat resets for new boxes to analyze

---
 src/client/views/nodes/DataVizBox/DataVizBox.scss | 4 ++++
 src/client/views/nodes/DataVizBox/DataVizBox.tsx  | 9 ++++++---
 src/client/views/pdf/GPTPopup/GPTPopup.tsx        | 1 +
 3 files changed, 11 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index 6b5738790..e9a346fbe 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -32,6 +32,10 @@
     .liveSchema-checkBox {
         margin-bottom: -35px;
     }
+    
+    .displaySchemaLive {
+        margin-bottom: 20px;
+    }
 
     .dataviz-sidebar {
         position: absolute;
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 0a7fe59a5..b4da41fa0 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -416,6 +416,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
     askGPT = action(async () => {
         GPTPopup.Instance.setSidebarId('data_sidebar');
         GPTPopup.Instance.addDoc = this.sidebarAddDocument;
+        GPTPopup.Instance.setDataJson("");
         GPTPopup.Instance.setMode(GPTPopupMode.DATA);
         let data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
         let input = JSON.stringify(data);
@@ -450,9 +451,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
                 </div>
 
                 {(this.layoutDoc && this.layoutDoc.dataViz_asSchema)?(
-                    <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
-                    <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
-                    Display Live Updates to Canvas
+                    <div className={'displaySchemaLive'}>
+                        <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
+                        <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
+                        Display Live Updates to Canvas
+                    </div>
                 </div>
                 ) : null}
 
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 5f399e86a..29b1ca365 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -59,6 +59,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     public dataChatPrompt: string | null = null;
     @action
     public setDataJson = (text: string) => {
+        if (text=="") this.dataChatPrompt = "";
         this.dataJson = text;
     };
 
-- 
cgit v1.2.3-70-g09d2

