// ZovOS AI — Frontend Application (function() { 'use strict'; // --- State --- let ws = null; let sessionId = 'default'; let isStreaming = false; let currentAssistantMsg = null; // --- DOM Elements --- const chatContainer = document.getElementById('chatContainer'); const messagesEl = document.getElementById('messages'); const welcomeScreen = document.getElementById('welcomeScreen'); const messageInput = document.getElementById('messageInput'); const btnSend = document.getElementById('btnSend'); const btnNewChat = document.getElementById('btnNewChat'); const providerSelect = document.getElementById('providerSelect'); const statusIndicator = document.getElementById('statusIndicator'); const statusText = statusIndicator.querySelector('span'); // --- WebSocket --- function connectWebSocket() { const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; const url = `${protocol}//${location.host}/ws`; ws = new WebSocket(url); ws.onopen = () => { setStatus('ready', 'Подключён'); console.log('[WS] Connected'); }; ws.onmessage = (event) => { const msg = JSON.parse(event.data); handleWSMessage(msg); }; ws.onclose = () => { setStatus('error', 'Отключён'); console.log('[WS] Disconnected, reconnecting in 3s...'); setTimeout(connectWebSocket, 3000); }; ws.onerror = (err) => { console.error('[WS] Error:', err); setStatus('error', 'Ошибка соединения'); }; } function handleWSMessage(msg) { switch (msg.type) { case 'stream': hideWelcome(); if (!currentAssistantMsg) { currentAssistantMsg = addMessage('assistant', ''); } appendToMessage(currentAssistantMsg, msg.content); scrollToBottom(); break; case 'tool_call': hideWelcome(); if (!currentAssistantMsg) { currentAssistantMsg = addMessage('assistant', ''); } addToolCall(currentAssistantMsg, msg.tool_name, msg.tool_args, msg.tool_id); scrollToBottom(); break; case 'tool_result': if (currentAssistantMsg) { addToolResult(currentAssistantMsg, msg.tool_name, msg.content, msg.tool_id); scrollToBottom(); } break; case 'done': isStreaming = false; if (currentAssistantMsg) { renderMarkdown(currentAssistantMsg); } currentAssistantMsg = null; setStatus('ready', 'Готов'); btnSend.disabled = false; messageInput.focus(); break; case 'error': isStreaming = false; currentAssistantMsg = null; addMessage('assistant', `⚠️ Ошибка: ${msg.content}`); setStatus('error', 'Ошибка'); btnSend.disabled = false; setTimeout(() => setStatus('ready', 'Готов'), 3000); break; } } // --- Messages --- function addMessage(role, content) { hideWelcome(); const msgEl = document.createElement('div'); msgEl.className = `message ${role}`; const avatar = document.createElement('div'); avatar.className = 'message-avatar'; avatar.textContent = role === 'user' ? '👤' : '🤖'; const body = document.createElement('div'); body.className = 'message-body'; const contentEl = document.createElement('div'); contentEl.className = 'message-content'; contentEl.textContent = content; body.appendChild(contentEl); msgEl.appendChild(avatar); msgEl.appendChild(body); messagesEl.appendChild(msgEl); scrollToBottom(); return contentEl; } function appendToMessage(contentEl, text) { contentEl.textContent += text; } function renderMarkdown(contentEl) { const raw = contentEl.textContent; if (typeof marked !== 'undefined') { contentEl.innerHTML = marked.parse(raw); } } function addToolCall(contentEl, name, args, id) { const parent = contentEl.closest('.message-body'); const el = document.createElement('div'); el.className = 'tool-call'; el.id = `tool-${id}`; let argsDisplay = args; try { argsDisplay = JSON.stringify(JSON.parse(args), null, 2); } catch(e) {} el.innerHTML = `