Changeset 0.9.0 (#50)
This commit is contained in:
138
src/js/app.js
138
src/js/app.js
@@ -10,6 +10,7 @@ const App = {
|
||||
isGenerating: false,
|
||||
abortController: null,
|
||||
serverSettings: {},
|
||||
policies: {},
|
||||
|
||||
// Find model by composite ID, with fallback to bare model_id match
|
||||
findModel(id) {
|
||||
@@ -226,19 +227,19 @@ async function fetchModels() {
|
||||
// collisions when the same model exists across team/personal/global providers
|
||||
const id = isPreset
|
||||
? (m.preset_id || m.id)
|
||||
: (m.config_id ? `${m.config_id}:${baseModelId}` : baseModelId);
|
||||
: ((m.config_id || m.provider_config_id) ? `${m.config_id || m.provider_config_id}:${baseModelId}` : baseModelId);
|
||||
return {
|
||||
id,
|
||||
baseModelId,
|
||||
name: m.display_name || baseModelId,
|
||||
provider: m.provider_name || m.provider || '',
|
||||
configId: m.config_id || null,
|
||||
configId: m.config_id || m.provider_config_id || null,
|
||||
capabilities: resolveCapabilities(m.capabilities, baseModelId),
|
||||
isPreset,
|
||||
presetId: m.preset_id || null,
|
||||
presetScope: m.preset_scope || null,
|
||||
presetAvatar: m.preset_avatar || null,
|
||||
presetTeamName: m.preset_team_name || null,
|
||||
presetId: m.preset_id || m.persona_id || null,
|
||||
presetScope: m.preset_scope || (isPreset ? m.scope : null) || null,
|
||||
presetAvatar: m.preset_avatar || (isPreset ? m.avatar : null) || null,
|
||||
presetTeamName: m.preset_team_name || (isPreset ? m.team_name : null) || null,
|
||||
source: m.source || (isPreset ? 'preset' : 'global'),
|
||||
teamName: m.preset_team_name || null,
|
||||
hidden: !isPreset && App.hiddenModels.has(baseModelId),
|
||||
@@ -1511,14 +1512,39 @@ async function handleCreateProvider() {
|
||||
const endpoint = document.getElementById('providerEndpoint').value.trim();
|
||||
const apiKey = document.getElementById('providerApiKey').value.trim();
|
||||
const model = document.getElementById('providerDefaultModel').value.trim();
|
||||
if (!name || !endpoint || !apiKey) return UI.toast('Fill in required fields', 'warning');
|
||||
try {
|
||||
await API.createConfig(name, provider, endpoint, apiKey, model);
|
||||
UI.toast('Provider added', 'success');
|
||||
UI.hideProviderForm();
|
||||
UI.loadProviderList();
|
||||
fetchModels();
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
|
||||
const editId = UI._editingProviderId;
|
||||
|
||||
if (editId) {
|
||||
// Update mode — api_key optional (blank = keep existing)
|
||||
if (!name || !endpoint) return UI.toast('Fill in required fields', 'warning');
|
||||
const patch = { name, provider, endpoint, model_default: model };
|
||||
if (apiKey) patch.api_key = apiKey;
|
||||
try {
|
||||
await API.updateConfig(editId, patch);
|
||||
UI.toast('Provider updated', 'success');
|
||||
UI._editingProviderId = null;
|
||||
document.getElementById('providerApiKey').placeholder = 'API key';
|
||||
UI.hideProviderForm();
|
||||
UI.loadProviderList();
|
||||
fetchModels();
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
} else {
|
||||
// Create mode
|
||||
if (!name || !endpoint || !apiKey) return UI.toast('Fill in required fields', 'warning');
|
||||
try {
|
||||
const result = await API.createConfig(name, provider, endpoint, apiKey, model);
|
||||
if (result.warning) {
|
||||
UI.toast(`Provider added — ${result.warning}`, 'warning');
|
||||
} else {
|
||||
const count = result.models_fetched || 0;
|
||||
UI.toast(`Provider added — ${count} model${count !== 1 ? 's' : ''} synced`, 'success');
|
||||
}
|
||||
UI.hideProviderForm();
|
||||
UI.loadProviderList();
|
||||
fetchModels();
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteProvider(id, name) {
|
||||
@@ -1531,21 +1557,53 @@ async function deleteProvider(id, name) {
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function refreshProviderModels(id, name) {
|
||||
try {
|
||||
UI.toast(`Fetching models for ${name}...`, 'info');
|
||||
const result = await API.fetchProviderModels(id);
|
||||
const total = result.total || 0;
|
||||
UI.toast(`${name}: ${total} model${total !== 1 ? 's' : ''} synced`, 'success');
|
||||
fetchModels(); // refresh the model selector
|
||||
} catch (e) {
|
||||
UI.toast(`Failed to fetch models: ${e.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function editProvider(id) {
|
||||
try {
|
||||
const cfg = await API.getConfig(id);
|
||||
// Populate the add form with existing values
|
||||
document.getElementById('providerName').value = cfg.name || '';
|
||||
document.getElementById('providerType').value = cfg.provider || 'openai';
|
||||
document.getElementById('providerEndpoint').value = cfg.endpoint || '';
|
||||
document.getElementById('providerApiKey').value = ''; // Don't expose key
|
||||
document.getElementById('providerApiKey').placeholder = cfg.has_key ? '(unchanged — leave blank to keep)' : 'API key';
|
||||
document.getElementById('providerDefaultModel').value = cfg.model_default || '';
|
||||
// Show form and switch to edit mode
|
||||
UI.showProviderForm();
|
||||
UI._editingProviderId = id;
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function handleSaveAdminSettings() {
|
||||
try {
|
||||
// Registration
|
||||
// Policies — send as string "true"/"false" so backend routes to platform_policies
|
||||
const reg = document.getElementById('adminRegToggle').checked;
|
||||
await API.adminUpdateSetting('registration_enabled', { value: reg });
|
||||
await API.adminUpdateSetting('allow_registration', { value: reg ? 'true' : 'false' });
|
||||
|
||||
// Registration default state
|
||||
// Registration default state → default_user_active policy
|
||||
const regState = document.getElementById('adminRegDefaultState').value;
|
||||
await API.adminUpdateSetting('registration_default_state', { value: regState });
|
||||
await API.adminUpdateSetting('default_user_active', { value: regState === 'active' ? 'true' : 'false' });
|
||||
|
||||
// User providers
|
||||
// User BYOK providers → allow_user_byok policy
|
||||
const userProviders = document.getElementById('adminUserProvidersToggle').checked;
|
||||
await API.adminUpdateSetting('user_providers_enabled', { value: userProviders });
|
||||
await API.adminUpdateSetting('allow_user_byok', { value: userProviders ? 'true' : 'false' });
|
||||
|
||||
// Banner
|
||||
// User presets → allow_user_personas policy
|
||||
const userPresets = document.getElementById('adminUserPresetsToggle').checked;
|
||||
await API.adminUpdateSetting('allow_user_personas', { value: userPresets ? 'true' : 'false' });
|
||||
|
||||
// Banner → global_settings (JSON value)
|
||||
const banner = {
|
||||
enabled: document.getElementById('adminBannerEnabled').checked,
|
||||
text: document.getElementById('adminBannerText').value,
|
||||
@@ -1556,8 +1614,11 @@ async function handleSaveAdminSettings() {
|
||||
await API.adminUpdateSetting('banner', { value: banner });
|
||||
|
||||
UI.toast('Settings saved', 'success');
|
||||
// Live-apply banner
|
||||
initBanners();
|
||||
// Live-apply: refresh policies and dependent UI
|
||||
await initBanners();
|
||||
UI.checkUserProvidersAllowed();
|
||||
UI.checkUserPresetsAllowed();
|
||||
fetchModels();
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
@@ -1695,7 +1756,17 @@ async function createGlobalProvider() {
|
||||
async function fetchAdminModels() {
|
||||
const hint = document.getElementById('adminModelsHint');
|
||||
hint.textContent = 'Fetching...';
|
||||
try { await API.adminFetchModels(); UI.toast('Models synced', 'success'); hint.textContent = ''; await UI.loadAdminModels(); }
|
||||
try {
|
||||
const resp = await API.adminFetchModels();
|
||||
const errs = resp.errors || [];
|
||||
if (errs.length > 0) {
|
||||
UI.toast('Fetch errors: ' + errs.join('; '), 'error');
|
||||
} else {
|
||||
UI.toast(`Models synced — ${resp.added || 0} added, ${resp.updated || 0} updated`, 'success');
|
||||
}
|
||||
hint.textContent = '';
|
||||
await UI.loadAdminModels();
|
||||
}
|
||||
catch (e) { UI.toast(e.message, 'error'); hint.textContent = 'Failed'; }
|
||||
}
|
||||
async function cycleModelVisibility(id, current) {
|
||||
@@ -1860,7 +1931,7 @@ async function createAdminPreset(vals) {
|
||||
description: vals.description || '',
|
||||
system_prompt: vals.system_prompt || '',
|
||||
};
|
||||
if (vals.api_config_id) preset.api_config_id = vals.api_config_id;
|
||||
if (vals.provider_config_id) preset.provider_config_id = vals.provider_config_id;
|
||||
if (vals.temperature != null) preset.temperature = vals.temperature;
|
||||
if (vals.max_tokens != null) preset.max_tokens = vals.max_tokens;
|
||||
|
||||
@@ -2425,19 +2496,18 @@ function initBranding() {
|
||||
|
||||
async function initBanners() {
|
||||
try {
|
||||
const data = await API.getPublicSettings?.() || [];
|
||||
const settings = data.settings || data || [];
|
||||
const arr = Array.isArray(settings) ? settings : [];
|
||||
const data = await API.getPublicSettings?.() || {};
|
||||
|
||||
// Store globally for user-facing checks — unwrap {value: X} wrapper
|
||||
// Store policies for user-facing checks (allow_user_byok, etc.)
|
||||
App.policies = data.policies || {};
|
||||
|
||||
// Also flatten into serverSettings for backward compat
|
||||
App.serverSettings = {};
|
||||
arr.forEach(s => {
|
||||
const v = s.value;
|
||||
App.serverSettings[s.key] = (v && typeof v === 'object' && 'value' in v) ? v.value : v;
|
||||
});
|
||||
if (data.banner) App.serverSettings.banner = data.banner;
|
||||
if (data.branding) App.serverSettings.branding = data.branding;
|
||||
|
||||
const root = document.documentElement;
|
||||
const banner = App.serverSettings.banner;
|
||||
const banner = data.banner;
|
||||
|
||||
// Clear previous banner state
|
||||
['bannerTop', 'bannerBottom'].forEach(id => {
|
||||
|
||||
Reference in New Issue
Block a user