diff options
author | bobzel <zzzman@gmail.com> | 2025-02-13 23:27:42 -0500 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2025-02-13 23:27:42 -0500 |
commit | 3f6a168b2916ccac707cf5ea1e4ef898a470d7d1 (patch) | |
tree | 04b7aa2a30054a726ac285dd288cfb1accf1648c | |
parent | 6b58fb76b11f21648ca2bc551f97b6e09e4be13b (diff) |
lots of cleanup in GPTpopup.
-rw-r--r-- | src/client/apis/gpt/GPT.ts | 20 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCardDeckView.tsx | 20 | ||||
-rw-r--r-- | src/client/views/global/globalScripts.ts | 5 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/DataVizBox.tsx | 4 | ||||
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 4 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 | ||||
-rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 17 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.scss | 45 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.tsx | 629 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 2 |
10 files changed, 285 insertions, 467 deletions
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<FieldViewProps>() { } // 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<FieldViewProps>() { }; 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<FieldViewProps>() { 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<FieldViewProps>() { 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<FormattedTextB /** * ApplyingChange - Marks whether an interactive text edit is currently in the process of being written to the database. - * This is needed to distinguish changes to text fields caused by editing vs those caused by changes to + * This is needed to distinguish changes to text fields caused by editing vs those caused by changes to * the prototype or other external edits */ public ApplyingChange: string = ''; @@ -1043,7 +1043,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB askGPT = action(async () => { 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<FormattedTextB } }; onSelectEnd = () => { - 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<AntimodeMenuProps> { @@ -98,15 +97,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { * Invokes the API with the selected text and stores it in the summarized text. * @param e pointer down event */ - gptSummarize = () => { - 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<object> { // eslint-disable-next-line no-use-before-define static Instance: GPTPopup; - private messagesEndRef: React.RefObject<HTMLDivElement>; - - @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<void>) | null = null; + private _messagesEndRef: React.RefObject<HTMLDivElement>; + 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<HTMLInputElement>) => { - this.chatSortPrompt = e.target.value; - }); + componentDidUpdate = () => this._gptProcessing && this.setStopAnimatingResponse(false); - @observable quizAnswer: string = ''; - - quizAnswerChanged = action((e: React.ChangeEvent<HTMLInputElement>) => { - 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<HTMLInputElement>) => (this._sortPrompt = e.target.value); + @observable private _quizAnswer: string = ''; + @action setQuizAnswer = (e: React.ChangeEvent<HTMLInputElement>) => (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<void>)) { + 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<object> { .then(res => (doc.gptRubric = res)) .catch(err => console.error('GPT call failed', err)); - @observable private regenerateCallback: (() => Promise<void>) | 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<void>)) { - 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<object> { /** * 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<HTMLInputElement>) => { - 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 = () => ( <div className="btns-wrapper-gpt"> @@ -419,7 +307,7 @@ export class GPTPopup extends ObservableReactComponent<object> { 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<object> { ); @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<object> { <div className="btns-wrapper-gpt"> <div className="chat-wrapper"> <div className="chat-bubbles"> - {this.conversationArray.map((message, index) => ( + {this._conversationArray.map((message, index) => ( <div key={index} className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}> {message} </div> ))} - {this.loading && <div className={`chat-bubble chat-message`}>...</div>} + {this._gptProcessing && <div className={`chat-bubble chat-message`}>...</div>} </div> - <div ref={this.messagesEndRef} style={{ height: '100px' }} /> + <div ref={this._messagesEndRef} style={{ height: '100px' }} /> </div> <div className="inputWrapper"> <input className="searchBox-input" defaultValue="" - value={isSort ? this.chatSortPrompt : this.quizAnswer} // Controlled input + value={isSort ? this._sortPrompt : this._quizAnswer} // Controlled input autoComplete="off" - onChange={isSort ? this.sortPromptChanged : this.quizAnswerChanged} - onKeyDown={e => { - 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<object> { sortBox = (isSort: boolean) => ( <div className="gptPopup-sortBox" style={{ height: '80%' }}> {this.heading(isSort ? 'SORTING' : 'QUIZ')} - {this.mode === GPTPopupMode.CARD ? this.cardMenu() : this.cardActual(isSort)} + {this._mode === GPTPopupMode.CARD ? this.cardMenu() : this.cardActual(isSort)} </div> ); @@ -505,7 +383,7 @@ export class GPTPopup extends ObservableReactComponent<object> { <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> {this.heading('GENERATED IMAGE')} <div className="image-content-wrapper"> - {this.imgUrls.map((rawSrc, i) => ( + {this._imgUrls.map((rawSrc, i) => ( <div key={rawSrc[0] + i} className="img-wrapper"> <div className="img-container"> <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" /> @@ -516,7 +394,7 @@ export class GPTPopup extends ObservableReactComponent<object> { </div> ))} </div> - {!this.loading && <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />} + {this._gptProcessing ? null : <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />} </div> ); @@ -524,44 +402,35 @@ export class GPTPopup extends ObservableReactComponent<object> { <> <div> {this.heading('SUMMARY')} - <div className="content-wrapper"> - {!this.loading && - (!this.done ? ( + <div className="gptPopup-content-wrapper"> + {!this._gptProcessing && + (!this._stopAnimatingResponse ? ( <TypeAnimation speed={50} sequence={[ - this.text, + this._responseText, () => { - setTimeout(() => { - this.setDone(true); - }, 500); + setTimeout(() => this.setStopAnimatingResponse(true), 500); }, ]} /> ) : ( - this.text + this._responseText ))} </div> </div> - {!this.loading && ( + {!this._gptProcessing && ( <div className="btns-wrapper"> - {this.done ? ( + {this._stopAnimatingResponse ? ( <> - <IconButton tooltip="Generate Again" onClick={this.generateSummary /* this.callSummaryApi */} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(SettingsManager.userVariantColor)} /> + <IconButton tooltip="Generate Again" onClick={() => this.generateSummary(this._selectedText + ' ')} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(SettingsManager.userVariantColor)} /> <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(SettingsManager.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(SettingsManager.userVariantColor)} - type={Type.TERT} - /> + <Button text="Stop Animation" onClick={() => this.setStopAnimatingResponse(true)} color={StrCast(SettingsManager.userVariantColor)} type={Type.TERT} /> </div> )} </div> @@ -573,33 +442,31 @@ export class GPTPopup extends ObservableReactComponent<object> { <> <div> {this.heading('ANALYSIS')} - <div className="content-wrapper"> - {!this.loading && - (!this.done ? ( + <div className="gptPopup-content-wrapper"> + {!this._gptProcessing && + (!this._stopAnimatingResponse ? ( <TypeAnimation speed={50} sequence={[ - this.text, + this._responseText, () => { - setTimeout(() => { - this.setDone(true); - }, 500); + setTimeout(() => this.setStopAnimatingResponse(true), 500); }, ]} /> ) : ( - this.text + this._responseText ))} </div> </div> - {!this.loading && ( + {!this._gptProcessing && ( <div className="btns-wrapper"> - {this.done ? ( - this.chatMode ? ( + {this._stopAnimatingResponse ? ( + this._chatEnabled ? ( <input defaultValue="" autoComplete="off" - onChange={this.dataPromptChanged} + onChange={e => (this._dataChatPrompt = e.target.value)} onKeyDown={e => { e.key === 'Enter' ? this.generateDataAnalysis() : null; e.stopPropagation(); @@ -613,21 +480,14 @@ export class GPTPopup extends ObservableReactComponent<object> { ) : ( <> <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} /> - <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} /> + <Button tooltip="Chat with AI" text="Chat with AI" onClick={() => this.setChatEnabled(true)} color={StrCast(SnappingManager.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(SnappingManager.userVariantColor)} - type={Type.TERT} - /> + <Button text="Stop Animation" onClick={() => this.setStopAnimatingResponse(true)} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} /> </div> )} </div> @@ -636,70 +496,53 @@ export class GPTPopup extends ObservableReactComponent<object> { ); aiWarning = () => - this.done ? ( + !this._stopAnimatingResponse ? null : ( <div className="ai-warning"> <FontAwesomeIcon icon="exclamation-circle" size="sm" style={{ paddingRight: '5px' }} /> AI generated responses can contain inaccurate or misleading content. </div> - ) : null; + ); heading = (headingText: string) => ( <div className="summary-heading"> <label className="summary-text">{headingText}</label> - {this.loading ? ( + {this._gptProcessing ? ( <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> ) : ( <> - {(this.mode === GPTPopupMode.SORT || this.mode === GPTPopupMode.QUIZ) && ( - <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this.mode = GPTPopupMode.CARD)} style={{ right: '50px', position: 'absolute' }} /> + {(this._mode === GPTPopupMode.SORT || this._mode === GPTPopupMode.QUIZ) && ( + <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.CARD)} style={{ right: '50px', position: 'absolute' }} /> )} <Toggle tooltip="Clear Chat filter" toggleType={ToggleType.BUTTON} type={Type.PRIM} - toggleStatus={Doc.hasDocFilter(this.collectionDoc, 'tags', '#chat')} - text={Doc.hasDocFilter(this.collectionDoc, 'tags', '#chat') ? 'filtered' : ''} + toggleStatus={Doc.hasDocFilter(this._collectionContext, 'tags', '#chat')} + text={Doc.hasDocFilter(this._collectionContext, 'tags', '#chat') ? 'filtered' : ''} color="red" - onClick={() => this.collectionDoc && Doc.setDocFilter(this.collectionDoc, 'tags', '#chat', 'remove')} - /> - <IconButton - color={StrCast(SettingsManager.userVariantColor)} - tooltip="close" - icon={<CgClose size="16px" />} - onClick={() => { - this.setVisible(false); - }} + onClick={() => this._collectionContext && Doc.setDocFilter(this._collectionContext, 'tags', '#chat', 'remove')} /> + <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} /> </> )} </div> ); render() { - let content; - - switch (this.mode) { - case GPTPopupMode.SUMMARY: - content = this.summaryBox(); - break; - case GPTPopupMode.DATA: - content = this.dataAnalysisBox(); - break; - case GPTPopupMode.IMAGE: - content = this.imageBox(); - break; - case GPTPopupMode.SORT: - case GPTPopupMode.CARD: - case GPTPopupMode.QUIZ: - content = this.sortBox(this.mode === GPTPopupMode.SORT); - break; - default: - content = null; - } - return ( - <div className="summary-box" style={{ display: this.Visible ? 'flex' : 'none' }}> - {content} + <div className="gptPopup-summary-box" style={{ display: this.Visible ? 'flex' : 'none' }}> + {(() => { + //prettier-ignore + switch (this._mode) { + case GPTPopupMode.SORT: + case GPTPopupMode.CARD: + case GPTPopupMode.QUIZ: return this.sortBox(this._mode === GPTPopupMode.SORT); + case GPTPopupMode.SUMMARY: return this.summaryBox(); + case GPTPopupMode.DATA: return this.dataAnalysisBox(); + case GPTPopupMode.IMAGE: return this.imageBox(); + default: return null; + } + })()} <div className="resize-handle" /> </div> ); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 8728ce99c..167421a4a 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -437,7 +437,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); } - GPTPopup.Instance.setSidebarId('data_sidebar'); + GPTPopup.Instance.setSidebarFieldKey('data_sidebar'); GPTPopup.Instance.addDoc = this._props.sidebarAddDoc; // allows for creating collection AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument; |