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
+34
-13
@@ -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,12 +440,26 @@ 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 id="modules">
|
||||
${order.filter(m => mods[m]).map(m => `
|
||||
<section class="module" data-mod="${m}">
|
||||
<div class="mod-head" draggable="true"><span class="mod-grip" title="Ziehen zum Umsortieren">⠿</span><h3>${MOD_TITLES[m]}</h3></div>
|
||||
<div class="mod-body">${mods[m]}</div>
|
||||
</section>`).join('')}
|
||||
<div class="detail-cols">
|
||||
<div class="detail-main">
|
||||
<div id="modules">
|
||||
${order.filter(m => mods[m]).map(m => `
|
||||
<section class="module" data-mod="${m}">
|
||||
<div class="mod-head" draggable="true"><span class="mod-grip" title="Ziehen zum Umsortieren">⠿</span><h3>${MOD_TITLES[m]}</h3></div>
|
||||
<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>`;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user