📜
settings.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// Global namespace to share state across modules window.App = window.App || {}; (() => { const els = { // dynamically injected container settingsOverlay: document.getElementById('settingsOverlay'), overlayBackdrop: document.getElementById('overlayBackdrop'), openSettings: document.getElementById('openSettings'), closeSettings: document.getElementById('closeSettings'), settingsBody: document.getElementById('settingsBody'), clearChat: null, }; const STORAGE_KEY = 'unified-chat-state-v2'; const defaultState = () => ({ settings: { model: 'deepseek-chat', maxTokens: 800, temperature: 0.7, forceTemperature: false, includeArtifacts: false, jsonFormat: false, system: 'You are a helpful, accurate assistant. Be concise and clear. Use markdown when it helps readability.', codeStyle: 'default', fullCodePrompt: 'When providing code examples, always include complete, runnable code with all necessary imports, setup, and context. Provide full file contents rather than partial snippets.', snippetsPrompt: 'Focus on providing concise code snippets that show only the relevant changes or key parts. Explain what each snippet does and where it should be used.', chunkedPrompt: 'When providing large code files or long responses, break them into logical chunks. End each chunk with a clear indication of what comes next. If approaching token limits, stop at a logical break point and indicate there\'s more to follow.' }, messages: [], stitcher: { chunks: [], isOpen: false } }); function loadState() { try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || defaultState(); } catch { return defaultState(); } } function saveState(s) { localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); } App.state = loadState(); App.saveState = saveState; App.defaultState = defaultState; // Render the settings body (so HTML is slimmer in index.html) function renderSettingsBody() { els.settingsBody.innerHTML = ` <label class="text-sm block">Model</label> <select id="model" class="w-full rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 px-3 py-2 text-sm"> <optgroup label="OpenAI"> <option>gpt-5</option> <option>gpt-5-mini</option> <option>gpt-5-nano</option> <option>gpt-5-thinking</option> <option>gpt-5-pro</option> <option>gpt-4o</option> <option>gpt-4o-mini</option> </optgroup> <optgroup label="DeepSeek"> <option selected>deepseek-chat</option> <option>deepseek-reasoner</option> </optgroup> <optgroup label="xAI (Grok)"> <option>grok-3</option> <option>grok-3-mini</option> <option>grok-code-fast-1</option> <option>grok-4-0709</option> </optgroup> </select> <div class="mt-3"> <label class="text-sm block">Max tokens: <span id="maxTokensVal" class="font-mono">${App.state.settings.maxTokens}</span></label> <input id="maxTokens" type="range" min="64" max="4096" step="32" value="${App.state.settings.maxTokens}" class="w-full"> </div> <div class="mt-3"> <label class="text-sm block">Temperature: <span id="tempVal" class="font-mono">${App.state.settings.temperature}</span></label> <input id="temperature" type="range" min="0" max="2" step="0.1" value="${App.state.settings.temperature}" class="w-full"> <label class="flex items-center gap-2 mt-1 text-xs"><input id="forceTemperature" type="checkbox" class="accent-indigo-600" ${App.state.settings.forceTemperature ? 'checked' : ''}> Force temperature (for GPT-5)</label> </div> <div class="mt-3"> <label class="text-sm block mb-1">System prompt</label> <textarea id="system" rows="3" class="w-full rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 px-3 py-2 text-sm" placeholder="You are a helpful, accurate assistant. Be concise and clear. Use markdown when it helps readability.">${App.state.settings.system || ''}</textarea> </div> <div class="mt-3"> <label class="text-sm block mb-2">Code Response Style</label> <div class="flex sm:flex-row flex-col mobile-tabs border border-zinc-300 dark:border-zinc-700 rounded-lg overflow-hidden"> <button id="tabDefault" class="mobile-tab-btn px-3 py-2 text-xs sm:text-xs">Default</button> <button id="tabFullCode" class="mobile-tab-btn px-3 py-2 text-xs sm:text-xs">Full Code</button> <button id="tabSnippets" class="mobile-tab-btn px-3 py-2 text-xs sm:text-xs">Snippets</button> <button id="tabChunked" class="mobile-tab-btn px-3 py-2 text-xs sm:text-xs">Chunked</button> </div> <div id="promptDefault" class="mt-2 p-3 rounded-lg bg-zinc-50 dark:bg-zinc-800 text-xs hidden"> <strong>Default:</strong> Normal responses with code in markdown blocks when helpful. </div> <div id="promptFullCode" class="mt-2 p-3 rounded-lg bg-zinc-50 dark:bg-zinc-800 text-xs hidden"> <strong>Full Code:</strong> Always provide complete, working code examples. <textarea id="fullCodePrompt" rows="3" class="w-full mt-2 rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 px-2 py-1 text-xs">${App.state.settings.fullCodePrompt || ''}</textarea> </div> <div id="promptSnippets" class="mt-2 p-3 rounded-lg bg-zinc-50 dark:bg-zinc-800 text-xs hidden"> <strong>Snippets:</strong> Focus on concise code snippets and key changes only. <textarea id="snippetsPrompt" rows="3" class="w-full mt-2 rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 px-2 py-1 text-xs">${App.state.settings.snippetsPrompt || ''}</textarea> </div> <div id="promptChunked" class="mt-2 p-3 rounded-lg bg-zinc-50 dark:bg-zinc-800 text-xs hidden"> <strong>Chunked:</strong> Break code into manageable chunks to fit token limits. <textarea id="chunkedPrompt" rows="3" class="w-full mt-2 rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 px-2 py-1 text-xs">${App.state.settings.chunkedPrompt || ''}</textarea> </div> </div> <div class="mt-3 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2"> <label class="flex items-center gap-2 text-sm"><input id="includeArtifacts" type="checkbox" class="accent-indigo-600" ${App.state.settings.includeArtifacts ? 'checked' : ''}> Include artifacts from session</label> <label class="flex items-center gap-2 text-sm"><input id="jsonFormat" type="checkbox" class="accent-indigo-600" ${App.state.settings.jsonFormat ? 'checked' : ''}> Response JSON</label> </div> <div class="mt-3 flex items-center justify-end gap-2"> <button id="clearChat" class="text-xs px-2 py-1 rounded-md border border-zinc-300 dark:border-zinc-700 hover:bg-zinc-100 dark:hover:bg-zinc-800">Clear</button> </div> <details class="mt-2 text-xs text-zinc-500"> <summary class="cursor-pointer mb-1">Session & CORS notes</summary> <p class="mt-1">Serve this file and <code>api.php</code> from the same origin to keep PHP session history. If cross-origin, enable credentials and set a specific <code>Access-Control-Allow-Origin</code> instead of <code>*</code>.</p> </details> `; } function bindAndHydrate() { const s = App.state.settings; const $ = id => document.getElementById(id); const model = $('model'); const maxTokens = $('maxTokens'); const maxTokensVal = $('maxTokensVal'); const temperature = $('temperature'); const tempVal = $('tempVal'); const forceTemperature = $('forceTemperature'); const includeArtifacts = $('includeArtifacts'); const jsonFormat = $('jsonFormat'); const system = $('system'); const fullCodePrompt = $('fullCodePrompt'); const snippetsPrompt = $('snippetsPrompt'); const chunkedPrompt = $('chunkedPrompt'); const tabDefault = $('tabDefault'); const tabFullCode = $('tabFullCode'); const tabSnippets = $('tabSnippets'); const tabChunked = $('tabChunked'); const promptDefault = $('promptDefault'); const promptFullCode = $('promptFullCode'); const promptSnippets = $('promptSnippets'); const promptChunked = $('promptChunked'); // Active tab function setActiveTab(tabName) { const allTabs = [tabDefault, tabFullCode, tabSnippets, tabChunked]; const panes = { default: promptDefault, fullCode: promptFullCode, snippets: promptSnippets, chunked: promptChunked }; allTabs.forEach(b => b.className = 'mobile-tab-btn px-3 py-2 text-xs sm:text-xs bg-zinc-100 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700 border-b sm:border-b-0 sm:border-r border-zinc-300 dark:border-zinc-700 last:border-b-0 last:border-r-0'); Object.values(panes).forEach(p => p.classList.add('hidden')); const map = { default: tabDefault, fullCode: tabFullCode, snippets: tabSnippets, chunked: tabChunked }; map[tabName].className = 'mobile-tab-btn px-3 py-2 text-xs sm:text-xs bg-indigo-600 text-white border-b sm:border-b-0 sm:border-r border-zinc-300 dark:border-zinc-700 last:border-b-0 last:border-r-0'; panes[tabName].classList.remove('hidden'); s.codeStyle = tabName; App.saveState(App.state); } setActiveTab(s.codeStyle || 'default'); // Handlers maxTokens.addEventListener('input', () => { maxTokensVal.textContent = maxTokens.value; s.maxTokens = +maxTokens.value; App.saveState(App.state); }); temperature.addEventListener('input', () => { tempVal.textContent = temperature.value; s.temperature = +temperature.value; App.saveState(App.state); }); forceTemperature.addEventListener('change', () => { s.forceTemperature = forceTemperature.checked; App.saveState(App.state); }); includeArtifacts.addEventListener('change', () => { s.includeArtifacts = includeArtifacts.checked; App.saveState(App.state); }); jsonFormat.addEventListener('change', () => { s.jsonFormat = jsonFormat.checked; App.saveState(App.state); }); model.addEventListener('change', () => { s.model = model.value; App.saveState(App.state); }); system.addEventListener('input', () => { s.system = system.value; App.saveState(App.state); }); fullCodePrompt.addEventListener('input', () => { s.fullCodePrompt = fullCodePrompt.value; App.saveState(App.state); }); snippetsPrompt.addEventListener('input', () => { s.snippetsPrompt = snippetsPrompt.value; App.saveState(App.state); }); chunkedPrompt.addEventListener('input', () => { s.chunkedPrompt = chunkedPrompt.value; App.saveState(App.state); }); tabDefault.addEventListener('click', () => setActiveTab('default')); tabFullCode.addEventListener('click', () => setActiveTab('fullCode')); tabSnippets.addEventListener('click', () => setActiveTab('snippets')); tabChunked.addEventListener('click', () => setActiveTab('chunked')); els.clearChat = $('clearChat'); els.clearChat.addEventListener('click', () => { App.state.messages = []; App.saveState(App.state); if (App.renderTranscript) App.renderTranscript(); const status = document.getElementById('status'); if (status) { status.textContent = 'Cleared local transcript.'; setTimeout(() => status.textContent = '', 1500); } closeSettings(); }); } // Open/close overlay function openSettings() { els.settingsOverlay.classList.remove('hidden'); renderSettingsBody(); bindAndHydrate(); setTimeout(() => { try { document.getElementById('model').focus(); } catch {} }, 0); } function closeSettings() { els.settingsOverlay.classList.add('hidden'); } // Expose for others App.openSettings = openSettings; App.closeSettings = closeSettings; // Wire chrome els.openSettings.addEventListener('click', openSettings); els.closeSettings.addEventListener('click', closeSettings); els.overlayBackdrop.addEventListener('click', closeSettings); // Small viewport helper function handleViewportChange() { const q = document.getElementById('question'); if (!q) return; if (window.innerWidth < 640) { q.rows = 2; document.body.style.maxWidth = '100vw'; } else { q.rows = 3; document.body.style.maxWidth = ''; } } window.addEventListener('resize', handleViewportChange); handleViewportChange(); })();