From 34ecaaffb1eebef6d509ed73db336c7bdb181e76 Mon Sep 17 00:00:00 2001
From: "A.J. Shulman" <Shulman.aj@gmail.com>
Date: Thu, 8 May 2025 15:54:56 -0400
Subject: improve: enhance ChatBox UI with consistent styling and better
 accessibility
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

• Fixed dictation button styling to match send button dimensions and colors
• Created dedicated DictationButton.scss for better button styling
• Removed vertical movement animations from all buttons for a more stable UI
• Fixed empty space issue below the "Show Agent Thoughts/Actions" button
• Implemented consistent hover effects across all interactive elements
• Ensured font size scaling works for agent thoughts/actions content
• Used Dash blue colors (#487af0, #3b6cd7) for consistent branding
• Improved microphone button state visualization with pulse animation
---
 .../nodes/chatbot/chatboxcomponents/ChatBox.tsx    | 106 ++++++++++++++++++---
 1 file changed, 92 insertions(+), 14 deletions(-)

(limited to 'src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx')

diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
index ba30cb42b..490739be6 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
@@ -40,7 +40,6 @@ import { ASSISTANT_ROLE, AssistantMessage, CHUNK_TYPE, Citation, ProcessingInfo,
 import { Vectorstore } from '../vectorstore/Vectorstore';
 import './ChatBox.scss';
 import MessageComponentBox from './MessageComponent';
-import { ProgressBar } from './ProgressBar';
 import { OpenWhere } from '../../OpenWhere';
 import { Upload } from '../../../../../server/SharedMediaTypes';
 import { DocumentMetadataTool } from '../tools/DocumentMetadataTool';
@@ -76,6 +75,8 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable private _linked_csv_files: { filename: string; id: string; text: string }[] = [];
     @observable private _isUploadingDocs: boolean = false;
     @observable private _citationPopup: { text: string; visible: boolean } = { text: '', visible: false };
+    @observable private _isFontSizeModalOpen: boolean = false;
+    @observable private _fontSize: 'small' | 'normal' | 'large' | 'xlarge' = 'normal';
 
     // Private properties for managing OpenAI API, vector store, agent, and UI elements
     private openai!: OpenAI; // Using definite assignment assertion
@@ -147,6 +148,9 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 this.dataDoc.data = JSON.stringify(serializableHistory);
             }
         );
+
+        // Initialize font size from saved preference
+        this.initFontSize();
     }
 
     /**
@@ -1074,12 +1078,61 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     _dictation: DictationButton | null = null;
+
+    /**
+     * Toggles the font size modal visibility
+     */
+    @action
+    toggleFontSizeModal = () => {
+        this._isFontSizeModalOpen = !this._isFontSizeModalOpen;
+    };
+
+    /**
+     * Changes the font size and applies it to the chat interface
+     * @param size The new font size to apply
+     */
+    @action
+    changeFontSize = (size: 'small' | 'normal' | 'large' | 'xlarge') => {
+        this._fontSize = size;
+        this._isFontSizeModalOpen = false;
+
+        // Save preference to localStorage if needed
+        if (typeof window !== 'undefined') {
+            localStorage.setItem('chatbox-font-size', size);
+        }
+    };
+
+    /**
+     * Initializes font size from saved preference
+     */
+    initFontSize = () => {
+        if (typeof window !== 'undefined') {
+            const savedSize = localStorage.getItem('chatbox-font-size');
+            if (savedSize && ['small', 'normal', 'large', 'xlarge'].includes(savedSize)) {
+                this._fontSize = savedSize as 'small' | 'normal' | 'large' | 'xlarge';
+            }
+        }
+    };
+
+    /**
+     * Renders a font size icon SVG
+     */
+    renderFontSizeIcon = () => (
+        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
+            <polyline points="4 7 4 4 20 4 20 7"></polyline>
+            <line x1="9" y1="20" x2="15" y2="20"></line>
+            <line x1="12" y1="4" x2="12" y2="20"></line>
+        </svg>
+    );
+
     /**
      * Renders the chat interface, including the message list, input field, and other UI elements.
      */
     render() {
+        const fontSizeClass = `font-size-${this._fontSize}`;
+
         return (
-            <div className="chat-box">
+            <div className={`chat-box ${fontSizeClass}`}>
                 {this._isUploadingDocs && (
                     <div className="uploading-overlay">
                         <div className="progress-container">
@@ -1095,6 +1148,29 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 )}
                 <div className="chat-header">
                     <h2>{this.userName()}&apos;s AI Assistant</h2>
+                    <div className="font-size-control" onClick={this.toggleFontSizeModal}>
+                        {this.renderFontSizeIcon()}
+                    </div>
+                    {this._isFontSizeModalOpen && (
+                        <div className="font-size-modal">
+                            <div className={`font-size-option ${this._fontSize === 'small' ? 'active' : ''}`} onClick={() => this.changeFontSize('small')}>
+                                <span className="option-label">Small</span>
+                                <span className="size-preview small">Aa</span>
+                            </div>
+                            <div className={`font-size-option ${this._fontSize === 'normal' ? 'active' : ''}`} onClick={() => this.changeFontSize('normal')}>
+                                <span className="option-label">Normal</span>
+                                <span className="size-preview normal">Aa</span>
+                            </div>
+                            <div className={`font-size-option ${this._fontSize === 'large' ? 'active' : ''}`} onClick={() => this.changeFontSize('large')}>
+                                <span className="option-label">Large</span>
+                                <span className="size-preview large">Aa</span>
+                            </div>
+                            <div className={`font-size-option ${this._fontSize === 'xlarge' ? 'active' : ''}`} onClick={() => this.changeFontSize('xlarge')}>
+                                <span className="option-label">Extra Large</span>
+                                <span className="size-preview xlarge">Aa</span>
+                            </div>
+                        </div>
+                    )}
                 </div>
                 <div className="chat-messages" ref={this.messagesRef}>
                     {this._history.map((message, index) => (
@@ -1106,18 +1182,20 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 </div>
 
                 <form onSubmit={this.askGPT} className="chat-input">
-                    <input
-                        ref={r => {
-                            this._textInputRef = r;
-                        }}
-                        type="text"
-                        name="messageInput"
-                        autoComplete="off"
-                        placeholder="Type your message here..."
-                        value={this._inputValue}
-                        onChange={action(e => (this._inputValue = e.target.value))}
-                        disabled={this._isLoading}
-                    />
+                    <div className="input-container">
+                        <input
+                            ref={r => {
+                                this._textInputRef = r;
+                            }}
+                            type="text"
+                            name="messageInput"
+                            autoComplete="off"
+                            placeholder="Type your message here..."
+                            value={this._inputValue}
+                            onChange={action(e => (this._inputValue = e.target.value))}
+                            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>
-- 
cgit v1.2.3-70-g09d2

