From 082fd5cf0c9e6bb44a5e6a784a8d617772aa9482 Mon Sep 17 00:00:00 2001 From: pimpithecat Date: Sun, 26 Oct 2025 23:01:05 +0800 Subject: [PATCH 1/2] feat: save and restore theme-based topics across sessions --- app/analyze/[videoId]/page.tsx | 55 ++++++++++++++++++++++++-- app/api/update-video-analysis/route.ts | 7 +++- lib/types.ts | 1 + lib/validation.ts | 3 +- 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/app/analyze/[videoId]/page.tsx b/app/analyze/[videoId]/page.tsx index 0844dd1..d5f8890 100644 --- a/app/analyze/[videoId]/page.tsx +++ b/app/analyze/[videoId]/page.tsx @@ -515,8 +515,25 @@ export default function AnalyzePage() { setVideoInfo(null); } - setTopics(hydratedTopics); - setBaseTopics(hydratedTopics); + // Separate base topics and theme topics + const baseTopicsFromCache = hydratedTopics.filter(topic => !topic.theme); + const themeTopicsFromCache = hydratedTopics.filter(topic => topic.theme); + + // Reconstruct themeTopicsMap + const reconstructedThemeMap: Record = {}; + themeTopicsFromCache.forEach(topic => { + if (topic.theme) { + if (!reconstructedThemeMap[topic.theme]) { + reconstructedThemeMap[topic.theme] = []; + } + reconstructedThemeMap[topic.theme].push(topic); + } + }); + + setTopics(baseTopicsFromCache); + setBaseTopics(baseTopicsFromCache); + setThemeTopicsMap(reconstructedThemeMap); + const initialKeys = new Set(); hydratedTopics.forEach(topic => { if (topic.quote?.timestamp && topic.quote.text) { @@ -525,7 +542,7 @@ export default function AnalyzePage() { } }); setUsedTopicKeys(initialKeys); - setSelectedTopic(hydratedTopics.length > 0 ? hydratedTopics[0] : null); + setSelectedTopic(baseTopicsFromCache.length > 0 ? baseTopicsFromCache[0] : null); // Set cached takeaways and questions if (cacheData.summary) { @@ -1293,11 +1310,41 @@ export default function AnalyzePage() { } }); setUsedTopicKeys(nextUsedKeys); - themedTopics = hydratedThemeTopics; + + // Tag theme topics with theme name + const themedTopicsWithTheme = hydratedThemeTopics.map(topic => ({ + ...topic, + theme: normalizedTheme + })); + + themedTopics = themedTopicsWithTheme; setThemeTopicsMap(prev => ({ ...prev, [normalizedTheme]: themedTopics || [] })); + + // Save theme topics to database (background operation) + backgroundOperation( + 'save-theme-topics', + async () => { + const allTopics = [ + ...baseTopics.map(t => ({ ...t, theme: null })), + ...Object.entries(themeTopicsMap).flatMap(([theme, topics]) => + topics.map(t => ({ ...t, theme })) + ), + ...themedTopicsWithTheme + ]; + + await fetch("/api/update-video-analysis", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + videoId, + topics: allTopics + }) + }); + } + ); } catch (error) { const isAbortError = typeof error === "object" && diff --git a/app/api/update-video-analysis/route.ts b/app/api/update-video-analysis/route.ts index 3c73bc3..43b023d 100644 --- a/app/api/update-video-analysis/route.ts +++ b/app/api/update-video-analysis/route.ts @@ -8,7 +8,8 @@ async function handler(req: NextRequest) { videoId, summary, suggestedQuestions, - translatedTranscripts + translatedTranscripts, + topics } = await req.json(); if (!videoId) { @@ -37,6 +38,10 @@ async function handler(req: NextRequest) { updateData.translated_transcripts = translatedTranscripts; } + if (topics !== undefined) { + updateData.topics = topics; + } + const { data: updatedVideo, error: updateError } = await supabase .from('video_analyses') .update(updateData) diff --git a/lib/types.ts b/lib/types.ts index 0f66946..cfbaf9a 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -15,6 +15,7 @@ export interface Topic { title: string; description?: string; duration: number; + theme?: string | null; // Theme name for theme-based topics, null/undefined for base topics segments: { start: number; end: number; diff --git a/lib/validation.ts b/lib/validation.ts index 4357d00..d9c04b5 100644 --- a/lib/validation.ts +++ b/lib/validation.ts @@ -184,7 +184,8 @@ export const checkVideoCacheRequestSchema = z.object({ export const updateVideoAnalysisRequestSchema = z.object({ videoId: youtubeIdSchema, summary: z.any().optional(), - suggestedQuestions: z.any().optional() + suggestedQuestions: z.any().optional(), + topics: z.array(z.any()).optional() }); // Rate limiting validation From 50b769ed79e2a1c17577d830f16d85039175513b Mon Sep 17 00:00:00 2001 From: pimpithecat Date: Sun, 26 Oct 2025 23:43:30 +0800 Subject: [PATCH 2/2] fix: change to anthropic_api_key authentication --- .github/workflows/claude-code-review.yml | 2 +- .github/workflows/claude.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 4caf96a..5e90d4b 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -35,7 +35,7 @@ jobs: id: claude-review uses: anthropics/claude-code-action@v1 with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} prompt: | Please review this pull request and provide feedback on: - Code quality and best practices diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index ae36c00..4b2e6d2 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -34,7 +34,7 @@ jobs: id: claude uses: anthropics/claude-code-action@v1 with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} # This is an optional setting that allows Claude to read CI results on PRs additional_permissions: |