feat: edit existing model entries in the Model Registry
- Inline edit form per model row (label, model name/ID, host/account, context, tags)
- Fetch models button in edit form for local models — same live-picker UX as Add Model
- POST /settings/local/models/{id}/edit route in local_llm.py
- Admin role badge (ADMIN/USER pill) in Account Settings page
- HELP.md updated: new tools table with admin/confirm markers, PWA install section
- TODO updated: tool expansions marked done, distill review and Unsloth resolved,
role-based access and admin badge added to completed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -182,10 +182,18 @@
|
||||
.fetch-status.err { color: #f87171; }
|
||||
|
||||
.model-row {
|
||||
background: var(--pg-bg); border: 1px solid var(--pg-border); border-radius: 8px;
|
||||
margin-bottom: 0.5rem; overflow: hidden;
|
||||
}
|
||||
.model-row-header {
|
||||
display: flex; align-items: flex-start; justify-content: space-between;
|
||||
gap: 0.75rem; padding: 0.75rem 0.9rem;
|
||||
background: var(--pg-bg); border: 1px solid var(--pg-border); border-radius: 8px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.model-btns { display: flex; gap: 0.4rem; align-items: flex-start; flex-shrink: 0; }
|
||||
.model-edit-form {
|
||||
padding: 0.85rem 0.9rem 0.9rem;
|
||||
border-top: 1px solid var(--pg-border);
|
||||
background: var(--pg-surface);
|
||||
}
|
||||
.model-info { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
|
||||
.model-label { font-size: 0.9rem; font-weight: 600; color: var(--pg-text); }
|
||||
@@ -670,6 +678,78 @@
|
||||
// Hide fetch button initially if no hosts
|
||||
if (!HAS_HOSTS) fetchBtn.style.display = 'none';
|
||||
|
||||
// ── Model inline edit toggle ──────────────────────────────────────────
|
||||
document.querySelectorAll('.model-edit-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
const form = document.getElementById('edit-form-' + id);
|
||||
const open = form.style.display !== 'none';
|
||||
form.style.display = open ? 'none' : 'block';
|
||||
btn.textContent = open ? 'Edit' : 'Close';
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('.model-edit-cancel').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
document.getElementById('edit-form-' + id).style.display = 'none';
|
||||
document.querySelector(`.model-edit-btn[data-id="${id}"]`).textContent = 'Edit';
|
||||
});
|
||||
});
|
||||
|
||||
// ── Edit form: fetch models from host ────────────────────────────────
|
||||
document.querySelectorAll('.edit-fetch-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const id = btn.dataset.id;
|
||||
const hostSel = document.getElementById('edit-host-' + id);
|
||||
const hostId = hostSel ? hostSel.value : '';
|
||||
const statusEl = document.getElementById('edit-fetch-status-' + id);
|
||||
btn.disabled = true;
|
||||
statusEl.textContent = 'Fetching…'; statusEl.className = 'fetch-status';
|
||||
const url = '/api/local-llm/fetch-models' + (hostId ? '?host_id=' + encodeURIComponent(hostId) : '');
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
if (data.error) {
|
||||
statusEl.textContent = '✗ ' + data.error; statusEl.className = 'fetch-status err';
|
||||
return;
|
||||
}
|
||||
const picker = document.getElementById('edit-model-picker-' + id);
|
||||
const wrap = document.getElementById('edit-model-select-wrap-' + id);
|
||||
picker.innerHTML = '<option value="">— select to fill —</option>';
|
||||
data.models.forEach(m => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = m.id;
|
||||
opt.textContent = m.name !== m.id ? `${m.name} (${m.id})` : m.id;
|
||||
opt.dataset.id = m.id;
|
||||
opt.dataset.name = m.name;
|
||||
picker.appendChild(opt);
|
||||
});
|
||||
wrap.style.display = 'block';
|
||||
statusEl.textContent = `✓ ${data.models.length} model${data.models.length !== 1 ? 's' : ''}`;
|
||||
statusEl.className = 'fetch-status ok';
|
||||
} catch (e) {
|
||||
statusEl.textContent = '✗ ' + e.message; statusEl.className = 'fetch-status err';
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('[id^="edit-model-picker-"]').forEach(picker => {
|
||||
picker.addEventListener('change', () => {
|
||||
const opt = picker.options[picker.selectedIndex];
|
||||
if (!opt.value) return;
|
||||
const id = picker.id.replace('edit-model-picker-', '');
|
||||
const form = document.getElementById('edit-form-' + id);
|
||||
form.querySelector('input[name="model_name"]').value = opt.dataset.id || opt.value;
|
||||
const labelInput = form.querySelector('input[name="label"]');
|
||||
if (!labelInput.value) {
|
||||
const n = opt.dataset.name;
|
||||
labelInput.value = (n && n !== opt.dataset.id) ? n : '';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── Claude CLI auth status ─────────────────────────────────────────────
|
||||
(async function() {
|
||||
const el = document.getElementById('claude-auth-status');
|
||||
|
||||
Reference in New Issue
Block a user