From 4997c3de20a381eac30224a7a550afa66174f07d Mon Sep 17 00:00:00 2001
From: Joanne <zehan_ding@brown.edu>
Date: Mon, 12 May 2025 20:53:12 -0400
Subject: added tutorial tool, still need to integrate with metadatatool

---
 .../CollectionFreeFormInfoState.tsx                | 52 ++++++++++++++++++++--
 1 file changed, 48 insertions(+), 4 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 437888ef2..8ed3e8e30 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -5,6 +5,7 @@ import * as React from 'react';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import './CollectionFreeFormView.scss';
+import { Button } from '../../../util/CurrentUserUtils';
 
 /**
  * An Fsa Arc.  The first array element is a test condition function that will be observed.
@@ -17,16 +18,26 @@ export type infoArc = [() => unknown, (res?: unknown) => infoState];
 export const StateMessage = Symbol('StateMessage');
 export const StateMessageGIF = Symbol('StateMessageGIF');
 export const StateEntryFunc = Symbol('StateEntryFunc');
+export const StateMessageButton = Symbol('StateMessageButton');
 export class infoState {
     [StateMessage]: string = '';
     [StateMessageGIF]?: string = '';
+    [StateMessageButton]?: Button[]; 
     [StateEntryFunc]?: () => unknown;
     [key: string]: infoArc;
-    constructor(message: string, arcs: { [key: string]: infoArc }, messageGif?: string, entryFunc?: () => unknown) {
+
+    constructor(
+        message: string, 
+        arcs: { [key: string]: infoArc }, 
+        messageGif?: string, 
+        buttons?: Button[], 
+        entryFunc?: () => unknown
+    ) {
         this[StateMessage] = message;
         Object.assign(this, arcs);
         this[StateMessageGIF] = messageGif;
         this[StateEntryFunc] = entryFunc;
+        this[StateMessageButton] = buttons;
     }
 }
 
@@ -44,14 +55,15 @@ export function InfoState(
     msg: string, //
     arcs: { [key: string]: infoArc },
     gif?: string,
+    button?: Button[],
     entryFunc?: () => unknown
 ) {
-    return new infoState(msg, arcs, gif, entryFunc);
+    return new infoState(msg, arcs, gif, button, entryFunc);
 }
 
 export interface CollectionFreeFormInfoStateProps {
     infoState: infoState;
-    next: (state: infoState) => unknown;
+    next: (state: infoState) => unknown;  // Ensure it's properly defined
     close: () => void;
 }
 
@@ -68,6 +80,10 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
     get State() {
         return this._props.infoState;
     }
+
+    set State(value: infoState) {
+        this._props.infoState = value;
+    }
     get Arcs() {
         return Object.keys(this.State ?? []).map(key => this.State?.[key]);
     }
@@ -97,6 +113,9 @@ 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)
         return (
             <div className="collectionFreeform-infoUI">
                 <p className="collectionFreeform-infoUI-msg">
@@ -110,11 +129,36 @@ 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) => (
+                        <button
+                            key={index}
+                            type="button"
+                            className="collectionFreeform-infoUI-skip-button"
+                            onClick={action(() => {
+                                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>
         );
-- 
cgit v1.2.3-70-g09d2


From 86c666427ff8b9d516450a150af641570e00f2d2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 1 Jul 2025 13:17:40 -0400
Subject: 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

---
 .../components/src/components/Button/Button.tsx    |   1 -
 .../src/components/Dropdown/Dropdown.tsx           |   2 +-
 src/client/views/DictationButton.tsx               |  33 +-
 .../CollectionFreeFormInfoState.tsx                |  53 +-
 .../CollectionFreeFormInfoUI.tsx                   | 536 +++++++++++----------
 src/client/views/nodes/ImageBox.tsx                |   1 -
 .../nodes/chatbot/chatboxcomponents/ChatBox.scss   |   1 -
 .../nodes/chatbot/chatboxcomponents/ChatBox.tsx    |  53 +-
 .../nodes/chatbot/tools/DocumentMetadataTool.ts    |  15 +-
 .../nodes/chatbot/utils/AgentDocumentManager.ts    |  86 ++--
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  27 +-
 11 files changed, 426 insertions(+), 382 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx')

diff --git a/packages/components/src/components/Button/Button.tsx b/packages/components/src/components/Button/Button.tsx
index dd6942a7e..57a870820 100644
--- a/packages/components/src/components/Button/Button.tsx
+++ b/packages/components/src/components/Button/Button.tsx
@@ -5,7 +5,6 @@ import { Colors, Size } from '../../global/globalEnums';
 import { getFontSize, getHeight } from '../../global/globalUtils';
 import { IconButton } from '../IconButton';
 import './Button.scss';
-// import { infoState } from '../../../../../src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState'; // This import was unused
 
 export interface IButtonProps extends IGlobalProps {
     onClick?: (event: React.MouseEvent) => void;
diff --git a/packages/components/src/components/Dropdown/Dropdown.tsx b/packages/components/src/components/Dropdown/Dropdown.tsx
index 7dcaad5c3..783c7daa4 100644
--- a/packages/components/src/components/Dropdown/Dropdown.tsx
+++ b/packages/components/src/components/Dropdown/Dropdown.tsx
@@ -151,7 +151,7 @@ export const Dropdown = (props: IDropdownProps) => {
                 placement={placement}
                 tooltip={tooltip}
                 tooltipPlacement={tooltipPlacement}
-                trigger={PopupTrigger.HOVER}
+                trigger={PopupTrigger.CLICK}
                 isOpen={active}
                 setOpen={setActiveChanged}
                 size={size}
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>
-- 
cgit v1.2.3-70-g09d2

