(function() { 'use strict'; var DEFAULT_BASE_URL = 'https://api.z.ai/api/coding/paas/v4'; var DEFAULT_MODEL = 'glm-5.1'; var STORAGE_KEY = 'zai_chat_'; var MODE_PROMPTS = { chat: 'You are a helpful, knowledgeable AI assistant. Be concise and accurate.', coding: 'You are an expert coding assistant. Write clean, efficient, well-documented code. Always use markdown code blocks with language tags. Explain your approach briefly before and after code. Handle edge cases and errors properly.', brainstorm: 'You are a creative brainstorming partner. Generate diverse ideas, explore unconventional angles, build on concepts, and help evaluate trade-offs. Think freely and expansively. Present ideas in organized lists or tables when appropriate.', agentic: 'You are an autonomous coding agent. Break down complex tasks into clear steps. Write production-quality code with proper error handling, tests, and documentation. Think through the architecture before coding. Use tool-calling format when appropriate: [SEARCH], [CREATE_FILE], [EDIT_FILE], [RUN_COMMAND]. Always verify your work.' }; var state = { apiKey: '', baseUrl: DEFAULT_BASE_URL, model: DEFAULT_MODEL, temperature: 0.7, maxTokens: 4096, streaming: true, webSearch: false, currentMode: 'chat', theme: 'dark', conversations: [], activeConversationId: null, isGenerating: false, abortController: null, streamingConvId: null, streamingContent: '', streamingResponseDiv: null, terminalOpen: false }; function $(sel) { return document.querySelector(sel); } function $$(sel) { return document.querySelectorAll(sel); } function loadState() { try { state.apiKey = localStorage.getItem(STORAGE_KEY + 'apiKey') || ''; state.baseUrl = localStorage.getItem(STORAGE_KEY + 'baseUrl') || DEFAULT_BASE_URL; state.model = localStorage.getItem(STORAGE_KEY + 'model') || DEFAULT_MODEL; state.temperature = parseFloat(localStorage.getItem(STORAGE_KEY + 'temperature')) || 0.7; state.maxTokens = parseInt(localStorage.getItem(STORAGE_KEY + 'maxTokens')) || 4096; state.streaming = localStorage.getItem(STORAGE_KEY + 'streaming') !== 'false'; state.webSearch = localStorage.getItem(STORAGE_KEY + 'webSearch') === 'true'; state.currentMode = localStorage.getItem(STORAGE_KEY + 'currentMode') || 'chat'; state.theme = localStorage.getItem(STORAGE_KEY + 'theme') || 'dark'; state.terminalOpen = localStorage.getItem(STORAGE_KEY + 'terminalOpen') === 'true'; var convData = localStorage.getItem(STORAGE_KEY + 'conversations'); state.conversations = convData ? JSON.parse(convData) : []; state.activeConversationId = localStorage.getItem(STORAGE_KEY + 'activeConv') || null; } catch(e) { console.error('Load state error:', e); } } function saveState() { try { localStorage.setItem(STORAGE_KEY + 'apiKey', state.apiKey); localStorage.setItem(STORAGE_KEY + 'baseUrl', state.baseUrl); localStorage.setItem(STORAGE_KEY + 'model', state.model); localStorage.setItem(STORAGE_KEY + 'temperature', state.temperature.toString()); localStorage.setItem(STORAGE_KEY + 'maxTokens', state.maxTokens.toString()); localStorage.setItem(STORAGE_KEY + 'streaming', state.streaming.toString()); localStorage.setItem(STORAGE_KEY + 'webSearch', state.webSearch.toString()); localStorage.setItem(STORAGE_KEY + 'currentMode', state.currentMode); localStorage.setItem(STORAGE_KEY + 'theme', state.theme); localStorage.setItem(STORAGE_KEY + 'terminalOpen', state.terminalOpen.toString()); localStorage.setItem(STORAGE_KEY + 'conversations', JSON.stringify(state.conversations)); localStorage.setItem(STORAGE_KEY + 'activeConv', state.activeConversationId || ''); } catch(e) { console.error('Save state error:', e); } } function genId() { return Date.now().toString(36) + Math.random().toString(36).substr(2, 9); } function getConversation(id) { var targetId = id || state.activeConversationId; if (!targetId) return null; return state.conversations.find(function(c) { return c.id === targetId; }); } function flushStreamingToConversation() { if (state.streamingConvId && state.streamingContent) { var conv = getConversation(state.streamingConvId); if (conv) { var lastMsg = conv.messages[conv.messages.length - 1]; if (lastMsg && lastMsg.role === 'assistant' && lastMsg._streaming) { lastMsg.content = state.streamingContent; delete lastMsg._streaming; } else { conv.messages.push({ role: 'assistant', content: state.streamingContent }); } saveState(); } } state.streamingConvId = null; state.streamingContent = ''; state.streamingResponseDiv = null; } function newConversation() { flushStreamingToConversation(); if (state.isGenerating) { stopGeneration(); } var conv = { id: genId(), title: 'New Chat', mode: state.currentMode, messages: [], createdAt: Date.now() }; state.conversations.unshift(conv); state.activeConversationId = conv.id; state.isGenerating = false; state.abortController = null; saveState(); renderConversationList(); renderMessages(); updateHeader(); updateSendButton(); updateTerminalVisibility(); } function switchConversation(id) { if (id === state.activeConversationId) { closeSidebar(); return; } flushStreamingToConversation(); if (state.isGenerating) { stopGeneration(); state.isGenerating = false; state.abortController = null; } state.activeConversationId = id; var conv = getConversation(); if (conv) { state.currentMode = conv.mode || 'chat'; updateModeSelector(); } saveState(); renderConversationList(); renderMessages(); updateHeader(); updateSendButton(); updateTerminalVisibility(); closeSidebar(); } function deleteConversation(id) { if (state.streamingConvId === id) { flushStreamingToConversation(); if (state.isGenerating) stopGeneration(); state.isGenerating = false; } state.conversations = state.conversations.filter(function(c) { return c.id !== id; }); if (state.activeConversationId === id) { state.activeConversationId = state.conversations.length > 0 ? state.conversations[0].id : null; } saveState(); renderConversationList(); renderMessages(); updateHeader(); updateTerminalVisibility(); } function updateHeader() { var conv = getConversation(); $('#conversation-title').textContent = conv ? conv.title : 'Z.AI Chat'; $('#current-mode-label').textContent = state.currentMode.charAt(0).toUpperCase() + state.currentMode.slice(1); } function showScreen(name) { $$('.screen').forEach(function(s) { s.classList.remove('active'); }); $('#' + name + '-screen').classList.add('active'); } function autoResize(el) { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 120) + 'px'; } function renderConversationList() { var list = $('#conversation-list'); if (!list) return; list.innerHTML = ''; state.conversations.forEach(function(conv) { var div = document.createElement('div'); div.className = 'conv-item' + (conv.id === state.activeConversationId ? ' active' : ''); var msgCount = conv.messages.length; div.innerHTML = '' + escapeHtml(conv.title) + (msgCount > 0 ? ' (' + msgCount + ')' : '') + '' + ''; div.addEventListener('click', function(e) { if (e.target.classList.contains('conv-delete')) { e.stopPropagation(); deleteConversation(e.target.dataset.id); return; } switchConversation(conv.id); }); list.appendChild(div); }); } function escapeHtml(text) { var d = document.createElement('div'); d.textContent = text; return d.innerHTML; } function renderMarkdown(text) { if (typeof marked !== 'undefined') { marked.setOptions({ highlight: function(code, lang) { if (typeof hljs !== 'undefined' && lang && hljs.getLanguage(lang)) { try { return hljs.highlight(code, { language: lang }).value; } catch(e) {} } return code; }, breaks: true, gfm: true }); return marked.parse(text); } return text.replace(//g, '>').replace(/\n/g, '
'); } function addCodeHeaders(container) { container.querySelectorAll('pre code').forEach(function(block) { var pre = block.parentElement; var lang = (block.className.match(/language-(\w+)/) || [])[1] || 'code'; if (!pre.previousElementSibling || !pre.previousElementSibling.classList.contains('code-header')) { var header = document.createElement('div'); header.className = 'code-header'; header.innerHTML = '' + escapeHtml(lang) + ''; pre.parentElement.insertBefore(header, pre); header.querySelector('.copy-btn').addEventListener('click', function() { navigator.clipboard.writeText(block.textContent).then(function() { this.textContent = 'Copied!'; setTimeout(function() { this.textContent = 'Copy'; }.bind(this), 2000); }.bind(this)); }); } }); } function renderMessages() { var container = $('#messages'); if (!container) return; container.innerHTML = ''; var conv = getConversation(); if (!conv || conv.messages.length === 0) { container.innerHTML = '
Start a conversation with Z.AI
'; return; } conv.messages.forEach(function(msg) { appendMessage(msg.role, msg.content, container, false); }); container.scrollTop = container.scrollHeight; updateTerminalContent(); } function appendMessage(role, content, container, animate) { container = container || $('#messages'); var div = document.createElement('div'); div.className = 'message ' + role; if (animate === false) div.style.animation = 'none'; if (role === 'assistant') { div.innerHTML = renderMarkdown(content); addCodeHeaders(div); } else { div.textContent = content; } container.appendChild(div); container.scrollTop = container.scrollHeight; return div; } function updateStreamingMessage(div, content) { div.innerHTML = renderMarkdown(content); addCodeHeaders(div); $('#messages').scrollTop = $('#messages').scrollHeight; } function showThinking() { var container = $('#messages'); var div = document.createElement('div'); div.className = 'message assistant'; div.id = 'thinking-msg'; div.innerHTML = '
Thinking...
'; container.appendChild(div); container.scrollTop = container.scrollHeight; } function removeThinking() { var el = $('#thinking-msg'); if (el) el.remove(); } async function sendMessage() { var input = $('#message-input'); var text = input.value.trim(); if (!text || state.isGenerating) return; if (!state.apiKey) { showScreen('setup'); return; } if (!state.activeConversationId) { newConversation(); } var conv = getConversation(); if (!conv) return; conv.mode = state.currentMode; if (conv.messages.length === 0) { conv.title = text.substring(0, 50) + (text.length > 50 ? '...' : ''); updateHeader(); renderConversationList(); } conv.messages.push({ role: 'user', content: text }); saveState(); input.value = ''; autoResize(input); updateSendButton(); appendMessage('user', text); state.isGenerating = true; state.streamingConvId = conv.id; state.streamingContent = ''; updateSendButton(); showThinking(); try { var systemPrompt = MODE_PROMPTS[state.currentMode] || MODE_PROMPTS.chat; var apiMessages = [{ role: 'system', content: systemPrompt }]; conv.messages.forEach(function(m) { if (m.role === 'user' || (m.role === 'assistant' && !m._streaming)) { apiMessages.push({ role: m.role, content: m.content }); } }); var requestBody = { model: state.model, messages: apiMessages, temperature: state.temperature, max_tokens: state.maxTokens, stream: state.streaming }; if (state.webSearch) { requestBody.tools = [{ type: 'web_search', web_search: { search_query: text, search_result: true } }]; } removeThinking(); var responseDiv = appendMessage('assistant', ''); state.streamingResponseDiv = responseDiv; if (state.streaming) { await streamResponse(requestBody, responseDiv, conv); } else { var result = await apiRequest(requestBody); var content = result.choices[0].message.content; updateStreamingMessage(responseDiv, content); state.streamingContent = content; conv.messages.push({ role: 'assistant', content: content }); } } catch(err) { removeThinking(); if (err.name !== 'AbortError') { appendMessage('system', 'Error: ' + (err.message || 'Request failed')); } if (state.streamingContent) { conv.messages.push({ role: 'assistant', content: state.streamingContent, _streaming: false }); } } finally { state.isGenerating = false; state.abortController = null; state.streamingConvId = null; state.streamingResponseDiv = null; updateSendButton(); saveState(); updateTerminalContent(); } } async function apiRequest(body) { var url = state.baseUrl.replace(/\/+$/, '') + '/chat/completions'; var resp = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + state.apiKey, 'Accept-Language': 'en-US,en' }, body: JSON.stringify(body) }); if (!resp.ok) { var errData = {}; try { errData = await resp.json(); } catch(e) {} throw new Error(errData.error?.message || 'API error ' + resp.status); } return await resp.json(); } var _streamAutoSaveCounter = 0; async function streamResponse(body, responseDiv, conv) { state.abortController = new AbortController(); body.stream = true; var url = state.baseUrl.replace(/\/+$/, '') + '/chat/completions'; var resp = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + state.apiKey, 'Accept-Language': 'en-US,en' }, body: JSON.stringify(body), signal: state.abortController.signal }); if (!resp.ok) { var errData = {}; try { errData = await resp.json(); } catch(e) {} throw new Error(errData.error?.message || 'API error ' + resp.status); } var reader = resp.body.getReader(); var decoder = new TextDecoder(); var fullContent = ''; var buffer = ''; _streamAutoSaveCounter = 0; while (true) { var chunk = await reader.read(); if (chunk.done) break; buffer += decoder.decode(chunk.value, { stream: true }); var lines = buffer.split('\n'); buffer = lines.pop() || ''; for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); if (!line || !line.startsWith('data:')) continue; var data = line.substring(5).trim(); if (data === '[DONE]') break; try { var parsed = JSON.parse(data); var delta = parsed.choices && parsed.choices[0] && parsed.choices[0].delta; if (delta && delta.content) { fullContent += delta.content; state.streamingContent = fullContent; if (responseDiv && responseDiv.parentElement) { updateStreamingMessage(responseDiv, fullContent); } _streamAutoSaveCounter++; if (_streamAutoSaveCounter % 20 === 0) { _saveStreamingProgress(conv, fullContent); } } } catch(e) {} } } conv.messages.push({ role: 'assistant', content: fullContent }); state.streamingContent = ''; } function _saveStreamingProgress(conv, content) { if (!conv) return; var last = conv.messages[conv.messages.length - 1]; if (last && last.role === 'assistant' && last._streaming) { last.content = content; } else { conv.messages.push({ role: 'assistant', content: content, _streaming: true }); } saveState(); } function stopGeneration() { if (state.abortController) { state.abortController.abort(); } flushStreamingToConversation(); } function updateSendButton() { var input = $('#message-input'); var sendBtn = $('#send-btn'); var stopBtn = $('#stop-btn'); if (state.isGenerating) { sendBtn.style.display = 'none'; stopBtn.style.display = 'flex'; } else { sendBtn.style.display = 'flex'; stopBtn.style.display = 'none'; sendBtn.disabled = !input.value.trim(); } } function updateModeSelector() { $$('.mode-btn').forEach(function(btn) { btn.classList.toggle('active', btn.dataset.mode === state.currentMode); }); } function openSidebar() { $('#sidebar').classList.add('open'); $('#sidebar-overlay').classList.add('active'); } function closeSidebar() { $('#sidebar').classList.remove('open'); $('#sidebar-overlay').classList.remove('active'); } function applyTheme(theme) { state.theme = theme; document.documentElement.setAttribute('data-theme', theme); var headerBtn = $('#theme-toggle-header'); if (headerBtn) { headerBtn.innerHTML = theme === 'dark' ? '☼' : '☾'; headerBtn.title = theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'; } var settingsToggle = $('#settings-darkmode'); if (settingsToggle) settingsToggle.checked = (theme === 'dark'); var metaTheme = document.querySelector('meta[name="theme-color"]'); if (metaTheme) metaTheme.content = theme === 'dark' ? '#1a1a2e' : '#ffffff'; saveState(); } function toggleTheme() { applyTheme(state.theme === 'dark' ? 'light' : 'dark'); } // ---- Terminal Panel ---- function parseTerminalEntries(content) { if (!content) return []; var entries = []; var toolRegex = /\[(CREATE_FILE|EDIT_FILE|DELETE_FILE|RUN_COMMAND|SEARCH|READ_FILE|BUILD|TEST)\]\s*\(([^)]*)\)\s*\n?([\s\S]*?)(?=\n\[|$)/gi; var match; while ((match = toolRegex.exec(content)) !== null) { entries.push({ type: 'tool', action: match[1], target: match[2].trim(), body: match[3].trim() }); } var codeBlockRegex = /```(\w*)\n([\s\S]*?)```/gi; var idx = 0; while ((match = codeBlockRegex.exec(content)) !== null) { var isTool = false; for (var e = 0; e < entries.length; e++) { if (entries[e].type === 'tool' && match.index >= content.indexOf(entries[e].body) - 20) { isTool = true; break; } } if (!isTool) { idx++; var firstLine = match[2].trim().split('\n')[0]; var isFilePath = /^(\/|\.\/|\.\.\/|[A-Za-z]:\\|[a-zA-Z0-9_\-]+\.[a-zA-Z]{1,4}$)/.test(firstLine) && firstLine.length < 120 && firstLine.split('\n').length === 1; entries.push({ type: 'code', lang: match[1] || 'text', code: match[2].trim(), index: idx, fileName: isFilePath ? firstLine : null }); } } entries.sort(function(a, b) { return content.indexOf(a.type === 'tool' ? '[' + a.action : '```' + (a.lang || '')) - content.indexOf(b.type === 'tool' ? '[' + b.action : '```' + (b.lang || '')); }); return entries; } function renderTerminalEntry(entry) { if (entry.type === 'tool') { var actionIcon = { CREATE_FILE: '+', EDIT_FILE: '~', DELETE_FILE: '-', RUN_COMMAND: '>', SEARCH: '?', READ_FILE: 'R', BUILD: 'B', TEST: 'T' }; var actionColor = { CREATE_FILE: 'var(--success)', EDIT_FILE: 'var(--warning)', DELETE_FILE: 'var(--danger)', RUN_COMMAND: 'var(--accent)', SEARCH: 'var(--text-secondary)', READ_FILE: 'var(--text-muted)', BUILD: 'var(--accent)', TEST: 'var(--success)' }; var icon = actionIcon[entry.action] || '>'; var color = actionColor[entry.action] || 'var(--accent)'; var html = '
'; html += '
[' + icon + '] ' + escapeHtml(entry.action) + ''; if (entry.target) html += ' ' + escapeHtml(entry.target) + ''; html += '
'; if (entry.body) { html += '
' + escapeHtml(entry.body.substring(0, 2000)) + (entry.body.length > 2000 ? '\n... (truncated)' : '') + '
'; } html += '
'; return html; } if (entry.type === 'code') { var label = entry.fileName ? escapeHtml(entry.fileName) : escapeHtml(entry.lang || 'code'); var displayCode = entry.fileName ? entry.code.split('\n').slice(1).join('\n') : entry.code; if (!displayCode.trim()) displayCode = entry.code; var html = '
'; html += '
' + label + ''; html += '
'; html += '
' + escapeHtml(displayCode.substring(0, 3000)) + (displayCode.length > 3000 ? '\n... (truncated)' : '') + '
'; html += '
'; return html; } return ''; } function updateTerminalContent() { var termBody = $('#terminal-body'); if (!termBody) return; termBody.innerHTML = ''; var conv = getConversation(); if (!conv) return; var lastAssistant = null; for (var i = conv.messages.length - 1; i >= 0; i--) { if (conv.messages[i].role === 'assistant') { lastAssistant = conv.messages[i]; break; } } if (!lastAssistant && !state.streamingContent) { termBody.innerHTML = '
No code output yet. Use Coding or Agentic mode to generate code.
'; return; } var content = state.streamingContent || (lastAssistant ? lastAssistant.content : ''); var entries = parseTerminalEntries(content); if (entries.length === 0) { termBody.innerHTML = '
No structured code blocks or tool calls detected in response.
'; return; } entries.forEach(function(entry) { termBody.innerHTML += renderTerminalEntry(entry); }); termBody.querySelectorAll('.term-copy-btn').forEach(function(btn) { btn.addEventListener('click', function() { var code = this.getAttribute('data-code').replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); navigator.clipboard.writeText(code).then(function() { this.textContent = 'Copied!'; setTimeout(function() { this.textContent = 'Copy'; }.bind(this), 2000); }.bind(this)); }); }); if (state.terminalOpen) { termBody.scrollTop = termBody.scrollHeight; } } function updateTerminalVisibility() { var panel = $('#terminal-panel'); var toggleBtn = $('#terminal-toggle'); if (!panel || !toggleBtn) return; var isDevMode = (state.currentMode === 'coding' || state.currentMode === 'agentic'); if (isDevMode) { panel.style.display = 'flex'; toggleBtn.style.display = 'flex'; } else { panel.style.display = 'none'; toggleBtn.style.display = 'none'; state.terminalOpen = false; } if (state.terminalOpen && isDevMode) { panel.classList.add('open'); } else { panel.classList.remove('open'); } var label = toggleBtn.querySelector('.terminal-label'); if (label) label.textContent = state.terminalOpen ? 'Hide Terminal' : 'Show Terminal'; } function toggleTerminal() { state.terminalOpen = !state.terminalOpen; var panel = $('#terminal-panel'); if (panel) panel.classList.toggle('open', state.terminalOpen); var label = $('#terminal-toggle .terminal-label'); if (label) label.textContent = state.terminalOpen ? 'Hide Terminal' : 'Show Terminal'; if (state.terminalOpen) updateTerminalContent(); saveState(); } // ---- Rest of init ---- async function testConnection(apiKey, baseUrl) { var url = (baseUrl || state.baseUrl).replace(/\/+$/, '') + '/chat/completions'; var resp = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + apiKey, 'Accept-Language': 'en-US,en' }, body: JSON.stringify({ model: state.model, messages: [{ role: 'user', content: 'Hi' }], max_tokens: 10 }) }); if (!resp.ok) { var errData = {}; try { errData = await resp.json(); } catch(e) {} throw new Error(errData.error?.message || 'Connection failed (' + resp.status + ')'); } return await resp.json(); } function populateSettings() { $('#settings-token').value = state.apiKey; $('#settings-url').value = state.baseUrl; $('#settings-model').value = state.model; $('#settings-temp').value = state.temperature; $('#temp-value').textContent = state.temperature; $('#settings-tokens').value = state.maxTokens; $('#tokens-value').textContent = state.maxTokens; $('#settings-websearch').checked = state.webSearch; $('#settings-streaming').checked = state.streaming; } function saveSettings() { state.apiKey = $('#settings-token').value.trim(); state.baseUrl = $('#settings-url').value.trim(); state.model = $('#settings-model').value; state.temperature = parseFloat($('#settings-temp').value); state.maxTokens = parseInt($('#settings-tokens').value); state.webSearch = $('#settings-websearch').checked; state.streaming = $('#settings-streaming').checked; saveState(); } function exportConversations() { var data = JSON.stringify(state.conversations, null, 2); var blob = new Blob([data], { type: 'application/json' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'zai-chat-export-' + new Date().toISOString().slice(0, 10) + '.json'; a.click(); URL.revokeObjectURL(url); } function init() { loadState(); if (state.apiKey) { showScreen('chat'); if (state.activeConversationId) { var conv = getConversation(); if (conv) { state.currentMode = conv.mode || 'chat'; } } renderConversationList(); renderMessages(); updateHeader(); updateModeSelector(); updateTerminalVisibility(); $('#api-token').value = state.apiKey; $('#base-url').value = state.baseUrl; } $('#connect-btn').addEventListener('click', async function() { var btn = this; var apiKey = $('#api-token').value.trim(); var baseUrl = $('#base-url').value; var errorEl = $('#setup-error'); if (!apiKey) { errorEl.textContent = 'Please enter your API key'; errorEl.style.display = 'block'; return; } btn.disabled = true; btn.querySelector('.btn-text').textContent = 'Connecting...'; btn.querySelector('.btn-loader').style.display = 'inline-block'; errorEl.style.display = 'none'; try { await testConnection(apiKey, baseUrl); state.apiKey = apiKey; state.baseUrl = baseUrl; saveState(); showScreen('chat'); newConversation(); } catch(err) { errorEl.textContent = err.message; errorEl.style.display = 'block'; } finally { btn.disabled = false; btn.querySelector('.btn-text').textContent = 'Connect'; btn.querySelector('.btn-loader').style.display = 'none'; } }); $('#api-token').addEventListener('keydown', function(e) { if (e.key === 'Enter') $('#connect-btn').click(); }); $('#message-input').addEventListener('input', function() { autoResize(this); updateSendButton(); }); $('#message-input').addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); $('#send-btn').addEventListener('click', sendMessage); $('#stop-btn').addEventListener('click', stopGeneration); $$('.mode-btn').forEach(function(btn) { btn.addEventListener('click', function() { state.currentMode = this.dataset.mode; updateModeSelector(); updateHeader(); updateTerminalVisibility(); saveState(); }); }); $('#menu-btn').addEventListener('click', openSidebar); $('#sidebar-close').addEventListener('click', closeSidebar); $('#sidebar-overlay').addEventListener('click', closeSidebar); $('#new-chat-btn').addEventListener('click', function() { newConversation(); }); $('#new-chat-sidebar').addEventListener('click', function() { newConversation(); closeSidebar(); }); $('#settings-btn').addEventListener('click', function() { populateSettings(); showScreen('settings'); }); $('#settings-back').addEventListener('click', function() { saveSettings(); showScreen('chat'); }); $('#settings-temp').addEventListener('input', function() { $('#temp-value').textContent = this.value; }); $('#settings-tokens').addEventListener('input', function() { $('#tokens-value').textContent = this.value; }); $('#settings-token').addEventListener('change', saveSettings); $('#settings-url').addEventListener('change', saveSettings); $('#settings-model').addEventListener('change', saveSettings); $('#settings-websearch').addEventListener('change', saveSettings); $('#settings-streaming').addEventListener('change', saveSettings); $('#theme-toggle-header').addEventListener('click', toggleTheme); $('#settings-darkmode').addEventListener('change', function() { applyTheme(this.checked ? 'dark' : 'light'); }); $('#terminal-toggle').addEventListener('click', toggleTerminal); $('#export-btn').addEventListener('click', exportConversations); $('#clear-btn').addEventListener('click', function() { if (confirm('Clear all conversations? This cannot be undone.')) { state.conversations = []; state.activeConversationId = null; saveState(); renderConversationList(); renderMessages(); updateHeader(); updateTerminalContent(); } }); updateModeSelector(); updateSendButton(); applyTheme(state.theme); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();