diff --git a/apps/transcripts/workspace.html b/apps/transcripts/workspace.html index b80194871..fd2c611e1 100644 --- a/apps/transcripts/workspace.html +++ b/apps/transcripts/workspace.html @@ -2266,12 +2266,20 @@ body.has-date-nav .workspace:has(.tr-wrap) { updateZoom(); }).observe(zoom); - // Load transcript ranges and compute dynamic timeline bounds - fetch(`/app/transcripts/api/ranges/${day}`) - .then(r => r.json()) - .then(data => { - // Compute and apply dynamic bounds - const bounds = computeTimelineBounds(data); + // Load transcript ranges and segments in parallel, initialize after both resolve + const rangesFetch = fetch(`/app/transcripts/api/ranges/${day}`).then(r => { + if (!r.ok) throw new Error(`Ranges failed: ${r.status}`); + return r.json(); + }); + const segmentsFetch = fetch(`/app/transcripts/api/segments/${day}`).then(r => { + if (!r.ok) throw new Error(`Segments failed: ${r.status}`); + return r.json(); + }); + + Promise.all([rangesFetch, segmentsFetch]) + .then(([rangesData, segmentsData]) => { + // Apply dynamic timeline bounds from ranges + const bounds = computeTimelineBounds(rangesData); timelineStart = bounds.start; timelineEnd = bounds.end; @@ -2283,26 +2291,22 @@ body.has-date-nav .workspace:has(.tr-wrap) { const mid = (timelineStart + timelineEnd) / 2; range = { start: snap(mid - DEFAULT_LEN / 2), end: snap(mid + DEFAULT_LEN / 2) }; - // Now build the grid and render + // Build the grid and render timeline buildGrid(); renderTimeline(); - // Add segment indicators - (data.audio || []).forEach(rg => { + // Add segment indicators from ranges + (rangesData.audio || []).forEach(rg => { const [s, e] = rg.map(parseTime); addSegmentIndicator('audio', s, e, 0); }); - (data.screen || []).forEach(rg => { + (rangesData.screen || []).forEach(rg => { const [s, e] = rg.map(parseTime); addSegmentIndicator('screen', s, e, 1); }); - }); - // Load segments for the zoom view - fetch(`/app/transcripts/api/segments/${day}`) - .then(r => r.json()) - .then(data => { - allSegments = data.segments || []; + // Store segments and update zoom + allSegments = segmentsData.segments || []; updateZoom(); // Check for hash fragment to auto-select segment @@ -2310,7 +2314,6 @@ body.has-date-nav .workspace:has(.tr-wrap) { if (hash) { const seg = allSegments.find(s => s.key === hash); if (seg) { - // Adjust range to include the segment const segStart = parseTime(seg.start); const segEnd = parseTime(seg.end); const rangeLen = range.end - range.start; @@ -2323,6 +2326,14 @@ body.has-date-nav .workspace:has(.tr-wrap) { selectSegment(seg, false); } } + }) + .catch(err => { + console.error('Failed to load transcript data:', err); + const errEl = document.createElement('div'); + errEl.className = 'tr-zoom-empty'; + errEl.textContent = 'Failed to load transcript data'; + zoomSegments.innerHTML = ''; + zoomSegments.appendChild(errEl); }); // Handle browser back/forward diff --git a/tests/baselines/api/transcripts/segments.json b/tests/baselines/api/transcripts/segments.json index 20a177388..aca747225 100644 --- a/tests/baselines/api/transcripts/segments.json +++ b/tests/baselines/api/transcripts/segments.json @@ -6,8 +6,8 @@ "start": "09:00", "stream": "default", "types": [ - "transcripts", - "percepts" + "audio", + "screen" ] }, { @@ -16,8 +16,8 @@ "start": "14:00", "stream": "default", "types": [ - "transcripts", - "percepts" + "audio", + "screen" ] }, { @@ -26,8 +26,8 @@ "start": "18:00", "stream": "default", "types": [ - "transcripts", - "percepts" + "audio", + "screen" ] } ] diff --git a/tests/test_cluster.py b/tests/test_cluster.py index 2cbf9786f..b602ae203 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -134,20 +134,20 @@ def test_cluster_segments(tmp_path, monkeypatch): assert segments[0]["key"] == "090000_300" assert segments[0]["start"] == "09:00" assert segments[0]["end"] == "09:05" - assert segments[0]["types"] == ["transcripts"] + assert segments[0]["types"] == ["audio"] # Check second segment (both transcripts and screen) assert segments[1]["key"] == "100000_600" assert segments[1]["start"] == "10:00" assert segments[1]["end"] == "10:10" - assert "transcripts" in segments[1]["types"] - assert "percepts" in segments[1]["types"] + assert "audio" in segments[1]["types"] + assert "screen" in segments[1]["types"] # Check third segment (screen only) assert segments[2]["key"] == "110000_300" assert segments[2]["start"] == "11:00" assert segments[2]["end"] == "11:05" - assert segments[2]["types"] == ["percepts"] + assert segments[2]["types"] == ["screen"] def test_cluster_period_uses_raw_screen(tmp_path, monkeypatch): @@ -333,7 +333,7 @@ def test_cluster_segments_with_split_screen(tmp_path, monkeypatch): assert len(segments) == 1 assert segments[0]["key"] == "100000_300" - assert "percepts" in segments[0]["types"] + assert "screen" in segments[0]["types"] def test_cluster_span(tmp_path, monkeypatch): diff --git a/think/cluster.py b/think/cluster.py index 6cfc7ffb8..e25711147 100644 --- a/think/cluster.py +++ b/think/cluster.py @@ -463,7 +463,7 @@ def cluster_segments(day: str) -> list[dict[str, Any]]: - key: segment directory name (HHMMSS_LEN format) - start: start time as HH:MM - end: end time as HH:MM - - types: list of content types present ("transcripts", "percepts", or both) + - types: list of content types present ("audio", "screen", or both) """ from think.utils import segment_parse @@ -490,11 +490,11 @@ def cluster_segments(day: str) -> list[dict[str, Any]]: or any(seg_path.glob("*_transcript.md")) or (seg_path / "imported.md").exists() ): - types.append("transcripts") + types.append("audio") # Check for screen content if (seg_path / "screen.jsonl").exists() or any(seg_path.glob("*_screen.jsonl")): - types.append("percepts") + types.append("screen") if not types: continue