From 39acf8be3a3c245b6f525360ccc8673da632f960 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 12 Feb 2025 13:54:42 -0500 Subject: chat box fits width by default --- src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx index f8fe531ab..16da360fc 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx @@ -1046,5 +1046,5 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { */ Docs.Prototypes.TemplateMap.set(DocumentType.CHAT, { layout: { view: ChatBox, dataField: 'data' }, - options: { acl: '', chat: '', chat_history: '', chat_thread_id: '', chat_assistant_id: '', chat_vector_store_id: '' }, + options: { acl: '', _layout_fitWidth: true, chat: '', chat_history: '', chat_thread_id: '', chat_assistant_id: '', chat_vector_store_id: '' }, }); -- cgit v1.2.3-70-g09d2 From 105eedb39e85bc240451b429608e153e5f154b1e Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 12 Feb 2025 14:47:16 -0500 Subject: made chatBox fit width by default --- src/client/util/CurrentUserUtils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 0783bb80e..6ca181d92 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -402,8 +402,8 @@ pie title Minerals in my tap water {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, isSystem: true, cloneFieldFilter: new List(["isSystem"]) }}, {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title_custom: true, waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, - {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }}, - {key: "Chat", creator: Docs.Create.ChatDocument, opts: { _width: 500, _height: 500, }}, + {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("", opts), opts: { _width: 300, _height: 300 }}, + {key: "Chat", creator: Docs.Create.ChatDocument, opts: { _width: 500, _height: 500, _layout_fitWidth: true, }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}}, {key: "ViewSlide", creator: slideView, opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}}, {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, _layout_dontCenter:'xy', dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }}, @@ -832,7 +832,7 @@ pie title Minerals in my tap water CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.NoteTaking, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.Calendar, CollectionViewType.Grid, CollectionViewType.Tree, CollectionViewType.Time, ]), - title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, shiftKey, _readOnly_); }'}}, + title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, shiftKey, _readOnly_); }'}}, { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}}, { title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} }, { title: "Template",icon: "scroll", toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF, scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} }, @@ -844,8 +844,8 @@ pie title Minerals in my tap water { title: "Chat", icon:"lightbulb", toolTip: "Toggle Chat Assistant",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat", funcs: {}, width: 30, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} }, { title: "Filter", icon: "=", toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.filterTools(), ignoreClick:true, toolType:DocumentType.COL, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string}, { title: "Sort", icon: "Sort", toolTip: "Sort Documents", subMenu: CurrentUserUtils.sortTools(), expertMode: false, toolType:DocumentType.COL, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available - { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available - { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available + { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available + { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:DocumentType.COL, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available -- cgit v1.2.3-70-g09d2 From fed9d4964c3d7074f9e78d873e2d5b08dcac6c13 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 12 Feb 2025 21:03:47 -0500 Subject: title image groups better --- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 5524fedb3..9cfb0416c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -461,6 +461,7 @@ export class MarqueeView extends ObservableReactComponent Date: Thu, 13 Feb 2025 10:19:35 -0500 Subject: trying to cleanup GPTpopup --- src/client/apis/gpt/GPT.ts | 1 - src/client/views/pdf/AnchorMenu.tsx | 14 +-- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 159 +++++++++++++---------------- 3 files changed, 74 insertions(+), 100 deletions(-) (limited to 'src') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 1894bb4df..dc4607b94 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -187,7 +187,6 @@ const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: s max_tokens: opts.maxTokens, }); lastResp = response.choices[0].message.content ?? ''; - console.log('RESP:' + lastResp); return lastResp; } catch (err) { console.log(err); diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 11f2f7988..18da01890 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -98,18 +98,14 @@ export class AnchorMenu extends AntimodeMenu { * Invokes the API with the selected text and stores it in the summarized text. * @param e pointer down event */ - gptSummarize = async () => { + gptSummarize = () => { GPTPopup.Instance.setVisible(true); GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY); GPTPopup.Instance.setLoading(true); - - try { - const res = await gptAPICall(this._selectedText, GPTCallType.SUMMARY); - GPTPopup.Instance.setText(res || 'Something went wrong.'); - } catch (err) { - console.error(err); - } - GPTPopup.Instance.setLoading(false); + gptAPICall(this._selectedText, GPTCallType.SUMMARY) + .then(res => GPTPopup.Instance.setText(res || 'Something went wrong.')) + .catch(err => console.error(err)) + .finally(() => GPTPopup.Instance.setLoading(false)); }; /* diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index f5a9f9e6a..628766209 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -19,6 +19,7 @@ import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocumentView } from '../../nodes/DocumentView'; import { AnchorMenu } from '../AnchorMenu'; import './GPTPopup.scss'; +import { isJSDocProtectedTag } from 'typescript'; export enum GPTPopupMode { SUMMARY, @@ -116,17 +117,12 @@ export class GPTPopup extends ObservableReactComponent { onSortComplete?: (sortResult: string, questionType: string, tag?: string) => void; onQuizRandom?: () => void; - @observable cardsDoneLoading = false; @observable collectionDoc: Doc | undefined = undefined; @action setCollectionDoc(doc: Doc | undefined) { this.collectionDoc = doc; } - @action setCardsDoneLoading(done: boolean) { - this.cardsDoneLoading = done; - } - @observable sortRespText: string = ''; @action setSortRespText(resp: string) { @@ -151,31 +147,22 @@ export class GPTPopup extends ObservableReactComponent { * When the cards are in quiz mode in the card view, allows gpt to determine whether the user's answer was correct * @returns */ - generateQuiz = async () => { - this.setLoading(true); - - await this.regenerateCallback?.(); - - const selected = DocumentView.SelectedDocs().lastElement(); - if (!StrCast(selected.gptRubric)) { - await this.generateRubric(StrCast(selected.gptInputText), selected); - } - - try { - const res = await gptAPICall('Question: ' + StrCast(selected.gptInputText) + ' UserAnswer: ' + this.quizAnswer + '. Rubric: ' + StrCast(selected.gptRubric), GPTCallType.QUIZ); - if (res) { - this.setQuizResp(res); - this.conversationArray.push(res); - - this.setLoading(false); - this.onQuizRandom?.(); - } else { - console.error('GPT provided no response'); - } - } catch (err) { - console.error('GPT call failed', err); - } - }; + generateQuiz = (selected: Doc) => + (this.regenerateCallback?.() ?? Promise.resolve()).then(() => + this.generateRubric(selected).then(() => + gptAPICall('Question: ' + StrCast(selected.gptInputText) + ' UserAnswer: ' + this.quizAnswer + '. Rubric: ' + StrCast(selected.gptRubric), GPTCallType.QUIZ) + .then(res => { + if (res) { + this.setQuizResp(res); + this.conversationArray.push(res); + this.onQuizRandom?.(); + } else { + console.error('GPT provided no response'); + } + }) + .catch(err => console.error('GPT call failed', err)) + ) + ); /** * Generates a rubric by which to compare the user's answer to @@ -183,15 +170,12 @@ export class GPTPopup extends ObservableReactComponent { * @param doc the doc the user is providing info about * @returns gpt's response */ - generateRubric = async (inputText: string, doc: Doc) => { - try { - const res = await gptAPICall(inputText, GPTCallType.RUBRIC); - doc.gptRubric = res; - return res; - } catch (err) { - console.error('GPT call failed', err); - } - }; + generateRubric = (doc: Doc) => + StrCast(doc.gptRubric) + ? Promise.resolve(StrCast(doc.gptRubric)) + : gptAPICall(StrCast(doc.gptInputText), GPTCallType.RUBRIC) + .then(res => (doc.gptRubric = res)) + .catch(err => console.error('GPT call failed', err)); @observable private regenerateCallback: (() => Promise) | null = null; @@ -466,9 +450,11 @@ export class GPTPopup extends ObservableReactComponent { ); } else { this.conversationArray.push(this.quizAnswer); - this.generateQuiz().then( + this.setLoading(true); + this.generateQuiz(DocumentView.SelectedDocs().lastElement()).then( action(() => { this.quizAnswer = ''; + this.setLoading(false); }) ); } @@ -477,60 +463,53 @@ export class GPTPopup extends ObservableReactComponent { } }; - cardActual = (opt: GPTPopupMode) => { - const isSort = opt === GPTPopupMode.SORT; - return ( -
-
-
- {this.conversationArray.map((message, index) => ( -
- {message} -
- ))} - {(!this.cardsDoneLoading || this.loading) &&
...
} -
- -
+ cardActual = (isSort: boolean) => ( +
+
+
+ {this.conversationArray.map((message, index) => ( +
+ {message} +
+ ))} + {this.loading &&
...
}
-
- { - this.handleKeyPress(e, isSort); - }} - type="text" - placeholder={`${isSort ? 'Have ChatGPT sort, tag, define, or filter your cards for you!' : 'Define the selected card!'}`} - /> -
+
- ); - }; +
+ { + this.handleKeyPress(e, isSort); + }} + type="text" + placeholder={`${isSort ? 'Have ChatGPT sort, tag, define, or filter your cards for you!' : 'Define the selected card!'}`} + /> +
+
+ ); - sortBox = () => ( + sortBox = (isSort: boolean) => (
- {this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')} - <> - { - !this.cardsDoneLoading ? ( -
-
- - {this.loading ? Loading... : Reading Cards...} -
-
- ) : this.mode === GPTPopupMode.CARD ? ( - this.cardMenu() - ) : ( - this.cardActual(this.mode) - ) // Call the functions to render JSX - } - + {this.heading(isSort ? 'SORTING' : 'QUIZ')} + {!this.cardsDoneLoading ? ( +
+
+ a + + {this.loading ? Loading... : Reading Cards...} +
+
+ ) : this.mode === GPTPopupMode.CARD ? ( + this.cardMenu() + ) : ( + this.cardActual(isSort) + )}
); @@ -724,7 +703,7 @@ export class GPTPopup extends ObservableReactComponent { case GPTPopupMode.SORT: case GPTPopupMode.CARD: case GPTPopupMode.QUIZ: - content = this.sortBox(); + content = this.sortBox(this.mode === GPTPopupMode.SORT); break; default: content = null; -- cgit v1.2.3-70-g09d2 From 6b58fb76b11f21648ca2bc551f97b6e09e4be13b Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 13 Feb 2025 10:21:35 -0500 Subject: from last --- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 628766209..2d95ac2eb 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -497,19 +497,7 @@ export class GPTPopup extends ObservableReactComponent { sortBox = (isSort: boolean) => (
{this.heading(isSort ? 'SORTING' : 'QUIZ')} - {!this.cardsDoneLoading ? ( -
-
- a - - {this.loading ? Loading... : Reading Cards...} -
-
- ) : this.mode === GPTPopupMode.CARD ? ( - this.cardMenu() - ) : ( - this.cardActual(isSort) - )} + {this.mode === GPTPopupMode.CARD ? this.cardMenu() : this.cardActual(isSort)}
); -- cgit v1.2.3-70-g09d2 From 3f6a168b2916ccac707cf5ea1e4ef898a470d7d1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 13 Feb 2025 23:27:42 -0500 Subject: lots of cleanup in GPTpopup. --- src/client/apis/gpt/GPT.ts | 20 +- .../views/collections/CollectionCardDeckView.tsx | 20 +- src/client/views/global/globalScripts.ts | 5 - src/client/views/nodes/DataVizBox/DataVizBox.tsx | 4 +- src/client/views/nodes/WebBox.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 +- src/client/views/pdf/AnchorMenu.tsx | 17 +- src/client/views/pdf/GPTPopup/GPTPopup.scss | 45 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 629 ++++++++------------- src/client/views/pdf/PDFViewer.tsx | 2 +- 10 files changed, 285 insertions(+), 467 deletions(-) (limited to 'src') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index dc4607b94..066510a6a 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -1,6 +1,14 @@ import { ChatCompletionMessageParam, Image } from 'openai/resources'; import { openai } from './setup'; +export enum GPTTypeStyle { + AssignTags = 1, + Filter = 2, + DocInfo = 3, + ChooseDoc = 4, + GeneralInfo = 5, + SortDocs = 6, +} enum GPTCallType { SUMMARY = 'summary', COMPLETION = 'completion', @@ -127,12 +135,12 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { temp: 0, prompt: `I'm going to provide you with a question. Based on the question, is the user asking you to - 1. Assigns docs with tags(like star / heart etc)/labels, - 2. Filter docs, - 3. Provide information about a specific doc - 4. Provide a specific doc based on a question/information - 5. Provide general information - 6. Put cards in a specific order. + ${GPTTypeStyle.AssignTags}. Assigns docs with tags(like star / heart etc)/labels, + ${GPTTypeStyle.ChooseDoc}. Filter docs, + ${GPTTypeStyle.DocInfo}. Provide information about a specific doc + ${GPTTypeStyle.Filter}. Provide a specific doc based on a question/information + ${GPTTypeStyle.GeneralInfo}. Provide general information + ${GPTTypeStyle.SortDocs}. Put cards in a specific order. Answer with only the number for 2-6. For number one, provide the number (1) and the appropriate tag`, }, subset: { diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 43464e50c..a3ec3884b 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -12,7 +12,7 @@ import { List } from '../../../fields/List'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types'; import { URLField } from '../../../fields/URLField'; -import { gptImageLabel } from '../../apis/gpt/GPT'; +import { gptImageLabel, GPTTypeStyle } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; @@ -80,7 +80,7 @@ export class CollectionCardView extends CollectionSubView() { childPairStringListAndUpdateSortDesc = () => this.childPairStringList().then(sortDesc => { GPTPopup.Instance.setSortDesc(sortDesc.join()); - GPTPopup.Instance.onSortComplete = this.processGptOutput; + GPTPopup.Instance.onGptResponse = this.processGptResponse; GPTPopup.Instance.onQuizRandom = this.quizMode; }); @@ -113,7 +113,7 @@ export class CollectionCardView extends CollectionSubView() { onGptHide = () => Doc.setDocFilter(this.Document, 'tags', '#chat', 'remove'); componentWillUnmount() { GPTPopup.Instance.setSortDesc(''); - GPTPopup.Instance.onSortComplete = undefined; + GPTPopup.Instance.onGptResponse = undefined; GPTPopup.Instance.onQuizRandom = undefined; GPTPopup.Instance.setRegenerateCallback(undefined, null); Object.keys(this._disposers).forEach(key => this._disposers[key]?.()); @@ -443,18 +443,18 @@ export class CollectionCardView extends CollectionSubView() { * @param questionType * @param tag */ - processGptOutput = (gptOutput: string, questionType: string, tag?: string) => + processGptResponse = (gptOutput: string, questionType: GPTTypeStyle, tag?: string) => undoable(() => { // Split the string into individual list items const listItems = gptOutput.split('======').filter(item => item.trim() !== ''); - if (questionType === '2' || questionType === '4') { + if (questionType === GPTTypeStyle.Filter || questionType === GPTTypeStyle.ChooseDoc) { this.childDocs.forEach(d => { TagItem.removeTagFromDoc(d, '#chat'); }); } - if (questionType === '6') { + if (questionType === GPTTypeStyle.SortDocs) { this.Document[this._props.fieldKey + '_sort'] = docSortings.Chat; } @@ -464,18 +464,18 @@ export class CollectionCardView extends CollectionSubView() { const doc = this._textToDoc.get(normalizedItem); if (doc) { switch (questionType) { - case '6': + case GPTTypeStyle.SortDocs: doc.chatIndex = index; break; - case '1': + case GPTTypeStyle.AssignTags: if (tag) { const hashTag = tag.startsWith('#') ? tag : '#' + tag[0].toLowerCase() + tag.slice(1); const filterTag = Doc.MyFilterHotKeys.map(key => StrCast(key.toolType)).find(key => key.includes(tag)) ?? hashTag; TagItem.addTagToDoc(doc, filterTag); } break; - case '2': - case '4': + case GPTTypeStyle.Filter: + case GPTTypeStyle.ChooseDoc: TagItem.addTagToDoc(doc, '#chat'); Doc.setDocFilter(this.Document, 'tags', '#chat', 'check'); break; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index b44292164..029c4dbc7 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -228,15 +228,10 @@ ScriptingGlobals.add(function showFreeform( if (GPTPopup.Instance.Visible){ doc[Doc.LayoutFieldKey(doc)+"_sort"] = ''; GPTPopup.Instance.setVisible(false); - } else { GPTPopup.Instance.setVisible(true); GPTPopup.Instance.setMode(GPTPopupMode.CARD); - GPTPopup.Instance.setCardsDoneLoading(true); - } - - }, }], ['toggle-tags', { diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index b874d077b..fa3ab73a7 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -489,7 +489,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { } // Changing which document to add the annotation to (the currently selected PDF) - GPTPopup.Instance.setSidebarId('data_sidebar'); + GPTPopup.Instance.setSidebarFieldKey('data_sidebar'); GPTPopup.Instance.addDoc = this.sidebarAddDocument; }; @@ -523,7 +523,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { }; askGPT = action(async () => { - GPTPopup.Instance.setSidebarId('data_sidebar'); + GPTPopup.Instance.setSidebarFieldKey('data_sidebar'); GPTPopup.Instance.addDoc = this.sidebarAddDocument; GPTPopup.Instance.createFilteredDoc = this.createFilteredDoc; GPTPopup.Instance.setDataJson(''); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 6026d9ca7..e7a10cc29 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -383,7 +383,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() { this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined); AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale); // Changing which document to add the annotation to (the currently selected WebBox) - GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`); + GPTPopup.Instance.setSidebarFieldKey(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`); GPTPopup.Instance.addDoc = this.sidebarAddDocument; } } else { @@ -446,7 +446,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() { this._textAnnotationCreator = () => this.createTextAnnotation(sel, selRange); (!sel.isCollapsed || this.marqueeing) && AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); // Changing which document to add the annotation to (the currently selected WebBox) - GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`); + GPTPopup.Instance.setSidebarFieldKey(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`); GPTPopup.Instance.addDoc = this.sidebarAddDocument; } }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index eb1f9d07b..17b9bce47 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -135,7 +135,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { try { - GPTPopup.Instance.setSidebarId(this.sidebarKey); + GPTPopup.Instance.setSidebarFieldKey(this.sidebarKey); GPTPopup.Instance.addDoc = this.sidebarAddDocument; const res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); if (!res) { @@ -1660,7 +1660,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - GPTPopup.Instance.setSidebarId(this.sidebarKey); + GPTPopup.Instance.setSidebarFieldKey(this.sidebarKey); GPTPopup.Instance.addDoc = this.sidebarAddDocument; document.removeEventListener('pointerup', this.onSelectEnd); }; diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 18da01890..f7070c780 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -1,5 +1,5 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from '@dash/components'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -9,16 +9,15 @@ import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtil import { emptyFunction, unimplementedFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; -import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; import { SettingsManager } from '../../util/SettingsManager'; import { undoBatch } from '../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; +import { ComparisonBox } from '../nodes/ComparisonBox'; import { DocumentView } from '../nodes/DocumentView'; import { DrawingOptions, SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; import './AnchorMenu.scss'; -import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup'; -import { ComparisonBox } from '../nodes/ComparisonBox'; +import { GPTPopup } from './GPTPopup/GPTPopup'; @observer export class AnchorMenu extends AntimodeMenu { @@ -98,15 +97,7 @@ export class AnchorMenu extends AntimodeMenu { * Invokes the API with the selected text and stores it in the summarized text. * @param e pointer down event */ - gptSummarize = () => { - GPTPopup.Instance.setVisible(true); - GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY); - GPTPopup.Instance.setLoading(true); - gptAPICall(this._selectedText, GPTCallType.SUMMARY) - .then(res => GPTPopup.Instance.setText(res || 'Something went wrong.')) - .catch(err => console.error(err)) - .finally(() => GPTPopup.Instance.setLoading(false)); - }; + gptSummarize = () => GPTPopup.Instance.generateSummary(this._selectedText); /* * Transfers the flashcard text generated by GPT on flashcards and creates a collection out them. diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 0247dc10c..9cf318dc0 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -5,7 +5,7 @@ $lightgrey: #ececec; $button: #5b97ff; $highlightedText: #82e0ff; -.summary-box { +.gptPopup-summary-box { position: fixed; top: 115px; left: 75px; @@ -35,7 +35,7 @@ $highlightedText: #82e0ff; right: 0; bottom: 0; cursor: se-resize; - } + } .summary-heading { display: flex; @@ -63,12 +63,12 @@ $highlightedText: #82e0ff; cursor: pointer; } - .content-wrapper { + .gptPopup-content-wrapper { padding-top: 10px; min-height: 50px; // max-height: 150px; overflow-y: auto; - height: 100% + height: 100%; } .btns-wrapper-gpt { @@ -78,7 +78,7 @@ $highlightedText: #82e0ff; align-items: center; flex-direction: column; - .inputWrapper{ + .inputWrapper { display: flex; justify-content: center; align-items: center; @@ -87,17 +87,15 @@ $highlightedText: #82e0ff; bottom: 0; width: 100%; background-color: white; - - } - .searchBox-input{ + .searchBox-input { height: 40px; border-radius: 10px; position: absolute; bottom: 10px; border-color: #5b97ff; - width: 90% + width: 90%; } .chat-wrapper { @@ -106,52 +104,41 @@ $highlightedText: #82e0ff; width: 100%; max-height: calc(100vh - 80px); overflow-y: auto; - padding-bottom: 60px; + padding-bottom: 60px; } - + .chat-bubbles { margin-top: 20px; display: flex; flex-direction: column; flex-grow: 1; } - + .chat-bubble { padding: 10px; margin-bottom: 10px; border-radius: 10px; max-width: 60%; } - + .user-message { background-color: #283d53; align-self: flex-end; color: whitesmoke; } - + .chat-message { background-color: #367ae7; align-self: flex-start; - color:whitesmoke; + color: whitesmoke; } - - - .summarizing { display: flex; align-items: center; } - - - - - - } - - .text-btn { &:hover { background-color: $button; @@ -198,18 +185,12 @@ $highlightedText: #82e0ff; color: #666; } - - - - @keyframes spin { to { transform: rotate(360deg); } } - - .image-content-wrapper { display: flex; flex-direction: column; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 2d95ac2eb..691c24792 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -1,6 +1,6 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, IconButton, Toggle, ToggleType, Type } from '@dash/components'; -import { action, makeObservable, observable, runInAction } from 'mobx'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { CgClose, CgCornerUpLeft } from 'react-icons/cg'; @@ -10,7 +10,7 @@ import { ClientUtils } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { Networking } from '../../../Network'; -import { GPTCallType, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT'; +import { GPTCallType, GPTTypeStyle, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT'; import { DocUtils } from '../../../documents/DocUtils'; import { Docs } from '../../../documents/Documents'; import { SettingsManager } from '../../../util/SettingsManager'; @@ -19,7 +19,6 @@ import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocumentView } from '../../nodes/DocumentView'; import { AnchorMenu } from '../AnchorMenu'; import './GPTPopup.scss'; -import { isJSDocProtectedTag } from 'typescript'; export enum GPTPopupMode { SUMMARY, @@ -42,133 +41,79 @@ export enum GPTQuizType { export class GPTPopup extends ObservableReactComponent { // eslint-disable-next-line no-use-before-define static Instance: GPTPopup; - private messagesEndRef: React.RefObject; - - @observable private chatMode: boolean = false; - private correlatedColumns: string[] = []; - - @observable public Visible: boolean = false; - @action public setVisible = (vis: boolean) => { - this.Visible = vis; - }; - @observable public loading: boolean = false; - @action public setLoading = (loading: boolean) => { - this.loading = loading; - }; - @observable public text: string = ''; - @action public setText = (text: string) => { - this.text = text; - }; - @observable public selectedText: string = ''; - @action public setSelectedText = (text: string) => { - this.selectedText = text; - }; - @observable public dataJson: string = ''; - public dataChatPrompt: string | undefined = undefined; - @action public setDataJson = (text: string) => { - if (text === '') this.dataChatPrompt = ''; - this.dataJson = text; - }; - - @observable public imgDesc: string = ''; - @action public setImgDesc = (text: string) => { - this.imgDesc = text; - }; - - @observable public imgUrls: string[][] = []; - @action public setImgUrls = (imgs: string[][]) => { - this.imgUrls = imgs; - }; - - @observable public mode: GPTPopupMode = GPTPopupMode.SUMMARY; - @action public setMode = (mode: GPTPopupMode) => { - this.mode = mode; - }; - - @observable public highlightRange: number[] = []; - @action callSummaryApi = () => {}; - - @observable private done: boolean = false; - @action public setDone = (done: boolean) => { - this.done = done; - this.chatMode = false; - }; - - // change what can be a ref into a ref - @observable private sidebarId: string = ''; - @action public setSidebarId = (id: string) => { - this.sidebarId = id; - }; - - @observable private imgTargetDoc: Doc | undefined; - @action public setImgTargetDoc = (anchor: Doc) => { - this.imgTargetDoc = anchor; - }; - - @observable private textAnchor: Doc | undefined; - @action public setTextAnchor = (anchor: Doc) => { - this.textAnchor = anchor; + private _regenerateCallback: (() => Promise) | null = null; + private _messagesEndRef: React.RefObject; + private _correlatedColumns: string[] = []; + private _dataChatPrompt: string | undefined = undefined; + private _imgTargetDoc: Doc | undefined; + private _textAnchor: Doc | undefined; + private _dataJson: string = ''; + private _sortDesc: string = ''; + private _sidebarFieldKey: string = ''; + private _imgDesc: string = ''; + private _selectedText: string = ''; + + public setImgDesc = (text: string) => (this._imgDesc = text); + public setSidebarFieldKey = (id: string) => (this._sidebarFieldKey = id); + public setSortDesc = (t: string) => (this._sortDesc = t); + public setImgTargetDoc = (anchor: Doc) => (this._imgTargetDoc = anchor); + public setTextAnchor = (anchor: Doc) => (this._textAnchor = anchor); + public onGptResponse?: (sortResult: string, questionType: GPTTypeStyle, tag?: string) => void; + public onQuizRandom?: () => void; + public setDataJson = (text: string) => { + if (text === '') this._dataChatPrompt = ''; + this._dataJson = text; }; - @observable public sortDesc: string = ''; - @action public setSortDesc = (t: string) => { - this.sortDesc = t; - }; - - onSortComplete?: (sortResult: string, questionType: string, tag?: string) => void; - onQuizRandom?: () => void; - - @observable collectionDoc: Doc | undefined = undefined; - @action setCollectionDoc(doc: Doc | undefined) { - this.collectionDoc = doc; - } - - @observable sortRespText: string = ''; - - @action setSortRespText(resp: string) { - this.sortRespText = resp; + constructor(props: object) { + super(props); + makeObservable(this); + GPTPopup.Instance = this; + this._messagesEndRef = React.createRef(); } - @observable chatSortPrompt: string = ''; - - sortPromptChanged = action((e: React.ChangeEvent) => { - this.chatSortPrompt = e.target.value; - }); + componentDidUpdate = () => this._gptProcessing && this.setStopAnimatingResponse(false); - @observable quizAnswer: string = ''; - - quizAnswerChanged = action((e: React.ChangeEvent) => { - this.quizAnswer = e.target.value; - }); - - @observable conversationArray: string[] = ['Hi! In this pop up, you can ask ChatGPT questions about your documents and filter / sort them. ']; + @observable private _conversationArray: string[] = ['Hi! In this pop up, you can ask ChatGPT questions about your documents and filter / sort them. ']; + @observable private _chatEnabled: boolean = false; + @action private setChatEnabled = (start: boolean) => (this._chatEnabled = start); + @observable public Visible: boolean = false; + @action public setVisible = (vis: boolean) => (this.Visible = vis); + @observable private _gptProcessing: boolean = false; + @action public setGptProcessing = (loading: boolean) => (this._gptProcessing = loading); + @observable private _responseText: string = ''; + @action public setResponseText = (text: string) => (this._responseText = text); + @observable private _imgUrls: string[][] = []; + @action public setImgUrls = (imgs: string[][]) => (this._imgUrls = imgs); + @observable private _mode: GPTPopupMode = GPTPopupMode.SUMMARY; + @action public setMode = (mode: GPTPopupMode) => (this._mode = mode); + @observable private _collectionContext: Doc | undefined = undefined; + @action setCollectionContext = (doc: Doc | undefined) => (this._collectionContext = doc); + @observable private _sortPrompt: string = ''; + @action setSortPrompt = (e: React.ChangeEvent) => (this._sortPrompt = e.target.value); + @observable private _quizAnswer: string = ''; + @action setQuizAnswer = (e: React.ChangeEvent) => (this._quizAnswer = e.target.value); + @observable private _stopAnimatingResponse: boolean = false; + @action public setStopAnimatingResponse = (done: boolean) => (this._stopAnimatingResponse = done); /** - * When the cards are in quiz mode in the card view, allows gpt to determine whether the user's answer was correct - * @returns + * Callback function that causes the card view to update the childpair string list + * @param callback */ - generateQuiz = (selected: Doc) => - (this.regenerateCallback?.() ?? Promise.resolve()).then(() => - this.generateRubric(selected).then(() => - gptAPICall('Question: ' + StrCast(selected.gptInputText) + ' UserAnswer: ' + this.quizAnswer + '. Rubric: ' + StrCast(selected.gptRubric), GPTCallType.QUIZ) - .then(res => { - if (res) { - this.setQuizResp(res); - this.conversationArray.push(res); - this.onQuizRandom?.(); - } else { - console.error('GPT provided no response'); - } - }) - .catch(err => console.error('GPT call failed', err)) - ) - ); + public setRegenerateCallback(collectionDoc: Doc | undefined, callback: null | (() => Promise)) { + this.setCollectionContext(collectionDoc); + this._regenerateCallback = callback; + } + + public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; + public createFilteredDoc: (axes?: string[]) => boolean = () => false; + public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; + public questionTypeNumberToStyle = (questionType: string) => +questionType.split(' ')[0][0]; /** - * Generates a rubric by which to compare the user's answer to - * @param inputText user's answer + * Generates a rubric for evaluating the user's description of the document's text * @param doc the doc the user is providing info about - * @returns gpt's response + * @returns gpt's response rubric */ generateRubric = (doc: Doc) => StrCast(doc.gptRubric) @@ -177,148 +122,129 @@ export class GPTPopup extends ObservableReactComponent { .then(res => (doc.gptRubric = res)) .catch(err => console.error('GPT call failed', err)); - @observable private regenerateCallback: (() => Promise) | null = null; - /** - * Callback function that causes the card view to update the childpair string list - * @param callback + * When the cards are in quiz mode in the card view, allows gpt to determine whether the user's answer was correct + * @param doc the doc the user is providing info about + * @param quizAnswer the user's answer/description for the document + * @returns */ - @action public setRegenerateCallback(collectionDoc: Doc | undefined, callback: null | (() => Promise)) { - this.setCollectionDoc(collectionDoc); - this.regenerateCallback = callback; - } - - public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; - public createFilteredDoc: (axes?: string[]) => boolean = () => false; - public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; - - @observable quizRespText: string = ''; - - @action setQuizResp(resp: string) { - this.quizRespText = resp; - } + generateQuizAnswerAnalysis = (doc: Doc, quizAnswer: string) => + (this._regenerateCallback?.() ?? Promise.resolve()).then( + () => + this.generateRubric(doc).then(() => + gptAPICall( + `Question: ${StrCast(doc.gptInputText)}; + UserAnswer: ${quizAnswer}; + Rubric: ${StrCast(doc.gptRubric)}`, + GPTCallType.QUIZ + ).then(res => { + this._conversationArray.push(res || 'GPT provided no answer'); + this.onQuizRandom?.(); + }) + .catch(err => console.error('GPT call failed', err)) + ) // prettier-ignore + ); /** * Generates a response to the user's question depending on the type of their question + * @param userPrompt the user's input that chat will respond to */ - generateCard = async () => { - this.setLoading(true); - - await this.regenerateCallback?.(); - - try { - const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE); - const questionNumber = questionType.split(' ')[0][0]; - const res = await (() => { - switch (questionNumber) { - case '1': - case '2': - case '4': return gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt); - case '6': return gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); - default: return gptAPICall(StrCast(DocumentView.SelectedDocs().lastElement()?.gptInputText), GPTCallType.INFO, this.chatSortPrompt); - }})(); // prettier-ignore - - // Trigger the callback with the result - if (this.onSortComplete) { - this.onSortComplete(res || 'Something went wrong :(', questionNumber, questionType.split(' ').slice(1).join(' ')); - - let explanation = res; - - if (questionType != '5' && questionType != '3') { - // Extract explanation surrounded by ------ at the top or both at the top and bottom - const explanationMatch = res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) || []; - explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found'; - } - - // Set the extracted explanation to sortRespText - this.setSortRespText(explanation); - runInAction(() => this.conversationArray.push(this.sortRespText)); - this.scrollToBottom(); - - console.log(res); - } - } catch (err) { - console.error(err); - } - - this.setLoading(false); - }; + generateQueryResponse = (userPrompt: string) => + (this._regenerateCallback ?? Promise.resolve)().then(() => + gptAPICall(userPrompt, GPTCallType.TYPE).then(questionType => + (() => { + switch (this.questionTypeNumberToStyle(questionType)) { + case GPTTypeStyle.AssignTags: + case GPTTypeStyle.Filter: + case GPTTypeStyle.ChooseDoc: return gptAPICall(this._sortDesc, GPTCallType.SUBSET, userPrompt); + case GPTTypeStyle.SortDocs: return gptAPICall(this._sortDesc, GPTCallType.SORT, userPrompt); + default: return gptAPICall(StrCast(DocumentView.SelectedDocs().lastElement()?.gptInputText), GPTCallType.INFO, userPrompt); + } // prettier-ignore + })().then( + action(res => { + // Trigger the callback with the result + this.onGptResponse?.(res || 'Something went wrong :(', this.questionTypeNumberToStyle(questionType), questionType.split(' ').slice(1).join(' ')); + this._conversationArray.push( + ![GPTTypeStyle.GeneralInfo, GPTTypeStyle.DocInfo].includes(this.questionTypeNumberToStyle(questionType))? + // Extract explanation surrounded by ------ at the top or both at the top and bottom + (res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) ?? [])[1]?.trim() ?? 'No explanation found' : res); + }) + ).catch(err => console.log(err)) + ).catch(err => console.log(err)) + ); // prettier-ignore /** * Generates a Dalle image and uploads it to the server. */ - generateImage = async () => { - if (this.imgDesc === '') return undefined; - this.setImgUrls([]); - this.setMode(GPTPopupMode.IMAGE); - this.setVisible(true); - this.setLoading(true); - - try { - const imageUrls = await gptImageCall(this.imgDesc); - if (imageUrls && imageUrls[0]) { - const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [imageUrls[0]] }); - const source = ClientUtils.prepend(result.accessPaths.agnostic.client); - this.setImgUrls([[imageUrls[0], source]]); - } - } catch (err) { - console.error(err); + generateImage = () => { + if (this._imgDesc !== '') { + this.setImgUrls([]); + this.setMode(GPTPopupMode.IMAGE); + this.setVisible(true); + this.setGptProcessing(true); + + return gptImageCall(this._imgDesc) + .then(imageUrls => + imageUrls?.[0] + ? Networking.PostToServer('/uploadRemoteImage', { sources: [imageUrls[0]] }).then(res => { + const source = ClientUtils.prepend(res[0].accessPaths.agnostic.client); + return this.setImgUrls([[imageUrls[0]!, source]]); + }) + : undefined + ) + .catch(err => console.error(err)) + .finally(() => this.setGptProcessing(false)); } - this.setLoading(false); return undefined; }; /** - * Completes an API call to generate a summary of - * this.selectedText in the popup. + * Completes an API call to generate a summary of the specified text + * + * @param text the text to summarizz */ - generateSummary = async () => { - GPTPopup.Instance.setVisible(true); - GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY); - GPTPopup.Instance.setLoading(true); - - try { - const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); - GPTPopup.Instance.setText(res || 'Something went wrong.'); - } catch (err) { - console.error(err); - } - GPTPopup.Instance.setLoading(false); + generateSummary = (text?: string) => { + this._selectedText = text ?? this._selectedText; + this.setVisible(true); + this.setMode(GPTPopupMode.SUMMARY); + this.setGptProcessing(true); + return gptAPICall(this._selectedText, GPTCallType.SUMMARY) + .then(res => this.setResponseText(res || 'Something went wrong.')) + .catch(err => console.error(err)) + .finally(() => this.setGptProcessing(false)); }; /** * Completes an API call to generate an analysis of * this.dataJson in the popup. */ - generateDataAnalysis = async () => { - GPTPopup.Instance.setVisible(true); - GPTPopup.Instance.setLoading(true); - try { - const res = await gptAPICall(this.dataJson, GPTCallType.DATA, this.dataChatPrompt); - const json = JSON.parse(res! as string); - const keys = Object.keys(json); - this.correlatedColumns = []; - this.correlatedColumns.push(json[keys[0]]); - this.correlatedColumns.push(json[keys[1]]); - GPTPopup.Instance.setText(json[keys[2]] || 'Something went wrong.'); - } catch (err) { - console.error(err); - } - GPTPopup.Instance.setLoading(false); + generateDataAnalysis = () => { + this.setVisible(true); + this.setGptProcessing(true); + return gptAPICall(this._dataJson, GPTCallType.DATA, this._dataChatPrompt) + .then(res => { + const json = JSON.parse(res! as string); + const keys = Object.keys(json); + this._correlatedColumns = []; + this._correlatedColumns.push(json[keys[0]]); + this._correlatedColumns.push(json[keys[1]]); + this.setResponseText(json[keys[2]] || 'Something went wrong.'); + }) + .catch(err => console.error(err)) + .finally(() => this.setGptProcessing(false)); }; /** * Transfers the summarization text to a sidebar annotation text document. */ private transferToText = () => { - const newDoc = Docs.Create.TextDocument(this.text.trim(), { + const newDoc = Docs.Create.TextDocument(this._responseText.trim(), { _width: 200, _height: 50, _layout_fitWidth: true, _layout_autoHeight: true, }); - this.addDoc(newDoc, this.sidebarId); - // newDoc.data = 'Hello world'; + this.addDoc(newDoc, this._sidebarFieldKey); const anchor = AnchorMenu.Instance?.GetAnchor(undefined, false); if (anchor) { DocUtils.MakeLink(newDoc, anchor, { @@ -330,73 +256,35 @@ export class GPTPopup extends ObservableReactComponent { /** * Creates a histogram to show the correlation relationship that was found */ - private createVisualization = () => { - this.createFilteredDoc(this.correlatedColumns); - }; + private createVisualization = () => this.createFilteredDoc(this._correlatedColumns); /** * Transfers the image urls to actual image docs */ private transferToImage = (source: string) => { - const textAnchor = this.textAnchor ?? this.imgTargetDoc; - if (!textAnchor) return; - const newDoc = Docs.Create.ImageDocument(source, { - x: NumCast(textAnchor.x) + NumCast(textAnchor._width) + 10, - y: NumCast(textAnchor.y), - _height: 200, - _width: 200, - data_nativeWidth: 1024, - data_nativeHeight: 1024, - }); - if (Doc.IsInMyOverlay(textAnchor)) { - newDoc.overlayX = textAnchor.x; - newDoc.overlayY = NumCast(textAnchor.y) + NumCast(textAnchor._height); - Doc.AddToMyOverlay(newDoc); - } else { - this.addToCollection?.(newDoc); - } - // Create link between prompt and image - 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) => { - this.dataChatPrompt = e.target.value; - }); - - private getPreviewUrl = (source: string) => source.split('.').join('_m.'); - - constructor(props: object) { - super(props); - makeObservable(this); - GPTPopup.Instance = this; - this.messagesEndRef = React.createRef(); - } - - scrollToBottom = () => { - setTimeout(() => { - // Code to execute after 1 second (1000 ms) - if (this.messagesEndRef.current) { - this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' }); + const textAnchor = this._textAnchor ?? this._imgTargetDoc; + if (textAnchor) { + const newDoc = Docs.Create.ImageDocument(source, { + x: NumCast(textAnchor.x) + NumCast(textAnchor._width) + 10, + y: NumCast(textAnchor.y), + _height: 200, + _width: 200, + data_nativeWidth: 1024, + data_nativeHeight: 1024, + }); + if (Doc.IsInMyOverlay(textAnchor)) { + newDoc.overlayX = textAnchor.x; + newDoc.overlayY = NumCast(textAnchor.y) + NumCast(textAnchor._height); + Doc.AddToMyOverlay(newDoc); + } else { + this.addToCollection?.(newDoc); } - }, 50); - }; - - componentDidUpdate = () => { - if (this.loading) { - this.setDone(false); + // Create link between prompt and image + DocUtils.MakeLink(textAnchor, newDoc, { link_relationship: 'Image Prompt' }); } }; - @observable quizMode: GPTQuizType = GPTQuizType.CURRENT; - @action setQuizMode(g: GPTQuizType) { - this.quizMode = g; - } + scrollToBottom = () => setTimeout(() => this._messagesEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }), 50); cardMenu = () => (
@@ -419,7 +307,7 @@ export class GPTPopup extends ObservableReactComponent { tooltip="Test your knowledge with ChatGPT!" text="Quiz Cards!" onClick={() => { - this.conversationArray = ['Define the selected card!']; + this._conversationArray = ['Define the selected card!']; this.setMode(GPTPopupMode.QUIZ); this.onQuizRandom?.(); }} @@ -437,27 +325,19 @@ export class GPTPopup extends ObservableReactComponent { ); @action - handleKeyPress = (e: React.KeyboardEvent, isSort: boolean) => { + handleKeyPress = async (e: React.KeyboardEvent, isSort: boolean) => { if (e.key === 'Enter') { e.stopPropagation(); + this.setGptProcessing(true); if (isSort) { - this.conversationArray.push(this.chatSortPrompt); - this.generateCard().then( - action(() => { - this.chatSortPrompt = ''; - }) - ); + this._conversationArray.push(this._sortPrompt); + await this.generateQueryResponse(this._sortPrompt).then(action(() => (this._sortPrompt = ''))); } else { - this.conversationArray.push(this.quizAnswer); - this.setLoading(true); - this.generateQuiz(DocumentView.SelectedDocs().lastElement()).then( - action(() => { - this.quizAnswer = ''; - this.setLoading(false); - }) - ); + this._conversationArray.push(this._quizAnswer); + await this.generateQuizAnswerAnalysis(DocumentView.SelectedDocs().lastElement(), this._quizAnswer).then(action(() => (this._quizAnswer = ''))); } + this.setGptProcessing(false); this.scrollToBottom(); } @@ -467,26 +347,24 @@ export class GPTPopup extends ObservableReactComponent {
- {this.conversationArray.map((message, index) => ( + {this._conversationArray.map((message, index) => (
{message}
))} - {this.loading &&
...
} + {this._gptProcessing &&
...
}
-
+
{ - this.handleKeyPress(e, isSort); - }} + onChange={isSort ? this.setSortPrompt : this.setQuizAnswer} + onKeyDown={e => this.handleKeyPress(e, isSort)} type="text" placeholder={`${isSort ? 'Have ChatGPT sort, tag, define, or filter your cards for you!' : 'Define the selected card!'}`} /> @@ -497,7 +375,7 @@ export class GPTPopup extends ObservableReactComponent { sortBox = (isSort: boolean) => (
{this.heading(isSort ? 'SORTING' : 'QUIZ')} - {this.mode === GPTPopupMode.CARD ? this.cardMenu() : this.cardActual(isSort)} + {this._mode === GPTPopupMode.CARD ? this.cardMenu() : this.cardActual(isSort)}
); @@ -505,7 +383,7 @@ export class GPTPopup extends ObservableReactComponent {
{this.heading('GENERATED IMAGE')}
- {this.imgUrls.map((rawSrc, i) => ( + {this._imgUrls.map((rawSrc, i) => (
dalle generation @@ -516,7 +394,7 @@ export class GPTPopup extends ObservableReactComponent { ))} - {!this.loading && } color={StrCast(Doc.UserDoc().userVariantColor)} />} + {this._gptProcessing ? null : } color={StrCast(Doc.UserDoc().userVariantColor)} />} ); @@ -524,44 +402,35 @@ export class GPTPopup extends ObservableReactComponent { <>
{this.heading('SUMMARY')} -
- {!this.loading && - (!this.done ? ( +
+ {!this._gptProcessing && + (!this._stopAnimatingResponse ? ( { - setTimeout(() => { - this.setDone(true); - }, 500); + setTimeout(() => this.setStopAnimatingResponse(true), 500); }, ]} /> ) : ( - this.text + this._responseText ))}
- {!this.loading && ( + {!this._gptProcessing && (
- {this.done ? ( + {this._stopAnimatingResponse ? ( <> - } color={StrCast(SettingsManager.userVariantColor)} /> + this.generateSummary(this._selectedText + ' ')} icon={} color={StrCast(SettingsManager.userVariantColor)} />
)}
@@ -573,33 +442,31 @@ export class GPTPopup extends ObservableReactComponent { <>
{this.heading('ANALYSIS')} -
- {!this.loading && - (!this.done ? ( +
+ {!this._gptProcessing && + (!this._stopAnimatingResponse ? ( { - setTimeout(() => { - this.setDone(true); - }, 500); + setTimeout(() => this.setStopAnimatingResponse(true), 500); }, ]} /> ) : ( - this.text + this._responseText ))}
- {!this.loading && ( + {!this._gptProcessing && (
- {this.done ? ( - this.chatMode ? ( + {this._stopAnimatingResponse ? ( + this._chatEnabled ? ( (this._dataChatPrompt = e.target.value)} onKeyDown={e => { e.key === 'Enter' ? this.generateDataAnalysis() : null; e.stopPropagation(); @@ -613,21 +480,14 @@ export class GPTPopup extends ObservableReactComponent { ) : ( <>