aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsrichman333 <sarah_n_richman@brown.edu>2024-02-19 16:19:05 -0500
committersrichman333 <sarah_n_richman@brown.edu>2024-02-19 16:19:05 -0500
commit88caa55967b1dbf670b156dd08efc4f559067af7 (patch)
tree137580d6fcddf8dc970a2920569a00be6433f8f8 /src
parent89bb6c977e57f696fd96e9e79bcb44840b20cb49 (diff)
ai (updates from sophie, then added dataviz summary)
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/gpt/GPT.ts32
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx32
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx34
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx11
4 files changed, 88 insertions, 21 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index cea862330..a0de6fa1f 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -1,9 +1,11 @@
import { ClientOptions, OpenAI } from 'openai';
+import { ChatCompletionMessageParam } from 'openai/resources';
enum GPTCallType {
SUMMARY = 'summary',
COMPLETION = 'completion',
EDIT = 'edit',
+ DATA = 'data',
}
type GPTCallOpts = {
@@ -13,10 +15,16 @@ type GPTCallOpts = {
prompt: string;
};
+/**
+ * Replace completions (deprecated) with chat
+ */
+
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: '' },
+ // newest model: gpt-4
+ summary: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize the text given in simpler terms.' },
+ edit: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' },
+ completion: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." },
+ data: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Analyze the user's data." },
};
/**
@@ -34,13 +42,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;
+ const content = response.choices[0].message.content;
+ return content;
} catch (err) {
console.log(err);
return 'Error connecting with API.';
@@ -60,8 +75,7 @@ const gptImageCall = async (prompt: string, n?: number) => {
n: n ?? 1,
size: '1024x1024',
});
- return response.data.map((data: any) => data.url);
- // return response.data.data[0].url;
+ return response.data.map(data => data.url);
} catch (err) {
console.error(err);
return;
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">
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f2c4c6c8f..8d5b0218d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -8,7 +8,7 @@ import { history } from 'prosemirror-history';
import { inputRules } from 'prosemirror-inputrules';
import { keymap } from 'prosemirror-keymap';
import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
-import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
+import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
@@ -972,9 +972,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
animateRes = (resIndex: number, newText: string) => {
+ if (!this._editorView) return;
if (resIndex < newText.length) {
- const marks = this._editorView?.state.storedMarks ?? [];
- this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText(newText[resIndex]).setStoredMarks(marks));
+ const marks = this._editorView.state.storedMarks ?? [];
+ this._editorView.dispatch(this._editorView.state.tr.insertText(newText[resIndex]).setStoredMarks(marks));
setTimeout(() => {
this.animateRes(resIndex + 1, newText);
}, 20);
@@ -983,15 +984,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
askGPT = action(async () => {
try {
- let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+ // let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+
+ let data = {
+ "Weight":[69,69,65,72,67,73,70,75,74,65,73,70,74,68,74,65,69,75,67,74,66,70,69,68,67,71,67,70,74,71,73,66,72,73,68,69,69,74,70,73],
+ "Size":[4.39,4.21,4.09,5.85,4.7,5.68,5.56,5.11,5.36,4.27,5.79,5.47,5.53,4.47,5.22,4.48,4.66,5.25,4.18,5.5,4.13,4.83,4.61,4.08,4.25,5.35,4.01,4.22,5.25,5.26,5.78,4.68,5.72,5.17,4.83,4.11,4.76,5.48,5.59,5.03],
+ "Class":["orange","orange","orange","apple","orange","apple","apple","apple","apple","orange","apple","apple","apple","orange","apple","orange","orange","apple","orange","apple","orange","orange","orange","orange","orange","apple","orange","orange","apple","apple","apple","orange","apple","apple","orange","orange","orange","apple","apple","apple"]
+ }
+ let input = JSON.stringify(data);
+
+
+ let res = await gptAPICall(input, GPTCallType.DATA);
+ console.log('GPT Result: ', res);
+ console.log(res);
if (!res) {
- console.error('GPT call failed');
this.animateRes(0, 'Something went wrong.');
} else {
- this.animateRes(0, res);
+ if (!this._editorView) return;
+ // No animation
+ // this._editorView.dispatch(this._editorView.state.tr.insertText(res));
+
+ // Animation
+ // Set selection at end
+ const sel = Selection.atEnd(this._editorView.state.doc);
+ this._editorView.dispatch(this._editorView.state.tr.setSelection(sel));
+ this.animateRes(0, '\n\n' + res);
}
} catch (err) {
- console.error('GPT call failed');
+ console.error(err);
this.animateRes(0, 'Something went wrong.');
}
});
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index da8a88803..6e5f48a2f 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -20,6 +20,7 @@ export enum GPTPopupMode {
SUMMARY,
EDIT,
IMAGE,
+ DATA,
}
interface GPTPopupProps {}
@@ -118,14 +119,16 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
try {
let image_urls = await gptImageCall(this.imgDesc);
+ console.log('Image urls: ', image_urls);
if (image_urls && image_urls[0]) {
const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
+ console.log('Upload result: ', result);
const source = Utils.prepend(result.accessPaths.agnostic.client);
+ console.log('Upload source: ', source);
this.setImgUrls([[image_urls[0], source]]);
}
} catch (err) {
- console.log(err);
- return '';
+ console.error(err);
}
this.setLoading(false);
};
@@ -241,7 +244,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
summaryBox = () => (
<>
<div>
- {this.heading('SUMMARY')}
+ {this.mode==GPTPopupMode.SUMMARY? this.heading('SUMMARY'): this.heading("ANALYSIS")}
<div className="content-wrapper">
{!this.loading &&
(!this.done ? (
@@ -308,7 +311,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.mode === GPTPopupMode.DATA)? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>}
</div>
);
}