class ChatManager { constructor() { this.sessionId = null; this.messages = []; this.isLoading = false; this.clarificationRound = 0; this.maxClarificationRounds = 6; this.pendingFiles = []; this.conversationHistories = []; this.messageCountForNaming = 0; this.messagesContainer = document.getElementById('chat-messages'); this.messageInput = document.getElementById('message-input'); this.sendBtn = document.getElementById('send-btn'); this.newChatBtn = document.getElementById('new-chat-btn'); this.clearChatBtn = document.getElementById('clear-chat-btn'); this.historyList = document.getElementById('history-list'); this.init(); } init() { if (!api.token && !localStorage.getItem('access_token') && !this.getCookie('access_token')) { const loginModal = new bootstrap.Modal(document.getElementById('loginModal')); loginModal.show(); } this.sendBtn.addEventListener('click', () => this.sendMessage()); this.messageInput.addEventListener('keydown', (e) => this.handleKeyDown(e)); this.newChatBtn?.addEventListener('click', () => this.newChat()); this.clearChatBtn?.addEventListener('click', () => this.clearChat()); this.messageInput.addEventListener('input', () => this.autoResize()); this.loadHistory(); } getCookie(name) { const matches = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^; ]*)')); return matches ? decodeURIComponent(matches[1]) : null; } handleKeyDown(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } } autoResize() { this.messageInput.style.height = 'auto'; this.messageInput.style.height = Math.min(this.messageInput.style.scrollHeight, 150) + 'px'; } async sendMessage() { const message = this.messageInput.value.trim(); if (!message || this.isLoading) return; if (!api.token && !localStorage.getItem('access_token')) { const loginModal = new bootstrap.Modal(document.getElementById('loginModal')); loginModal.show(); return; } this.messageInput.value = ''; this.messageInput.style.height = 'auto'; this.addMessage(message, 'user'); this.showTyping(); this.updateWorkStatus('working'); this.isLoading = true; this.messageCountForNaming++; try { const res = await api.sendChat(message, this.sessionId); this.hideTyping(); if (res.data) { this.sessionId = res.data.session_id; const data = res.data; this.updateStatus({ model: data.model || 'qwen3:14b', elapsed: data.elapsed_seconds || 0, messageCount: this.messages.length }); if (data.type === 'clarification') { this.handleClarification(data); } else if (data.type === 'blocked') { this.addMessage(data.message, 'bot', { warning: true }); } else if (data.type === 'async_task') { this.addMessage(data.message, 'bot', { agents: data.agents || [], elapsed: data.elapsed_seconds || 0 }); } else { this.addMessage(data.message, 'bot', { model: data.model || 'qwen3:14b', agents: data.agents || [], elapsed: data.elapsed_seconds || 0 }); } if (data.tool_calls) { this.handleToolCalls(data.tool_calls); } if (this.messageCountForNaming >= 5 && !this.sessionNameGenerated) { this.generateSessionName(); } } } catch (error) { this.hideTyping(); this.addMessage(`抱歉,发生了错误:${error.message}`, 'bot', { error: true }); this.updateWorkStatus('error'); } finally { this.isLoading = false; this.updateWorkStatus('idle'); } } generateSessionName() { const userMessages = this.messages.filter(m => m.role === 'user'); if (userMessages.length === 0) return; const firstMsg = userMessages[0].content; const summary = firstMsg.length > 20 ? firstMsg.substring(0, 20) + '...' : firstMsg; this.sessionNameGenerated = true; const existingIndex = this.conversationHistories.findIndex(h => h.sessionId === this.sessionId); if (existingIndex >= 0) { this.conversationHistories[existingIndex].name = summary; this.conversationHistories[existingIndex].preview = userMessages[userMessages.length - 1].content.substring(0, 30); } this.saveHistory(); this.renderHistoryList(); } updateStatus({ model, elapsed, messageCount }) { const modelDisplay = model ? this.getModelDisplayName(model) : 'qwen3:14b'; document.getElementById('current-model-name').textContent = modelDisplay; document.getElementById('active-model').textContent = modelDisplay; if (elapsed) { document.getElementById('response-time').textContent = elapsed.toFixed(1) + 's'; } if (messageCount) { document.getElementById('message-count').textContent = Math.ceil(messageCount / 2); } } updateWorkStatus(status) { const statusEl = document.getElementById('work-status'); statusEl.className = 'status-value'; switch (status) { case 'working': statusEl.textContent = '处理中'; statusEl.classList.add('working'); break; case 'idle': statusEl.textContent = '空闲'; statusEl.classList.add('idle'); break; case 'error': statusEl.textContent = '错误'; statusEl.classList.add('error'); break; } } handleClarification(data) { this.clarificationRound = data.round || 1; this.maxClarificationRounds = data.max_rounds || 6; const questions = data.clarification_questions || []; const needsFileUpload = data.needs_file_upload || false; const suggestedTypes = data.suggested_file_types || []; const message = data.message || ''; let clarificationHtml = `
$2');
formatted = formatted.replace(/`([^`]+)`/g, '$1');
formatted = formatted.replace(/\*\*([^*]+)\*\*/g, '$1');
formatted = formatted.replace(/\*([^*]+)\*/g, '$1');
formatted = formatted.replace(/\n/g, '${formatted}
`; } showTyping() { const typingDiv = document.createElement('div'); typingDiv.className = 'message bot-message typing'; typingDiv.id = 'typing-indicator'; typingDiv.innerHTML = ` `; this.messagesContainer.appendChild(typingDiv); this.scrollToBottom(); } hideTyping() { const typing = document.getElementById('typing-indicator'); if (typing) { typing.remove(); } } handleToolCalls(toolCalls) { if (!Array.isArray(toolCalls)) return; for (const call of toolCalls) { const toolName = call.function?.name || call.name; this.addSystemMessage(` 调用工具: ${toolName}`); } } addSystemMessage(content) { const messageDiv = document.createElement('div'); messageDiv.className = 'message system-message'; messageDiv.innerHTML = ` `; this.messagesContainer.appendChild(messageDiv); this.scrollToBottom(); } scrollToBottom() { this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight; } loadHistory() { try { const saved = localStorage.getItem('ai-chat-histories'); if (saved) { this.conversationHistories = JSON.parse(saved); this.renderHistoryList(); } } catch (e) { console.error('Failed to load history:', e); } } saveHistory() { try { localStorage.setItem('ai-chat-histories', JSON.stringify(this.conversationHistories)); } catch (e) { console.error('Failed to save history:', e); } } renderHistoryList() { if (!this.historyList) return; if (this.conversationHistories.length === 0) { this.historyList.innerHTML = `暂无对话记录