Keep the selected LLM profile visible while activating it.
Some checks failed
Deploy Development / deploy (push) Successful in 56s
Test Suite / pytest-backend (push) Failing after 2m48s
Test Suite / smoke-dev (push) Successful in 0s
Test Suite / frontend-build (push) Successful in 15s

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:
Lars 2026-09-08 12:56:22 +02:00
parent bef422a429
commit 59921fc5b5
2 changed files with 44 additions and 13 deletions

View File

@ -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()

View File

@ -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>
) )
})} })}