diff options
author | bobzel <zzzman@gmail.com> | 2025-07-01 13:17:40 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2025-07-01 13:17:40 -0400 |
commit | 86c666427ff8b9d516450a150af641570e00f2d2 (patch) | |
tree | a12c359f8a06cd11bedd09bccd3d4bf7d7cba678 /src | |
parent | b3e9d7473e46bd05baf978607cbc60dfc32a71b0 (diff) |
reverted chat send to use dash component Button, and dictation to use Toggle. Reverted Dropdown to use trigger of CLICK (hover doesn't work well). got rid of currentuserutil button references in infoUI & replaced with info-specific button descriptions. fixed up a bunch of lint/typing errors
Diffstat (limited to 'src')
9 files changed, 425 insertions, 380 deletions
diff --git a/src/client/views/DictationButton.tsx b/src/client/views/DictationButton.tsx index fc3165f67..882e857c5 100644 --- a/src/client/views/DictationButton.tsx +++ b/src/client/views/DictationButton.tsx @@ -1,9 +1,10 @@ -import { makeObservable, observable, action } from 'mobx'; +import { Toggle, ToggleType } from '@dash/components'; +import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import './DictationButton.scss'; import { DictationManager } from '../util/DictationManager'; import { SnappingManager } from '../util/SnappingManager'; +import './DictationButton.scss'; export interface DictationButtonProps { setInput: (val: string) => void; @@ -26,9 +27,21 @@ export class DictationButton extends React.Component<DictationButtonProps> { render() { return ( - <button - className={`dictation-button ${this._isRecording ? 'recording' : ''}`} - title="Record" + <Toggle + // className={`dictation-button ${this._isRecording ? 'recording' : ''}`} + // title="Record" + tooltip={`Dictation: ${this._isRecording ? 'on' : 'off'}`} + icon={ + <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> + <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path> + <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path> + <line x1="12" y1="19" x2="12" y2="23"></line> + <line x1="8" y1="23" x2="16" y2="23"></line> + </svg> + } + color={SnappingManager.userVariantColor} + toggleType={ToggleType.BUTTON} + toggleStatus={this._isRecording} onClick={action(() => { if (!this._isRecording) { this._isRecording = true; @@ -49,14 +62,8 @@ export class DictationButton extends React.Component<DictationButtonProps> { } else { this.stopDictation(); } - })}> - <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> - <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path> - <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path> - <line x1="12" y1="19" x2="12" y2="23"></line> - <line x1="8" y1="23" x2="16" y2="23"></line> - </svg> - </button> + })} + /> ); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 8ed3e8e30..48cab9c7b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -3,16 +3,24 @@ import { IReactionDisposer, action, makeObservable, observable, reaction } from import { observer } from 'mobx-react'; import * as React from 'react'; import { SettingsManager } from '../../../util/SettingsManager'; +import { ButtonType } from '../../nodes/FontIconBox/FontIconBox'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import './CollectionFreeFormView.scss'; -import { Button } from '../../../util/CurrentUserUtils'; +export interface InfoButton { + targetState?: infoState; + // DocumentOptions fields a button can set + title?: string; + toolTip?: string; + btnType?: ButtonType; + // fields that do not correspond to DocumentOption fields + scripts?: { script?: string; onClick?: string; onDoubleClick?: string }; +} /** * An Fsa Arc. The first array element is a test condition function that will be observed. * The second array element is a function that will be invoked when the first test function * returns a truthy value */ -// eslint-disable-next-line no-use-before-define export type infoArc = [() => unknown, (res?: unknown) => infoState]; export const StateMessage = Symbol('StateMessage'); @@ -22,19 +30,13 @@ export const StateMessageButton = Symbol('StateMessageButton'); export class infoState { [StateMessage]: string = ''; [StateMessageGIF]?: string = ''; - [StateMessageButton]?: Button[]; + [StateMessageButton]?: InfoButton[]; [StateEntryFunc]?: () => unknown; [key: string]: infoArc; - constructor( - message: string, - arcs: { [key: string]: infoArc }, - messageGif?: string, - buttons?: Button[], - entryFunc?: () => unknown - ) { + constructor(message: string, arcs?: { [key: string]: infoArc }, messageGif?: string, buttons?: InfoButton[], entryFunc?: () => unknown) { this[StateMessage] = message; - Object.assign(this, arcs); + Object.assign(this, arcs ?? {}); this[StateMessageGIF] = messageGif; this[StateEntryFunc] = entryFunc; this[StateMessageButton] = buttons; @@ -53,9 +55,9 @@ export class infoState { */ export function InfoState( msg: string, // - arcs: { [key: string]: infoArc }, + arcs?: { [key: string]: infoArc }, gif?: string, - button?: Button[], + button?: InfoButton[], entryFunc?: () => unknown ) { return new infoState(msg, arcs, gif, button, entryFunc); @@ -63,7 +65,7 @@ export function InfoState( export interface CollectionFreeFormInfoStateProps { infoState: infoState; - next: (state: infoState) => unknown; // Ensure it's properly defined + next: (state: infoState) => unknown; // Ensure it's properly defined close: () => void; } @@ -114,8 +116,8 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec render() { const gif = this.State?.[StateMessageGIF]; const buttons = this.State?.[StateMessageButton]; - console.log("Rendering CollectionFreeFormInfoState with state:", this.props.infoState); - console.log(buttons) + console.log('Rendering CollectionFreeFormInfoState with state:', this.props.infoState); + console.log(buttons); return ( <div className="collectionFreeform-infoUI"> <p className="collectionFreeform-infoUI-msg"> @@ -129,11 +131,11 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec {this._expanded ? 'Less...' : 'More...'} </button> </p> - + <div className={'collectionFreeform-' + (!this._expanded || !gif ? 'hidden' : 'infoUI-gif-container')}> <img src={`/assets/${gif}`} alt="state message gif" /> </div> - + {/* Render the buttons for skipping */} <div className={'collectionFreeform-' + (!buttons || buttons.length === 0 ? 'hidden' : 'infoUI-button-container')}> {buttons?.map((button, index) => ( @@ -142,23 +144,16 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec type="button" className="collectionFreeform-infoUI-skip-button" onClick={action(() => { - console.log("Attempting transition to:", button.targetState); + console.log('Attempting transition to:', button.targetState); this.props.next(button.targetState as infoState); // ✅ Use the prop instead - })}> + })}> {button.title} </button> ))} </div> - + <div className="collectionFreeform-infoUI-close"> - <IconButton - icon="x" - color={SettingsManager.userColor} - size={Size.XSMALL} - type={Type.TERT} - background={SettingsManager.userBackgroundColor} - onClick={action(() => this.props.close())} - /> + <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(() => this.props.close())} /> </div> </div> ); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index efc22f523..147c900be 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -1,17 +1,13 @@ -import { makeObservable, observable, runInAction } from 'mobx'; +import { action, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc } from '../../../../fields/Doc'; -import { ObservableReactComponent } from '../../ObservableReactComponent'; -import { CollectionFreeFormInfoState, infoState } from './CollectionFreeFormInfoState'; -import { Button } from '../../../util/CurrentUserUtils'; -import { InfoState } from './CollectionFreeFormInfoState'; -import { DocButtonState } from '../../nodes/DocumentLinksButton'; -import { InkTool } from '../../../../fields/InkField'; -import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { CollectionFreeFormView } from '.'; -import { DocListCast } from '../../../../fields/Doc'; +import { Doc, DocListCast } from '../../../../fields/Doc'; +import { InkTool } from '../../../../fields/InkField'; +import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { ButtonType } from '../../nodes/FontIconBox/FontIconBox'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { CollectionFreeFormInfoState, InfoButton, infoState, InfoState } from './CollectionFreeFormInfoState'; export interface CollectionFreeFormInfoUIProps { Document: Doc; @@ -22,28 +18,19 @@ export interface CollectionFreeFormInfoUIProps { @observer export class CollectionFreeFormInfoUI extends ObservableReactComponent<CollectionFreeFormInfoUIProps> { - _originalBackground: string | undefined; - private tutorialStates: { [key: string]: infoState } = {}; + private _originalBackground: string | undefined; + private _tutorialStates: { [key: string]: infoState } = {}; public static Init() { - CollectionFreeFormView.SetInfoUICreator( - (doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => - <CollectionFreeFormInfoUI - Document={doc} - LayoutDoc={layout} - childDocs={childDocs} - close={close} - /> - ); + CollectionFreeFormView.SetInfoUICreator((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => <CollectionFreeFormInfoUI Document={doc} LayoutDoc={layout} childDocs={childDocs} close={close} />); } constructor(props: CollectionFreeFormInfoUIProps) { super(props); makeObservable(this); - this.tutorialStates = {}; // Initialize an empty object + this._tutorialStates = {}; // Initialize an empty object this.currState = this.setupStates(); // Call setupStates() here } - @observable _currState: infoState | undefined = undefined; @observable _nextState: infoState | undefined = undefined; // Track next state @@ -51,52 +38,47 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio get currState() { return this._currState; } - + set currState(val) { - runInAction(() => { - this._currState = val; - }); + runInAction(() => (this._currState = val)); } - componentWillUnmount(): void { + componentWillUnmount() { this._props.Document.backgroundColor = this._originalBackground; } - skipToState = (newState: infoState) => { - runInAction(() => { - if (!this._currState) { - this._currState = newState; // Assign directly if undefined - } else { - this._currState = newState; - } - }); - }; + skipToState = action((newState: infoState) => (this._currState = newState)); createNextButton = (newState: ReturnType<typeof InfoState>) => { return { - title: "Next", - toolTip: "Next", + title: 'Next', + toolTip: 'Next', btnType: ButtonType.ClickButton, scripts: { - onClick: `this.skipToState(${newState})` + onClick: `this.skipToState(${newState})`, }, - targetState: newState + targetState: newState, }; }; setupStates = () => { let docCounter = this._props.childDocs().length; - let lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] + let lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; let linkCounter = Doc.Links(lastDocCreated)?.length; - let presentationCounter = DocListCast(Doc.ActivePresentation?.data).length + let presentationCounter = DocListCast(Doc.ActivePresentation?.data).length; this._originalBackground = this._props.Document.backgroundColor as string; - - this.tutorialStates.multipleDocs = InfoState("Let's create a new link! Click the link icon on one document and connect it to another.", { - // eslint-disable-next-line no-use-before-define - linkStarted: [() => DocumentLinksButton.StartLink, () => { - linkCounter = Doc.Links(lastDocCreated).length - // eslint-disable-next-line no-use-before-define - return startedLink}], + + this._tutorialStates.multipleDocs = InfoState( + "Let's create a new link! Click the link icon on one document and connect it to another.", + { + linkStarted: [ + () => DocumentLinksButton.StartLink, + () => { + linkCounter = Doc.Links(lastDocCreated).length; + // eslint-disable-next-line no-use-before-define + return startedLink; + }, + ], // docCreated: [() => this._props.childDocs().length > docCounter, () => { // docCounter += 1 // lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] @@ -106,238 +88,306 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio 'dash-create-link-board.gif' ); - this.tutorialStates.presentDocs = InfoState("Select a document then click the 'pin' button in the top left to create your presentation.", { - // eslint-disable-next-line no-use-before-define - docPinned: [() => DocListCast(Doc.ActivePresentation?.data).length > presentationCounter, () => { - presentationCounter++ - // eslint-disable-next-line no-use-before-define - return pinnedDoc}], + this._tutorialStates.presentDocs = InfoState( + "Select a document then click the 'pin' button in the top left to create your presentation.", + { + docPinned: [ + () => DocListCast(Doc.ActivePresentation?.data).length > presentationCounter, + () => { + presentationCounter++; + // eslint-disable-next-line no-use-before-define + return pinnedDoc; + }, + ], }, - 'pin-explanation.gif'); - - this.tutorialStates.nestedCollections = InfoState("Want to learn how to create a nested collection? Click the : button and add a 'collection' doc", { - // eslint-disable-next-line no-use-before-define - docCreated: [() => this._props.childDocs().length > docCounter, () => { - docCounter += 1 - lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] - // eslint-disable-next-line no-use-before-define - return marqueeSelection}] - }, - 'dash-nested-collection.gif') - - - this.tutorialStates.makePresentation = InfoState("Add a new document to create a presentation!", { - docCreated: [() => this._props.childDocs().length > docCounter, () => { - docCounter += 1 - lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] - // eslint-disable-next-line no-use-before-define - return this.tutorialStates.presentDocs}], - }); - - const skipToLinksButton: Button = { - title: "Links Tutorial", - toolTip: "Skip", - btnType: ButtonType.ClickButton, - scripts: { - onClick: "this.skipToState(this.tutorialStates.multipleDocs)" + 'pin-explanation.gif' + ); + + this._tutorialStates.nestedCollections = InfoState( + "Want to learn how to create a nested collection? Click the : button and add a 'collection' doc", + { + docCreated: [ + () => this._props.childDocs().length > docCounter, + () => { + docCounter += 1; + lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; + // eslint-disable-next-line no-use-before-define + return marqueeSelection; + }, + ], }, - targetState: this.tutorialStates.multipleDocs - }; + 'dash-nested-collection.gif' + ); + + this._tutorialStates.makePresentation = InfoState('Add a new document to create a presentation!', { + docCreated: [ + () => this._props.childDocs().length > docCounter, + () => { + docCounter += 1; + lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; + return this._tutorialStates.presentDocs; + }, + ], + }); - const skipToPinsButton: Button = { - title: "Pins Tutorial", - toolTip: "Skip", + const skipToLinksButton: InfoButton = { + title: 'Links Tutorial', + toolTip: 'Skip', btnType: ButtonType.ClickButton, scripts: { - onClick: "this.skipToState(this.tutorialStates.makePresentation)" + onClick: 'this.skipToState(this.tutorialStates.multipleDocs)', }, - targetState: this.tutorialStates.makePresentation + targetState: this._tutorialStates.multipleDocs, }; - const skipToPresentationButton: Button = { - title: "Collections Tutorial", - toolTip: "Skip", + const skipToPinsButton: InfoButton = { + title: 'Pins Tutorial', + toolTip: 'Skip', btnType: ButtonType.ClickButton, scripts: { - onClick: "this.skipToState(this.tutorialStates.nestedCollections)" + onClick: 'this.skipToState(this.tutorialStates.makePresentation)', }, - targetState: this.tutorialStates.nestedCollections + targetState: this._tutorialStates.makePresentation, }; - const ending = InfoState("If you have any more questions, feel free to ask Dash's AI Bot!") + // const skipToPresentationButton: Button = { + // title: "Collections Tutorial", + // toolTip: "Skip", + // btnType: ButtonType.ClickButton, + // scripts: { + // onClick: "this.skipToState(this.tutorialStates.nestedCollections)" + // }, + // targetState: this.tutorialStates.nestedCollections + // }; + + const ending = InfoState("If you have any more questions, feel free to ask Dash's AI Bot!"); // Traditional tutorial - const completed = InfoState( - "Eager to learn more? Click the ? icon in the top right corner to read our full documentation.", - { docRemoved: [() => this._props.childDocs().length === 1, () => this.tutorialStates.start] }, - 'documentation.png', - ); + const completed = InfoState('Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', { docRemoved: [() => this._props.childDocs().length === 1, () => this._tutorialStates.start] }, 'documentation.png'); const penMode = InfoState("You're in pen mode! Click and drag to draw your first masterpiece, then click the Ink button once you're done.", { activePen: [() => Doc.ActiveTool !== InkTool.Ink, () => completed], }); - + const briefArtisticFeature = InfoState("Finally, want to explore the art feature of Dash? Click the 'Ink' button on the hotbar then click the pen button.", { penModeActivated: [() => Doc.ActiveTool === InkTool.Ink, () => penMode], }); - - const activatePresentation = InfoState("Lastly, click the linked node and start the presentation!", { - presentation: [() => Doc.ActivePresentation?.presentation_status === "auto", () => briefArtisticFeature], + + const activatePresentation = InfoState('Lastly, click the linked node and start the presentation!', { + presentation: [() => Doc.ActivePresentation?.presentation_status === 'auto', () => briefArtisticFeature], }); - - const deletePresentation = InfoState("Cool! Click 'setOnClick to follow primary link' for your non-presentation doc and try deleting the presentation.", { - docRemoved: [() => this._props.childDocs().length < docCounter, () => { - docCounter -= 1 - return activatePresentation}], - }, - 'onclick-node.gif'); - - const trailedPresentation = InfoState("Try linking your presentation to the last doc you created (now highlighted).", { - linkAdd: [() => Doc.Links(lastDocCreated)?.length > linkCounter, () => { - linkCounter += 1 - return deletePresentation - }], - docAdded: [() => this._props.childDocs().length > docCounter, () => { - docCounter += 1 - // Last doc that is not the presentation - lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 2] - linkCounter = Doc.Links(lastDocCreated)?.length - return deletePresentation}] - }, - 'link-presentation.gif'); - - const pinnedPresentation = InfoState("Want to see something cool? Zoom out, click the trail button on the presentation, and drag it inside the canvas.", { - docAdded: [() => this._props.childDocs().length > docCounter, () => { - docCounter += 1 - // Last doc that is not the presentation - lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 2] - Doc.HighlightDoc(lastDocCreated) - linkCounter = Doc.Links(lastDocCreated)?.length - return trailedPresentation}], - }, - 'dash-trail-explanation.gif'); - - const pinnedDoc2 = InfoState("You pinned another doc. Press autoplay to the right to show your presentation!", { - autoPresentation: [() => Doc.ActivePresentation?.presentation_status === "auto", () => pinnedPresentation], + + const deletePresentation = InfoState( + "Cool! Click 'setOnClick to follow primary link' for your non-presentation doc and try deleting the presentation.", + { + docRemoved: [ + () => this._props.childDocs().length < docCounter, + () => { + docCounter -= 1; + return activatePresentation; + }, + ], + }, + 'onclick-node.gif' + ); + + const trailedPresentation = InfoState( + 'Try linking your presentation to the last doc you created (now highlighted).', + { + linkAdd: [ + () => Doc.Links(lastDocCreated)?.length > linkCounter, + () => { + linkCounter += 1; + return deletePresentation; + }, + ], + docAdded: [ + () => this._props.childDocs().length > docCounter, + () => { + docCounter += 1; + // Last doc that is not the presentation + lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 2]; + linkCounter = Doc.Links(lastDocCreated)?.length; + return deletePresentation; + }, + ], + }, + 'link-presentation.gif' + ); + + const pinnedPresentation = InfoState( + 'Want to see something cool? Zoom out, click the trail button on the presentation, and drag it inside the canvas.', + { + docAdded: [ + () => this._props.childDocs().length > docCounter, + () => { + docCounter += 1; + // Last doc that is not the presentation + lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 2]; + Doc.HighlightDoc(lastDocCreated); + linkCounter = Doc.Links(lastDocCreated)?.length; + return trailedPresentation; + }, + ], + }, + 'dash-trail-explanation.gif' + ); + + const pinnedDoc2 = InfoState('You pinned another doc. Press autoplay to the right to show your presentation!', { + autoPresentation: [() => Doc.ActivePresentation?.presentation_status === 'auto', () => pinnedPresentation], }); - const pinnedDoc = InfoState("You just pinned your doc. Pin another doc to add to the presentation!", { - // eslint-disable-next-line no-use-before-define - addedDoc: [() => this._props.childDocs().length > docCounter, () => { - docCounter += 1 - lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] - return pinnedDoc}], - docPinned: [() => DocListCast(Doc.ActivePresentation?.data).length > presentationCounter, () => { - presentationCounter++ - // eslint-disable-next-line no-use-before-define - return pinnedDoc2}], + const pinnedDoc = InfoState('You just pinned your doc. Pin another doc to add to the presentation!', { + addedDoc: [ + () => this._props.childDocs().length > docCounter, + () => { + docCounter += 1; + lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; + return pinnedDoc; + }, + ], + docPinned: [ + () => DocListCast(Doc.ActivePresentation?.data).length > presentationCounter, + () => { + presentationCounter++; + return pinnedDoc2; + }, + ], }); - - const editLink = InfoState("Want to make your link visible? Click 'show link'.", { - docCreated: [() => this._props.childDocs().length > docCounter, () => { - docCounter += 1 - lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] - return this.tutorialStates.makePresentation}], - }, - 'show-link.gif'); - - const madeLink = InfoState("You made your first link! You can view your links by selecting the blue dot.", { - linkViewed: [() => DocButtonState.Instance.LinkEditorDocView, () => { - docCounter = this._props.childDocs().length - return editLink}], - }, - 'dash-following-link.gif'); - - const startedLink = InfoState("Now click the highlighted link icon on your other document.", { - linkAdd: [() => Doc.Links(lastDocCreated)?.length > linkCounter, () => { - linkCounter += 1 - return madeLink - }] - }, - 'dash-create-link-board.gif'); - - this.tutorialStates.movedDoc = InfoState("Great moves! Try creating a second document.", { - docCreated: [() => this._props.childDocs().length > docCounter, () => { - docCounter += 1 - lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] - return this.tutorialStates.multipleDocs}], - }, - 'dash-colon-menu.gif'); // prettier-ignore - - this.tutorialStates.start = InfoState("Welcome to Dash! Click anywhere and begin typing ':' to create your first document.", { - docCreated: [() => this._props.childDocs().length > docCounter, () => { - docCounter += 1 - lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] - return this.tutorialStates.movedDoc}] - }, - undefined, - [skipToLinksButton, skipToPinsButton]) - + + const editLink = InfoState( + "Want to make your link visible? Click 'show link'.", + { + docCreated: [ + () => this._props.childDocs().length > docCounter, + () => { + docCounter += 1; + lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; + return this._tutorialStates.makePresentation; + }, + ], + }, + 'show-link.gif' + ); + + const madeLink = InfoState( + 'You made your first link! You can view your links by selecting the blue dot.', + { + linkViewed: [ + () => DocButtonState.Instance.LinkEditorDocView, + () => { + docCounter = this._props.childDocs().length; + return editLink; + }, + ], + }, + 'dash-following-link.gif' + ); + + const startedLink = InfoState( + 'Now click the highlighted link icon on your other document.', + { + linkAdd: [ + () => Doc.Links(lastDocCreated)?.length > linkCounter, + () => { + linkCounter += 1; + return madeLink; + }, + ], + }, + 'dash-create-link-board.gif' + ); + + this._tutorialStates.movedDoc = InfoState( + "Great moves! Try creating a second document.", + { + docCreated: [ + () => this._props.childDocs().length > docCounter, + () => { + docCounter += 1 + lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] + return this._tutorialStates.multipleDocs + } + ], + }, + 'dash-colon-menu.gif'); // prettier-ignore + + this._tutorialStates.start = InfoState( + "Welcome to Dash! Click anywhere and begin typing ':' to create your first document.", + { + docCreated: [ + () => this._props.childDocs().length > docCounter, + () => { + docCounter += 1; + lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; + return this._tutorialStates.movedDoc; + }, + ], + }, + undefined, + [skipToLinksButton, skipToPinsButton] + ); + // Information on created nested collections - const createdMarquee = InfoState("Next, right click and drag a square to create the collection", { - // eslint-disable-next-line no-use-before-define - marqueeMade: [() => this._props.childDocs().length < docCounter, () => { - docCounter -= 1 - return ending}], - }, - 'dash-create-collection-marquee.gif') - - const marqueeSelection = InfoState("Want an easier way to make a collection of docs? First add two docs you want to make a collection of", { - marqueeMade: [() => this._props.childDocs().length > docCounter, () => { - docCounter += 1 - lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] - return createdMarquee}] - }) + const createdMarquee = InfoState( + 'Next, right click and drag a square to create the collection', + { + marqueeMade: [ + () => this._props.childDocs().length < docCounter, + () => { + docCounter -= 1; + return ending; + }, + ], + }, + 'dash-create-collection-marquee.gif' + ); + + const marqueeSelection = InfoState('Want an easier way to make a collection of docs? First add two docs you want to make a collection of', { + marqueeMade: [ + () => this._props.childDocs().length > docCounter, + () => { + docCounter += 1; + lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; + return createdMarquee; + }, + ], + }); // Explanation of importing - const easierImport = InfoState("Or, for easier access, you can drag any of the accepted file types from your computer or a webpage and drop it into your dashboard. This includes images, videos, audio, pdfs, and more!", { - }, - 'dash-', - [this.createNextButton(ending)]) - - this.tutorialStates.importFile = InfoState("Want to learn how to import a file? Import using the import menu on the left hand side", { - }, - 'dash-import.gif', - [this.createNextButton(easierImport)]) + const easierImport = InfoState('Or, for easier access, you can drag any of the accepted file types from your computer or a webpage and drop it into your dashboard. This includes images, videos, audio, pdfs, and more!', {}, 'dash-', [ + this.createNextButton(ending), + ]); + + this._tutorialStates.importFile = InfoState('Want to learn how to import a file? Import using the import menu on the left hand side', {}, 'dash-import.gif', [this.createNextButton(easierImport)]); // Editing documents // Accessed by right-clicking anywhere on the target document or selecting the three bars menu at the bottom of the document chrome - const extraContentsOfDoc = InfoState("Lastly, all documents also have a context-sensitive toolbar. The toolbar contents vary depending on the document type.", { - }, - 'context-toolbar.png', - [this.createNextButton(ending)]) - - const contentsofDoc = InfoState("You can access the context of a doc through right-clicking anywhere on the target document or selecting the three bars menu at the bottom of the document chrome", { - }, - 'dash-context-menu.gif', - [this.createNextButton(extraContentsOfDoc)]) - - const propertiesofDoc = InfoState("You can also access the properties of a doc through the double arrows in the top right or the single arrow on the right edge of the screen", { - }, - 'dash-properties-pane.gif', - [this.createNextButton(contentsofDoc)]) - - this.tutorialStates.editingDocuments = InfoState("Want to learn how to edit a document? Either left or right click the document", { - }, - 'document-chrome.png', - [this.createNextButton(propertiesofDoc)]) - return this.tutorialStates.start - }; + const extraContentsOfDoc = InfoState('Lastly, all documents also have a context-sensitive toolbar. The toolbar contents vary depending on the document type.', {}, 'context-toolbar.png', [this.createNextButton(ending)]); + + const contentsofDoc = InfoState('You can access the context of a doc through right-clicking anywhere on the target document or selecting the three bars menu at the bottom of the document chrome', {}, 'dash-context-menu.gif', [ + this.createNextButton(extraContentsOfDoc), + ]); + const propertiesofDoc = InfoState('You can also access the properties of a doc through the double arrows in the top right or the single arrow on the right edge of the screen', {}, 'dash-properties-pane.gif', [ + this.createNextButton(contentsofDoc), + ]); + + this._tutorialStates.editingDocuments = InfoState('Want to learn how to edit a document? Either left or right click the document', {}, 'document-chrome.png', [this.createNextButton(propertiesofDoc)]); + return this._tutorialStates.start; + }; render() { - if (!this.currState) return null; - - return ( - <CollectionFreeFormInfoState - next={this.skipToState} // This ensures skipToState is passed correctly - close={this._props.close} - infoState={this.currState} + return !this.currState ? null : ( + <CollectionFreeFormInfoState + next={this.skipToState} // This ensures skipToState is passed correctly + close={this._props.close} + infoState={this.currState} /> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 78bacdcac..fb2346bd1 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -49,7 +49,6 @@ import { gptImageLabel } from '../../apis/gpt/GPT'; const DefaultPath = '/assets/unknown-file-icon-hi.png'; export class ImageEditorData { - // eslint-disable-next-line no-use-before-define private static _instance: ImageEditorData; private static get imageData() { return (ImageEditorData._instance ?? new ImageEditorData()).imageData; } // prettier-ignore @observable imageData: { rootDoc: Doc | undefined; open: boolean; source: string; addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean> } = observable({ rootDoc: undefined, open: false, source: '', addDoc: undefined }); diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss index 4a916e86c..0bacc70c2 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss @@ -183,7 +183,6 @@ $font-size-xlarge: 18px; box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.05); position: relative; align-items: center; - gap: 12px; z-index: 5; transition: padding 0.2s ease; diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx index 19459b025..636b77b38 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx @@ -7,10 +7,12 @@ * with support for follow-up questions and citation management. */ +import { Button, Size, Type } from '@dash/components'; import { ObservableSet, action, computed, makeObservable, observable, observe, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import OpenAI, { ClientOptions } from 'openai'; import * as React from 'react'; +import { AiOutlineSend } from 'react-icons/ai'; import { v4 as uuidv4 } from 'uuid'; import { ClientUtils, OmitKeys } from '../../../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../../../fields/Doc'; @@ -18,7 +20,9 @@ import { DocData, DocViews } from '../../../../../fields/DocSymbols'; import { Id } from '../../../../../fields/FieldSymbols'; import { RichTextField } from '../../../../../fields/RichTextField'; import { ScriptField } from '../../../../../fields/ScriptField'; -import { CsvCast, DocCast, PDFCast, RTFCast, StrCast, VideoCast, AudioCast } from '../../../../../fields/Types'; +import { AudioCast, CsvCast, DocCast, NumCast, PDFCast, RTFCast, StrCast, VideoCast } from '../../../../../fields/Types'; +import { Upload } from '../../../../../server/SharedMediaTypes'; +import { DocServer } from '../../../../DocServer'; import { DocUtils } from '../../../../documents/DocUtils'; import { CollectionViewType, DocumentType } from '../../../../documents/DocumentTypes'; import { Docs, DocumentOptions } from '../../../../documents/Documents'; @@ -26,23 +30,23 @@ import { DocumentManager } from '../../../../util/DocumentManager'; import { ImageUtils } from '../../../../util/Import & Export/ImageUtils'; import { LinkManager } from '../../../../util/LinkManager'; import { CompileError, CompileScript } from '../../../../util/Scripting'; +import { SnappingManager } from '../../../../util/SnappingManager'; import { DictationButton } from '../../../DictationButton'; import { ViewBoxAnnotatableComponent } from '../../../DocComponent'; import { AudioBox } from '../../AudioBox'; import { DocumentView, DocumentViewInternal } from '../../DocumentView'; import { FieldView, FieldViewProps } from '../../FieldView'; +import { OpenWhere } from '../../OpenWhere'; import { PDFBox } from '../../PDFBox'; import { ScriptingBox } from '../../ScriptingBox'; import { VideoBox } from '../../VideoBox'; import { Agent } from '../agentsystem/Agent'; import { supportedDocTypes } from '../types/tool_types'; import { ASSISTANT_ROLE, AssistantMessage, CHUNK_TYPE, Citation, ProcessingInfo, SimplifiedChunk, TEXT_TYPE } from '../types/types'; +import { AgentDocumentManager } from '../utils/AgentDocumentManager'; import { Vectorstore } from '../vectorstore/Vectorstore'; import './ChatBox.scss'; import MessageComponentBox from './MessageComponent'; -import { OpenWhere } from '../../OpenWhere'; -import { Upload } from '../../../../../server/SharedMediaTypes'; -import { AgentDocumentManager } from '../utils/AgentDocumentManager'; export type parsedDocData = { doc_type: string; @@ -555,8 +559,8 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { })(); if (ndoc) { - ndoc.x = NumCast((options.x as number) ?? 0) + (insideCol ? 0 : NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc.width)) + 100; - ndoc.y = NumCast(options.y as number) + (insideCol ? 0 : NumCast(this.layoutDoc.y)); + ndoc.x = ((options.x as number) ?? 0) + (insideCol ? 0 : NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc.width)) + 100; + ndoc.y = ((options.y as number) ?? 0) + (insideCol ? 0 : NumCast(this.layoutDoc.y)); } return ndoc; }; @@ -655,8 +659,8 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } else { console.warn(`Chunk not found for chunk ID: ${chunkId}`); } - return; - } + return; + } console.log(`Found chunk in document:`, foundChunk); @@ -665,7 +669,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const directMatchSegmentStart = this.getDirectMatchingSegmentStart(doc, citation.direct_text || '', foundChunk.indexes || []); if (directMatchSegmentStart) { await this.goToMediaTimestamp(doc, directMatchSegmentStart, foundChunk.chunkType); - } else { + } else { console.error('No direct matching segment found for the citation.'); } } else if (foundChunk.chunkType === CHUNK_TYPE.TABLE || foundChunk.chunkType === CHUNK_TYPE.IMAGE) { @@ -710,7 +714,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { // If specific indexes are provided, filter segments by those indexes if (indexesOfSegments && indexesOfSegments.length > 0) { - segments = original_segments.filter((segment: any) => indexesOfSegments.includes(segment.index)); + segments = original_segments.filter(segment => indexesOfSegments.includes(segment.index)); } // If no segments match the indexes, use all segments @@ -719,7 +723,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } // First try to find an exact match - const exactMatch = segments.find((segment: any) => segment.text && segment.text.includes(citationText)); + const exactMatch = segments.find(segment => segment.text && segment.text.includes(citationText)); if (exactMatch) { return exactMatch.start; @@ -828,7 +832,8 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { existingDoc._width = x2 - x1; existingDoc._height = y2 - y1; } - const highlightDoc = existingDoc ?? this.createImageCitationHighlight(x1, y1, x2, y2, citation, annotationKey, doc); + // const highlightDoc = + existingDoc ?? this.createImageCitationHighlight(x1, y1, x2, y2, citation, annotationKey, doc); //doc.layout_scroll = y1; doc._layout_curPage = foundChunk.startPage + 1; @@ -922,7 +927,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { console.error(`Maximum verification attempts (${attempt}) reached for document ${doc.id}`); // Last resort: force re-creation of the document view - if (isPDF) { + if (isPDF) { console.log('Forcing document recreation as last resort'); DocumentManager.Instance.showDocument(doc, { willZoomCentered: true, @@ -950,7 +955,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return; } - this.processPDFDocumentView(doc, isPDF, citation, foundChunk); + this.processPDFDocumentView(doc, isPDF, citation, foundChunk); } catch (error) { console.error(`Error on verification attempt ${attempt}:`, error); if (attempt < 5) { @@ -1454,16 +1459,16 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { disabled={this._isLoading} /> </div> - <button className="submit-button" onClick={() => this._dictation?.stopDictation()} type="submit" disabled={this._isLoading || !this._inputValue.trim()}> - {this._isLoading ? ( - <div className="spinner"></div> - ) : ( - <svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"> - <line x1="22" y1="2" x2="11" y2="13"></line> - <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon> - </svg> - )} - </button> + <Button + // className="submit-button" + onClick={() => this._dictation?.stopDictation()} + type={Type.PRIM} + tooltip="Send to AI" + color={SnappingManager.userVariantColor} + inactive={this._isLoading || !this._inputValue.trim()} + icon={<AiOutlineSend />} + size={Size.LARGE} + /> <DictationButton ref={this.setDictationRef} setInput={this.setChatInput} inputRef={this._textInputRef} /> </form> {/* Popup for citation */} diff --git a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts index a55f901e1..da4a4ae29 100644 --- a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts +++ b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts @@ -1,16 +1,7 @@ -import { Doc, FieldType } from '../../../../../fields/Doc'; -import { DocData } from '../../../../../fields/DocSymbols'; +import { Parameter, ParametersType, supportedDocTypes, ToolInfo } from '../types/tool_types'; import { Observation } from '../types/types'; -import { ParametersType, ToolInfo, Parameter } from '../types/tool_types'; -import { BaseTool } from './BaseTool'; -import { DocumentOptions } from '../../../../documents/Documents'; -import { CollectionFreeFormDocumentView } from '../../../nodes/CollectionFreeFormDocumentView'; -import { v4 as uuidv4 } from 'uuid'; -import { LinkManager } from '../../../../util/LinkManager'; -import { DocCast, StrCast } from '../../../../../fields/Types'; -import { supportedDocTypes } from '../types/tool_types'; -import { parsedDoc } from '../chatboxcomponents/ChatBox'; import { AgentDocumentManager } from '../utils/AgentDocumentManager'; +import { BaseTool } from './BaseTool'; // Define the parameters for the DocumentMetadataTool const parameterDefinitions: ReadonlyArray<Parameter> = [ @@ -598,7 +589,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp message: string; fieldName?: string; originalFieldName?: string; - newValue?: any; + newValue?: string | number | boolean | object; warning?: string; }[] = []; diff --git a/src/client/views/nodes/chatbot/utils/AgentDocumentManager.ts b/src/client/views/nodes/chatbot/utils/AgentDocumentManager.ts index 3c8b49f33..dcb708450 100644 --- a/src/client/views/nodes/chatbot/utils/AgentDocumentManager.ts +++ b/src/client/views/nodes/chatbot/utils/AgentDocumentManager.ts @@ -1,7 +1,5 @@ import { action, computed, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; -import { observer } from 'mobx-react'; -import { v4 as uuidv4 } from 'uuid'; -import { Doc, StrListCast } from '../../../../../fields/Doc'; +import { Doc, FieldResult, StrListCast } from '../../../../../fields/Doc'; import { DocData } from '../../../../../fields/DocSymbols'; import { Id } from '../../../../../fields/FieldSymbols'; import { List } from '../../../../../fields/List'; @@ -30,7 +28,7 @@ export class AgentDocumentManager { @observable private documentsById: ObservableMap<string, AgentDocument>; private chatBox: ChatBox; private chatBoxDocument: Doc | null = null; - private fieldMetadata: Record<string, any> = {}; + private fieldMetadata: Record<string, any> = {}; // bcz: CHANGE any to a proper type! @observable private simplifiedChunks: ObservableMap<string, SimplifiedChunk>; /** @@ -103,6 +101,7 @@ export class AgentDocumentManager { for (const [fieldName, fieldInfo] of documentOptionsEntries) { // Extract field information const fieldData: Record<string, any> = { + // bcz: CHANGE any to a proper type! name: fieldName, withoutUnderscore: fieldName.startsWith('_') ? fieldName.substring(1) : fieldName, description: '', @@ -223,6 +222,7 @@ export class AgentDocumentManager { const dataDoc = agentDoc.dataDoc; const metadata: Record<string, any> = { + // bcz: CHANGE any to a proper type! id: layoutDoc[Id] || dataDoc[Id] || '', title: layoutDoc.title || '', type: layoutDoc.type || '', @@ -235,7 +235,7 @@ export class AgentDocumentManager { // Process all known field definitions Object.keys(this.fieldMetadata).forEach(fieldName => { - const fieldDef = this.fieldMetadata[fieldName]; + // const fieldDef = this.fieldMetadata[fieldName]; const strippedName = fieldName.startsWith('_') ? fieldName.substring(1) : fieldName; // Check if field exists on layout document @@ -307,7 +307,7 @@ export class AgentDocumentManager { * @param value The field value to format * @returns A JSON-friendly representation of the field value */ - private formatFieldValue(value: any): any { + private formatFieldValue(value: FieldResult | undefined) { if (value === undefined || value === null) { return null; } @@ -330,12 +330,12 @@ export class AgentDocumentManager { if (rtfObj.doc && rtfObj.doc.content) { // Recursively extract text from the content let plainText = ''; - const extractText = (node: any) => { + const extractText = (node: { text: string; content?: unknown[] }) => { if (node.text) { plainText += node.text; } if (node.content && Array.isArray(node.content)) { - node.content.forEach((child: any) => extractText(child)); + node.content.forEach(child => extractText(child as { text: string; content?: unknown[] })); } }; @@ -351,7 +351,7 @@ export class AgentDocumentManager { }; } } - } catch (e) { + } catch { // If parsing fails, just treat as a regular string } } @@ -366,7 +366,7 @@ export class AgentDocumentManager { try { // Try to convert to JSON string return JSON.stringify(value); - } catch (e) { + } catch { return '[Complex Object]'; } } @@ -381,26 +381,24 @@ export class AgentDocumentManager { * @param fieldValue The string value to convert * @returns The converted value with the appropriate type */ - private convertFieldValue(fieldName: string, fieldValue: any): any { + private convertFieldValue(fieldName: string, fieldValueIn: string | number | boolean): FieldResult | undefined { // If fieldValue is already a number or boolean, we don't need to convert it from string - if (typeof fieldValue === 'number' || typeof fieldValue === 'boolean') { - return fieldValue; + if (typeof fieldValueIn === 'number' || typeof fieldValueIn === 'boolean') { + return fieldValueIn; } // If fieldValue is a string "true" or "false", convert to boolean - if (typeof fieldValue === 'string') { - if (fieldValue.toLowerCase() === 'true') { + if (typeof fieldValueIn === 'string') { + if (fieldValueIn.toLowerCase() === 'true') { return true; } - if (fieldValue.toLowerCase() === 'false') { + if (fieldValueIn.toLowerCase() === 'false') { return false; } } - // If fieldValue is not a string (and not a number or boolean), convert it to string - if (typeof fieldValue !== 'string') { - fieldValue = String(fieldValue); - } + // coerce fieldvValue to a string + const fieldValue = typeof fieldValueIn !== 'string' ? String(fieldValueIn) : fieldValueIn; // Special handling for text field - convert to proper RichTextField format if (fieldName === 'text') { @@ -408,7 +406,7 @@ export class AgentDocumentManager { // Check if it's already a valid JSON RichTextField JSON.parse(fieldValue); return fieldValue; - } catch (e) { + } catch { // It's a plain text string, so convert it to RichTextField format const rtf = { doc: { @@ -462,21 +460,21 @@ export class AgentDocumentManager { // Try to convert to date (stored as number timestamp) try { return new Date(fieldValue).getTime(); - } catch (e) { + } catch { return fieldValue; } } else if (fieldType.includes('list') || fieldType.includes('array')) { // Try to parse as JSON array try { - return JSON.parse(fieldValue); - } catch (e) { + return JSON.parse(fieldValue) as FieldResult; // bcz: this needs to be typed properly. Dash fields can't accept a generic 'objext' + } catch { return fieldValue; } } else if (fieldType === 'json' || fieldType === 'object') { // Try to parse as JSON object try { - return JSON.parse(fieldValue); - } catch (e) { + return JSON.parse(fieldValue) as FieldResult; // bcz: this needs to be typed properly. Dash fields can't accept a generic 'objext' + } catch { return fieldValue; } } @@ -492,6 +490,7 @@ export class AgentDocumentManager { public getAllFieldMetadata() { // Start with our already populated fieldMetadata from the DocumentOptions class const result: Record<string, any> = { + // bcz: CHANGE any to a proper type! fieldCount: Object.keys(this.fieldMetadata).length, fields: {}, fieldsByType: { @@ -526,6 +525,7 @@ export class AgentDocumentManager { // Create structured field metadata const fieldData: Record<string, any> = { + // bcz: CHANGE any to a proper type! name: fieldName, displayName: strippedName, description: fieldInfo.description || '', @@ -618,12 +618,12 @@ export class AgentDocumentManager { message: string; fieldName?: string; originalFieldName?: string; - newValue?: any; + newValue?: string | number | boolean | object; warning?: string; } { // Normalize field name (handle with/without underscore) let normalizedFieldName = fieldName.startsWith('_') ? fieldName : fieldName; - const strippedFieldName = fieldName.startsWith('_') ? fieldName.substring(1) : fieldName; + // const strippedFieldName = fieldName.startsWith('_') ? fieldName.substring(1) : fieldName; // Handle common field name aliases (width → _width, height → _height) // Many document fields use '_' prefix for layout properties @@ -690,7 +690,7 @@ export class AgentDocumentManager { } // Set the field value on the target document - targetDoc[normalizedFieldName] = convertedValue; + targetDoc[normalizedFieldName] = convertedValue; // bcz: converteValue needs to be typed properly. Dash fields can't accept a generic 'objext' return { success: true, @@ -712,19 +712,19 @@ export class AgentDocumentManager { * @param documentId Optional ID of a specific document to get metadata for * @returns Document metadata or metadata for all documents */ - public getDocumentMetadata(documentId?: string): any { + public getDocumentMetadata(documentId?: string) { if (documentId) { console.log(`Returning document metadata for docID, ${documentId}:`, this.extractDocumentMetadata(documentId)); return this.extractDocumentMetadata(documentId); } else { // Get metadata for all documents - const documentsMetadata: Record<string, Record<string, any>> = {}; - for (const documentId of this.documentsById.keys()) { - const metadata = this.extractDocumentMetadata(documentId); + const documentsMetadata: Record<string, Record<string, any>> = {}; // bcz: CHANGE any to a proper type! + for (const docid of this.documentsById.keys()) { + const metadata = this.extractDocumentMetadata(docid); if (metadata) { - documentsMetadata[documentId] = metadata; + documentsMetadata[docid] = metadata; } else { - console.warn(`No metadata found for document with ID: ${documentId}`); + console.warn(`No metadata found for document with ID: ${docid}`); } } return { @@ -842,7 +842,7 @@ export class AgentDocumentManager { * @returns The ID of the created document */ - public async createDocInDash(docType: string, data: string, options?: any): Promise<string> { + public async createDocInDash(docType: string, data: string, options?: DocumentOptions): Promise<string> { // Validate doc_type if (!this.isValidDocType(docType)) { throw new Error(`Invalid document type: ${docType}`); @@ -1054,13 +1054,13 @@ export class AgentDocumentManager { endPage: chunk.metadata.end_page, location: chunk.metadata.location, } as SimplifiedChunk); - } else if (docType === 'csv') { + } else if (docType === 'csv' && 'row_start' in chunk.metadata && 'row_end' in chunk.metadata && 'col_start' in chunk.metadata && 'col_end' in chunk.metadata) { simplifiedChunks.push({ ...baseChunk, - rowStart: (chunk.metadata as any).row_start, - rowEnd: (chunk.metadata as any).row_end, - colStart: (chunk.metadata as any).col_start, - colEnd: (chunk.metadata as any).col_end, + rowStart: chunk.metadata.row_start, + rowEnd: chunk.metadata.row_end, + colStart: chunk.metadata.col_start, + colEnd: chunk.metadata.col_end, } as SimplifiedChunk); } else { // Default for other document types @@ -1077,7 +1077,7 @@ export class AgentDocumentManager { * @returns The simplified chunk if found, undefined otherwise */ @action - public getSimplifiedChunkById(chunkId: string): any | undefined { + public getSimplifiedChunkById(chunkId: string) { return { foundChunk: this.simplifiedChunks.get(chunkId), doc: this.getDocument(this.simplifiedChunks.get(chunkId)?.doc_id || chunkId), dataDoc: this.getDataDocument(this.simplifiedChunks.get(chunkId)?.doc_id || chunkId) }; } @@ -1098,7 +1098,7 @@ export class AgentDocumentManager { * @param doc The document containing original media segments * @returns Array of media segments or empty array if none exist */ - public getOriginalSegments(doc: Doc): any[] { + public getOriginalSegments(doc: Doc): { text: string; index: string; start: number }[] { if (!doc || !doc.original_segments) { return []; } diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 9c37428ee..6e0d58932 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -1,4 +1,4 @@ -import { Button, IconButton, Toggle, ToggleType, Type } from '@dash/components'; +import { Button, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; @@ -9,7 +9,10 @@ import ReactLoading from 'react-loading'; import { TypeAnimation } from 'react-type-animation'; import { ClientUtils } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; +import { List } from '../../../../fields/List'; import { NumCast, StrCast } from '../../../../fields/Types'; +import { ImageField } from '../../../../fields/URLField'; +import { Upload } from '../../../../server/SharedMediaTypes'; import { Networking } from '../../../Network'; import { DataSeperator, DescriptionSeperator, DocSeperator, GPTCallType, GPTDocCommand, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT'; import { DocUtils } from '../../../documents/DocUtils'; @@ -21,17 +24,14 @@ import { DictationButton } from '../../DictationButton'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { TagItem } from '../../TagsView'; import { ChatSortField, docSortings } from '../../collections/CollectionSubView'; +import { ComparisonBox } from '../../nodes/ComparisonBox'; import { DocumentView, DocumentViewInternal } from '../../nodes/DocumentView'; +import { OpenWhere } from '../../nodes/OpenWhere'; +import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler'; +import { FireflyImageDimensions } from '../../smartdraw/FireflyConstants'; import { SmartDrawHandler } from '../../smartdraw/SmartDrawHandler'; import { AnchorMenu } from '../AnchorMenu'; import './GPTPopup.scss'; -import { FireflyImageDimensions } from '../../smartdraw/FireflyConstants'; -import { Upload } from '../../../../server/SharedMediaTypes'; -import { OpenWhere } from '../../nodes/OpenWhere'; -import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler'; -import { ImageField } from '../../../../fields/URLField'; -import { List } from '../../../../fields/List'; -import { ComparisonBox } from '../../nodes/ComparisonBox'; export enum GPTPopupMode { SUMMARY, // summary of seleted document text @@ -45,7 +45,6 @@ export enum GPTPopupMode { @observer export class GPTPopup extends ObservableReactComponent<object> { - // eslint-disable-next-line no-use-before-define static Instance: GPTPopup; static ChatTag = '#chat'; // tag used by GPT popup to filter docs private _askDictation: DictationButton | null = null; @@ -530,14 +529,14 @@ export class GPTPopup extends ObservableReactComponent<object> { style={{ color: 'black' }} placeholder={placeholder} /> - <Button // - text="Send" - type={Type.TERT} + <Button //\ + type={Type.PRIM} + tooltip="Send to AI" icon={<AiOutlineSend />} iconPlacement="right" - color={SettingsManager.userColor} - background={SettingsManager.userVariantColor} + background={SnappingManager.userVariantColor} onClick={() => this.callGpt(this._mode)} + size={Size.LARGE} /> <DictationButton ref={this.setDictationRef} setInput={onChange} /> </div> |