Changeset 0.22.7 (#149)
This commit is contained in:
123
src/js/app.js
123
src/js/app.js
@@ -1,119 +1,14 @@
|
||||
// ==========================================
|
||||
// Chat Switchboard – Application
|
||||
// Chat Switchboard – Chat Surface Application
|
||||
// ==========================================
|
||||
// State, init, boot, auth, listener dispatch.
|
||||
// Domain logic lives in chat.js, notes.js, settings-handlers.js,
|
||||
// admin-handlers.js. UI rendering in ui-*.js files.
|
||||
|
||||
const App = {
|
||||
chats: [],
|
||||
currentChatId: null,
|
||||
models: [],
|
||||
hiddenModels: new Set(),
|
||||
isGenerating: false,
|
||||
abortController: null,
|
||||
serverSettings: {},
|
||||
policies: {},
|
||||
stagedAttachments: [],
|
||||
channelAttachments: {}, // messageId → [attachment, ...] — populated on chat load
|
||||
storageConfigured: false,
|
||||
projects: [], // v0.19.0: loaded from /api/v1/projects
|
||||
collapsedProjects: {}, // projectId → bool — sidebar collapse state
|
||||
activeProjectId: null, // v0.19.1: new chats auto-assign to this project
|
||||
showArchivedProjects: false, // v0.19.2: toggle archived projects in sidebar
|
||||
|
||||
// Find model by composite ID, with fallback to bare model_id match
|
||||
findModel(id) {
|
||||
if (!id) return null;
|
||||
// Exact match on composite ID (configId:modelId)
|
||||
let m = App.models.find(m => m.id === id);
|
||||
if (m) return m;
|
||||
// Fallback: bare model_id (backward compat with stored settings)
|
||||
return App.models.find(m => m.baseModelId === id) || null;
|
||||
},
|
||||
|
||||
settings: {
|
||||
model: '',
|
||||
stream: true,
|
||||
showThinking: false,
|
||||
systemPrompt: '',
|
||||
maxTokens: 0, // 0 = auto from model capabilities
|
||||
temperature: 0.7,
|
||||
},
|
||||
};
|
||||
|
||||
// ── Models ───────────────────────────────────
|
||||
|
||||
// ── Capability Resolution ───────────────────────────────────────
|
||||
// The backend is the source of truth for model capabilities.
|
||||
// Resolution chain on the server: catalog (provider API sync) → heuristic.
|
||||
// The frontend does NOT maintain a static model table — the same model
|
||||
// can have different capabilities on different providers (e.g. DeepSeek
|
||||
// on Venice has no tool_calling, same model on OpenRouter does).
|
||||
|
||||
// resolveCapabilities returns backend caps as-is. No client-side override.
|
||||
function resolveCapabilities(backendCaps, modelId) {
|
||||
return backendCaps && Object.keys(backendCaps).length > 0 ? { ...backendCaps } : {};
|
||||
}
|
||||
|
||||
async function fetchModels() {
|
||||
try {
|
||||
const data = await API.listEnabledModels();
|
||||
App.defaultModel = data.default_model || '';
|
||||
// Load user model preferences
|
||||
try {
|
||||
const prefData = await API.getModelPreferences();
|
||||
App.hiddenModels = new Set(
|
||||
(prefData.preferences || []).filter(p => p.hidden).map(p => p.model_id)
|
||||
);
|
||||
} catch (e) {
|
||||
// Keep existing preferences on failure (auth dead, network issue).
|
||||
// Only init to empty if there's nothing to preserve.
|
||||
if (!App.hiddenModels) App.hiddenModels = new Set();
|
||||
}
|
||||
|
||||
App.models = (data.models || []).map(m => {
|
||||
const isPreset = !!m.is_preset;
|
||||
const baseModelId = m.model_id || m.id;
|
||||
// Presets use preset_id; base models use configId:modelId to avoid
|
||||
// collisions when the same model exists across team/personal/global providers
|
||||
const id = isPreset
|
||||
? (m.preset_id || m.id)
|
||||
: ((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 || m.provider_config_id || null,
|
||||
model_type: m.model_type || 'chat',
|
||||
capabilities: resolveCapabilities(m.capabilities, baseModelId),
|
||||
isPreset,
|
||||
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),
|
||||
};
|
||||
});
|
||||
|
||||
// Sort: presets first (global > team > personal), then regular models
|
||||
const scopeOrder = { global: 0, team: 1, personal: 2 };
|
||||
App.models.sort((a, b) => {
|
||||
if (a.isPreset && !b.isPreset) return -1;
|
||||
if (!a.isPreset && b.isPreset) return 1;
|
||||
if (a.isPreset && b.isPreset) {
|
||||
const sa = scopeOrder[a.presetScope] ?? 9;
|
||||
const sb = scopeOrder[b.presetScope] ?? 9;
|
||||
if (sa !== sb) return sa - sb;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
console.log(`📋 Loaded ${App.models.length} models (${App.models.filter(m => m.isPreset).length} presets, ${App.models.filter(m => m.hidden).length} hidden)`);
|
||||
} catch (e) { console.warn('Model fetch failed:', e.message); }
|
||||
// Chat-specific init, boot, auth, listener dispatch.
|
||||
// App state, fetchModels, resolveCapabilities defined in app-state.js.
|
||||
// Domain logic in chat.js, notes.js, settings-handlers.js, admin-handlers.js.
|
||||
|
||||
// ── Chat Surface Model Load ────────────────
|
||||
// Wraps shared fetchModels() with chat-specific UI updates.
|
||||
async function fetchModelsAndUpdateUI() {
|
||||
await fetchModels();
|
||||
UI.updateModelSelector();
|
||||
UI.updateCapabilityBadges();
|
||||
}
|
||||
@@ -185,7 +80,7 @@ async function startApp() {
|
||||
await loadChats();
|
||||
await loadProjects();
|
||||
await loadProjectChannelPositions(); // v0.19.2: load channel order per project
|
||||
await fetchModels();
|
||||
await fetchModelsAndUpdateUI();
|
||||
|
||||
// Guard: if token was invalidated during startup (401 → refresh fail → clearTokens),
|
||||
// kick back to login instead of rendering a broken UI.
|
||||
|
||||
Reference in New Issue
Block a user