Glossar: Eintraege bearbeitbar (PUT /api/glossary/:id + GUI-Editier-Flow)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0113vDKdSNtD3Dwcw4QMu9yV
This commit is contained in:
co-authored by
Claude Fable 5
parent
fdae4ce119
commit
1016eaa095
@@ -52,6 +52,11 @@ export async function addGlossary(term, soundalikes) {
|
|||||||
export async function deleteGlossary(id) {
|
export async function deleteGlossary(id) {
|
||||||
await pool.execute('DELETE FROM glossary WHERE id=?', [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) {
|
export async function addVoiceprint(name, vec) {
|
||||||
const [res] = await pool.execute(
|
const [res] = await pool.execute(
|
||||||
|
|||||||
@@ -254,6 +254,14 @@ app.post('/api/glossary', wrap(async (req, res) => {
|
|||||||
res.json(await db.addGlossary(term, soundalikes));
|
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.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 ---
|
// --- LLM ---
|
||||||
app.get('/api/llm/status', (req, res) => res.json(llm.status()));
|
app.get('/api/llm/status', (req, res) => res.json(llm.status()));
|
||||||
|
|||||||
+23
-6
@@ -3,7 +3,8 @@ import './style.css';
|
|||||||
const api = {
|
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 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 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');
|
const app = document.querySelector('#app');
|
||||||
@@ -187,36 +188,52 @@ async function viewGlossary() {
|
|||||||
const v = document.querySelector('#view');
|
const v = document.querySelector('#view');
|
||||||
v.innerHTML = `
|
v.innerHTML = `
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>Begriff hinzufügen</h3>
|
<h3 id="gformtitle">Begriff hinzufügen</h3>
|
||||||
<div class="hint">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.</div>
|
<div class="hint">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.</div>
|
||||||
<label class="lbl">Begriff (korrekte Schreibweise)</label>
|
<label class="lbl">Begriff (korrekte Schreibweise)</label>
|
||||||
<input id="gterm" class="input" placeholder="z.B. Aime"/>
|
<input id="gterm" class="input" placeholder="z.B. Aime"/>
|
||||||
<label class="lbl">Verhörer / Soundalikes (optional, Komma-getrennt)</label>
|
<label class="lbl">Verhörer / Soundalikes (optional, Komma-getrennt)</label>
|
||||||
<input id="gsound" class="input" placeholder="z.B. Amy, Ami"/>
|
<input id="gsound" class="input" placeholder="z.B. Amy, Ami"/>
|
||||||
<button id="gadd" class="btn primary">Hinzufügen</button>
|
<button id="gadd" class="btn primary">Hinzufügen</button>
|
||||||
|
<button id="gcancel" class="btn" style="display:none">Abbrechen</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card"><h3>Glossar</h3><div id="glist" class="list">Lade…</div></div>`;
|
<div class="card"><h3>Glossar</h3><div id="glist" class="list">Lade…</div></div>`;
|
||||||
const listEl = v.querySelector('#glist');
|
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() {
|
async function load() {
|
||||||
try {
|
try {
|
||||||
const gs = await api.get('/api/glossary');
|
const gs = await api.get('/api/glossary');
|
||||||
listEl.innerHTML = gs.length ? gs.map(g => `
|
listEl.innerHTML = gs.length ? gs.map(g => `
|
||||||
<div class="item"><div class="grow"><b>📖 ${escapeHtml(g.term)}</b>
|
<div class="item"><div class="grow"><b>📖 ${escapeHtml(g.term)}</b>
|
||||||
${g.soundalikes ? `<div class="muted">↔ ${escapeHtml(g.soundalikes)}</div>` : ''}</div>
|
${g.soundalikes ? `<div class="muted">↔ ${escapeHtml(g.soundalikes)}</div>` : ''}</div>
|
||||||
|
<button class="btn" data-edit="${g.id}">✏️</button>
|
||||||
<button class="btn danger" data-del="${g.id}">🗑</button></div>`).join('')
|
<button class="btn danger" data-del="${g.id}">🗑</button></div>`).join('')
|
||||||
: '<div class="muted">Noch keine Begriffe. Oben hinzufügen.</div>';
|
: '<div class="muted">Noch keine Begriffe. Oben hinzufügen.</div>';
|
||||||
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 = '<div class="err">' + e.message + '</div>'; }
|
} catch (e) { listEl.innerHTML = '<div class="err">' + e.message + '</div>'; }
|
||||||
}
|
}
|
||||||
await load();
|
await load();
|
||||||
v.querySelector('#gadd').onclick = async () => {
|
cancelBtn.onclick = () => setEdit(null);
|
||||||
|
saveBtn.onclick = async () => {
|
||||||
const term = v.querySelector('#gterm').value.trim();
|
const term = v.querySelector('#gterm').value.trim();
|
||||||
if (!term) { toast('Bitte Begriff eingeben', true); return; }
|
if (!term) { toast('Bitte Begriff eingeben', true); return; }
|
||||||
const soundalikes = v.querySelector('#gsound').value.trim();
|
const soundalikes = v.querySelector('#gsound').value.trim();
|
||||||
try {
|
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 ✓`);
|
toast(`„${term}" gespeichert ✓`);
|
||||||
v.querySelector('#gterm').value = ''; v.querySelector('#gsound').value = '';
|
setEdit(null);
|
||||||
load();
|
load();
|
||||||
} catch (e) { toast(e.message, true); }
|
} catch (e) { toast(e.message, true); }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,13 @@
|
|||||||
Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum,
|
Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum,
|
||||||
Was, Warum, betroffene Dateien/Backups).
|
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
|
## 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
|
- **Bug:** residenter rkllm (5,9 GB) ließ den TitaNet-spk-embed-Helper OOM-sterben („spk beendet" pro
|
||||||
|
|||||||
Reference in New Issue
Block a user