Keep the selected LLM profile visible while activating it.
The dropdown was bound to the previous server value, so a profile without a model snapped back to OpenRouter before the error was obvious. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
bef422a429
commit
59921fc5b5
|
|
@ -104,14 +104,19 @@ def seed_provider_settings(conn) -> None:
|
||||||
|
|
||||||
def seed_llm_profiles(conn) -> None:
|
def seed_llm_profiles(conn) -> None:
|
||||||
"""Named presets. Existing rows are not overwritten. Keys never stored."""
|
"""Named presets. Existing rows are not overwritten. Keys never stored."""
|
||||||
ollama = conn.execute("SELECT id FROM llm_profiles WHERE id = ?", (OLLAMA_LAN_ID,)).fetchone()
|
ollama = conn.execute("SELECT * FROM llm_profiles WHERE id = ?", (OLLAMA_LAN_ID,)).fetchone()
|
||||||
if not ollama:
|
if not ollama:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO llm_profiles (id, title, name, url, model, zdr, no_train)
|
INSERT INTO llm_profiles (id, title, name, url, model, zdr, no_train)
|
||||||
VALUES (?, ?, ?, ?, ?, 1, 1)
|
VALUES (?, ?, ?, ?, ?, 1, 1)
|
||||||
""",
|
""",
|
||||||
(OLLAMA_LAN_ID, "Ollama (LAN)", "ollama", OLLAMA_LAN_URL, ""),
|
(OLLAMA_LAN_ID, "Ollama (LAN)", "ollama", OLLAMA_LAN_URL, "phi3:mini"),
|
||||||
|
)
|
||||||
|
elif not (dict(ollama).get("model") or "").strip():
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE llm_profiles SET model = ?, updated = datetime('now') WHERE id = ?",
|
||||||
|
("phi3:mini", OLLAMA_LAN_ID),
|
||||||
)
|
)
|
||||||
for role in ROLES:
|
for role in ROLES:
|
||||||
row = conn.execute("SELECT * FROM provider_settings WHERE role = ?", (role,)).fetchone()
|
row = conn.execute("SELECT * FROM provider_settings WHERE role = ?", (role,)).fetchone()
|
||||||
|
|
|
||||||
|
|
@ -113,8 +113,29 @@ export default function AdminProvidersPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const applyRole = async (role, profileId) => {
|
const applyRole = async (role, profileId) => {
|
||||||
|
const profile = profiles.find((item) => item.id === profileId)
|
||||||
|
if (!profile) return
|
||||||
setError('')
|
setError('')
|
||||||
setNotice('')
|
setNotice('')
|
||||||
|
setForms((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[role]: {
|
||||||
|
...(prev[role] || emptyRole()),
|
||||||
|
role,
|
||||||
|
name: profile.name || '',
|
||||||
|
url: profile.url || '',
|
||||||
|
model: profile.model || '',
|
||||||
|
zdr: Boolean(profile.zdr),
|
||||||
|
no_train: Boolean(profile.no_train),
|
||||||
|
profile_id: profile.id,
|
||||||
|
key: '',
|
||||||
|
saveAsTitle: ''
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
if (!profile.model) {
|
||||||
|
setError('Das Profil braucht ein Modell, bevor es einer Stufe zugeordnet werden kann.')
|
||||||
|
return
|
||||||
|
}
|
||||||
setSaving(`activate-${role}`)
|
setSaving(`activate-${role}`)
|
||||||
try {
|
try {
|
||||||
const payload = await api(`/api/admin/providers/${role}/activate`, {
|
const payload = await api(`/api/admin/providers/${role}/activate`, {
|
||||||
|
|
@ -123,7 +144,7 @@ export default function AdminProvidersPage() {
|
||||||
body: { profile_id: profileId }
|
body: { profile_id: profileId }
|
||||||
})
|
})
|
||||||
applyPayload(payload)
|
applyPayload(payload)
|
||||||
setNotice(`${role === 'detect' ? 'Maskierung' : 'Sprachmodell'}: Profil übernommen.`)
|
setNotice(`${role === 'detect' ? 'Maskierung' : 'Sprachmodell'}: ${profile.title} ist aktiv.`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message)
|
setError(err.message)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -226,21 +247,21 @@ export default function AdminProvidersPage() {
|
||||||
<>
|
<>
|
||||||
{data.roles.map((role) => {
|
{data.roles.map((role) => {
|
||||||
const form = forms[role.role] || emptyRole()
|
const form = forms[role.role] || emptyRole()
|
||||||
|
const selected = profiles.find((item) => item.id === form.profile_id)
|
||||||
|
const view = selected
|
||||||
|
? { ...role, ...form, local: selected.local, ready: selected.local ? Boolean(form.url && form.model) : role.ready }
|
||||||
|
: role
|
||||||
return (
|
return (
|
||||||
<form
|
<div key={role.role} className="stack">
|
||||||
key={role.role}
|
|
||||||
className="stack"
|
|
||||||
onSubmit={(e) => { e.preventDefault(); saveRole(role.role) }}
|
|
||||||
>
|
|
||||||
<h2>{role.title || role.role}</h2>
|
<h2>{role.title || role.role}</h2>
|
||||||
{role.task && <p className="muted">{role.task}</p>}
|
{role.task && <p className="muted">{role.task}</p>}
|
||||||
<dl className="meta">
|
<dl className="meta">
|
||||||
<dt>Status</dt>
|
<dt>Status</dt>
|
||||||
<dd>{statusLabel(role)}</dd>
|
<dd>{statusLabel(view)}</dd>
|
||||||
<dt>Key</dt>
|
<dt>Key</dt>
|
||||||
<dd>{role.key_present ? (role.key_source === 'generate' ? 'über Sprachmodell' : 'gesetzt') : (role.local ? 'nicht nötig (lokal)' : 'fehlt')}</dd>
|
<dd>{view.key_present ? (view.key_source === 'generate' ? 'über Sprachmodell' : 'gesetzt') : (view.local ? 'nicht nötig (lokal)' : 'fehlt')}</dd>
|
||||||
<dt>Modus</dt>
|
<dt>Modus</dt>
|
||||||
<dd>{role.local ? 'lokal' : 'extern'} · {role.mode}</dd>
|
<dd>{view.local ? 'lokal' : 'extern'} · {view.mode}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<label>
|
<label>
|
||||||
Aktives Profil
|
Aktives Profil
|
||||||
|
|
@ -260,6 +281,10 @@ export default function AdminProvidersPage() {
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<form
|
||||||
|
className="stack"
|
||||||
|
onSubmit={(e) => { e.preventDefault(); saveRole(role.role) }}
|
||||||
|
>
|
||||||
<label>
|
<label>
|
||||||
Name
|
Name
|
||||||
<input value={form.name} onChange={(e) => updateRole(role.role, 'name', e.target.value)} />
|
<input value={form.name} onChange={(e) => updateRole(role.role, 'name', e.target.value)} />
|
||||||
|
|
@ -293,19 +318,20 @@ export default function AdminProvidersPage() {
|
||||||
kein Provider-Training
|
kein Provider-Training
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
API-Key {role.key_present ? '(leer lassen, um den bestehenden zu behalten)' : '(für externe URL)'}
|
API-Key {view.key_present ? '(leer lassen, um den bestehenden zu behalten)' : '(für externe URL)'}
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
value={form.key}
|
value={form.key}
|
||||||
onChange={(e) => updateRole(role.role, 'key', e.target.value)}
|
onChange={(e) => updateRole(role.role, 'key', e.target.value)}
|
||||||
placeholder={role.local ? 'lokal ohne Key' : (role.role === 'detect' ? 'leer = Key des Sprachmodells' : '')}
|
placeholder={view.local ? 'lokal ohne Key' : (role.role === 'detect' ? 'leer = Key des Sprachmodells' : '')}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<button type="submit" disabled={saving === role.role}>
|
<button type="submit" disabled={saving === role.role}>
|
||||||
{saving === role.role ? 'Speichert …' : 'Stufe speichern'}
|
{saving === role.role ? 'Speichert …' : 'Stufe speichern'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user