/** * Enhanced Chat Interface - Similar to chat.z.ai * Features: Better input, chat history, session resumption, smooth animations */ // ============================================ // Enhanced Chat Input Experience // ============================================ // Auto-focus chat input when switching to chat view function focusChatInput() { setTimeout(() => { const input = document.getElementById('chat-input'); if (input) { input.focus(); // Move cursor to end input.setSelectionRange(input.value.length, input.value.length); } }, 100); } // Smooth textarea resize with animation function enhanceChatInput() { const input = document.getElementById('chat-input'); if (!input) return; // Auto-resize with smooth transition input.style.transition = 'height 0.2s ease'; input.addEventListener('input', function() { this.style.height = 'auto'; const newHeight = Math.min(this.scrollHeight, 200); this.style.height = newHeight + 'px'; }); // Focus animation input.addEventListener('focus', function() { this.parentElement.classList.add('input-focused'); }); input.addEventListener('blur', function() { this.parentElement.classList.remove('input-focused'); }); } // ============================================ // Chat History & Session Management // ============================================ // Auto-load chat history when page loads // Make this a named function so it can be called to refresh the sidebar async function loadChatHistory() { try { const res = await fetch('/claude/api/claude/sessions'); const data = await res.json(); const historyList = document.getElementById('chat-history-list'); if (!historyList) return; // Skip update if we're showing "Loading session..." to avoid conflicts // with attachToSession's loading state if (historyList.textContent.includes('Loading session')) { console.log('[loadChatHistory] Skipping update - showing loading state'); return; } // Combine active and historical sessions const allSessions = [ ...(data.active || []).map(s => ({...s, status: 'active'})), ...(data.historical || []).map(s => ({...s, status: 'historical'})) ]; // Sort by creation date (newest first) allSessions.sort((a, b) => new Date(b.createdAt || b.created_at) - new Date(a.createdAt || a.created_at)); if (allSessions.length === 0) { historyList.innerHTML = '
No chat history yet
'; return; } historyList.innerHTML = allSessions.map(session => { const title = session.metadata?.project || session.project || session.id.substring(0, 12) + '...'; const date = new Date(session.createdAt || session.created_at).toLocaleDateString(); const isActive = session.id === (window.attachedSessionId || null); return `
${session.status === 'historical' ? '📁' : 'đŸ’Ŧ'}
${title}
${date} ${session.status === 'historical' ? 'Historical' : 'Active'}
${session.status === 'historical' ? 'Resume' : ''}
`; }).join(''); } catch (error) { console.error('[loadChatHistory] Error loading chat history:', error); } } // Auto-load chat history when page loads if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', loadChatHistory); } else { // DOM already loaded, load immediately loadChatHistory(); } // Resume historical session async function resumeSession(sessionId) { console.log('Resuming historical session:', sessionId); // Show loading message if (typeof appendSystemMessage === 'function') { appendSystemMessage('📂 Loading historical session...'); } try { // Load the historical session const res = await fetch('/claude/api/claude/sessions/' + sessionId); // Check if response is OK if (!res.ok) { const errorText = await res.text(); console.error('Session fetch error:', res.status, errorText); // Handle 404 - session not found if (res.status === 404) { if (typeof appendSystemMessage === 'function') { appendSystemMessage('❌ Session not found. It may have been deleted or the ID is incorrect.'); } return; } throw new Error(`HTTP ${res.status}: ${errorText}`); } // Parse JSON with error handling let data; try { data = await res.json(); } catch (jsonError) { const responseText = await res.text(); console.error('JSON parse error:', jsonError); console.error('Response text:', responseText); throw new Error('Invalid JSON response from server'); } if (data.session) { if (typeof attachToSession === 'function') { attachToSession(sessionId); } // Update UI const sessionIdEl = document.getElementById('current-session-id'); if (sessionIdEl) sessionIdEl.textContent = sessionId; // Load session messages if (typeof clearChatDisplay === 'function') { clearChatDisplay(); } // Add historical messages if (data.session.outputBuffer && data.session.outputBuffer.length > 0) { data.session.outputBuffer.forEach(entry => { if (typeof appendMessage === 'function') { appendMessage('assistant', entry.content, false); } }); } // Show resume message const sessionDate = new Date(data.session.createdAt || data.session.created_at); if (typeof appendSystemMessage === 'function') { appendSystemMessage('✅ Resumed historical session from ' + sessionDate.toLocaleString()); appendSystemMessage('â„šī¸ This is a read-only historical session. Start a new chat to continue working.'); } // Update active state in sidebar if (typeof loadChatHistory === 'function') { loadChatHistory(); } // Subscribe to session (for any future updates) if (typeof subscribeToSession === 'function') { subscribeToSession(sessionId); } } else { throw new Error('No session data in response'); } } catch (error) { console.error('Error resuming session:', error); if (typeof appendSystemMessage === 'function') { appendSystemMessage('❌ Failed to resume session: ' + error.message); // Remove the loading message const messagesContainer = document.getElementById('chat-messages'); if (messagesContainer) { const loadingMessages = messagesContainer.querySelectorAll('.chat-system'); loadingMessages.forEach(msg => { if (msg.textContent.includes('Loading historical session')) { msg.remove(); } }); } } } } // ============================================ // Enhanced Message Rendering // ============================================ // Enhanced append with animations function appendMessageWithAnimation(role, content, animate = true) { const messagesContainer = document.getElementById('chat-messages'); if (!messagesContainer) return; const messageDiv = document.createElement('div'); messageDiv.className = `chat-message chat-message-${role} ${animate ? 'message-appear' : ''}`; const avatar = role === 'user' ? '👤' : '🤖'; const label = role === 'user' ? 'You' : 'Claude'; // Strip dyad tags for display const displayContent = stripDyadTags(content); messageDiv.innerHTML = `
${avatar}
${label} ${new Date().toLocaleTimeString()}
${formatMessageText(displayContent)}
`; messagesContainer.appendChild(messageDiv); // Scroll to bottom messagesContainer.scrollTop = messagesContainer.scrollHeight; // Update token usage if (typeof updateTokenUsage === 'function') { updateTokenUsage(content.length); } } // Strip dyad tags from message for display function stripDyadTags(content) { let stripped = content; // Remove dyad-write tags and replace with placeholder stripped = stripped.replace(/([\s\S]*?)<\/dyad-write>/g, (match, content) => { return `
📄 Code generated
${escapeHtml(content.trim())}
`; }); // Remove other dyad tags stripped = stripped.replace(/]+>/g, (match) => { const tagType = match.match(/dyad-(\w+)/)?.[1] || 'operation'; const icons = { 'rename': 'âœī¸', 'delete': 'đŸ—‘ī¸', 'add-dependency': 'đŸ“Ļ', 'command': '⚡' }; return `${icons[tagType] || 'âš™ī¸'} ${tagType}`; }); return stripped; } // Format message text with markdown-like rendering function formatMessageText(text) { // Basic markdown-like formatting let formatted = escapeHtml(text); // Code blocks formatted = formatted.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => { return `
${escapeHtml(code.trim())}
`; }); // Inline code formatted = formatted.replace(/`([^`]+)`/g, '$1'); // Bold formatted = formatted.replace(/\*\*([^*]+)\*\*/g, '$1'); // Links formatted = formatted.replace(/https?:\/\/[^\s]+/g, '$&'); // Line breaks formatted = formatted.replace(/\n/g, '
'); return formatted; } // Escape HTML function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // ============================================ // Quick Actions & Suggestions // ============================================ // Show quick action suggestions function showQuickActions() { const messagesContainer = document.getElementById('chat-messages'); if (!messagesContainer) return; const quickActions = document.createElement('div'); quickActions.className = 'quick-actions'; quickActions.innerHTML = `
💡 Quick Actions
`; messagesContainer.appendChild(quickActions); } // Execute quick action function executeQuickAction(action) { const actions = { 'create-react': 'Create a React app with components and routing', 'create-nextjs': 'Create a Next.js app with server-side rendering', 'create-vue': 'Create a Vue 3 app with composition API', 'create-html': 'Create a responsive HTML5 page with modern styling', 'explain-code': 'Explain the codebase structure and main files', 'fix-bug': 'Help me fix a bug in my code' }; const prompt = actions[action]; if (prompt) { const input = document.getElementById('chat-input'); if (input) { input.value = prompt; input.focus(); // Auto-send after short delay setTimeout(() => { if (typeof sendChatMessage === 'function') { sendChatMessage(); } }, 300); } } } // ============================================ // Enhanced Chat View Loading // ============================================ // Hook into loadChatView to add enhancements document.addEventListener('DOMContentLoaded', () => { // Wait for chat-functions.js to load setTimeout(() => { // Override loadChatView to add enhancements if (typeof window.loadChatView === 'function') { const originalLoadChatView = window.loadChatView; window.loadChatView = async function() { // Call original function first await originalLoadChatView.call(this); // Add our enhancements setTimeout(() => { enhanceChatInput(); focusChatInput(); // Show quick actions on first load const messagesContainer = document.getElementById('chat-messages'); if (messagesContainer && messagesContainer.querySelector('.chat-welcome')) { showQuickActions(); } }, 100); }; } }, 1000); }); // Auto-start enhancements when chat view is active const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.target.id === 'chat-view' && mutation.target.classList.contains('active')) { enhanceChatInput(); focusChatInput(); } }); }); // Start observing after DOM loads if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { const chatView = document.getElementById('chat-view'); if (chatView) observer.observe(chatView, { attributes: true }); }, 1500); }); } else { setTimeout(() => { const chatView = document.getElementById('chat-view'); if (chatView) observer.observe(chatView, { attributes: true }); }, 1500); } // Export functions if (typeof window !== 'undefined') { window.resumeSession = resumeSession; window.executeQuickAction = executeQuickAction; window.showQuickActions = showQuickActions; window.enhanceChatInput = enhanceChatInput; window.focusChatInput = focusChatInput; window.appendMessageWithAnimation = appendMessageWithAnimation; }