// ============================================ // Chat Interface Functions // ============================================ let chatSessionId = null; let chatMessages = []; let attachedSessionId = null; // Reset all chat state function resetChatState() { console.log('Resetting chat state...'); chatSessionId = null; chatMessages = []; attachedSessionId = null; console.log('Chat state reset complete'); } // Load Chat View async function loadChatView() { console.log('[loadChatView] Loading chat view...'); // Reset state on view load to prevent stale session references resetChatState(); // Load chat sessions try { console.log('[loadChatView] Fetching sessions...'); const res = await fetch('/claude/api/claude/sessions'); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${await res.text()}`); } const data = await res.json(); console.log('[loadChatView] Sessions data received:', data); const sessionsListEl = document.getElementById('chat-history-list'); if (!sessionsListEl) { console.error('[loadChatView] chat-history-list element not found!'); return; } // ONLY show active sessions - no historical sessions in chat view // Historical sessions are read-only and can't receive new messages const activeSessions = (data.active || []).filter(s => s.status === 'running'); console.log('Active sessions (can receive messages):', activeSessions.length); if (activeSessions.length > 0) { sessionsListEl.innerHTML = activeSessions.map(session => { const projectName = session.metadata && session.metadata.project ? session.metadata.project : session.id.substring(0, 20); return `
đŸ’Ŧ
${projectName}
${new Date(session.createdAt).toLocaleDateString()} Running
`; }).join(''); } else { sessionsListEl.innerHTML = `

No active sessions

`; } console.log('[loadChatView] Chat view loaded successfully'); } catch (error) { console.error('[loadChatView] Error loading chat sessions:', error); const sessionsListEl = document.getElementById('chat-history-list'); if (sessionsListEl) { sessionsListEl.innerHTML = `

Error: ${error.message}

`; } } } // Start New Chat async function startNewChat() { // Reset all state first resetChatState(); // Clear current chat clearChatDisplay(); appendSystemMessage('Creating new chat session...'); // Create new session try { console.log('Creating new Claude Code session...'); const res = await fetch('/claude/api/claude/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ workingDir: '/home/uroma/obsidian-vault', metadata: { type: 'chat', source: 'web-ide' } }) }); const data = await res.json(); console.log('Session creation response:', data); if (data.success) { attachedSessionId = data.session.id; chatSessionId = data.session.id; console.log('New session created:', data.session.id); // Update UI document.getElementById('current-session-id').textContent = data.session.id; document.getElementById('chat-title').textContent = 'New Chat'; // Subscribe to session via WebSocket subscribeToSession(data.session.id); // Reload sessions list loadChatView(); // Show success message appendSystemMessage('✅ New chat session started! You can now chat with Claude Code.'); } else { console.error('Session creation failed:', data); appendSystemMessage('❌ Failed to create session: ' + (data.error || 'Unknown error')); } } catch (error) { console.error('Error starting new chat:', error); appendSystemMessage('❌ Failed to start new chat session: ' + error.message); } } // Attach to Existing Session function attachToSession(sessionId) { attachedSessionId = sessionId; chatSessionId = sessionId; // Update UI document.getElementById('current-session-id').textContent = sessionId; // Load session messages loadSessionMessages(sessionId); // Subscribe to session via WebSocket subscribeToSession(sessionId); // Update active state in sidebar document.querySelectorAll('.chat-session-item').forEach(item => { item.classList.remove('active'); if (item.getAttribute('onclick') && item.getAttribute('onclick').includes(sessionId)) { item.classList.add('active'); } }); appendSystemMessage('Attached to session: ' + sessionId); } // Subscribe to session via WebSocket function subscribeToSession(sessionId) { if (window.ws && window.ws.readyState === WebSocket.OPEN) { window.ws.send(JSON.stringify({ type: 'subscribe', sessionId: sessionId })); console.log('Subscribed to session:', sessionId); } } // Load Session Messages async function loadSessionMessages(sessionId) { try { const res = await fetch('/claude/api/claude/sessions/' + sessionId); const data = await res.json(); if (data.session) { clearChatDisplay(); // Add existing messages from output buffer data.session.outputBuffer.forEach(entry => { appendMessage('assistant', entry.content, false); }); } } catch (error) { console.error('Error loading session messages:', error); } } // Handle Chat Key Press function handleChatKeypress(event) { const input = document.getElementById('chat-input'); // Update character count const charCount = input.value.length; document.getElementById('char-count').textContent = charCount + ' characters'; // Send on Enter (but allow Shift+Enter for new line) if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendChatMessage(); } // Auto-resize textarea input.style.height = 'auto'; input.style.height = Math.min(input.scrollHeight, 150) + 'px'; } // Send Chat Message async function sendChatMessage() { const input = document.getElementById('chat-input'); const message = input.value.trim(); if (!message) return; if (!attachedSessionId) { appendSystemMessage('Please start or attach to a session first.'); return; } // Add user message to chat appendMessage('user', message); clearInput(); // Show streaming indicator showStreamingIndicator(); try { // Check WebSocket state if (!window.ws) { console.error('WebSocket is null/undefined'); appendSystemMessage('WebSocket not initialized. Please refresh the page.'); hideStreamingIndicator(); return; } const state = window.ws.readyState; const stateName = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'][state] || 'UNKNOWN'; console.log('WebSocket state:', state, stateName); if (state !== WebSocket.OPEN) { console.error('WebSocket not in OPEN state:', stateName); appendSystemMessage(`WebSocket not ready (state: ${stateName}). Retrying...`); hideStreamingIndicator(); // Trigger reconnection if closed if (state === WebSocket.CLOSED) { console.log('WebSocket closed, triggering reconnection...'); if (typeof connectWebSocket === 'function') { connectWebSocket(); } } return; } // Send command via WebSocket window.ws.send(JSON.stringify({ type: 'command', sessionId: attachedSessionId, command: message })); console.log('Sent command via WebSocket:', message.substring(0, 50)); } catch (error) { console.error('Error sending message:', error); hideStreamingIndicator(); appendSystemMessage('Failed to send message: ' + error.message); } } // Append Message to Chat function appendMessage(role, content, scroll) { const messagesContainer = document.getElementById('chat-messages'); // Remove welcome message if present const welcome = messagesContainer.querySelector('.chat-welcome'); if (welcome) { welcome.remove(); } // Remove streaming indicator if present const streaming = messagesContainer.querySelector('.streaming-indicator'); if (streaming) { streaming.remove(); } const messageDiv = document.createElement('div'); messageDiv.className = 'chat-message ' + role; const avatar = document.createElement('div'); avatar.className = 'chat-message-avatar'; avatar.textContent = role === 'user' ? '👤' : '🤖'; const contentDiv = document.createElement('div'); contentDiv.className = 'chat-message-content'; const bubble = document.createElement('div'); bubble.className = 'chat-message-bubble'; // Format content (handle code blocks, etc.) bubble.innerHTML = formatMessage(content); const timestamp = document.createElement('div'); timestamp.className = 'chat-message-timestamp'; timestamp.textContent = new Date().toLocaleTimeString(); contentDiv.appendChild(bubble); contentDiv.appendChild(timestamp); messageDiv.appendChild(avatar); messageDiv.appendChild(contentDiv); messagesContainer.appendChild(messageDiv); // Scroll to bottom if (scroll) { messagesContainer.scrollTop = messagesContainer.scrollHeight; } // Update token usage (estimated) updateTokenUsage(content.length); } // Append System Message function appendSystemMessage(text) { const messagesContainer = document.getElementById('chat-messages'); // Remove welcome message if present const welcome = messagesContainer.querySelector('.chat-welcome'); if (welcome) { welcome.remove(); } const systemDiv = document.createElement('div'); systemDiv.className = 'chat-message assistant'; systemDiv.style.opacity = '0.8'; const avatar = document.createElement('div'); avatar.className = 'chat-message-avatar'; avatar.textContent = 'â„šī¸'; const contentDiv = document.createElement('div'); contentDiv.className = 'chat-message-content'; const bubble = document.createElement('div'); bubble.className = 'chat-message-bubble'; bubble.innerHTML = '' + escapeHtml(text) + ''; contentDiv.appendChild(bubble); systemDiv.appendChild(avatar); systemDiv.appendChild(contentDiv); messagesContainer.appendChild(systemDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } // Format Message (handle code blocks, markdown, etc.) function formatMessage(content) { // Escape HTML first let formatted = escapeHtml(content); // Handle code blocks formatted = formatted.replace(/```(\w+)?\n([\s\S]*?)```/g, function(match, lang, code) { return '
' + code.trim() + '
'; }); // Handle inline code formatted = formatted.replace(/`([^`]+)`/g, '$1'); // Handle line breaks formatted = formatted.replace(/\n/g, '
'); return formatted; } // Show Streaming Indicator function showStreamingIndicator() { const messagesContainer = document.getElementById('chat-messages'); // Remove existing streaming indicator const existing = messagesContainer.querySelector('.streaming-indicator'); if (existing) { existing.remove(); } const streamingDiv = document.createElement('div'); streamingDiv.className = 'streaming-indicator'; streamingDiv.innerHTML = '
'; messagesContainer.appendChild(streamingDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } // Hide Streaming Indicator function hideStreamingIndicator() { const streaming = document.querySelector('.streaming-indicator'); if (streaming) { streaming.remove(); } } // Clear Chat Display function clearChatDisplay() { const messagesContainer = document.getElementById('chat-messages'); messagesContainer.innerHTML = ''; // Reset token usage document.getElementById('token-usage').textContent = '0 tokens used'; } // Clear Chat function clearChat() { if (confirm('Clear all messages in this chat?')) { clearChatDisplay(); // Show welcome message again const messagesContainer = document.getElementById('chat-messages'); messagesContainer.innerHTML = `

👋 Chat Cleared

Start a new conversation with Claude Code.

`; } } // Clear Input function clearInput() { const input = document.getElementById('chat-input'); input.value = ''; input.style.height = 'auto'; document.getElementById('char-count').textContent = '0 characters'; } // Update Token Usage function updateTokenUsage(charCount) { // Rough estimation: ~4 characters per token const estimatedTokens = Math.ceil(charCount / 4); const currentUsage = parseInt(document.getElementById('token-usage').textContent) || 0; document.getElementById('token-usage').textContent = (currentUsage + estimatedTokens) + ' tokens used'; } // Show Attach CLI Modal function showAttachCliModal() { document.getElementById('modal-overlay').classList.remove('hidden'); document.getElementById('attach-cli-modal').classList.remove('hidden'); } // Submit Attach CLI Session async function submitAttachCliSession() { const sessionId = document.getElementById('cli-session-id').value.trim(); if (!sessionId) { alert('Please enter a session ID'); return; } attachToSession(sessionId); closeModal(); } // Attach File (placeholder for now) function attachFile() { appendSystemMessage('File attachment feature coming soon! For now, use @filename to reference files.'); } // Show Chat Settings (placeholder) function showChatSettings() { appendSystemMessage('Chat settings coming soon!'); } // Export variables to window for global access if (typeof window !== 'undefined') { window.attachedSessionId = attachedSessionId; window.chatSessionId = chatSessionId; window.chatMessages = chatMessages; // Create a proxy to keep window vars in sync Object.defineProperty(window, 'attachedSessionId', { get: function() { return attachedSessionId; }, set: function(value) { attachedSessionId = value; } }); Object.defineProperty(window, 'chatSessionId', { get: function() { return chatSessionId; }, set: function(value) { chatSessionId = value; } }); }