Changeset 0.8.6 (#49)
This commit is contained in:
@@ -343,6 +343,39 @@
|
||||
</div>
|
||||
<button class="btn-small" id="settingsTeamAddMemberBtn" style="margin-top:6px">+ Add Member</button>
|
||||
</section>
|
||||
<!-- Team Providers -->
|
||||
<section class="settings-section">
|
||||
<h4 style="font-size:13px;margin-bottom:4px">Providers</h4>
|
||||
<div id="settingsTeamProviders"></div>
|
||||
<div id="settingsTeamEditProvider" style="display:none;margin-top:8px" class="admin-inline-form">
|
||||
<div class="form-grid-2col">
|
||||
<div class="form-group"><label>Name</label><input id="teamProviderEditName"></div>
|
||||
<div class="form-group"><label>Endpoint</label><input id="teamProviderEditEndpoint"></div>
|
||||
<div class="form-group"><label>API Key</label><input id="teamProviderEditKey" type="password" placeholder="(unchanged)"></div>
|
||||
<div class="form-group"><label>Default Model</label><input id="teamProviderEditDefault" placeholder="optional"></div>
|
||||
</div>
|
||||
<div style="margin:6px 0"><label><input type="checkbox" id="teamProviderEditPrivate"> Private provider (data stays on-prem)</label></div>
|
||||
<div class="form-row">
|
||||
<button class="btn-small btn-primary" id="settingsTeamEditProviderSubmit">Update</button>
|
||||
<button class="btn-small" id="settingsTeamCancelEditProvider">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsTeamAddProvider" style="display:none;margin-top:8px" class="admin-inline-form">
|
||||
<div class="form-grid-2col">
|
||||
<div class="form-group"><label>Name</label><input id="teamProviderName" placeholder="e.g. Team OpenAI"></div>
|
||||
<div class="form-group"><label>Provider</label><select id="teamProviderType"></select></div>
|
||||
<div class="form-group"><label>Endpoint</label><input id="teamProviderEndpoint" placeholder="https://api.openai.com/v1"></div>
|
||||
<div class="form-group"><label>API Key</label><input id="teamProviderKey" type="password" placeholder="sk-..."></div>
|
||||
</div>
|
||||
<div style="margin:6px 0"><label><input type="checkbox" id="teamProviderPrivate"> Private provider (data stays on-prem)</label></div>
|
||||
<div class="form-row">
|
||||
<button class="btn-small btn-primary" id="settingsTeamAddProviderSubmit">Add Provider</button>
|
||||
<button class="btn-small" id="settingsTeamCancelProvider">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-small" id="settingsTeamAddProviderBtn" style="margin-top:6px">+ Add Provider</button>
|
||||
</section>
|
||||
|
||||
<!-- Team Presets -->
|
||||
<section class="settings-section">
|
||||
<h4 style="font-size:13px;margin-bottom:6px">Team Presets</h4>
|
||||
@@ -444,6 +477,8 @@
|
||||
<section class="settings-section" style="margin-bottom:12px;padding:10px;background:rgba(255,255,255,0.03);border-radius:8px">
|
||||
<label class="checkbox-label"><input type="checkbox" id="adminTeamPrivatePolicy"> Require private providers (data stays on-prem)</label>
|
||||
<p class="section-hint" style="margin:4px 0 0">Team members can only use providers marked as "private".</p>
|
||||
<label class="checkbox-label" style="margin-top:8px"><input type="checkbox" id="adminTeamAllowProviders" checked> Allow team admins to manage providers</label>
|
||||
<p class="section-hint" style="margin:4px 0 0">When disabled, team admins cannot add or modify team providers.</p>
|
||||
</section>
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-small btn-primary" id="adminAddMemberBtn">+ Add Member</button>
|
||||
|
||||
@@ -341,6 +341,13 @@ const API = {
|
||||
teamDeletePreset(teamId, presetId) { return this._del(`/api/v1/teams/${teamId}/presets/${presetId}`); },
|
||||
teamListAvailableModels(teamId) { return this._get(`/api/v1/teams/${teamId}/models`); },
|
||||
|
||||
// ── Team Providers ──────────────────────
|
||||
teamListProviders(teamId) { return this._get(`/api/v1/teams/${teamId}/providers`); },
|
||||
teamCreateProvider(teamId, provider) { return this._post(`/api/v1/teams/${teamId}/providers`, provider); },
|
||||
teamUpdateProvider(teamId, providerId, updates) { return this._put(`/api/v1/teams/${teamId}/providers/${providerId}`, updates); },
|
||||
teamDeleteProvider(teamId, providerId) { return this._del(`/api/v1/teams/${teamId}/providers/${providerId}`); },
|
||||
teamListProviderModels(teamId, providerId) { return this._get(`/api/v1/teams/${teamId}/providers/${providerId}/models`); },
|
||||
|
||||
// ── User Presets ─────────────────────────
|
||||
listPresets() { return this._get('/api/v1/presets'); },
|
||||
createPreset(preset) { return this._post('/api/v1/presets', preset); },
|
||||
|
||||
145
src/js/app.js
145
src/js/app.js
@@ -11,6 +11,16 @@ const App = {
|
||||
abortController: null,
|
||||
serverSettings: {},
|
||||
|
||||
// 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,
|
||||
@@ -211,8 +221,12 @@ async function fetchModels() {
|
||||
|
||||
App.models = (data.models || []).map(m => {
|
||||
const isPreset = !!m.is_preset;
|
||||
const id = isPreset ? (m.preset_id || m.id) : (m.model_id || m.id);
|
||||
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.config_id}:${baseModelId}` : baseModelId);
|
||||
return {
|
||||
id,
|
||||
baseModelId,
|
||||
@@ -225,6 +239,8 @@ async function fetchModels() {
|
||||
presetScope: m.preset_scope || null,
|
||||
presetAvatar: m.preset_avatar || null,
|
||||
presetTeamName: m.preset_team_name || null,
|
||||
source: m.source || (isPreset ? 'preset' : 'global'),
|
||||
teamName: m.preset_team_name || null,
|
||||
hidden: !isPreset && App.hiddenModels.has(baseModelId),
|
||||
};
|
||||
});
|
||||
@@ -337,7 +353,7 @@ async function sendMessage() {
|
||||
input.style.height = 'auto';
|
||||
|
||||
const selectedId = UI.getModelValue();
|
||||
const modelInfo = App.models.find(m => m.id === selectedId);
|
||||
const modelInfo = App.findModel(selectedId);
|
||||
// For presets, send the base model ID; for regular models, send the model ID
|
||||
const model = modelInfo?.baseModelId || selectedId;
|
||||
const presetId = modelInfo?.isPreset ? modelInfo.presetId : null;
|
||||
@@ -430,7 +446,7 @@ async function regenerateMessage(messageId) {
|
||||
if (App.isGenerating || !App.currentChatId) return;
|
||||
|
||||
const selectedId = UI.getModelValue();
|
||||
const modelInfo = App.models.find(m => m.id === selectedId);
|
||||
const modelInfo = App.findModel(selectedId);
|
||||
const model = modelInfo?.baseModelId || selectedId;
|
||||
const presetId = modelInfo?.isPreset ? modelInfo.presetId : null;
|
||||
|
||||
@@ -501,7 +517,7 @@ async function submitEdit(messageId, newContent) {
|
||||
if (!newContent.trim()) return;
|
||||
|
||||
const selectedId = UI.getModelValue();
|
||||
const modelInfo = App.models.find(m => m.id === selectedId);
|
||||
const modelInfo = App.findModel(selectedId);
|
||||
const model = modelInfo?.baseModelId || selectedId;
|
||||
const presetId = modelInfo?.isPreset ? modelInfo.presetId : null;
|
||||
|
||||
@@ -876,7 +892,7 @@ function initListeners() {
|
||||
tab.addEventListener('click', () => UI.switchSettingsTab(tab.dataset.stab));
|
||||
});
|
||||
document.getElementById('settingsModel').addEventListener('change', function() {
|
||||
const model = App.models.find(m => m.id === this.value);
|
||||
const model = App.findModel(this.value);
|
||||
const caps = model?.capabilities || lookupKnownCaps(this.value) || {};
|
||||
const hint = document.getElementById('settingsMaxHint');
|
||||
if (hint) {
|
||||
@@ -1030,6 +1046,14 @@ function initListeners() {
|
||||
UI.toast(this.checked ? 'Private providers required' : 'Private provider policy removed');
|
||||
} catch (e) { UI.toast(e.message, 'error'); this.checked = !this.checked; }
|
||||
});
|
||||
document.getElementById('adminTeamAllowProviders')?.addEventListener('change', async function() {
|
||||
try {
|
||||
await API.adminUpdateTeam(UI._teamEditId, {
|
||||
settings: JSON.stringify({ allow_team_providers: this.checked })
|
||||
});
|
||||
UI.toast(this.checked ? 'Team providers enabled' : 'Team providers disabled');
|
||||
} catch (e) { UI.toast(e.message, 'error'); this.checked = !this.checked; }
|
||||
});
|
||||
document.getElementById('adminCancelMemberBtn')?.addEventListener('click', () => {
|
||||
document.getElementById('adminAddMemberForm').style.display = 'none';
|
||||
});
|
||||
@@ -1100,6 +1124,86 @@ function initListeners() {
|
||||
UI.loadTeamPresetModelDropdown(UI._managingTeamId);
|
||||
});
|
||||
|
||||
// Team — providers
|
||||
const defaultEndpoints = {
|
||||
openai: 'https://api.openai.com/v1',
|
||||
anthropic: 'https://api.anthropic.com',
|
||||
venice: 'https://api.venice.ai/api/v1',
|
||||
openrouter: 'https://openrouter.ai/api/v1',
|
||||
};
|
||||
document.getElementById('settingsTeamAddProviderBtn')?.addEventListener('click', () => {
|
||||
const form = document.getElementById('settingsTeamAddProvider');
|
||||
form.style.display = form.style.display === 'none' ? '' : 'none';
|
||||
// Populate provider type dropdown
|
||||
const sel = document.getElementById('teamProviderType');
|
||||
if (sel && sel.options.length === 0) {
|
||||
['openai', 'anthropic', 'venice', 'openrouter'].forEach(p => {
|
||||
const labels = { openai: 'OpenAI-compatible', anthropic: 'Anthropic', venice: 'Venice.ai', openrouter: 'OpenRouter' };
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p;
|
||||
opt.textContent = labels[p] || p;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
});
|
||||
document.getElementById('teamProviderType')?.addEventListener('change', function() {
|
||||
const ep = document.getElementById('teamProviderEndpoint');
|
||||
if (!ep.value || Object.values(defaultEndpoints).includes(ep.value)) {
|
||||
ep.value = defaultEndpoints[this.value] || '';
|
||||
}
|
||||
});
|
||||
document.getElementById('settingsTeamCancelProvider')?.addEventListener('click', () => {
|
||||
document.getElementById('settingsTeamAddProvider').style.display = 'none';
|
||||
});
|
||||
document.getElementById('settingsTeamAddProviderSubmit')?.addEventListener('click', async () => {
|
||||
const teamId = UI._managingTeamId;
|
||||
const name = document.getElementById('teamProviderName').value.trim();
|
||||
const provider = document.getElementById('teamProviderType').value;
|
||||
const endpoint = document.getElementById('teamProviderEndpoint').value.trim();
|
||||
const apiKey = document.getElementById('teamProviderKey').value;
|
||||
const isPrivate = document.getElementById('teamProviderPrivate').checked;
|
||||
if (!name) return UI.toast('Name required', 'error');
|
||||
if (!endpoint) return UI.toast('Endpoint required', 'error');
|
||||
try {
|
||||
await API.teamCreateProvider(teamId, { name, provider, endpoint, api_key: apiKey, is_private: isPrivate });
|
||||
document.getElementById('settingsTeamAddProvider').style.display = 'none';
|
||||
document.getElementById('teamProviderName').value = '';
|
||||
document.getElementById('teamProviderEndpoint').value = '';
|
||||
document.getElementById('teamProviderKey').value = '';
|
||||
document.getElementById('teamProviderPrivate').checked = false;
|
||||
UI.toast('Provider added');
|
||||
await UI.loadTeamManageProviders(teamId);
|
||||
await fetchModels();
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
});
|
||||
|
||||
// Team — edit provider
|
||||
document.getElementById('settingsTeamCancelEditProvider')?.addEventListener('click', () => {
|
||||
document.getElementById('settingsTeamEditProvider').style.display = 'none';
|
||||
});
|
||||
document.getElementById('settingsTeamEditProviderSubmit')?.addEventListener('click', async () => {
|
||||
const teamId = UI._managingTeamId;
|
||||
const provId = UI._editingTeamProviderId;
|
||||
if (!provId) return;
|
||||
const updates = {};
|
||||
const name = document.getElementById('teamProviderEditName').value.trim();
|
||||
const endpoint = document.getElementById('teamProviderEditEndpoint').value.trim();
|
||||
const apiKey = document.getElementById('teamProviderEditKey').value;
|
||||
const defaultModel = document.getElementById('teamProviderEditDefault').value.trim();
|
||||
const isPrivate = document.getElementById('teamProviderEditPrivate').checked;
|
||||
if (name) updates.name = name;
|
||||
if (endpoint) updates.endpoint = endpoint;
|
||||
if (apiKey) updates.api_key = apiKey;
|
||||
if (defaultModel) updates.model_default = defaultModel;
|
||||
updates.is_private = isPrivate;
|
||||
try {
|
||||
await API.teamUpdateProvider(teamId, provId, updates);
|
||||
document.getElementById('settingsTeamEditProvider').style.display = 'none';
|
||||
UI.toast('Provider updated');
|
||||
await UI.loadTeamManageProviders(teamId);
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
});
|
||||
|
||||
// User — personal presets
|
||||
var _userPresetForm = null;
|
||||
document.getElementById('userAddPresetBtn')?.addEventListener('click', () => {
|
||||
@@ -1876,6 +1980,37 @@ async function settingsDeleteTeamPreset(teamId, presetId, name) {
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function settingsToggleTeamProvider(teamId, providerId, currentlyActive) {
|
||||
try {
|
||||
await API.teamUpdateProvider(teamId, providerId, { is_active: !currentlyActive });
|
||||
UI.toast(currentlyActive ? 'Provider deactivated' : 'Provider activated');
|
||||
await UI.loadTeamManageProviders(teamId);
|
||||
await fetchModels();
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function settingsDeleteTeamProvider(teamId, providerId, name) {
|
||||
if (!confirm(`Delete team provider "${name}"? This will remove all models from this provider for your team.`)) return;
|
||||
try {
|
||||
await API.teamDeleteProvider(teamId, providerId);
|
||||
UI.toast('Provider deleted');
|
||||
await UI.loadTeamManageProviders(teamId);
|
||||
await fetchModels();
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
function settingsEditTeamProvider(teamId, provId, name, endpoint, defaultModel, isPrivate) {
|
||||
UI._editingTeamProviderId = provId;
|
||||
document.getElementById('settingsTeamAddProvider').style.display = 'none';
|
||||
const form = document.getElementById('settingsTeamEditProvider');
|
||||
form.style.display = '';
|
||||
document.getElementById('teamProviderEditName').value = name;
|
||||
document.getElementById('teamProviderEditEndpoint').value = endpoint;
|
||||
document.getElementById('teamProviderEditKey').value = '';
|
||||
document.getElementById('teamProviderEditDefault').value = defaultModel;
|
||||
document.getElementById('teamProviderEditPrivate').checked = isPrivate;
|
||||
}
|
||||
|
||||
// ── Notes Panel ─────────────────────────────
|
||||
|
||||
var _editingNoteId = null;
|
||||
|
||||
100
src/js/ui.js
100
src/js/ui.js
@@ -389,7 +389,7 @@ const UI = {
|
||||
document.getElementById('typingIndicator')?.remove();
|
||||
|
||||
const label = modelName || 'Assistant';
|
||||
const currentModel = App.models.find(m => m.id === App.settings.model);
|
||||
const currentModel = App.findModel(App.settings.model);
|
||||
const streamAvatar = currentModel?.presetAvatar || null;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'message assistant';
|
||||
@@ -557,10 +557,14 @@ const UI = {
|
||||
});
|
||||
|
||||
addGroup('🔧 My Presets', personalPresets);
|
||||
addGroup('Models', models);
|
||||
|
||||
// Restore selection
|
||||
const match = App.models.find(m => m.id === current);
|
||||
// No team model groups — team providers are for preset building only.
|
||||
// Team members access team models through curated presets.
|
||||
addGroup('Models', models.filter(m => m.source !== 'personal'));
|
||||
addGroup('🔑 My Providers', models.filter(m => m.source === 'personal'));
|
||||
|
||||
// Restore selection (supports both composite and bare model IDs)
|
||||
const match = App.findModel(current);
|
||||
if (match) {
|
||||
UI.setModelValue(match.id, match.name + (match.provider ? ` (${match.provider})` : ''));
|
||||
} else if (App.models.length > 0) {
|
||||
@@ -588,7 +592,8 @@ const UI = {
|
||||
div.className = 'model-dropdown-item';
|
||||
div.dataset.value = m.id;
|
||||
const avatar = m.presetAvatar ? `<img src="${m.presetAvatar}" class="dropdown-avatar" alt="">` : '';
|
||||
div.innerHTML = `${avatar}<span class="item-label">${esc(m.name || m.id)}</span>${m.provider ? `<span class="item-provider">${esc(m.provider)}</span>` : ''}`;
|
||||
const teamBadge = m.source === 'team' && m.teamName ? `<span class="badge-team" style="font-size:9px;padding:0 4px">👥 ${esc(m.teamName)}</span>` : '';
|
||||
div.innerHTML = `${avatar}<span class="item-label">${esc(m.name || m.id)}</span>${teamBadge}${m.provider ? `<span class="item-provider">${esc(m.provider)}</span>` : ''}`;
|
||||
div.addEventListener('click', () => {
|
||||
UI.setModelValue(m.id, (m.name || m.id) + (m.provider ? ` (${m.provider})` : ''));
|
||||
UI._closeModelDropdown();
|
||||
@@ -619,7 +624,7 @@ const UI = {
|
||||
getSelectedModelCaps() {
|
||||
const modelId = UI.getModelValue();
|
||||
if (!modelId) return {};
|
||||
const model = App.models.find(m => m.id === modelId);
|
||||
const model = App.findModel(modelId);
|
||||
// Model in list has resolved caps; fallback to client-side lookup
|
||||
if (model?.capabilities && Object.keys(model.capabilities).length > 0) {
|
||||
return model.capabilities;
|
||||
@@ -708,7 +713,7 @@ const UI = {
|
||||
document.getElementById('sendBtn').disabled = on;
|
||||
if (on) {
|
||||
const container = document.getElementById('chatMessages');
|
||||
const currentModel = App.models.find(m => m.id === App.settings.model);
|
||||
const currentModel = App.findModel(App.settings.model);
|
||||
const typingAvatar = currentModel?.presetAvatar || null;
|
||||
const typing = document.createElement('div');
|
||||
typing.id = 'typingIndicator';
|
||||
@@ -921,7 +926,13 @@ const UI = {
|
||||
document.getElementById('settingsTeamManageName').textContent = teamName;
|
||||
document.getElementById('settingsTeamAddMember').style.display = 'none';
|
||||
document.getElementById('settingsTeamAddPreset').style.display = 'none';
|
||||
await Promise.all([UI.loadTeamManageMembers(teamId), UI.loadTeamManagePresets(teamId)]);
|
||||
document.getElementById('settingsTeamAddProvider').style.display = 'none';
|
||||
document.getElementById('settingsTeamEditProvider').style.display = 'none';
|
||||
await Promise.all([
|
||||
UI.loadTeamManageMembers(teamId),
|
||||
UI.loadTeamManageProviders(teamId),
|
||||
UI.loadTeamManagePresets(teamId),
|
||||
]);
|
||||
},
|
||||
|
||||
async loadTeamManageMembers(teamId) {
|
||||
@@ -948,6 +959,42 @@ const UI = {
|
||||
} catch (e) { el.innerHTML = `<div class="error-hint">${esc(e.message)}</div>`; }
|
||||
},
|
||||
|
||||
async loadTeamManageProviders(teamId) {
|
||||
const el = document.getElementById('settingsTeamProviders');
|
||||
if (!el) return;
|
||||
el.innerHTML = '<div class="loading">Loading...</div>';
|
||||
const addBtn = document.getElementById('settingsTeamAddProviderBtn');
|
||||
try {
|
||||
const resp = await API.teamListProviders(teamId);
|
||||
const provs = resp.providers || [];
|
||||
const allowed = resp.allow_team_providers !== false;
|
||||
|
||||
// Show/hide add button and entire section based on policy
|
||||
if (addBtn) addBtn.style.display = allowed ? '' : 'none';
|
||||
|
||||
if (!allowed && provs.length === 0) {
|
||||
el.innerHTML = '<div class="empty-hint">Team provider management is disabled by your administrator</div>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = provs.map(p => {
|
||||
const statusCls = p.is_active ? 'badge-admin' : 'badge-user';
|
||||
const statusLabel = p.is_active ? 'active' : 'inactive';
|
||||
const privateBadge = p.is_private ? ' <span class="badge-user" style="font-size:9px">🔒 private</span>' : '';
|
||||
return `<div class="admin-preset-row" style="padding:6px 0">
|
||||
<div class="preset-info">
|
||||
<strong>${esc(p.name)}</strong>
|
||||
<div class="preset-meta" style="font-size:11px">${esc(p.provider)} · ${p.has_key ? '🔑' : 'no key'} <span class="${statusCls}" style="font-size:10px">${statusLabel}</span>${privateBadge}</div>
|
||||
</div>
|
||||
<div class="preset-actions">
|
||||
<button class="btn-small" onclick="settingsEditTeamProvider('${teamId}','${p.id}','${esc(p.name)}','${esc(p.endpoint || '')}','${esc(p.model_default || '')}',${!!p.is_private})" title="Edit">✎</button>
|
||||
<button class="admin-model-toggle ${p.is_active ? 'enabled' : ''}" onclick="settingsToggleTeamProvider('${teamId}','${p.id}',${p.is_active})">${p.is_active ? '✓ Active' : 'Inactive'}</button>
|
||||
<button class="btn-delete" onclick="settingsDeleteTeamProvider('${teamId}','${p.id}','${esc(p.name)}')" title="Delete">✕</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('') || '<div class="empty-hint">No team providers — add one to give your team access to additional models</div>';
|
||||
} catch (e) { el.innerHTML = `<div class="error-hint">${esc(e.message)}</div>`; }
|
||||
},
|
||||
|
||||
async loadTeamManagePresets(teamId) {
|
||||
const el = document.getElementById('settingsTeamPresets');
|
||||
el.innerHTML = '<div class="loading">Loading...</div>';
|
||||
@@ -975,13 +1022,27 @@ const UI = {
|
||||
const resp = await API.teamListAvailableModels(teamId);
|
||||
const models = resp.models || [];
|
||||
sel.innerHTML = '<option value="">Select base model...</option>';
|
||||
models.forEach(m => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = m.model_id || m.id;
|
||||
const vis = m.visibility === 'team' ? ' [team-only]' : '';
|
||||
opt.textContent = (m.model_id || m.id) + (m.provider_name ? ` (${m.provider_name})` : '') + vis;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
|
||||
// Group by source for clarity
|
||||
const globalModels = models.filter(m => m.source !== 'team');
|
||||
const teamModels = models.filter(m => m.source === 'team');
|
||||
|
||||
const addGroup = (label, items) => {
|
||||
if (!items.length) return;
|
||||
const group = document.createElement('optgroup');
|
||||
group.label = label;
|
||||
items.forEach(m => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = m.model_id || m.id;
|
||||
const vis = m.visibility === 'team' ? ' [team-only]' : '';
|
||||
opt.textContent = (m.model_id || m.id) + (m.provider_name ? ` (${m.provider_name})` : '') + vis;
|
||||
group.appendChild(opt);
|
||||
});
|
||||
sel.appendChild(group);
|
||||
};
|
||||
|
||||
addGroup('Global Models', globalModels);
|
||||
addGroup('Team Provider Models', teamModels);
|
||||
} catch (e) {
|
||||
// Fallback to App.models if team endpoint fails
|
||||
sel.innerHTML = '<option value="">Select base model...</option>';
|
||||
@@ -1350,12 +1411,14 @@ const UI = {
|
||||
document.getElementById('adminTeamDetailName').textContent = teamName;
|
||||
document.getElementById('adminAddMemberForm').style.display = 'none';
|
||||
|
||||
// Load team settings for policy checkbox
|
||||
// Load team settings for policy checkboxes
|
||||
try {
|
||||
const team = await API.adminGetTeam(teamId);
|
||||
const settings = typeof team.settings === 'string' ? JSON.parse(team.settings || '{}') : (team.settings || {});
|
||||
document.getElementById('adminTeamPrivatePolicy').checked = !!settings.require_private_providers;
|
||||
} catch (e) { /* proceed with unchecked default */ }
|
||||
// allow_team_providers defaults to true if not explicitly set
|
||||
document.getElementById('adminTeamAllowProviders').checked = settings.allow_team_providers !== false;
|
||||
} catch (e) { /* proceed with defaults */ }
|
||||
|
||||
await this.loadTeamMembers(teamId);
|
||||
},
|
||||
@@ -1493,7 +1556,8 @@ const UI = {
|
||||
if (caps.tool_calling) badges.push('<span class="cap-badge cap-accent">🔧</span>');
|
||||
if (caps.vision) badges.push('<span class="cap-badge cap-accent">👁</span>');
|
||||
if (caps.thinking) badges.push('<span class="cap-badge cap-accent">💭</span>');
|
||||
const src = m.source === 'personal' ? '<span class="badge-user" style="font-size:10px">personal</span>' : '';
|
||||
const src = m.source === 'personal' ? '<span class="badge-user" style="font-size:10px">personal</span>'
|
||||
: m.source === 'team' ? `<span class="badge-team" style="font-size:10px">👥 ${esc(m.team_name || 'team')}</span>` : '';
|
||||
const hidden = App.hiddenModels.has(mid);
|
||||
const toggleCls = hidden ? '' : 'enabled';
|
||||
const toggleLabel = hidden ? 'Hidden' : '✓ Visible';
|
||||
|
||||
Reference in New Issue
Block a user