From 1016eaa095f572848181e7fc861f296d89d765bc Mon Sep 17 00:00:00 2001 From: schakal00783 Date: Thu, 16 Jul 2026 23:55:27 +0200 Subject: [PATCH] Glossar: Eintraege bearbeitbar (PUT /api/glossary/:id + GUI-Editier-Flow) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0113vDKdSNtD3Dwcw4QMu9yV --- app/db.js | 5 +++++ app/server.js | 8 ++++++++ app/web/src/main.js | 29 +++++++++++++++++++++++------ docs/08-changelog.md | 7 +++++++ 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/app/db.js b/app/db.js index 185f2f6..08a892c 100644 --- a/app/db.js +++ b/app/db.js @@ -52,6 +52,11 @@ export async function addGlossary(term, soundalikes) { export async function deleteGlossary(id) { await pool.execute('DELETE FROM glossary WHERE id=?', [id]); } +export async function updateGlossary(id, term, soundalikes) { + await pool.execute('UPDATE glossary SET term=?, soundalikes=? WHERE id=?', [term, soundalikes || null, id]); + const [rows] = await pool.execute('SELECT id, term, soundalikes, enabled FROM glossary WHERE id=?', [id]); + return rows[0] ? { ...rows[0], enabled: !!rows[0].enabled } : null; +} export async function addVoiceprint(name, vec) { const [res] = await pool.execute( diff --git a/app/server.js b/app/server.js index 20c6bd9..c5c6ff2 100644 --- a/app/server.js +++ b/app/server.js @@ -254,6 +254,14 @@ app.post('/api/glossary', wrap(async (req, res) => { res.json(await db.addGlossary(term, soundalikes)); })); app.delete('/api/glossary/:id', wrap(async (req, res) => { await db.deleteGlossary(+req.params.id); res.json({ ok: true }); })); +app.put('/api/glossary/:id', wrap(async (req, res) => { + const term = (req.body && req.body.term || '').trim(); + const soundalikes = (req.body && req.body.soundalikes || '').trim(); + if (!term) return res.status(400).json({ error: 'Begriff fehlt' }); + const g = await db.updateGlossary(+req.params.id, term, soundalikes); + if (!g) return res.status(404).json({ error: 'Eintrag nicht gefunden' }); + res.json(g); +})); // --- LLM --- app.get('/api/llm/status', (req, res) => res.json(llm.status())); diff --git a/app/web/src/main.js b/app/web/src/main.js index 494617a..816a674 100644 --- a/app/web/src/main.js +++ b/app/web/src/main.js @@ -3,7 +3,8 @@ import './style.css'; const api = { async get(u) { const r = await fetch(u); if (!r.ok) throw new Error((await r.json()).error || r.status); return r.json(); }, async post(u, b) { const r = await fetch(u, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(b || {}) }); if (!r.ok) throw new Error((await r.json()).error || r.status); return r.json(); }, - async del(u) { const r = await fetch(u, { method: 'DELETE' }); return r.json(); } + async del(u) { const r = await fetch(u, { method: 'DELETE' }); return r.json(); }, + async put(u, b) { const r = await fetch(u, { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(b || {}) }); if (!r.ok) throw new Error((await r.json()).error || r.status); return r.json(); } }; const app = document.querySelector('#app'); @@ -187,36 +188,52 @@ async function viewGlossary() { const v = document.querySelector('#view'); v.innerHTML = `
-

Begriff hinzufügen

+

Begriff hinzufügen

Eigennamen & Fachbegriffe, die die Spracherkennung verhört (z.B. „Aime" wird als „Amy" gehört). Die KI-Korrektur ersetzt lautähnliche Wörter dann durch die richtige Schreibweise.
+

Glossar

Lade…
`; const listEl = v.querySelector('#glist'); + let editId = null; // id of the entry currently loaded into the form + const formTitle = v.querySelector('#gformtitle'), saveBtn = v.querySelector('#gadd'), cancelBtn = v.querySelector('#gcancel'); + function setEdit(g) { + editId = g ? g.id : null; + formTitle.textContent = g ? `„${g.term}" bearbeiten` : 'Begriff hinzufügen'; + saveBtn.textContent = g ? 'Speichern' : 'Hinzufügen'; + cancelBtn.style.display = g ? '' : 'none'; + v.querySelector('#gterm').value = g ? g.term : ''; + v.querySelector('#gsound').value = g ? (g.soundalikes || '') : ''; + if (g) v.querySelector('#gterm').focus(); + } async function load() { try { const gs = await api.get('/api/glossary'); listEl.innerHTML = gs.length ? gs.map(g => `
📖 ${escapeHtml(g.term)} ${g.soundalikes ? `
↔ ${escapeHtml(g.soundalikes)}
` : ''}
+
`).join('') : '
Noch keine Begriffe. Oben hinzufügen.
'; - listEl.querySelectorAll('[data-del]').forEach(b => b.onclick = async () => { await api.del('/api/glossary/' + b.dataset.del); load(); }); + listEl.querySelectorAll('[data-del]').forEach(b => b.onclick = async () => { await api.del('/api/glossary/' + b.dataset.del); if (editId === +b.dataset.del) setEdit(null); load(); }); + listEl.querySelectorAll('[data-edit]').forEach(b => b.onclick = () => setEdit(gs.find(g => g.id === +b.dataset.edit))); } catch (e) { listEl.innerHTML = '
' + e.message + '
'; } } await load(); - v.querySelector('#gadd').onclick = async () => { + cancelBtn.onclick = () => setEdit(null); + saveBtn.onclick = async () => { const term = v.querySelector('#gterm').value.trim(); if (!term) { toast('Bitte Begriff eingeben', true); return; } const soundalikes = v.querySelector('#gsound').value.trim(); try { - await api.post('/api/glossary', { term, soundalikes }); + if (editId != null) await api.put('/api/glossary/' + editId, { term, soundalikes }); + else await api.post('/api/glossary', { term, soundalikes }); toast(`„${term}" gespeichert ✓`); - v.querySelector('#gterm').value = ''; v.querySelector('#gsound').value = ''; + setEdit(null); load(); } catch (e) { toast(e.message, true); } }; diff --git a/docs/08-changelog.md b/docs/08-changelog.md index 21651e2..828b6db 100644 --- a/docs/08-changelog.md +++ b/docs/08-changelog.md @@ -3,6 +3,13 @@ Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum, Was, Warum, betroffene Dateien/Backups). +## 2026-07-16 — Glossar: Einträge bearbeitbar (`.bak-gedit`) + +User-Wunsch: gespeicherte Glossar-Begriffe nachträglich ändern. Neu: `PUT /api/glossary/:id` +(`db.updateGlossary`), GUI-Stift-Button je Eintrag lädt Begriff+Soundalikes ins Formular +(Titel „… bearbeiten", Speichern/Abbrechen), Update statt Neuanlage. Bundle `index-NRK6cGUI.js`. +API-verifiziert (POST→PUT→DELETE). Dateien: `server.js`, `db.js`, `web/src/main.js`. + ## 2026-07-16 — rkllm vor Diar/Identify freigeben (TitaNet-OOM) + #71 validiert - **Bug:** residenter rkllm (5,9 GB) ließ den TitaNet-spk-embed-Helper OOM-sterben („spk beendet" pro