GUI: Transkript als eigene sticky Scroll-Spalte rechts
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
4500e03ae4
commit
62e1dec960
+28
-7
@@ -389,7 +389,9 @@ async function openRecording(id) {
|
||||
const durMs = r.duration_ms || (r.words && r.words.length ? r.words[r.words.length - 1].t_end_ms : 0) || 1;
|
||||
|
||||
// Draggable module blocks; the saved order wins, unknown/new modules append.
|
||||
const MOD_TITLES = { analyse: 'Zusammenfassung', viz: 'Sprecher-Verlauf', todos: 'ToDos', speakers: 'Sprecher', transcript: 'Transkript' };
|
||||
// The transcript is NOT a module: it lives in its own sticky, inner-scrolling
|
||||
// right column so the (long) text never stretches the page.
|
||||
const MOD_TITLES = { analyse: 'Zusammenfassung', viz: 'Sprecher-Verlauf', todos: 'ToDos', speakers: 'Sprecher' };
|
||||
const mods = {
|
||||
analyse: `
|
||||
${r.summary ? `<p class="summary">${escapeHtml(r.summary)}</p>` : '<div class="muted">Noch keine Zusammenfassung.</div>'}
|
||||
@@ -420,12 +422,8 @@ async function openRecording(id) {
|
||||
<button class="btn small spk-save" data-sp="${sp}">Speichern</button>
|
||||
</div>`).join('')}
|
||||
</div>` : '',
|
||||
transcript: `
|
||||
${r.transcript_clean ? `<h3>Dialog <span class="muted" style="font-weight:400">(KI · Sprecher aus Kontext)</span></h3><div class="dialog">${renderDialog(r.transcript_clean)}</div>` : ''}
|
||||
<div class="hint">Klick auf ein Wort → Audio springt dorthin. Leertaste = Play/Pause.</div>
|
||||
<div id="transcript" class="transcript">${renderTurns(r.words, names, scls, spkOrder)}</div>`,
|
||||
};
|
||||
const defOrder = ['analyse', 'viz', 'todos', 'speakers', 'transcript'];
|
||||
const defOrder = ['analyse', 'viz', 'todos', 'speakers'];
|
||||
let order = [];
|
||||
try { order = JSON.parse(localStorage.getItem('aime-detail-order') || '[]'); } catch {}
|
||||
order = order.filter(m => mods[m] !== undefined).concat(defOrder.filter(m => !order.includes(m)));
|
||||
@@ -442,6 +440,8 @@ async function openRecording(id) {
|
||||
${(errored && !transcribeFailed) ? `<div class="err-inline">KI-Analyse fehlgeschlagen. <button class="btn" id="reanalyze">Erneut versuchen</button></div>` : ''}
|
||||
${(!pending && !errored && !transcribing && !transcribeFailed) ? `<button class="btn small" id="reanalyze">↻ Analyse neu</button>` : ''}
|
||||
</div>
|
||||
<div class="detail-cols">
|
||||
<div class="detail-main">
|
||||
<div id="modules">
|
||||
${order.filter(m => mods[m]).map(m => `
|
||||
<section class="module" data-mod="${m}">
|
||||
@@ -449,6 +449,18 @@ async function openRecording(id) {
|
||||
<div class="mod-body">${mods[m]}</div>
|
||||
</section>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-side">
|
||||
<section class="module transcript-panel" id="transcript-panel">
|
||||
<div class="mod-head"><h3>Transkript</h3></div>
|
||||
<div class="mod-body">
|
||||
${r.transcript_clean ? `<h3>Dialog <span class="muted" style="font-weight:400">(KI · Sprecher aus Kontext)</span></h3><div class="dialog">${renderDialog(r.transcript_clean)}</div>` : ''}
|
||||
<div class="hint">Klick auf ein Wort → Audio springt dorthin. Leertaste = Play/Pause.</div>
|
||||
<div id="transcript" class="transcript">${renderTurns(r.words, names, scls, spkOrder)}</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Drag & drop: grab a module by its header, drop it above/below the others;
|
||||
@@ -587,7 +599,16 @@ async function openRecording(id) {
|
||||
let active = null;
|
||||
for (const s of spans) { if (+s.dataset.start <= t) active = s; else break; }
|
||||
spans.forEach(s => s.classList.toggle('active', s === active));
|
||||
if (active) { const r2 = active.getBoundingClientRect(); if (r2.top < 90 || r2.bottom > innerHeight - 40) active.scrollIntoView({ block: 'center', behavior: 'smooth' }); }
|
||||
// Follow the active word INSIDE the transcript panel (it scrolls itself now —
|
||||
// scrollIntoView would also yank the page).
|
||||
if (active) {
|
||||
const panel = detail.querySelector('#transcript-panel');
|
||||
if (panel) {
|
||||
const pr = panel.getBoundingClientRect(), ar = active.getBoundingClientRect();
|
||||
if (ar.top < pr.top + 40 || ar.bottom > pr.bottom - 40)
|
||||
panel.scrollTo({ top: panel.scrollTop + (ar.top - pr.top) - panel.clientHeight / 2, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
updateViz(t);
|
||||
};
|
||||
// Rename a speaker + (optionally) learn their voice into the DB.
|
||||
|
||||
+11
-1
@@ -199,8 +199,18 @@ audio { width: 100%; margin-bottom: 10px; }
|
||||
.item.rec:hover { border-color: var(--accent); }
|
||||
.item.rec.sel { border-color: var(--accent); background: var(--accent-soft); }
|
||||
|
||||
/* ---- Detail-Module (Drag & Drop) ---- */
|
||||
/* ---- Detail: Module links, Transkript als eigene Scroll-Spalte rechts ---- */
|
||||
.status-line { margin: 4px 0 10px; }
|
||||
.detail-cols { display: flex; gap: 12px; align-items: flex-start; }
|
||||
.detail-main { flex: 1 1 54%; min-width: 0; }
|
||||
.detail-side { flex: 1 1 46%; min-width: 0; position: sticky; top: 12px; }
|
||||
.transcript-panel { max-height: calc(100vh - 90px); overflow-y: auto; }
|
||||
.transcript-panel .mod-head { position: sticky; top: 0; z-index: 1; cursor: default; }
|
||||
@media (max-width: 1100px) {
|
||||
.detail-cols { flex-direction: column; }
|
||||
.detail-side { position: static; width: 100%; }
|
||||
.transcript-panel { max-height: 60vh; }
|
||||
}
|
||||
#modules { display: flex; flex-direction: column; gap: 10px; }
|
||||
.module { background: var(--inset); border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
|
||||
.module.dragging { opacity: .45; border-style: dashed; }
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum,
|
||||
Was, Warum, betroffene Dateien/Backups).
|
||||
|
||||
## 2026-07-17 — GUI: Transkript als eigene Scroll-Spalte
|
||||
|
||||
Transkript (+ Dialog) aus den Drag&Drop-Modulen herausgelöst: eigene, sticky Spalte rechts neben
|
||||
Zusammenfassung/Verlauf/ToDos/Sprecher, Höhe auf Viewport begrenzt, scrollt INTERN durch und folgt
|
||||
dem aktiven Wort im Panel (statt die Seite zu ziehen). Unter 1100 px stapeln die Spalten
|
||||
(Transkript max. 60 vh). Bundle .
|
||||
|
||||
## 2026-07-17 — GUI: Sprecher-Verlauf-Visualisierung + Drag&Drop-Module
|
||||
|
||||
Detail-Ansicht ist jetzt modular: fester Kopf (Titel, Player, Status), darunter Blöcke
|
||||
|
||||
Reference in New Issue
Block a user