/* =============================================================
MODEL DROPDOWN — load and repopulate on provider change
============================================================= */
let _allModels = {};
async function loadModels() {
try {
const data = await api('/models');
_allModels = data.models || {};
repopulateModelDropdown(state.provider);
} catch (e) {
console.warn('Could not load models:', e);
}
}
function repopulateModelDropdown(provider) {
const sel = dom.modelInput;
const models = _allModels[provider] || [];
const current = state.model;
sel.innerHTML = models.map(m =>
``
).join('');
if (models.length === 0) {
sel.innerHTML = '';
} else if (!models.includes(current)) {
state.model = models[0];
sel.value = models[0];
api('/config', { method: 'PATCH', body: JSON.stringify({ key: 'global.model', value: models[0] }) })
.catch(e => console.warn('Could not persist model change:', e));
}
}
dom.providerSelect.addEventListener('change', async () => {
state.provider = dom.providerSelect.value;
repopulateModelDropdown(state.provider);
await api('/config', { method: 'PATCH', body: JSON.stringify({ key: 'global.provider', value: state.provider }) });
});
dom.modelInput.addEventListener('change', async () => {
state.model = dom.modelInput.value;
if (state.model) {
await api('/config', { method: 'PATCH', body: JSON.stringify({ key: 'global.model', value: state.model }) });
updateTokenDisplay();
}
});
/* =============================================================
MODEL MANAGER
============================================================= */
function renderModelList(models) {
const container = $('model-list-container');
container.innerHTML = '';
const providers = ['google', 'openai', 'anthropic', 'ollama'];
providers.forEach(provider => {
const list = models[provider] || [];
const group = document.createElement('div');
group.className = 'model-provider-group';
group.innerHTML = `
`;
if (list.length === 0) {
group.innerHTML += 'No models added
';
} else {
list.forEach(model => {
const item = document.createElement('div');
item.className = 'model-item';
item.innerHTML = `
${escHtml(model)}
`;
group.appendChild(item);
});
}
container.appendChild(group);
});
container.querySelectorAll('.model-item-delete').forEach(btn => {
btn.addEventListener('click', async () => {
const provider = btn.dataset.provider;
const model = btn.dataset.model;
if (!confirm(`Remove "${model}" from ${provider}?`)) return;
try {
const data = await api('/models', {
method: 'DELETE',
body: JSON.stringify({ provider, model_name: model }),
});
_allModels = data.models || _allModels;
renderModelList(_allModels);
repopulateModelDropdown(state.provider);
} catch (e) {
$('model-mgr-status').textContent = '✗ ' + (e.message || 'Failed');
$('model-mgr-status').className = 'key-status err';
}
});
});
}
$('btn-model-add').addEventListener('click', async () => {
const provider = $('model-mgr-provider').value;
const name = $('model-mgr-name').value.trim();
if (!name) { $('model-mgr-status').textContent = 'Enter a model name.'; $('model-mgr-status').className = 'key-status err'; return; }
try {
const data = await api('/models', {
method: 'POST',
body: JSON.stringify({ provider, model_name: name }),
});
_allModels = data.models || _allModels;
$('model-mgr-name').value = '';
renderModelList(_allModels);
repopulateModelDropdown(state.provider);
$('model-mgr-status').textContent = '✓ Added';
$('model-mgr-status').className = 'key-status ok';
setTimeout(() => { $('model-mgr-status').textContent = ''; $('model-mgr-status').className = 'key-status'; }, 2000);
} catch (e) {
$('model-mgr-status').textContent = '✗ ' + (e.message || 'Failed');
$('model-mgr-status').className = 'key-status err';
}
});
/* =============================================================
BOOT
============================================================= */
window.addEventListener('resize', _syncZoomDependentLayout);
document.addEventListener('DOMContentLoaded', _syncZoomDependentLayout);
async function boot() {
console.log('🚀 Booting Local Search Agent UI...');
await loadConfig();
console.log('✓ Config loaded');
await loadModels();
console.log('✓ Models loaded');
dom.providerSelect.value = state.provider;
await loadWorkspaces();
console.log('✓ Workspaces loaded:', state.workspaces.length, 'workspaces');
if (state.workspace) {
console.log('📂 Active workspace:', state.workspace);
await loadSessions();
if (state.sessions.length > 0) {
await loadSession(state.sessions[0].session_id);
} else {
await newChat();
}
await refreshSyncToggle();
} else {
console.warn('⚠️ No workspace selected. Create one to start.');
}
updateTokenDisplay();
console.log('🔍 Starting health checks...');
pollHealth();
setInterval(pollHealth, 15000);
console.log('✅ Boot complete');
}
boot().catch(e => console.error('Boot error:', e));