// ========================================== // UI Functions // ========================================== function updateSettingsUI() { document.getElementById('apiEndpoint').value = State.settings.apiEndpoint || ''; document.getElementById('apiKey').value = State.settings.apiKey || ''; const modelSelect = document.getElementById('model'); const modelCustom = document.getElementById('modelCustom'); const savedModel = State.settings.model || 'gpt-3.5-turbo'; populateModelSelect(modelSelect); const optionExists = Array.from(modelSelect.options).some(opt => opt.value === savedModel); if (optionExists) { modelSelect.value = savedModel; modelCustom.value = ''; } else { modelSelect.value = ''; modelCustom.value = savedModel; } document.getElementById('streamResponse').checked = State.settings.stream; document.getElementById('saveHistory').checked = State.settings.saveHistory; document.getElementById('showThinking').checked = State.settings.showThinking; document.getElementById('systemPrompt').value = State.settings.systemPrompt || ''; document.getElementById('maxTokens').value = State.settings.maxTokens; document.getElementById('temperature').value = State.settings.temperature; document.getElementById('topP').value = State.settings.topP; document.getElementById('presencePenalty').value = State.settings.presencePenalty; } function populateModelSelect(selectElement) { const currentValue = selectElement.value; selectElement.innerHTML = ''; State.models.forEach(model => { const option = document.createElement('option'); option.value = model.id; option.textContent = model.id + (model.owned_by ? ` (${model.owned_by})` : ''); selectElement.appendChild(option); }); if (currentValue) { selectElement.value = currentValue; } } function updateQuickModelSelector() { const quickSelect = document.getElementById('quickModel'); const currentModel = State.settings.model; quickSelect.innerHTML = ''; if (State.models.length === 0) { quickSelect.innerHTML = ''; if (currentModel) { const opt = document.createElement('option'); opt.value = currentModel; opt.textContent = currentModel; quickSelect.appendChild(opt); quickSelect.value = currentModel; } return; } State.models.forEach(model => { const option = document.createElement('option'); option.value = model.id; option.textContent = model.id; quickSelect.appendChild(option); }); if (currentModel) { const exists = Array.from(quickSelect.options).some(opt => opt.value === currentModel); if (exists) { quickSelect.value = currentModel; } else { const opt = document.createElement('option'); opt.value = currentModel; opt.textContent = currentModel + ' (custom)'; quickSelect.insertBefore(opt, quickSelect.firstChild); quickSelect.value = currentModel; } } } function openSettings() { updateSettingsUI(); updateProfileSection(); updateProviderSection(); document.getElementById('settingsModal').classList.add('active'); } function updateProfileSection() { const profileSection = document.getElementById('profileSection'); const unmanagedSettings = document.getElementById('unmanagedSettings'); const modeBadge = document.getElementById('settingsModeBadge'); if (Backend.isManaged) { profileSection.style.display = ''; unmanagedSettings.style.display = 'none'; modeBadge.textContent = '๐ Managed Mode โ API keys are configured by your admin or in Providers below'; modeBadge.className = 'settings-mode-badge mode-managed'; loadProfileIntoSettings(); } else { profileSection.style.display = 'none'; unmanagedSettings.style.display = ''; modeBadge.textContent = '๐ฑ Offline Mode โ Configure your own API endpoint and key'; modeBadge.className = 'settings-mode-badge mode-unmanaged'; } document.getElementById('profileChangePwForm').style.display = 'none'; } async function loadProfileIntoSettings() { try { const profile = await Backend.getProfile(); document.getElementById('profileDisplayName').value = profile.display_name || ''; document.getElementById('profileEmail').value = profile.email || ''; } catch (e) { console.warn('Failed to load profile:', e); } } async function saveProfile() { if (!Backend.isManaged) return; const displayName = document.getElementById('profileDisplayName').value.trim(); const email = document.getElementById('profileEmail').value.trim(); const updates = {}; if (displayName !== (Backend.user.display_name || '')) { updates.display_name = displayName || null; } if (email && email !== Backend.user.email) { updates.email = email; } if (Object.keys(updates).length === 0) return; try { const profile = await Backend.updateProfile(updates); // Update local user state if (profile.display_name !== undefined) Backend.user.display_name = profile.display_name; if (profile.email) Backend.user.email = profile.email; Backend.save(); showToast('โ Profile updated', 'success'); } catch (e) { showToast('โ ' + e.message, 'error'); } } async function handleChangePassword() { const current = document.getElementById('profileCurrentPw').value; const newPw = document.getElementById('profileNewPw').value; if (!current || !newPw) { showToast('โ ๏ธ Fill in both fields', 'warning'); return; } if (newPw.length < 8) { showToast('โ ๏ธ New password must be at least 8 characters', 'warning'); return; } try { await Backend.changePassword(current, newPw); showToast('โ Password updated', 'success'); document.getElementById('profileCurrentPw').value = ''; document.getElementById('profileNewPw').value = ''; document.getElementById('profileChangePwForm').style.display = 'none'; } catch (e) { showToast('โ ' + e.message, 'error'); } } // โโ API Providers (managed mode) โโโโโโโโโโโโ function updateProviderSection() { const managed = document.getElementById('managedProviders'); if (Backend.isManaged) { managed.style.display = ''; loadProviderList(); } else { managed.style.display = 'none'; } } async function loadProviderList() { const container = document.getElementById('providerList'); container.innerHTML = '
${code.trim()}`;
});
// Inline code
formatted = formatted.replace(/`([^`]+)`/g, '$1');
// Bold
formatted = formatted.replace(/\*\*([^*]+)\*\*/g, '$1');
// Italic
formatted = formatted.replace(/\*([^*]+)\*/g, '$1');
// Line breaks (but not inside pre/code)
formatted = formatted.replace(/\n/g, '