From 18774b42e3c8e1e899978fe9f16a4d123adee803 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts Date: Wed, 1 Jan 2025 22:32:19 -0800 Subject: monorepo setup --- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/pdf/GPTPopup') diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index a7e78bcea..22ca9dcd7 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, IconButton, Type } from 'browndash-components'; +import { Button, IconButton, Type } from '@dash/components'; import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -- cgit v1.2.3-70-g09d2 From e7b0b92adf3404bc24e1b9d5d6a857a0ad6e6418 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Jan 2025 10:53:58 -0500 Subject: cleaning up card deck view. --- src/client/apis/gpt/GPT.ts | 26 ++++++++-- src/client/views/TagsView.tsx | 2 +- .../views/collections/CollectionCardDeckView.scss | 5 +- .../views/collections/CollectionCardDeckView.tsx | 60 +++++++++++++++++++--- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 49 +++++++++--------- 5 files changed, 101 insertions(+), 41 deletions(-) (limited to 'src/client/views/pdf/GPTPopup') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index cf3a28a8e..d96972784 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -16,10 +16,10 @@ enum GPTCallType { PRONUNCIATION = 'pronunciation', DRAW = 'draw', COLOR = 'color', - RUBRIC = 'rubric', - TYPE = 'type', - SUBSET = 'subset', - INFO = 'info', + RUBRIC = 'rubric', // needs to be filled in below + TYPE = 'type', // needs to be filled in below + SUBSET = 'subset', // needs to be filled in below + INFO = 'info', // needs to be filled in below TEMPLATE = 'template', VIZSUM = 'vizsum', VIZSUM2 = 'vizsum2', @@ -84,6 +84,18 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { temp: 0.1, //0.3 prompt: "BRIEFLY (<50 words) describe any differences between the rubric and the user's answer answer in second person. If there are no differences, say correct", }, + type: { + model: 'gpt-4-turbo', + maxTokens: 512, + temp: 0.5, + prompt: '', + }, + info: { + model: 'gpt-4-turbo', + maxTokens: 512, + temp: 0.5, + prompt: '', + }, template: { model: 'gpt-4-turbo', maxTokens: 512, @@ -131,8 +143,12 @@ let lastResp = ''; * @returns AI Output */ const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: string, dontCache?: boolean) => { - const inputText = [GPTCallType.SUMMARY, GPTCallType.FLASHCARD, GPTCallType.QUIZ, GPTCallType.STACK].includes(callType) ? inputTextIn + '.' : inputTextIn; + const inputText = inputTextIn + ([GPTCallType.SUMMARY, GPTCallType.FLASHCARD, GPTCallType.QUIZ, GPTCallType.STACK].includes(callType) ? '.' : ''); const opts = callTypeMap[callType]; + if (!opts) { + console.log('The query type:' + callType + ' requires a configuration.'); + return 'Error connecting with API.'; + } if (lastCall === inputText && dontCache !== true) return lastResp; try { lastCall = inputText; diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 73f404596..9e936ea54 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -9,7 +9,7 @@ import { emptyFunction } from '../../Utils'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; -import { DocCast, NumCast, StrCast } from '../../fields/Types'; +import { DocCast, StrCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from '../util/DragManager'; import { SnappingManager } from '../util/SnappingManager'; diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index 5283601bf..79c53db08 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -28,11 +28,14 @@ .collectionCardView-cardwrapper { display: grid; - grid-template-columns: repeat(10, 1fr); transform-origin: left 50%; align-items: center; z-index: 0; // so that setting z-index of active card doesn't make it land on top of things outside of the card-wrapper } +.collectionCardView-cardSizeDragger { + position: absolute; + top: 0; +} .no-card-span { position: relative; diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 836a5a2c3..800eb4914 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -1,8 +1,9 @@ -import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, trace } from 'mobx'; +import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; -import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnNever, returnZero } from '../../../ClientUtils'; +import * as CSS from 'csstype'; +import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnNever, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Animation, DocData } from '../../../fields/DocSymbols'; @@ -18,7 +19,7 @@ import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoable } from '../../util/UndoManager'; +import { undoable, UndoManager } from '../../util/UndoManager'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { TagItem } from '../TagsView'; @@ -27,6 +28,8 @@ import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { SettingsManager } from '../../util/SettingsManager'; enum cardSortings { Time = 'time', @@ -52,12 +55,13 @@ export class CollectionCardView extends CollectionSubView() { private _oldWheel: HTMLElement | null = null; private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center) private _setCurDocScript = () => ScriptField.MakeScript('scriptContext.layoutDoc._card_curDoc=this', { scriptContext: 'any' })!; + private _draggerRef = React.createRef(); @observable _forceChildXf = 0; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap(); - @observable _maxRowCount = 10; @observable _docDraggedIndex: number = -1; + @observable _cursor: CSS.Property.Cursor = 'ew-resize'; constructor(props: SubCollectionViewProps) { super(props); @@ -74,6 +78,12 @@ export class CollectionCardView extends CollectionSubView() { // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); }; + @computed get cardWidth() { + return NumCast(this.layoutDoc._cardWidth, 50); + } + @computed get _maxRowCount() { + return Math.ceil(this.cardDeckWidth / this.cardWidth); + } /** * Callback to ensure gpt's text versions of the child docs are updated */ @@ -167,6 +177,14 @@ export class CollectionCardView extends CollectionSubView() { return this._props.NativeDimScaling?.() || 1; } + @computed get xMargin() { + return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth())); + } + + @computed get cardDeckWidth() { + return this._props.PanelWidth() - 2 * this.xMargin; + } + /** * When in quiz mode, randomly selects a document */ @@ -505,7 +523,6 @@ export class CollectionCardView extends CollectionSubView() { const normalizedItem = item.trim(); // find the corresponding Doc in the textToDoc map const doc = this._textToDoc.get(normalizedItem); - if (doc) { switch (questionType) { case '6': @@ -513,9 +530,7 @@ export class CollectionCardView extends CollectionSubView() { break; case '1': { const allHotKeys = Doc.MyFilterHotKeys; - let myTag = ''; - if (tag) { for (let i = 0; i < allHotKeys.length; i++) { // bcz: CHECK THIS CODE OUT -- SOMETHING CHANGED @@ -526,7 +541,7 @@ export class CollectionCardView extends CollectionSubView() { } } - if (myTag != '') { + if (myTag) { doc[myTag] = true; } } @@ -591,6 +606,26 @@ export class CollectionCardView extends CollectionSubView() { } }); + cardSizerDown = (e: React.PointerEvent) => { + runInAction(() => { + this._cursor = 'grabbing'; + }); + const batch = UndoManager.StartBatch('card view size'); + setupMoveUpEvents( + this, + e, + (e: PointerEvent, down: number[], delta: number[]) => { + this.layoutDoc._cardWidth = Math.max(10, delta[0] < 0 ? Math.floor(this.cardWidth + delta[0]) : Math.ceil(this.cardWidth + delta[0])); + return false; + }, + action(() => { + this._cursor = 'ew-resize'; + batch.end(); + }), + emptyFunction + ); + }; + /** * turns off the _dropped flag at the end of a drag/drop, or releases the focused Doc if a different Doc is clicked */ @@ -701,6 +736,7 @@ export class CollectionCardView extends CollectionSubView() {
@@ -717,6 +753,14 @@ export class CollectionCardView extends CollectionSubView() { {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)}
+ +
+ +
); } diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 22ca9dcd7..4028ef479 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, Type } from '@dash/components'; -import { action, makeObservable, observable } from 'mobx'; +import { action, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { CgClose, CgCornerUpLeft } from 'react-icons/cg'; @@ -37,10 +37,8 @@ export enum GPTQuizType { MULTIPLE = 2, } -interface GPTPopupProps {} - @observer -export class GPTPopup extends ObservableReactComponent { +export class GPTPopup extends ObservableReactComponent { // eslint-disable-next-line no-use-before-define static Instance: GPTPopup; private messagesEndRef: React.RefObject; @@ -188,16 +186,9 @@ export class GPTPopup extends ObservableReactComponent { this.setLoading(true); this.setSortDone(false); - const quizType = this.quizMode; - const selected = DocumentView.SelectedDocs().lastElement(); const questionText = 'Question: ' + StrCast(selected['gptInputText']); - - if (StrCast(selected['gptRubric']) === '') { - const rubricText = 'Rubric: ' + (await this.generateRubric(StrCast(selected['gptInputText']), selected)); - } - const rubricText = 'Rubric: ' + StrCast(selected['gptRubric']); const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText; @@ -214,7 +205,7 @@ export class GPTPopup extends ObservableReactComponent { this.setLoading(false); this.setSortDone(true); } catch (err) { - console.error('GPT call failed'); + console.error('GPT call failed', err); } if (this.onQuizRandom) { @@ -234,7 +225,7 @@ export class GPTPopup extends ObservableReactComponent { doc['gptRubric'] = res; return res; } catch (err) { - console.error('GPT call failed'); + console.error('GPT call failed', err); } }; @@ -262,7 +253,6 @@ export class GPTPopup extends ObservableReactComponent { * Generates a response to the user's question depending on the type of their question */ generateCard = async () => { - console.log(this.chatSortPrompt + 'USER PROMPT'); this.setLoading(true); this.setSortDone(false); @@ -271,10 +261,8 @@ export class GPTPopup extends ObservableReactComponent { } try { - // const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE); const questionNumber = questionType.split(' ')[0]; - console.log(questionType); let res = ''; switch (questionNumber) { @@ -287,10 +275,12 @@ export class GPTPopup extends ObservableReactComponent { res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); break; default: - const selected = DocumentView.SelectedDocs().lastElement(); - const questionText = StrCast(selected!['gptInputText']); + { + const selected = DocumentView.SelectedDocs().lastElement(); + const questionText = StrCast(selected?.gptInputText); - res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt); + res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt); + } break; } @@ -308,7 +298,7 @@ export class GPTPopup extends ObservableReactComponent { // Set the extracted explanation to sortRespText this.setSortRespText(explanation); - this.conversationArray.push(this.sortRespText); + runInAction(() => this.conversationArray.push(this.sortRespText)); this.scrollToBottom(); console.log(res); @@ -448,7 +438,7 @@ export class GPTPopup extends ObservableReactComponent { private getPreviewUrl = (source: string) => source.split('.').join('_m.'); - constructor(props: GPTPopupProps) { + constructor(props: object) { super(props); makeObservable(this); GPTPopup.Instance = this; @@ -515,18 +505,25 @@ export class GPTPopup extends ObservableReactComponent { ); - handleKeyPress = async (e: React.KeyboardEvent, isSort: boolean) => { + @action + handleKeyPress = (e: React.KeyboardEvent, isSort: boolean) => { if (e.key === 'Enter') { e.stopPropagation(); if (isSort) { this.conversationArray.push(this.chatSortPrompt); - await this.generateCard(); - this.chatSortPrompt = ''; + this.generateCard().then( + action(() => { + this.chatSortPrompt = ''; + }) + ); } else { this.conversationArray.push(this.quizAnswer); - await this.generateQuiz(); - this.quizAnswer = ''; + this.generateQuiz().then( + action(() => { + this.quizAnswer = ''; + }) + ); } this.scrollToBottom(); -- cgit v1.2.3-70-g09d2 From 6fa61343a3e63a61747bc7123810190057d0bfe4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Jan 2025 12:14:12 -0500 Subject: cleaned up filter icons. --- src/client/util/CurrentUserUtils.ts | 38 +++++++++++++++++----- src/client/views/MainView.tsx | 25 +++----------- .../views/collections/CollectionCardDeckView.tsx | 20 +++--------- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 34 +++++++------------ 4 files changed, 48 insertions(+), 69 deletions(-) (limited to 'src/client/views/pdf/GPTPopup') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e806675aa..306914450 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -40,6 +40,7 @@ import { ColorScheme } from "./SettingsManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; import { DocumentView } from "../views/nodes/DocumentView"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; export interface Button { // DocumentOptions fields a button can set @@ -67,6 +68,21 @@ export interface Button { subMenu?: Button[]; } +// Not really necessary, but for now, all tags should start with a capital first letter +export type TagName = + // eslint-disable-next-line @typescript-eslint/no-unused-vars + T extends `${infer First}${infer Rest}` + ? First extends Uppercase + ? First extends Lowercase + ? never // If it's the same when uppercased and lowercased, it's not a letter. + : T // Otherwise, it's a valid capitalized string. + : never + : never; +export function ToTagName(key: string):"Tag"{ + return ((str => str[0].toUpperCase() + str.slice(1))(key.startsWith('#') ? key.substring(1) : key)) as "Tag"; +} + + export let resolvedPorts: { server: number, socket: number }; export class CurrentUserUtils { @@ -703,18 +719,22 @@ pie title Minerals in my tap water ]}, ] } + + static filterBtnDesc(tag:TagName|"Tag", icon:IconProp):Button { + return { title: tag, isSystem: false, icon: icon.toString(), toolTip:`Click to toggle visibility of ${tag} tagged Docs`, btnType: ButtonType.ToggleButton, expertMode: false, toolType:`#${tag.toLowerCase()}`, funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}} + } static filterTools(): Button[] { - const defaultTagButtonDescs = [ - { title: "Star", isSystem: false,icon: "star", toolTip:"Click to toggle visibility of Star tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#star", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}, - { title: "Like", isSystem: false,icon: "heart", toolTip:"Click to toggle visibility of Like tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#like", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}, - { title: "Todo", isSystem: false,icon: "bolt", toolTip:"Click to toggle visibility of Todo tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#todo", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}, - { title: "Idea", isSystem: false,icon: "cloud", toolTip:"Click to toggle visibility of Idea tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#idea", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}, + // If there's no active dashboard, then a default set of tags are added, otherwise, the user controls which tags are kept + const tagButtonDescs = Doc.UserDoc().activeDashboard ? [] : [ + this.filterBtnDesc("Star", "star"), + this.filterBtnDesc("Like", "heart"), + this.filterBtnDesc("Todo", "bolt"), + this.filterBtnDesc("Idea", "cloud") ]; - // hack: if there's no dashboard, create default filters. otherwise, just make sure that the Options button is preserved return [ - { title:"Options",isSystem: true,icon: "gear", toolTip:"Click to customize list of filter buttons", btnType: ButtonType.ClickButton, expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}}, - ...(Doc.UserDoc().activeDashboard ? [] : defaultTagButtonDescs) + { title:"Options",isSystem: true,icon: "gear", toolTip:"Click to customize list of filter buttons", btnType: ButtonType.ClickButton, expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}}, + ...tagButtonDescs ] } static viewTools(): Button[] { @@ -866,7 +886,7 @@ pie title Minerals in my tap water childDontRegisterViews: true, flexGap: 0, _height: 30, _width: 30, ignoreClick: !params.scripts?.onClick, linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc}; - const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) ); + const items = (menuBtn?:Doc) => !menuBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtn) ); const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList; const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title), (opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7abca5197..d748b70ae 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -20,7 +20,7 @@ import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; import { CalendarManager } from '../util/CalendarManager'; import { CaptureManager } from '../util/CaptureManager'; -import { Button, CurrentUserUtils } from '../util/CurrentUserUtils'; +import { CurrentUserUtils, ToTagName } from '../util/CurrentUserUtils'; import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; import { dropActionType } from '../util/DropActionTypes'; @@ -63,7 +63,6 @@ import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu'; import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { DocButtonState } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; -import { ButtonType } from './nodes/FontIconBox/FontIconBox'; import { ImageEditorData as ImageEditor } from './nodes/ImageBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview'; @@ -874,25 +873,9 @@ export class MainView extends ObservableReactComponent { * @param hotKey tite of the new hotkey */ addHotKey = (hotKey: string) => { - const buttons = DocCast(Doc.UserDoc().myContextMenuBtns); - const filter = DocCast(buttons.Filter); - const title = hotKey.startsWith('#') ? hotKey.substring(1) : hotKey; - - const newKey: Button = { - title, - icon: 'question', - toolTip: `Click to toggle the ${title}'s group's visibility`, - btnType: ButtonType.ToggleButton, - expertMode: false, - toolType: '#' + title, - funcs: {}, - scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' }, - }; - - const newBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter); - newBtn.isSystem = newBtn[DocData].isSystem = undefined; - - Doc.AddToFilterHotKeys(newBtn); + const filterIcons = DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter); + const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons); + Doc.AddToFilterHotKeys(menuDoc); }; @computed get mainInnerContent() { diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 366d0f448..b40aa37de 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -462,24 +462,13 @@ export class CollectionCardView extends CollectionSubView() { case '6': doc.chatIndex = index; break; - case '1': { - const allHotKeys = Doc.MyFilterHotKeys; - let myTag = ''; + case '1': if (tag) { - for (let i = 0; i < allHotKeys.length; i++) { - const keyTag = StrCast(allHotKeys[i].toolType); - if (keyTag.includes(tag)) { - myTag = keyTag; - break; - } - } - - if (myTag) { - TagItem.addTagToDoc(doc, myTag); - } + 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': doc.chatFilter = true; @@ -584,7 +573,6 @@ export class CollectionCardView extends CollectionSubView() { * Actually renders all the cards */ @computed get renderCards() { - console.log(this.docDraggedIndex, this.childDocs[0].title, this.childDocs[1].title); // Map sorted documents to their rendered components return this.childDocs.map((doc, index) => { const cardsInRow = this.cardsInRowThatIncludesCardIndex(index); diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 4028ef479..c33b81eb4 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -188,8 +188,8 @@ export class GPTPopup extends ObservableReactComponent { const selected = DocumentView.SelectedDocs().lastElement(); - const questionText = 'Question: ' + StrCast(selected['gptInputText']); - const rubricText = 'Rubric: ' + StrCast(selected['gptRubric']); + const questionText = 'Question: ' + StrCast(selected.gptInputText); + const rubricText = 'Rubric: ' + StrCast(selected.gptRubric); const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText; try { @@ -262,27 +262,15 @@ export class GPTPopup extends ObservableReactComponent { try { const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE); - const questionNumber = questionType.split(' ')[0]; - let res = ''; - - switch (questionNumber) { - case '1': - case '2': - case '4': - res = await gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt); - break; - case '6': - res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); - break; - default: - { - const selected = DocumentView.SelectedDocs().lastElement(); - const questionText = StrCast(selected?.gptInputText); - - res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt); - } - break; - } + 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) { -- cgit v1.2.3-70-g09d2 From 45aa1b54ab37d821257fb00b3be5ef5eca03ef2c Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Jan 2025 15:09:10 -0500 Subject: generlized some of cardDeck ui. showTags is under View for all collections. chat popup is available for all docs, but only works for card views. added clear filters button for chat popup. --- src/client/util/CurrentUserUtils.ts | 34 +++---- .../views/collections/CollectionCardDeckView.tsx | 106 +++++++++---------- .../CollectionFreeFormLayoutEngines.tsx | 2 + .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + .../views/nodes/CollectionFreeFormDocumentView.tsx | 2 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 113 ++++++++------------- 6 files changed, 118 insertions(+), 140 deletions(-) (limited to 'src/client/views/pdf/GPTPopup') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 306914450..b75961575 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -710,8 +710,6 @@ pie title Minerals in my tap water { title: "Type", icon:"eye", toolTip:"Sort by document type", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Color", icon:"palette", toolTip:"Sort by document color", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"color", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Tags", icon:"bolt", toolTip:"Sort by document's tags", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"tag", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Chat Popup",icon:"lightbulb", toolTip:"Toggle the chat popup's visibility", width: 45, btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} }, - { title: "Show Tags", icon:"id-card", toolTip:"Toggle tag annotation panel", width: 45, btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-tags",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} }, { title: "Sort", icon: "sort" , toolTip: "Manage sort order / lock status", btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true, subMenu: [ { title: "Ascending", toolTip: "Sort the cards in ascending order", btnType: ButtonType.ToggleButton, icon: "sort-up", toolType:"up", ignoreClick: true, scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} }, @@ -736,13 +734,15 @@ pie title Minerals in my tap water { title:"Options",isSystem: true,icon: "gear", toolTip:"Click to customize list of filter buttons", btnType: ButtonType.ClickButton, expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}}, ...tagButtonDescs ] - } + } static viewTools(): Button[] { return [ - { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - { title: "Fit All", icon: "object-group", toolTip: "Fit Docs to View (double click to make sticky)",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform - { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Show Tags", icon: "id-card", toolTip: "Toggle tags", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"toggle-tags",funcs: { }, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} }, + { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Fit All", icon: "object-group", toolTip:"Fit Docs to View (double tap to persist)", + btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform + { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform ] } static textTools():Button[] { @@ -832,21 +832,21 @@ pie title Minerals in my tap water 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: "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_); }'} }, - { title: "Fill", icon: "fill-drip", toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'} }, // Only when a document is selected - { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform - { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, - { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, - { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_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_); }'} }, + { title: "Fill", icon: "fill-drip", toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'} }, // Only when a document is selected + { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform + { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, + { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, + { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, + { 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: "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:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, 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 { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index b40aa37de..f24673c39 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -56,7 +56,6 @@ export class CollectionCardView extends CollectionSubView() { constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); - this.setRegenerateCallback(); } protected createDashEventsTarget = (ele: HTMLDivElement | null) => { this._dropDisposer?.(); @@ -74,23 +73,19 @@ export class CollectionCardView extends CollectionSubView() { @computed get _maxRowCount() { return Math.ceil(this.cardDeckWidth / this.cardWidth); } - /** - * Callback to ensure gpt's text versions of the child docs are updated - */ - setRegenerateCallback = () => GPTPopup.Instance.setRegenerateCallback(this.childPairStringListAndUpdateSortDesc); /** * update's gpt's doc-text list and initializes callbacks */ - @action - childPairStringListAndUpdateSortDesc = async () => { - const sortDesc = await this.childPairStringList(); // Await the promise to get the string result - GPTPopup.Instance.setSortDesc(sortDesc.join()); - GPTPopup.Instance.onSortComplete = (sortResult: string, questionType: string, tag?: string) => this.processGptOutput(sortResult, questionType, tag); - GPTPopup.Instance.onQuizRandom = () => this.quizMode(); - }; + childPairStringListAndUpdateSortDesc = () => + this.childPairStringList().then(sortDesc => { + GPTPopup.Instance.setSortDesc(sortDesc.join()); + GPTPopup.Instance.onSortComplete = this.processGptOutput; + GPTPopup.Instance.onQuizRandom = this.quizMode; + }); componentDidMount() { + GPTPopup.Instance.setRegenerateCallback(this.Document, this.childPairStringListAndUpdateSortDesc); this._props.setContentViewBox?.(this); // if card deck moves, then the child doc views are hidden so their screen to local transforms will return empty rectangles // when inquired from the dom (below in childScreenToLocal). When the doc is actually rendered, we need to act like the @@ -112,6 +107,10 @@ export class CollectionCardView extends CollectionSubView() { } componentWillUnmount() { + GPTPopup.Instance.setSortDesc(''); + GPTPopup.Instance.onSortComplete = undefined; + GPTPopup.Instance.onQuizRandom = undefined; + GPTPopup.Instance.setRegenerateCallback(undefined, null); Object.keys(this._disposers).forEach(key => this._disposers[key]?.()); this._dropDisposer?.(); } @@ -166,8 +165,7 @@ export class CollectionCardView extends CollectionSubView() { * When in quiz mode, randomly selects a document */ quizMode = () => { - const randomIndex = Math.floor(Math.random() * this.childDocs.length); - this.layoutDoc._card_curDoc = this.childDocs[randomIndex]; + this.layoutDoc._card_curDoc = this.childDocs[Math.floor(Math.random() * this.childDocs.length)]; }; setHoveredNodeIndex = action((index: number) => { @@ -437,49 +435,51 @@ export class CollectionCardView extends CollectionSubView() { * Processes gpt's output depending on the type of question the user asked. Converts gpt's string output to * usable code * @param gptOutput + * @param questionType + * @param tag */ - @action - processGptOutput = undoable((gptOutput: string, questionType: string, tag?: string) => { - // Split the string into individual list items - const listItems = gptOutput.split('======').filter(item => item.trim() !== ''); - - if (questionType === '2' || questionType === '4') { - this.childDocs.forEach(d => { - d.chatFilter = false; - }); - } + processGptOutput = (gptOutput: string, questionType: string, tag?: string) => + undoable(() => { + // Split the string into individual list items + const listItems = gptOutput.split('======').filter(item => item.trim() !== ''); + + if (questionType === '2' || questionType === '4') { + this.childDocs.forEach(d => { + d.chatFilter = false; + }); + } - if (questionType === '6') { - this.Document[this._props.fieldKey + '_sort'] = docSortings.Chat; - } + if (questionType === '6') { + this.Document[this._props.fieldKey + '_sort'] = docSortings.Chat; + } - listItems.forEach((item, index) => { - const normalizedItem = item.trim(); - // find the corresponding Doc in the textToDoc map - const doc = this._textToDoc.get(normalizedItem); - if (doc) { - switch (questionType) { - case '6': - doc.chatIndex = index; - break; - case '1': - 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': - doc.chatFilter = true; - Doc.setDocFilter(DocCast(this.Document.embedContainer), 'chatFilter', true, 'match'); - break; + listItems.forEach((item, index) => { + const normalizedItem = item.trim(); + // find the corresponding Doc in the textToDoc map + const doc = this._textToDoc.get(normalizedItem); + if (doc) { + switch (questionType) { + case '6': + doc.chatIndex = index; + break; + case '1': + 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': + doc.chatFilter = true; + Doc.setDocFilter(DocCast(this.Document), 'chatFilter', true, 'check'); + break; + } + } else { + console.warn(`No matching document found for item: ${normalizedItem}`); } - } else { - console.warn(`No matching document found for item: ${normalizedItem}`); - } - }); - }, ''); + }); + }, '')(); childScreenToLocal = computedFn((doc: Doc, index: number, isSelected: boolean) => () => { // need to explicitly trigger an invalidation since we're reading everything from the Dom diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 272c13546..bebdbd731 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -44,6 +44,7 @@ export interface PoolData { transition?: string; highlight?: boolean; pointerEvents?: string; + showTags?: boolean; } export interface ViewDefResult { @@ -425,6 +426,7 @@ function normalizeResults( opacity: newPosRaw.opacity, color: newPosRaw.color, pair: ele[1].pair, + showTags: newPosRaw.showTags, }; if (newPosRaw.transition) newPos.transition = newPosRaw.transition; poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 796949378..69fdf52ff 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1658,6 +1658,7 @@ export class CollectionFreeFormView extends CollectionSubView { @observable private chatMode: boolean = false; private correlatedColumns: string[] = []; - @observable - public visible: boolean = false; - @action - public setVisible = (vis: boolean) => { + @observable public visible: boolean = false; + @action public setVisible = (vis: boolean) => { this.visible = vis; }; - @observable - public loading: boolean = false; - @action - public setLoading = (loading: boolean) => { + @observable public loading: boolean = false; + @action public setLoading = (loading: boolean) => { this.loading = loading; }; - @observable - public text: string = ''; - @action - public setText = (text: string) => { + @observable public text: string = ''; + @action public setText = (text: string) => { this.text = text; }; - @observable - public selectedText: string = ''; - @action - public setSelectedText = (text: string) => { + @observable public selectedText: string = ''; + @action public setSelectedText = (text: string) => { this.selectedText = text; }; - @observable - public dataJson: string = ''; + @observable public dataJson: string = ''; public dataChatPrompt: string | undefined = undefined; - @action - public setDataJson = (text: string) => { + @action public setDataJson = (text: string) => { if (text === '') this.dataChatPrompt = ''; this.dataJson = text; }; - @observable - public imgDesc: string = ''; - @action - public setImgDesc = (text: string) => { + @observable public imgDesc: string = ''; + @action public setImgDesc = (text: string) => { this.imgDesc = text; }; - @observable - public imgUrls: string[][] = []; - @action - public setImgUrls = (imgs: string[][]) => { + @observable public imgUrls: string[][] = []; + @action public setImgUrls = (imgs: string[][]) => { this.imgUrls = imgs; }; - @observable - public mode: GPTPopupMode = GPTPopupMode.SUMMARY; - @action - public setMode = (mode: GPTPopupMode) => { + @observable public mode: GPTPopupMode = GPTPopupMode.SUMMARY; + @action public setMode = (mode: GPTPopupMode) => { this.mode = mode; }; - @observable - public highlightRange: number[] = []; + @observable public highlightRange: number[] = []; @action callSummaryApi = () => {}; - @observable - private done: boolean = false; - @action - public setDone = (done: boolean) => { + @observable private done: boolean = false; + @action public setDone = (done: boolean) => { this.done = done; this.chatMode = false; }; - @observable - private sortDone: boolean = false; // this is so redundant but the og done variable was causing weird unknown problems and im just a girl - - @action - public setSortDone = (done: boolean) => { - this.sortDone = done; - }; - // change what can be a ref into a ref - @observable - private sidebarId: string = ''; - @action - public setSidebarId = (id: string) => { + @observable private sidebarId: string = ''; + @action public setSidebarId = (id: string) => { this.sidebarId = id; }; - @observable - private imgTargetDoc: Doc | undefined; - @action - public setImgTargetDoc = (anchor: Doc) => { + @observable private imgTargetDoc: Doc | undefined; + @action public setImgTargetDoc = (anchor: Doc) => { this.imgTargetDoc = anchor; }; - @observable - private textAnchor: Doc | undefined; - @action - public setTextAnchor = (anchor: Doc) => { + @observable private textAnchor: Doc | undefined; + @action public setTextAnchor = (anchor: Doc) => { this.textAnchor = anchor; }; - @observable - public sortDesc: string = ''; - + @observable public sortDesc: string = ''; @action public setSortDesc = (t: string) => { this.sortDesc = t; }; @@ -153,8 +118,12 @@ export class GPTPopup extends ObservableReactComponent { @observable onQuizRandom?: () => void; @observable cardsDoneLoading = false; + @observable collectionDoc: Doc | undefined = undefined; + @action setCollectionDoc(doc: Doc | undefined) { + this.collectionDoc = doc; + } + @action setCardsDoneLoading(done: boolean) { - console.log(done + 'HI HIHI'); this.cardsDoneLoading = done; } @@ -184,7 +153,6 @@ export class GPTPopup extends ObservableReactComponent { */ generateQuiz = async () => { this.setLoading(true); - this.setSortDone(false); const selected = DocumentView.SelectedDocs().lastElement(); @@ -203,7 +171,6 @@ export class GPTPopup extends ObservableReactComponent { this.conversationArray.push(res); this.setLoading(false); - this.setSortDone(true); } catch (err) { console.error('GPT call failed', err); } @@ -235,7 +202,8 @@ export class GPTPopup extends ObservableReactComponent { * Callback function that causes the card view to update the childpair string list * @param callback */ - @action public setRegenerateCallback(callback: () => Promise) { + @action public setRegenerateCallback(collectionDoc: Doc | undefined, callback: null | (() => Promise)) { + this.setCollectionDoc(collectionDoc); this.regenerateCallback = callback; } @@ -254,7 +222,6 @@ export class GPTPopup extends ObservableReactComponent { */ generateCard = async () => { this.setLoading(true); - this.setSortDone(false); if (this.regenerateCallback) { await this.regenerateCallback(); @@ -296,7 +263,6 @@ export class GPTPopup extends ObservableReactComponent { } this.setLoading(false); - this.setSortDone(true); }; /** @@ -554,7 +520,7 @@ export class GPTPopup extends ObservableReactComponent { }; sortBox = () => ( -
+
{this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')} <> { @@ -727,6 +693,15 @@ export class GPTPopup extends ObservableReactComponent { {(this.mode === GPTPopupMode.SORT || this.mode === GPTPopupMode.QUIZ) && ( } onClick={() => (this.mode = GPTPopupMode.CARD)} style={{ right: '50px', position: 'absolute' }} /> )} + this.collectionDoc && (this.collectionDoc.childFilters = undefined)} + /> Date: Wed, 29 Jan 2025 15:44:07 -0500 Subject: fixing card quiz mode --- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/pdf/GPTPopup') diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 63130d056..da0cbea7a 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -158,7 +158,7 @@ export class GPTPopup extends ObservableReactComponent { const questionText = 'Question: ' + StrCast(selected.gptInputText); const rubricText = 'Rubric: ' + StrCast(selected.gptRubric); - const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText; + const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + rubricText; try { const res = await gptAPICall(queryText, GPTCallType.QUIZ); @@ -189,7 +189,7 @@ export class GPTPopup extends ObservableReactComponent { generateRubric = async (inputText: string, doc: Doc) => { try { const res = await gptAPICall(inputText, GPTCallType.RUBRIC); - doc['gptRubric'] = res; + doc.gptRubric = res; return res; } catch (err) { console.error('GPT call failed', err); -- cgit v1.2.3-70-g09d2 From 1e673454f8cabf894e8dfec36734d2cb41caa7b1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 3 Feb 2025 10:23:56 -0500 Subject: changed filter 'check' to be exact match and 'match' to be substring. fixed GPTpopup filtering to use a #chat tag instead of a chatFilter field. --- src/client/apis/gpt/GPT.ts | 2 +- src/client/documents/DocUtils.ts | 4 +- src/client/util/CurrentUserUtils.ts | 3 +- src/client/views/TagsView.scss | 4 +- src/client/views/TagsView.tsx | 2 +- .../views/collections/CollectionCardDeckView.tsx | 11 +++-- src/client/views/global/globalScripts.ts | 4 +- src/client/views/nodes/ComparisonBox.tsx | 2 +- src/client/views/nodes/IconTagBox.scss | 2 - src/client/views/pdf/GPTPopup/GPTPopup.tsx | 57 ++++++++++------------ 10 files changed, 44 insertions(+), 47 deletions(-) (limited to 'src/client/views/pdf/GPTPopup') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 269a4284a..1894bb4df 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -42,7 +42,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { model: 'gpt-4o', maxTokens: 2048, temp: 0.7, - prompt: 'Create a stack of flashcards out of this text with each question and answer labeled as question and answer. Create a title that represents the question in just a few words and label it "title". For some questions, ask "what is this image of" but tailored to stacks theme and the image and write a keyword that represents the image and label it "keyword". Otherwise, write none. Do not label each flashcard and do not include asterisks.', + prompt: 'Create a stack of at least 10 flashcards out of this text with each question and answer labeled as question and answer. Each flashcard should have a title that represents the question in just a few words and label it "title". For some questions, ask "what is this image of" but tailored to stacks theme and the image and write a keyword that represents the image and label it "keyword". Otherwise, write none. Do not label each flashcard and do not include asterisks.', }, completion: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." }, diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index d11a3e235..23032b62e 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -72,9 +72,9 @@ export namespace DocUtils { } const vals = StrListCast(fieldVal); // list typing is very imperfect. casting to a string list doesn't mean that the entries will actually be strings if (vals.length) { - return vals.some(v => typeof v === 'string' && v.includes(value as string)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return vals.some(v => typeof v === 'string' && v === (value as string)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } - return Field.toString(fieldVal as FieldType).includes(value as string); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return Field.toString(fieldVal as FieldType) === (value as string); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } /** * @param docs diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b75961575..0fd09b479 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -728,7 +728,8 @@ pie title Minerals in my tap water this.filterBtnDesc("Star", "star"), this.filterBtnDesc("Like", "heart"), this.filterBtnDesc("Todo", "bolt"), - this.filterBtnDesc("Idea", "cloud") + this.filterBtnDesc("Idea", "cloud"), + this.filterBtnDesc("Chat", "robot") ]; return [ { title:"Options",isSystem: true,icon: "gear", toolTip:"Click to customize list of filter buttons", btnType: ButtonType.ClickButton, expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}}, diff --git a/src/client/views/TagsView.scss b/src/client/views/TagsView.scss index 303a08e1e..b21d303fb 100644 --- a/src/client/views/TagsView.scss +++ b/src/client/views/TagsView.scss @@ -12,7 +12,7 @@ .tagsView-list { display: flex; flex-wrap: wrap; - height: inherit; + height: 1; .iconButton-container { min-height: unset !important; } @@ -58,7 +58,7 @@ } .tagsView-editing-box { - margin-top: 8px; + margin-top: 20px; } .tagsView-input-box { diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 9e936ea54..b70e21918 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -146,7 +146,7 @@ export class TagItem extends ObservableReactComponent { } } } - doc[DocData].tags = new List((doc[DocData].tags as List).filter(label => label !== tag)); + doc[DocData].tags = new List(StrListCast(doc[DocData].tags).filter(label => label !== tag)); }; private _ref: React.RefObject; diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index f24673c39..43464e50c 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -85,6 +85,10 @@ export class CollectionCardView extends CollectionSubView() { }); componentDidMount() { + this._disposers.chatVis = reaction( + () => GPTPopup.Instance.Visible, + vis => !vis && this.onGptHide() + ); GPTPopup.Instance.setRegenerateCallback(this.Document, this.childPairStringListAndUpdateSortDesc); this._props.setContentViewBox?.(this); // if card deck moves, then the child doc views are hidden so their screen to local transforms will return empty rectangles @@ -106,6 +110,7 @@ export class CollectionCardView extends CollectionSubView() { ); } + onGptHide = () => Doc.setDocFilter(this.Document, 'tags', '#chat', 'remove'); componentWillUnmount() { GPTPopup.Instance.setSortDesc(''); GPTPopup.Instance.onSortComplete = undefined; @@ -445,7 +450,7 @@ export class CollectionCardView extends CollectionSubView() { if (questionType === '2' || questionType === '4') { this.childDocs.forEach(d => { - d.chatFilter = false; + TagItem.removeTagFromDoc(d, '#chat'); }); } @@ -471,8 +476,8 @@ export class CollectionCardView extends CollectionSubView() { break; case '2': case '4': - doc.chatFilter = true; - Doc.setDocFilter(DocCast(this.Document), 'chatFilter', true, 'check'); + TagItem.addTagToDoc(doc, '#chat'); + Doc.setDocFilter(this.Document, 'tags', '#chat', 'check'); break; } } else { diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index b02eabab0..b44292164 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -223,9 +223,9 @@ ScriptingGlobals.add(function showFreeform( setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = true; }, }], ['toggle-chat', { - checkResult: (doc: Doc) => GPTPopup.Instance.visible, + checkResult: (doc: Doc) => GPTPopup.Instance.Visible, setDoc: (doc: Doc, dv: DocumentView) => { - if (GPTPopup.Instance.visible){ + if (GPTPopup.Instance.Visible){ doc[Doc.LayoutFieldKey(doc)+"_sort"] = ''; GPTPopup.Instance.setVisible(false); diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index f5291a4c1..53fbd11c5 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -510,7 +510,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() * Calls GPT for each flashcard type. */ askGPT = async (callType: GPTCallType) => { - const questionText = 'Question: ' + this.frontText; + const questionText = this.frontText; const queryText = questionText + (callType == GPTCallType.QUIZ ? ' UserAnswer: ' + this._inputValue + '. ' + ' Rubric: ' + this.backText : ''); this.loading = true; diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss index 90cc06092..c79d662f4 100644 --- a/src/client/views/nodes/IconTagBox.scss +++ b/src/client/views/nodes/IconTagBox.scss @@ -10,8 +10,6 @@ gap: 5px; padding-left: 5px; padding-right: 5px; - padding-top: 2px; - padding-bottom: 2px; button { pointer-events: auto; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index da0cbea7a..f5a9f9e6a 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -46,9 +46,9 @@ export class GPTPopup extends ObservableReactComponent { @observable private chatMode: boolean = false; private correlatedColumns: string[] = []; - @observable public visible: boolean = false; + @observable public Visible: boolean = false; @action public setVisible = (vis: boolean) => { - this.visible = vis; + this.Visible = vis; }; @observable public loading: boolean = false; @action public setLoading = (loading: boolean) => { @@ -114,8 +114,8 @@ export class GPTPopup extends ObservableReactComponent { this.sortDesc = t; }; - @observable onSortComplete?: (sortResult: string, questionType: string, tag?: string) => void; - @observable onQuizRandom?: () => void; + onSortComplete?: (sortResult: string, questionType: string, tag?: string) => void; + onQuizRandom?: () => void; @observable cardsDoneLoading = false; @observable collectionDoc: Doc | undefined = undefined; @@ -154,30 +154,27 @@ export class GPTPopup extends ObservableReactComponent { generateQuiz = async () => { this.setLoading(true); - const selected = DocumentView.SelectedDocs().lastElement(); + await this.regenerateCallback?.(); - const questionText = 'Question: ' + StrCast(selected.gptInputText); - const rubricText = 'Rubric: ' + StrCast(selected.gptRubric); - const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + rubricText; + const selected = DocumentView.SelectedDocs().lastElement(); + if (!StrCast(selected.gptRubric)) { + await this.generateRubric(StrCast(selected.gptInputText), selected); + } try { - const res = await gptAPICall(queryText, GPTCallType.QUIZ); - if (!res) { - console.error('GPT call failed'); - return; - } - console.log(res); - this.setQuizResp(res); - this.conversationArray.push(res); + 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.setLoading(false); + this.onQuizRandom?.(); + } else { + console.error('GPT provided no response'); + } } catch (err) { console.error('GPT call failed', err); } - - if (this.onQuizRandom) { - this.onQuizRandom(); - } }; /** @@ -223,9 +220,7 @@ export class GPTPopup extends ObservableReactComponent { generateCard = async () => { this.setLoading(true); - if (this.regenerateCallback) { - await this.regenerateCallback(); - } + await this.regenerateCallback?.(); try { const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE); @@ -442,9 +437,7 @@ export class GPTPopup extends ObservableReactComponent { onClick={() => { this.conversationArray = ['Define the selected card!']; this.setMode(GPTPopupMode.QUIZ); - if (this.onQuizRandom) { - this.onQuizRandom(); - } + this.onQuizRandom?.(); }} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} @@ -694,13 +687,13 @@ export class GPTPopup extends ObservableReactComponent { } onClick={() => (this.mode = GPTPopupMode.CARD)} style={{ right: '50px', position: 'absolute' }} /> )} this.collectionDoc && (this.collectionDoc.childFilters = undefined)} + onClick={() => this.collectionDoc && Doc.setDocFilter(this.collectionDoc, 'tags', '#chat', 'remove')} /> { } return ( -
+
{content}
-- cgit v1.2.3-70-g09d2 From 8ec3055971ed3d79289e90c1fc2d0db5df131fe5 Mon Sep 17 00:00:00 2001 From: bobzel 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/client/views/pdf/GPTPopup') 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/client/views/pdf/GPTPopup') 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/client/views/pdf/GPTPopup') 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 { ) : ( <>