diff --git a/webapp/chat-app/src/components/ChatInterface.tsx b/webapp/chat-app/src/components/ChatInterface.tsx index a5f8807a..d061c449 100644 --- a/webapp/chat-app/src/components/ChatInterface.tsx +++ b/webapp/chat-app/src/components/ChatInterface.tsx @@ -86,7 +86,7 @@ const ChatInterface: React.FC = ({ debugLog('Processing HTML message'); const newMessage = { id: `${Date.now()}`, - content: data.data, + content: data.data || '', type: 'response' as const, timestamp: data.timestamp, isHtml: true, diff --git a/webapp/chat-app/src/components/MessageList.tsx b/webapp/chat-app/src/components/MessageList.tsx index b6e7156e..eeb7a4b2 100644 --- a/webapp/chat-app/src/components/MessageList.tsx +++ b/webapp/chat-app/src/components/MessageList.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import styled from 'styled-components'; import {useSelector} from 'react-redux'; import {RootState} from '../store'; @@ -7,6 +7,17 @@ import {Message} from '../types'; import {updateTabs} from '../utils/tabHandling'; import {handleMessageAction} from '../utils/messageHandling'; +const expandMessageReferences = (content: string, messages: Message[]): string => { + if (!content) return ''; + return content.replace(/\{([^}]+)}/g, (match, messageId) => { + const referencedMessage = messages.find(m => m.id === messageId); + if (referencedMessage) { + return expandMessageReferences(referencedMessage.content, messages); + } + return match; + }); +}; + const MessageListContainer = styled.div` flex: 1; overflow-y: auto; @@ -25,14 +36,14 @@ const MessageContent = styled.div` } } `; -const extractMessageAction = (target: HTMLElement): {messageId: string | undefined, action: string | undefined} => { +const extractMessageAction = (target: HTMLElement): { messageId: string | undefined, action: string | undefined } => { // Check for data attributes - const messageId = target.getAttribute('data-message-id') ?? - target.getAttribute('data-id') ?? - undefined; - let action = target.getAttribute('data-message-action') ?? - target.getAttribute('data-action') ?? - undefined; + const messageId = target.getAttribute('data-message-id') ?? + target.getAttribute('data-id') ?? + undefined; + let action = target.getAttribute('data-message-action') ?? + target.getAttribute('data-action') ?? + undefined; // Check element classes if (!action) { if (target.classList.contains('href-link')) action = 'link'; @@ -58,7 +69,7 @@ const MessageItem = styled.div<{ type: 'user' | 'system' | 'response' }>` default: return '#f8f9fa'; } - }}; +}}; color: ${({type}) => type === 'user' || type === 'system' ? '#fff' : '#212529'}; `; const handleClick = (e: React.MouseEvent) => { @@ -73,65 +84,81 @@ const handleClick = (e: React.MouseEvent) => { }; interface MessageListProps { - messages?: Message[]; - } + messages?: Message[]; +} - const MessageList: React.FC = ({messages: propMessages}) => { - logger.component('MessageList', 'Rendering component', {hasPropMessages: !!propMessages}); +const MessageList: React.FC = ({messages: propMessages}) => { + logger.component('MessageList', 'Rendering component', {hasPropMessages: !!propMessages}); - // Log when component is mounted/unmounted - React.useEffect(() => { - logger.component('MessageList', 'Component mounted', {timestamp: new Date().toISOString()}); - return () => { - logger.component('MessageList', 'Component unmounted', {timestamp: new Date().toISOString()}); - }; - }, []); + // Log when component is mounted/unmounted + React.useEffect(() => { + logger.component('MessageList', 'Component mounted', {timestamp: new Date().toISOString()}); + return () => { + logger.component('MessageList', 'Component unmounted', {timestamp: new Date().toISOString()}); + }; + }, []); - const storeMessages = useSelector((state: RootState) => state.messages.messages); - // Ensure messages is always an array - const messages = Array.isArray(propMessages) ? propMessages : - Array.isArray(storeMessages) ? storeMessages : []; + const storeMessages = useSelector((state: RootState) => state.messages.messages); + // Ensure messages is always an array + const messages = Array.isArray(propMessages) ? propMessages : + Array.isArray(storeMessages) ? storeMessages : []; + const processMessageContent = useCallback((content: string) => { + return expandMessageReferences(content, messages); + }, [messages]); - React.useEffect(() => { - logger.debug('MessageList - Messages updated', { - messageCount: messages.length, - messages: messages, - source: propMessages ? 'props' : 'store' - }); - // Process tabs after messages update - requestAnimationFrame(() => { - updateTabs(); + React.useEffect(() => { + logger.debug('MessageList - Messages updated', { + messageCount: messages.length, + messages: messages, + source: propMessages ? 'props' : 'store' + }); + // Process tabs after messages update + requestAnimationFrame(() => { + updateTabs(); + // Process message references in visible tab content + document.querySelectorAll('.tab-content.active').forEach(tab => { + const content = tab.innerHTML; + const expandedContent = processMessageContent(content); + if (content !== expandedContent) { + tab.innerHTML = expandedContent; + } }); - }, [messages]); + }); + }, [messages]); - return ( - - {messages.map((message) => { - logger.debug('MessageList - Rendering message', { - id: message.id, - type: message.type, - timestamp: message.timestamp, - contentLength: message.content?.length || 0 - }); - // Log message render before returning JSX - logger.debug('MessageList - Message rendered', { - id: message.id, - type: message.type - }); + return ( + - return ( - - - - ); - })} - - ); - }; + {messages.map((message) => { + logger.debug('MessageList - Rendering message', { + id: message.id, + type: message.type, + timestamp: message.timestamp, + contentLength: message.content?.length || 0 + }); + // Log message render before returning JSX + logger.debug('MessageList - Message rendered', { + id: message.id, + type: message.type + }); + return ( + + + + ); + })} + + ); +}; export default MessageList; \ No newline at end of file diff --git a/webapp/chat-app/src/services/appConfig.ts b/webapp/chat-app/src/services/appConfig.ts index 2bd435c6..930fc0f7 100644 --- a/webapp/chat-app/src/services/appConfig.ts +++ b/webapp/chat-app/src/services/appConfig.ts @@ -3,48 +3,22 @@ import {logger} from '../utils/logger'; import {setAppInfo} from '../store/slices/configSlice'; export const fetchAppConfig = async (sessionId: string) => { - try { - logger.info('Fetching app config for session:', sessionId); - const response = await fetch(`appInfo?session=${sessionId}`); - - if (!response.ok) { - throw new Error('Network response was not ok'); - } - - const data = await response.json(); - logger.info('Received app config:', data); - - store.dispatch(setAppInfo(data)); - - return data; - } catch (error) { - logger.error('Failed to fetch app config:', error); - throw error; - } -}; + try { + logger.info('Fetching app config for session:', sessionId); + const response = await fetch(`appInfo?session=${sessionId}`); -export const applyMenubarConfig = (showMenubar: boolean) => { - if (showMenubar === false) { - const elements = { - menubar: document.getElementById('toolbar'), - namebar: document.getElementById('namebar'), - mainInput: document.getElementById('main-input'), - session: document.getElementById('session') - }; + if (!response.ok) { + throw new Error('Network response was not ok'); + } - if (elements.menubar) elements.menubar.style.display = 'none'; - if (elements.namebar) elements.namebar.style.display = 'none'; - - if (elements.mainInput) { - elements.mainInput.style.top = '0px'; - } - - if (elements.session) { - elements.session.style.top = '0px'; - elements.session.style.width = '100%'; - elements.session.style.position = 'absolute'; + const data = await response.json(); + logger.info('Received app config:', data); + + store.dispatch(setAppInfo(data)); + + return data; + } catch (error) { + logger.error('Failed to fetch app config:', error); + throw error; } - - logger.info('Applied menubar config: hidden'); - } -}; \ No newline at end of file +}; diff --git a/webapp/chat-app/src/services/websocket.ts b/webapp/chat-app/src/services/websocket.ts index 16fa67c1..8a01b671 100644 --- a/webapp/chat-app/src/services/websocket.ts +++ b/webapp/chat-app/src/services/websocket.ts @@ -14,26 +14,12 @@ export class WebSocketService { private isReconnecting = false; private connectionTimeout: NodeJS.Timeout | null = null; private messageQueue: string[] = []; - private readonly HEARTBEAT_INTERVAL = 30000; - - queueMessage(message: string) { - console.debug('[WebSocket] Queuing message:', message.substring(0, 100) + (message.length > 100 ? '...' : '')); - this.messageQueue.push(message); - this.processMessageQueue(); - } public getSessionId(): string { console.debug('[WebSocket] Getting session ID:', this.sessionId); return this.sessionId; } - public getUrl(): string { - const config = this.getConfig(); - if (!config) return 'not available'; - const path = this.getWebSocketPath(); - return `${config.protocol}//${config.url}:${config.port}${path}ws`; - } - public addErrorHandler(handler: (error: Error) => void): void { this.errorHandlers.push(handler); console.log('[WebSocket] Error handler added'); @@ -65,25 +51,10 @@ export class WebSocketService { console.log('[WebSocket] Connection handler removed'); } - private debugLog(message: string, ...args: any[]) { - if (this.DEBUG) { - console.debug(`[WebSocket] ${message}`, ...args); - } - } - public isConnected(): boolean { return this.ws?.readyState === WebSocket.OPEN; } - private stopHeartbeat() { - if (this.heartbeatInterval) { - clearInterval(this.heartbeatInterval); - this.heartbeatInterval = null; - console.log('[WebSocket] Stopped heartbeat monitoring'); - } - } - - connect(sessionId: string): void { try { if (!sessionId) { @@ -104,7 +75,14 @@ export class WebSocketService { const path = this.getWebSocketPath(); // Only create new connection if not already connected or reconnecting if (!this.isConnected() && !this.isReconnecting) { - const wsUrl = `${config.protocol}//${config.url}:${config.port}${path}ws?sessionId=${sessionId}`; + // Construct URL with proper handling of default ports + let wsUrl = `${config.protocol}//${config.url}`; + // Only add port if it's non-standard + if ((config.protocol === 'ws:' && config.port !== '80') || + (config.protocol === 'wss:' && config.port !== '443')) { + wsUrl += `:${config.port}`; + } + wsUrl += `${path}ws?sessionId=${sessionId}`; console.log(`[WebSocket] Connecting to: ${wsUrl}`); this.ws = new WebSocket(wsUrl); this.setupEventHandlers(); @@ -115,7 +93,7 @@ export class WebSocketService { this.ws?.close(); this.attemptReconnect(); } - }, 5000); + }, 10000); // Increase timeout to 10 seconds } } catch (error) { console.error('[WebSocket] Connection error:', error); @@ -147,13 +125,17 @@ export class WebSocketService { } } - private processMessageQueue() { - if (this.ws?.readyState === WebSocket.OPEN) { - console.debug(`[WebSocket] Processing message queue (${this.messageQueue.length} messages)`); - while (this.messageQueue.length > 0) { - const message = this.messageQueue.shift(); - if (message) this.send(message); - } + private debugLog(message: string, ...args: any[]) { + if (this.DEBUG) { + console.debug(`[WebSocket] ${message}`, ...args); + } + } + + private stopHeartbeat() { + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval); + this.heartbeatInterval = null; + console.log('[WebSocket] Stopped heartbeat monitoring'); } } @@ -176,9 +158,10 @@ export class WebSocketService { } } console.debug('[WebSocket] Using default config'); + const defaultPort = window.location.protocol === 'https:' ? '443' : '8083'; return { url: window.location.hostname, - port: state.config.websocket.port || window.location.port || '8083', + port: state.config?.websocket?.port || window.location.port || defaultPort, protocol: window.location.protocol === 'https:' ? 'wss:' : 'ws:' }; } @@ -187,9 +170,14 @@ export class WebSocketService { const path = window.location.pathname; const strings = path.split('/'); let wsPath = '/'; - if (strings.length >= 2 && strings[1] !== '' && strings[1] !== 'index.html') { + // Simplify path handling to avoid potential issues + if (strings.length >= 2 && strings[1]) { wsPath = '/' + strings[1] + '/'; } + // Ensure path ends with trailing slash + if (!wsPath.endsWith('/')) { + wsPath += '/'; + } console.debug(`[WebSocket] Calculated WebSocket path: ${wsPath}`); return wsPath; } diff --git a/webapp/chat-app/src/store/Provider.tsx b/webapp/chat-app/src/store/Provider.tsx deleted file mode 100644 index 259ab734..00000000 --- a/webapp/chat-app/src/store/Provider.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, {useEffect} from 'react'; -import {Provider} from 'react-redux'; -import {store} from './index'; - -interface StoreProviderProps { - children: React.ReactNode; -} - -const LOG_PREFIX = '[StoreProvider]'; - - -export const StoreProvider: React.FC = ({children}) => { - useEffect(() => { - console.group(`${LOG_PREFIX} Lifecycle`); - console.log('🟢 Component Mounted'); - console.debug('Initial Store State:', store.getState()); - console.groupEnd(); - - return () => { - console.group(`${LOG_PREFIX} Lifecycle`); - console.log('🔴 Component Unmounted'); - console.debug('Final Store State:', store.getState()); - console.groupEnd(); - }; - }, []); - console.log(`${LOG_PREFIX} 🔄 Rendering`); - - return {children}; -}; - -// Log store initialization -console.group(`${LOG_PREFIX} Initialization`); -console.log('📦 Store Module Initialized'); -console.debug('Store Instance:', store); -console.debug('Initial State:', store.getState()); -console.groupEnd(); - - -export default StoreProvider; \ No newline at end of file diff --git a/webapp/chat-app/src/store/index.ts b/webapp/chat-app/src/store/index.ts index e4ec11d4..1d686667 100644 --- a/webapp/chat-app/src/store/index.ts +++ b/webapp/chat-app/src/store/index.ts @@ -55,7 +55,6 @@ export const store = configureStore({ }); export type RootState = ReturnType; -export type AppDispatch = typeof store.dispatch; // Add development-only warning if (process.env.NODE_ENV === 'development') { console.log('%c Redux Store Initialized in Development Mode', diff --git a/webapp/chat-app/src/themes/ThemeProvider.tsx b/webapp/chat-app/src/themes/ThemeProvider.tsx index 7ff09e25..09516770 100644 --- a/webapp/chat-app/src/themes/ThemeProvider.tsx +++ b/webapp/chat-app/src/themes/ThemeProvider.tsx @@ -10,7 +10,6 @@ interface ThemeProviderProps { const LOG_PREFIX = '[ThemeProvider]'; - export const ThemeProvider: React.FC = ({children}) => { const currentTheme = useSelector((state: RootState) => state.ui.theme); const isInitialMount = useRef(true); diff --git a/webapp/chat-app/src/types/global.d.ts b/webapp/chat-app/src/types/global.d.ts index a4521553..d4ad5d7d 100644 --- a/webapp/chat-app/src/types/global.d.ts +++ b/webapp/chat-app/src/types/global.d.ts @@ -17,20 +17,20 @@ declare global { protocol: string; }; }; - // Extended console interface with additional logging methods - console: { - debug(...args: any[]): void; - info(...args: any[]): void; - warn(...args: any[]): void; - error(...args: any[]): void; - trace(...args: any[]): void; - // Add custom console methods - group(label?: string): void; - groupEnd(): void; - table(tabularData: any, properties?: string[]): void; - time(label: string): void; - timeEnd(label: string): void; - }; + // Extended console interface with additional logging methods + console: { + debug(...args: any[]): void; + info(...args: any[]): void; + warn(...args: any[]): void; + error(...args: any[]): void; + trace(...args: any[]): void; + // Add custom console methods + group(label?: string): void; + groupEnd(): void; + table(tabularData: any, properties?: string[]): void; + time(label: string): void; + timeEnd(label: string): void; + }; } } diff --git a/webapp/chat-app/src/types/mermaid.d.ts b/webapp/chat-app/src/types/mermaid.d.ts index 54145f66..d5cc7ad1 100644 --- a/webapp/chat-app/src/types/mermaid.d.ts +++ b/webapp/chat-app/src/types/mermaid.d.ts @@ -6,8 +6,8 @@ declare module 'mermaid' { parse: (text: string) => void; parseError?: Error; } - + const mermaid: MermaidAPI; export default mermaid; - export type { MermaidAPI }; + export type {MermaidAPI}; } \ No newline at end of file diff --git a/webapp/chat-app/src/types/qrcode.d.ts b/webapp/chat-app/src/types/qrcode.d.ts index 15a5b98d..8e12a968 100644 --- a/webapp/chat-app/src/types/qrcode.d.ts +++ b/webapp/chat-app/src/types/qrcode.d.ts @@ -10,7 +10,6 @@ declare module 'qrcode-generator' { } export interface QRCodeGenerator { - (type?: number, errorCorrectionLevel?: string): QRCode; TypeNumber: number; ErrorCorrectionLevel: { L: string; @@ -18,6 +17,8 @@ declare module 'qrcode-generator' { Q: string; H: string; }; + + (type?: number, errorCorrectionLevel?: string): QRCode; } const qrcode: QRCodeGenerator; diff --git a/webapp/chat-app/src/utils/logger.ts b/webapp/chat-app/src/utils/logger.ts index ead3dcdc..99e07472 100644 --- a/webapp/chat-app/src/utils/logger.ts +++ b/webapp/chat-app/src/utils/logger.ts @@ -1,6 +1,7 @@ import {LogEntry, LogLevel} from '../types'; class Logger { + private static instance: Logger; private readonly LOG_LEVELS = { debug: 0, info: 1, @@ -9,15 +10,8 @@ class Logger { }; private isDevelopment = process.env.NODE_ENV === 'development'; private groupDepth = 0; - private static instance: Logger; private logHistory: LogEntry[] = []; private logLevel: LogLevel = 'info'; - private readonly logLevelSeverity: Record = { - debug: 0, - info: 1, - warn: 2, - error: 3 - }; private constructor() { console.log('%cLogger initialized', 'color: #8a2be2; font-weight: bold;'); @@ -30,11 +24,6 @@ class Logger { return Logger.instance; } - public setLogLevel(level: LogLevel): void { - this.logLevel = level; - console.log(`%cLog level set to: ${level}`, 'color: #8a2be2; font-style: italic;'); - } - public debug(message: string, data?: unknown): void { this.log('debug', message, data); } @@ -60,26 +49,6 @@ class Logger { this.log('debug', formattedMessage, formattedData); } - public getHistory(): LogEntry[] { - return [...this.logHistory]; - } - - public clearHistory(): void { - this.logHistory = []; - console.clear(); - console.log('%cLog history cleared', 'color: #8a2be2; font-style: italic;'); - } - - public group(label: string): void { - this.groupDepth++; - console.group(label); - } - - public groupEnd(): void { - this.groupDepth = Math.max(0, this.groupDepth - 1); - console.groupEnd(); - } - private log(level: LogLevel, message: string, data?: unknown): void { // Skip non-critical logs in production if (!this.isDevelopment && level !== 'error') { diff --git a/webapp/chat-app/src/utils/messageHandling.ts b/webapp/chat-app/src/utils/messageHandling.ts index b8e127e5..5ebdcd7c 100644 --- a/webapp/chat-app/src/utils/messageHandling.ts +++ b/webapp/chat-app/src/utils/messageHandling.ts @@ -5,9 +5,35 @@ import {updateTabs} from './tabHandling'; import WebSocketService from '../services/websocket'; import {logger} from './logger'; +const messageCache = new Map(); +const MAX_EXPANSION_DEPTH = 10; +const expandMessageReferences = (content: string, depth = 0): string => { + if (depth > MAX_EXPANSION_DEPTH) { + logger.warn('Max message expansion depth reached', {content}); + return content; + } + return content.replace(/\{([^}]+)}/g, (match, messageId) => { + const cachedContent = messageCache.get(messageId); + if (cachedContent) { + return expandMessageReferences(cachedContent, depth + 1); + } + return match; + }); +}; +const processTabContent = () => { + const activeTabs = document.querySelectorAll('.tab-content.active'); + activeTabs.forEach(tab => { + const content = tab.innerHTML; + const expandedContent = expandMessageReferences(content); + if (content !== expandedContent) { + tab.innerHTML = expandedContent; + } + }); +}; + export const handleMessageAction = (messageId: string, action: string) => { logger.debug('Processing message action', {messageId, action}); - + // Handle text submit actions specially if (action === 'text-submit') { const input = document.querySelector(`.reply-input[data-message-id="${messageId}"]`) as HTMLTextAreaElement; @@ -29,7 +55,7 @@ export const handleMessageAction = (messageId: string, action: string) => { } // Handle run/play button clicks if (action === 'run') { - logger.debug('Processing run action', {messageId}); + logger.debug('Processing run action', {messageId}); WebSocketService.send(`!${messageId},run`); return; } @@ -57,8 +83,9 @@ export const setupMessageHandling = () => { const handleMessage = (message: Message) => { const {id, version, content} = message; console.log(`[MessageHandler] Processing message: ${id} (v${version})`); - + messageVersions.set(id, version); + messageCache.set(id, content); messageMap.set(id, content); console.log(`[MessageHandler] Stored message content: "${content}"`); @@ -74,6 +101,7 @@ export const setupMessageHandling = () => { if (message.isHtml) { requestAnimationFrame(() => { updateTabs(); + processTabContent(); }); } }; @@ -83,29 +111,4 @@ export const setupMessageHandling = () => { messageVersions, messageMap }; -}; - -export const substituteMessages = ( - messageContent: string, - messageMap: Map, - depth = 0 -) : string => { - const MAX_DEPTH = 10; - if (depth > MAX_DEPTH) { - console.warn(`[MessageSubstitution] Max depth (${MAX_DEPTH}) reached for content: "${messageContent}"`); - return messageContent; - } - console.log(`[MessageSubstitution] Processing substitutions at depth ${depth}: "${messageContent}"`); - - - return messageContent.replace(/\{([^}]+)}/g, (match, id) => { - console.log(`[MessageSubstitution] Found reference: ${id}`); - const substitution = messageMap.get(id); - if (substitution) { - console.log(`[MessageSubstitution] Substituting ${id} with: "${substitution}"`); - return substituteMessages(substitution, messageMap, depth + 1); - } - console.log(`[MessageSubstitution] No substitution found for: ${id}`); - return match; - }); }; \ No newline at end of file diff --git a/webapp/chat-app/src/utils/tabHandling.ts b/webapp/chat-app/src/utils/tabHandling.ts index 264ec3ef..b5d97b4f 100644 --- a/webapp/chat-app/src/utils/tabHandling.ts +++ b/webapp/chat-app/src/utils/tabHandling.ts @@ -177,34 +177,4 @@ function setupTabContainer(container: TabContainer) { } } - - function restoreTabState(container: HTMLElement) { - try { - const containerId = container.id; - const savedTab = localStorage.getItem(`tab_state_${containerId}`) - || tabStates.get(containerId)?.activeTab; - - if (savedTab) { - const button = container.querySelector( - `.tab-button[data-for-tab="${savedTab}"]` - ) as HTMLElement; - - if (button) { - console.log(`${LOG_PREFIX} Restoring tab state:`, { - containerId, - savedTab - }); - setActiveTab(button, container); - } - } else { - // Set first tab as active by default - const firstButton = container.querySelector('.tab-button') as HTMLElement; - if (firstButton) { - setActiveTab(firstButton, container); - } - } - } catch (error) { - console.warn(`${LOG_PREFIX} Failed to restore tab state:`, error); - } - } } \ No newline at end of file diff --git a/webapp/chat-app/src/utils/uiHandlers.ts b/webapp/chat-app/src/utils/uiHandlers.ts index 1846155f..922029ab 100644 --- a/webapp/chat-app/src/utils/uiHandlers.ts +++ b/webapp/chat-app/src/utils/uiHandlers.ts @@ -32,7 +32,7 @@ export const setupUIHandlers = () => { const target = event.target as HTMLElement; const messageAction = target.getAttribute('data-message-action'); const messageId = target.getAttribute('data-message-id'); - + if (messageAction && messageId) { event.preventDefault(); console.log(`Message action triggered - ID: ${messageId}, Action: ${messageAction}`);