// settings.js - Settings Management
(function () {
console.log("[settings] Loading Settings interface...");
// --- Model Configuration ---
const MODEL_CONFIG = {
'grok-code-fast-1': { provider: 'xai', name: 'Grok Code Fast 1', icon: 'Robot', color: '#8b5cf6' },
'deepseek-chat': { provider: 'deepseek', name: 'DeepSeek Chat', icon: 'Brain', color: '#3b82f6' },
'deepseek-reasoner': { provider: 'deepseek', name: 'DeepSeek Reasoner',icon: 'Brain', color: '#3b82f6' },
'gpt-4o': { provider: 'openai', name: 'GPT-4o', icon: 'Star', color: '#10b981' },
'gpt-4o-mini': { provider: 'openai', name: 'GPT-4o Mini', icon: 'Star', color: '#10b981' },
'grok-3': { provider: 'xai', name: 'Grok 3', icon: 'Robot', color: '#8b5cf6' },
/* === CLAUDE MODELS (one of each) === */
'claude-sonnet-4-5': {
provider: 'anthropic',
name: 'Claude Sonnet 4.5',
icon: 'Feather',
color: '#fbbf24'
},
'claude-haiku-4-5': {
provider: 'anthropic',
name: 'Claude Haiku 4.5',
icon: 'Zap',
color: '#38bdf8'
},
'claude-opus-4-1': {
provider: 'anthropic',
name: 'Claude Opus 4.1',
icon: 'Crown',
color: '#ef4444'
}
};
const API_ENDPOINT = 'api.php';
// --- Default prompts (fallback if .txt files fail) ---
const DEFAULT_PROMPTS = {
chat: 'You are a helpful AI assistant for web development.',
snippets: 'You are an expert at writing small, focused code snippets. Provide concise, working code examples.',
fullCode: 'You are an expert full-stack developer. Provide complete, production-ready code solutions.'
};
// --- Load a .txt file ---
async function loadPromptFile(filename) {
try {
const resp = await fetch(filename + "?v=" + Date.now());
if (!resp.ok) throw new Error();
return (await resp.text()).trim();
} catch (e) {
console.warn(`[settings] Failed to load ${filename}`);
return null;
}
}
// --- Load prompts from .txt files if not in localStorage ---
async function loadPromptsFromFiles() {
const [chat, snippets, fullCode] = await Promise.all([
loadPromptFile('chat.txt'),
loadPromptFile('snippet.txt'),
loadPromptFile('full.txt')
]);
return {
chat: chat || DEFAULT_PROMPTS.chat,
snippets: snippets || DEFAULT_PROMPTS.snippets,
fullCode: fullCode || DEFAULT_PROMPTS.fullCode
};
}
// --- Load settings from localStorage, fallback to .txt files ---
let settings = {
defaultModel: localStorage.getItem('settings_defaultModel') || 'grok-code-fast-1',
maxTokens: parseInt(localStorage.getItem('settings_maxTokens')) || 2000,
temperature: parseFloat(localStorage.getItem('settings_temperature')) || 0.7,
chatPrompt: localStorage.getItem('settings_chatPrompt'),
snippetsPrompt: localStorage.getItem('settings_snippetsPrompt'),
fullCodePrompt: localStorage.getItem('settings_fullCodePrompt'),
responseMode: localStorage.getItem('settings_responseMode') || 'normal',
currentMode: localStorage.getItem('settings_currentMode') || 'chat'
};
// Load from .txt if any prompt is missing
if (!settings.chatPrompt || !settings.snippetsPrompt || !settings.fullCodePrompt) {
(async () => {
const filePrompts = await loadPromptsFromFiles();
settings.chatPrompt = settings.chatPrompt || filePrompts.chat;
settings.snippetsPrompt = settings.snippetsPrompt || filePrompts.snippets;
settings.fullCodePrompt = settings.fullCodePrompt || filePrompts.fullCode;
console.log("[settings] Loaded default prompts from .txt files");
})();
}
// --- Save settings to localStorage ---
function saveSettings() {
localStorage.setItem('settings_defaultModel', settings.defaultModel);
localStorage.setItem('settings_maxTokens', settings.maxTokens);
localStorage.setItem('settings_temperature', settings.temperature);
localStorage.setItem('settings_chatPrompt', settings.chatPrompt);
localStorage.setItem('settings_snippetsPrompt', settings.snippetsPrompt);
localStorage.setItem('settings_fullCodePrompt', settings.fullCodePrompt);
localStorage.setItem('settings_responseMode', settings.responseMode);
localStorage.setItem('settings_currentMode', settings.currentMode);
console.log('[settings] Settings saved:', settings);
window.dispatchEvent(new CustomEvent('settingsUpdated', { detail: settings }));
}
// --- Reset prompts to .txt files ---
async function resetToDefaultPrompts() {
const filePrompts = await loadPromptsFromFiles();
settings.chatPrompt = filePrompts.chat;
settings.snippetsPrompt = filePrompts.snippets;
settings.fullCodePrompt = filePrompts.fullCode;
localStorage.removeItem('settings_chatPrompt');
localStorage.removeItem('settings_snippetsPrompt');
localStorage.removeItem('settings_fullCodePrompt');
saveSettings();
return filePrompts;
}
// --- Settings Slide Configuration ---
const settingsSlide = {
title: 'Settings',
html: `
<div style="height: 100%; overflow-y: auto; padding: 20px; background: #0a0a0a;">
<h2 style="color: #fff; font-size: 24px; margin-bottom: 24px; font-weight: 700;">Settings</h2>
<!-- AI Configuration Section -->
<div style="background: #1a1a1a; border: 2px solid #2a2a2a; border-radius: 8px; padding: 20px; margin-bottom: 20px;">
<h3 style="color: #fff; font-size: 18px; margin-bottom: 16px; font-weight: 600;">AI Configuration</h3>
<div style="margin-bottom: 20px;">
<label style="display: block; color: #888; font-size: 13px; font-weight: 600; margin-bottom: 8px;">Default AI Model:</label>
<select id="settingsDefaultModel" style="width: 100%; padding: 10px 12px; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 4px; color: #e0e0e0; font-size: 14px; font-family: monospace; cursor: pointer; outline: none;">
<option value="grok-code-fast-1">Grok Code Fast 1</option>
<option value="deepseek-chat">DeepSeek Chat</option>
<option value="deepseek-reasoner">DeepSeek Reasoner</option>
<option value="gpt-4o">GPT-4o</option>
<option value="gpt-4o-mini">GPT-4o Mini</option>
<option value="grok-3">Grok 3</option>
<option value="claude-sonnet-4-5">Claude Sonnet 4.5</option>
<option value="claude-haiku-4-5">Claude Haiku 4.5</option>
<option value="claude-opus-4-1">Claude Opus 4.1</option>
</select>
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; color: #888; font-size: 13px; font-weight: 600; margin-bottom: 8px;">Max Tokens: <span id="maxTokensValue" style="color: #16a34a;">2000</span></label>
<input type="range" id="settingsMaxTokens" min="500" max="8000" step="100" value="2000" style="width: 100%; cursor: pointer;" />
<div style="display: flex; justify-content: space-between; color: #666; font-size: 11px; margin-top: 4px;">
<span>500</span>
<span>8000</span>
</div>
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; color: #888; font-size: 13px; font-weight: 600; margin-bottom: 8px;">Temperature: <span id="temperatureValue" style="color: #16a34a;">0.7</span></label>
<input type="range" id="settingsTemperature" min="0" max="2" step="0.1" value="0.7" style="width: 100%; cursor: pointer;" />
<div style="display: flex; justify-content: space-between; color: #666; font-size: 11px; margin-top: 4px;">
<span>0.0 (Focused)</span>
<span>2.0 (Creative)</span>
</div>
</div>
<button id="saveSettingsBtn" style="width: 100%; padding: 12px; background: #16a34a; border: 1px solid #15803d; border-radius: 4px; color: #fff; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s;">Save Settings</button>
</div>
<!-- System Prompts Section -->
<div style="background: #1a1a1a; border: 2px solid #2a2a2a; border-radius: 8px; padding: 20px; margin-bottom: 20px;">
<h3 style="color: #fff; font-size: 18px; margin-bottom: 16px; font-weight: 600;">System Prompts & Mode</h3>
<div style="margin-bottom: 20px;">
<label style="display: block; color: #888; font-size: 13px; font-weight: 600; margin-bottom: 8px;">Active Mode:</label>
<select id="settingsCurrentMode" style="width: 100%; padding: 10px 12px; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 4px; color: #e0e0e0; font-size: 14px; font-family: monospace; cursor: pointer; outline: none;">
<option value="chat">Chat Mode</option>
<option value="snippets">Snippets Mode</option>
<option value="fullcode">Full Code Mode</option>
</select>
<div style="margin-top: 8px; padding: 8px 12px; background: rgba(139, 92, 246, 0.1); border: 1px solid #8b5cf6; border-radius: 4px; color: #c4b5fd; font-size: 11px; line-height: 1.4;">
This determines which system prompt is used for your conversations.
</div>
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; color: #888; font-size: 13px; font-weight: 600; margin-bottom: 8px;">Chat Mode Prompt:</label>
<textarea id="settingsChatPrompt" placeholder="System prompt for general chat..." style="width: 100%; min-height: 80px; padding: 10px 12px; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 4px; color: #e0e0e0; font-size: 13px; font-family: 'Segoe UI', sans-serif; resize: vertical; outline: none;"></textarea>
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; color: #888; font-size: 13px; font-weight: 600; margin-bottom: 8px;">Snippets Mode Prompt:</label>
<textarea id="settingsSnippetsPrompt" placeholder="System prompt for code snippets..." style="width: 100%; min-height: 80px; padding: 10px 12px; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 4px; color: #e0e0e0; font-size: 13px; font-family: 'Segoe UI', sans-serif; resize: vertical; outline: none;"></textarea>
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; color: #888; font-size: 13px; font-weight: 600; margin-bottom: 8px;">Full Code Mode Prompt:</label>
<textarea id="settingsFullCodePrompt" placeholder="System prompt for full code solutions..." style="width: 100%; min-height: 80px; padding: 10px 12px; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 4px; color: #e0e0e0; font-size: 13px; font-family: 'Segoe UI', sans-serif; resize: vertical; outline: none;"></textarea>
</div>
<div style="display: flex; gap: 10px; margin-bottom: 0;">
<button id="resetPromptsBtn" style="flex: 1; padding: 10px; background: #dc2626; border: 1px solid #b91c1c; border-radius: 4px; color: #fff; font-size: 13px; font-weight: 600; cursor: pointer;">Return to Default</button>
<select id="settingsResponseMode" style="flex: 1; padding: 10px 12px; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 4px; color: #e0e0e0; font-size: 14px; font-family: monospace; cursor: pointer; outline: none;">
<option value="normal">Normal (Formatted response)</option>
<option value="raw">Raw (Plain text only)</option>
</select>
</div>
<div style="margin-top: 8px; padding: 8px 12px; background: rgba(59, 130, 246, 0.1); border: 1px solid #3b82f6; border-radius: 4px; color: #93c5fd; font-size: 11px; line-height: 1.4;">
<strong>Normal:</strong> AI provides explanations with code.<br>
<strong>Raw:</strong> AI returns only code without commentary.
</div>
</div>
<!-- Model Information -->
<div style="background: #1a1a1a; border: 2px solid #2a2a2a; border-radius: 8px; padding: 20px;">
<h3 style="color: #fff; font-size: 18px; margin-bottom: 16px; font-weight: 600;">Model Information</h3>
<div id="modelInfoContainer" style="font-size: 13px; line-height: 1.6;"></div>
</div>
</div>
`,
onRender(el) {
console.log('[settings] Rendering settings interface');
const defaultModelSelect = el.querySelector('#settingsDefaultModel');
const maxTokensSlider = el.querySelector('#settingsMaxTokens');
const maxTokensValue = el.querySelector('#maxTokensValue');
const temperatureSlider = el.querySelector('#settingsTemperature');
const temperatureValue = el.querySelector('#temperatureValue');
const chatPrompt = el.querySelector('#settingsChatPrompt');
const snippetsPrompt = el.querySelector('#settingsSnippetsPrompt');
const fullCodePrompt = el.querySelector('#settingsFullCodePrompt');
const responseMode = el.querySelector('#settingsResponseMode');
const currentMode = el.querySelector('#settingsCurrentMode');
const saveBtn = el.querySelector('#saveSettingsBtn');
const resetBtn = el.querySelector('#resetPromptsBtn');
const modelInfoContainer = el.querySelector('#modelInfoContainer');
// Populate current values
defaultModelSelect.value = settings.defaultModel;
maxTokensSlider.value = settings.maxTokens;
maxTokensValue.textContent = settings.maxTokens;
temperatureSlider.value = settings.temperature;
temperatureValue.textContent = settings.temperature.toFixed(1);
chatPrompt.value = settings.chatPrompt;
snippetsPrompt.value = settings.snippetsPrompt;
fullCodePrompt.value = settings.fullCodePrompt;
responseMode.value = settings.responseMode;
currentMode.value = settings.currentMode;
// Live updates
maxTokensSlider.addEventListener('input', () => {
maxTokensValue.textContent = maxTokensSlider.value;
});
temperatureSlider.addEventListener('input', () => {
temperatureValue.textContent = parseFloat(temperatureSlider.value).toFixed(1);
});
// Model info
function updateModelInfo() {
const modelKey = defaultModelSelect.value;
const model = MODEL_CONFIG[modelKey];
if (!model) return;
const r = parseInt(model.color.slice(1,3), 16);
const g = parseInt(model.color.slice(3,5), 16);
const b = parseInt(model.color.slice(5,7), 16);
modelInfoContainer.innerHTML = `
<div style="padding: 12px; background: rgba(${r}, ${g}, ${b}, 0.1); border: 1px solid ${model.color}; border-radius: 6px; margin-bottom: 12px;">
<div style="font-size: 16px; margin-bottom: 8px;">${model.icon} <strong style="color: #fff;">${model.name}</strong></div>
<div style="color: #888;">Provider: <span style="color: #e0e0e0; font-family: monospace;">${model.provider}</span></div>
</div>
<div style="color: #666; font-size: 12px; line-height: 1.6;">
<p style="margin-bottom: 8px;"><strong style="color: #888;">Max Tokens:</strong> Controls the maximum length of the response.</p>
<p style="margin-bottom: 8px;"><strong style="color: #888;">Temperature:</strong> Controls creativity (0 = focused, 2 = creative).</p>
</div>
`;
}
defaultModelSelect.addEventListener('change', updateModelInfo);
updateModelInfo();
// Save
saveBtn.addEventListener('click', () => {
settings.defaultModel = defaultModelSelect.value;
settings.maxTokens = parseInt(maxTokensSlider.value);
settings.temperature = parseFloat(temperatureSlider.value);
settings.chatPrompt = chatPrompt.value.trim();
settings.snippetsPrompt = snippetsPrompt.value.trim();
settings.fullCodePrompt = fullCodePrompt.value.trim();
settings.responseMode = responseMode.value;
settings.currentMode = currentMode.value;
saveSettings();
saveBtn.textContent = 'Saved!';
saveBtn.style.background = '#15803d';
setTimeout(() => {
saveBtn.textContent = 'Save Settings';
saveBtn.style.background = '#16a34a';
}, 1500);
});
// Reset to .txt defaults
resetBtn.addEventListener('click', async () => {
if (!confirm("Restore all prompts from .txt files?")) return;
resetBtn.disabled = true;
resetBtn.textContent = "Loading...";
try {
const filePrompts = await resetToDefaultPrompts();
chatPrompt.value = filePrompts.chat;
snippetsPrompt.value = filePrompts.snippets;
fullCodePrompt.value = filePrompts.fullCode;
alert("Prompts restored!");
} catch (e) {
alert("Failed to load defaults.");
} finally {
resetBtn.disabled = false;
resetBtn.textContent = "Return to Default";
}
});
// Hover
saveBtn.addEventListener('mouseenter', () => {
if (saveBtn.textContent === 'Save Settings') saveBtn.style.background = '#15803d';
});
saveBtn.addEventListener('mouseleave', () => {
if (saveBtn.textContent === 'Save Settings') saveBtn.style.background = '#16a34a';
});
}
};
// --- Expose Settings API ---
window.Settings = {
open: () => {
if (window.AppOverlay) {
window.AppOverlay.open([settingsSlide]);
} else {
console.error('[settings] AppOverlay not available');
}
},
slide: settingsSlide,
get: () => ({ ...settings }),
getModelConfig: (modelKey) => MODEL_CONFIG[modelKey],
getAllModels: () => MODEL_CONFIG,
getApiEndpoint: () => API_ENDPOINT
};
console.log('[settings] Settings interface loaded with settings:', settings);
})();