From 95dd4e120793e55960506efec5e041632821db4d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:47:21 +0000 Subject: [PATCH] Fix blank screen on empty topics and improve timestamp handling - Added "No highlights found" empty state to `app/analyze/[videoId]/page.tsx` to prevent blank screens when AI generates zero topics. - Updated `lib/ai-processing.ts` to use shared `formatTimestamp` utility, fixing a bug where local `formatTime` incorrectly formatted >60m timestamps as MM:SS (e.g. 70:00), causing validation failures. - Relaxed `parseTimestamp` validation in `lib/timestamp-utils.ts` to support MM:SS formats where minutes >= 60 as a fallback. --- app/analyze/[videoId]/page.tsx | 36 ++++++++++++++++++++++++++++++++++ lib/ai-processing.ts | 22 +++++++-------------- lib/timestamp-utils.ts | 5 ++++- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/app/analyze/[videoId]/page.tsx b/app/analyze/[videoId]/page.tsx index 8ba3af1..31e8d72 100644 --- a/app/analyze/[videoId]/page.tsx +++ b/app/analyze/[videoId]/page.tsx @@ -1909,6 +1909,42 @@ export default function AnalyzePage() { )} + {videoId && topics.length === 0 && pageState === 'IDLE' && !error && ( +
+ +
+
+

+ No highlights found +

+

+ We processed the transcript but couldn't find any standout highlights matching your criteria. This sometimes happens if the video is very short, has no dialogue, or if the AI filters were too strict. +

+
+
+ + Go to home + + +
+
+
+
+ )} + {videoId && topics.length > 0 && pageState === 'IDLE' && (
{error && ( diff --git a/lib/ai-processing.ts b/lib/ai-processing.ts index 7974cd9..dce60e9 100644 --- a/lib/ai-processing.ts +++ b/lib/ai-processing.ts @@ -14,7 +14,7 @@ import { import { generateAIResponse } from '@/lib/ai-client'; import { getProviderKey } from '@/lib/ai-providers'; import { topicGenerationSchema } from '@/lib/schemas'; -import { parseTimestampRange } from '@/lib/timestamp-utils'; +import { parseTimestampRange, formatTimestamp } from '@/lib/timestamp-utils'; import { getLanguageName } from '@/lib/language-utils'; import { repairJson } from '@/lib/json-utils'; import { z } from 'zod'; @@ -210,7 +210,7 @@ function buildChunkPrompt( language?: string ): string { const transcript = formatTranscriptWithTimestamps(chunk.segments); - const chunkWindow = `[${formatTime(chunk.start)}-${formatTime(chunk.end)}]`; + const chunkWindow = `[${formatTimestamp(chunk.start)}-${formatTimestamp(chunk.end)}]`; const videoInfoBlock = formatVideoInfoForPrompt(videoInfo); const themeInstruction = theme ? ` Focus exclusively on material that clearly expresses the theme "${theme}". Skip anything unrelated.\n` @@ -283,7 +283,7 @@ function buildReducePrompt( .map((candidate, idx) => { const timestamp = candidate.quote?.timestamp ?? '[??:??-??:??]'; const quoteText = candidate.quote?.text ?? ''; - const chunkWindow = `[${formatTime(candidate.chunkStart)}-${formatTime( + const chunkWindow = `[${formatTimestamp(candidate.chunkStart)}-${formatTimestamp( candidate.chunkEnd )}]`; return `Candidate ${idx + 1} @@ -464,7 +464,7 @@ function buildFallbackTopics( fallbackTopics.push({ title: theme ? `${theme} — part ${i + 1}` : `Part ${i + 1}`, quote: { - timestamp: `[${formatTime(startTime)}-${formatTime(endTime)}]`, + timestamp: `[${formatTimestamp(startTime)}-${formatTimestamp(endTime)}]`, text: chunkSegments .map((s) => s.text) @@ -602,7 +602,7 @@ ${transcriptWithTimestamps} { title: fallbackLabel, quote: { - timestamp: `[00:00-${formatTime(fallbackEnd)}]`, + timestamp: `[00:00-${formatTimestamp(fallbackEnd)}]`, text: fullText.substring(0, 200) } } @@ -629,21 +629,13 @@ function combineTranscript(segments: TranscriptSegment[]): string { function formatTranscriptWithTimestamps(segments: TranscriptSegment[]): string { return segments .map((s) => { - const startTime = formatTime(s.start); - const endTime = formatTime(s.start + s.duration); + const startTime = formatTimestamp(s.start); + const endTime = formatTimestamp(s.start + s.duration); return `[${startTime}-${endTime}] ${s.text}`; }) .join('\n'); } -function formatTime(seconds: number): string { - const mins = Math.floor(seconds / 60); - const secs = Math.floor(seconds % 60); - return `${mins.toString().padStart(2, '0')}:${secs - .toString() - .padStart(2, '0')}`; -} - async function findExactQuotes( transcript: TranscriptSegment[], quotes: Array<{ timestamp: string; text: string }>, diff --git a/lib/timestamp-utils.ts b/lib/timestamp-utils.ts index 75e1233..9fea845 100644 --- a/lib/timestamp-utils.ts +++ b/lib/timestamp-utils.ts @@ -27,7 +27,10 @@ export function parseTimestamp(timestamp: string): number | null { // Validate time values if (hours < 0 || hours >= 24) return null; - if (minutes < 0 || minutes >= 60) return null; + if (minutes < 0) return null; + // Relax minute check to allow MM:SS where MM >= 60 (fallback scenarios) + // unless hours are present, in which case strict 0-59 applies + if (hours > 0 && minutes >= 60) return null; if (seconds < 0 || seconds >= 60) return null; return hours * 3600 + minutes * 60 + seconds;