Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@google/genai": "^1.43.0",
"@google/generative-ai": "^0.24.1",
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR migrates routes to @google/genai, but @google/generative-ai remains in dependencies. Keeping both SDKs increases bundle size and maintenance overhead. If nothing else in apps/web still imports @google/generative-ai, remove it from dependencies (and update the lockfile) to avoid duplicate SDKs.

Suggested change
"@google/generative-ai": "^0.24.1",

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The pull request migrates from @google/generative-ai to @google/genai. Since the old dependency @google/generative-ai is no longer used in the updated API routes, it should be removed from package.json to keep the project's dependencies clean and reduce bundle size.

"@stripe/stripe-js": "^2.0.0",
"@supabase/supabase-js": "^2.39.0",
Expand Down
58 changes: 49 additions & 9 deletions apps/web/src/app/api/extract-events/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import OpenAI from 'openai';
import { GoogleGenerativeAI } from '@google/generative-ai';
import { GoogleGenAI, Type } from '@google/genai';
import { NextResponse } from 'next/server';

let _openai: OpenAI | null = null;
Expand All @@ -8,13 +8,13 @@ function getOpenAI() {
return _openai;
}

let _gemini: GoogleGenerativeAI | null = null;
let _gemini: GoogleGenAI | null = null;
function getGemini() {
if (!_gemini) _gemini = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
if (!_gemini) _gemini = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || '' });
return _gemini;
}

// JSON Schema for structured extraction via Responses API
// JSON Schema for structured extraction via OpenAI Responses API
const extractionSchema = {
type: 'object' as const,
properties: {
Expand Down Expand Up @@ -54,6 +54,43 @@ const extractionSchema = {
additionalProperties: false,
};

// Gemini responseSchema using @google/genai Type system
const geminiResponseSchema = {
type: Type.OBJECT,
properties: {
events: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
type: { type: Type.STRING, enum: ['action', 'topic', 'insight', 'tool', 'resource'] },
title: { type: Type.STRING },
description: { type: Type.STRING },
timestamp: { type: Type.STRING, nullable: true },
priority: { type: Type.STRING, enum: ['high', 'medium', 'low'] },
},
required: ['type', 'title', 'description', 'priority'],
},
},
actions: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
title: { type: Type.STRING },
description: { type: Type.STRING },
category: { type: Type.STRING, enum: ['setup', 'build', 'deploy', 'learn', 'research', 'configure'] },
estimatedMinutes: { type: Type.NUMBER, nullable: true },
},
required: ['title', 'description', 'category'],
},
},
summary: { type: Type.STRING },
topics: { type: Type.ARRAY, items: { type: Type.STRING } },
},
required: ['events', 'actions', 'summary', 'topics'],
};

const SYSTEM_PROMPT = `You are an expert content analyst. Extract structured data from video transcripts.
Be specific and practical — no vague or generic items.
For events: classify type (action/topic/insight/tool/resource) and priority (high/medium/low).
Expand Down Expand Up @@ -94,15 +131,18 @@ async function extractWithOpenAI(trimmed: string, videoTitle?: string, videoUrl?
}

async function extractWithGemini(trimmed: string, videoTitle?: string, videoUrl?: string) {
const model = getGemini().getGenerativeModel({
const ai = getGemini();
const response = await ai.models.generateContent({
model: 'gemini-2.0-flash',
generationConfig: {
responseMimeType: 'application/json',
contents: `${SYSTEM_PROMPT}\n\n${buildUserPrompt(trimmed, videoTitle, videoUrl)}`,
config: {
temperature: 0.3,
responseMimeType: 'application/json',
responseSchema: geminiResponseSchema,
tools: [{ googleSearch: {} }],
},
});
const result = await model.generateContent(`${SYSTEM_PROMPT}\n\n${buildUserPrompt(trimmed, videoTitle, videoUrl)}`);
const text = result.response.text();
const text = response.text ?? '';
return JSON.parse(text);
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will throw if Gemini returns an empty string, whitespace, or any non-JSON text (even with responseMimeType/responseSchema, failures can still occur). Prefer consuming the SDK’s structured/parsed output if available for responseSchema; otherwise wrap JSON.parse in a try/catch and surface a controlled error (or fallback) that includes enough context to debug without logging the full transcript.

Suggested change
return JSON.parse(text);
if (!text || !text.trim()) {
throw new Error('Gemini returned an empty response while JSON was expected from gemini-2.0-flash');
}
try {
return JSON.parse(text);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(`Failed to parse Gemini JSON response from gemini-2.0-flash: ${message}`);
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The call to JSON.parse(text) will throw an unhandled exception if text is an empty string, which can occur if the Gemini API returns an empty response (response.text is null or undefined). This would cause the API to return a 500 error. It's safer to handle this case to prevent the request from crashing.

Suggested change
return JSON.parse(text);
return text ? JSON.parse(text) : {};

}

Expand Down
76 changes: 60 additions & 16 deletions apps/web/src/app/api/transcribe/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import OpenAI from 'openai';
import { GoogleGenerativeAI } from '@google/generative-ai';
import { GoogleGenAI } from '@google/genai';
import { NextResponse } from 'next/server';

let _openai: OpenAI | null = null;
Expand All @@ -8,9 +8,9 @@ function getOpenAI() {
return _openai;
}

let _gemini: GoogleGenerativeAI | null = null;
let _gemini: GoogleGenAI | null = null;
function getGemini() {
if (!_gemini) _gemini = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
if (!_gemini) _gemini = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || '' });
return _gemini;
}

Expand Down Expand Up @@ -121,32 +121,76 @@ Be thorough — capture all key points, quotes, and technical details.`,
}
}

// Strategy 3: Gemini fallback (when OpenAI unavailable)
// Strategy 3: Gemini with direct YouTube URL processing + Google Search grounding
if (url && !audioUrl && process.env.GEMINI_API_KEY) {
try {
const model = getGemini().getGenerativeModel({
const ai = getGemini();
const result = await ai.models.generateContent({
model: 'gemini-2.0-flash',
generationConfig: { temperature: 0.2 },
contents: [
{
role: 'user',
parts: [
{
fileData: {
mimeType: 'video/*',
fileUri: url,
},
Comment on lines +135 to +138
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the @google/genai API, fileData.fileUri is typically expected to reference an uploaded file resource (or a supported file URI), not an arbitrary YouTube URL. As written, this call is likely to fail consistently and always fall back. Consider either (1) uploading the video/audio to Gemini Files first and passing that returned URI, or (2) only using fileData for known supported URIs (e.g., a previously-uploaded file URI / storage URI) and otherwise go straight to the text-based fallback.

Copilot uses AI. Check for mistakes.
},
{
text: 'Provide a complete, detailed transcript of this video. ' +
'Include all spoken content verbatim. ' +
'Include timestamps where possible in [MM:SS] format. ' +
'Be thorough and comprehensive — capture every key point, quote, and technical detail.',
},
],
},
],
config: {
temperature: 0.2,
tools: [{ googleSearch: {} }],
},
});

const result = await model.generateContent(
`You are a video content transcription assistant. ` +
`For the following YouTube video URL, provide a detailed transcript or content summary. ` +
`Include all key points, technical details, quotes, and actionable insights. ` +
`Be thorough and comprehensive.\n\nVideo URL: ${url}`
);
const text = result.response.text();
const text = result.text ?? '';

if (text.length > 100) {
return NextResponse.json({
success: true,
transcript: text,
source: 'gemini',
source: 'gemini-video',
wordCount: text.split(/\s+/).length,
});
}
} catch (e) {
console.warn('Gemini transcript fallback failed:', e);
console.warn('Gemini video URL processing failed, trying text fallback:', e);

// Fallback: text-based Gemini with Google Search grounding
try {
const ai = getGemini();
const result = await ai.models.generateContent({
model: 'gemini-2.0-flash',
contents: `You are a video content transcription assistant. ` +
`For the following YouTube video URL, provide a detailed transcript or content summary. ` +
`Include all key points, technical details, quotes, and actionable insights. ` +
`Be thorough and comprehensive.\n\nVideo URL: ${url}`,
config: {
temperature: 0.2,
tools: [{ googleSearch: {} }],
},
});
const text = result.text ?? '';

if (text.length > 100) {
return NextResponse.json({
success: true,
transcript: text,
source: 'gemini',
wordCount: text.split(/\s+/).length,
});
}
} catch (e2) {
console.warn('Gemini text fallback also failed:', e2);
}
}
Comment on lines 126 to 194
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error handling logic for Gemini video processing has a deeply nested try...catch block. Additionally, the call to getGemini() is duplicated in both the main try block (line 127) and the fallback catch block (line 169). To improve readability and maintainability, consider hoisting the const ai = getGemini(); call to line 126, before the first try block. This will simplify the code and remove the duplication.

}

Expand Down
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/1247
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/1247_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/1249
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/1249_fsm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/1249_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/1255
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/1255_fsm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/1255_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/1259
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/1259_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2579
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2600
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2600_fsm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2600_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2601
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2601_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2602
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2602_fsm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2602_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2603
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2603_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2604
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2605
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2605_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2606
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2606_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2608
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2608_fsm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2608_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2609
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2609_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2610
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2610_fsm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2610_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2616
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2616_fsm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2616_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2617
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2617_fsm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2617_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2620
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2650
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2651
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2652
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2653
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2654
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2655
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2656
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2657
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2658
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2659
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2660
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2661
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2662
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2663
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2664
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2665
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2666
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2667
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2673
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2674
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2675
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2678
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2679
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2686
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2687
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2688
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2689
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2690
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2691
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2699
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2701
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2702
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2703
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2704
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2753
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2753_fsm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2753_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2754
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2755
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2756
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/2757
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/3079
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/3079_vm
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/3080
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/3081
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/base/1/3455
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/global/1262
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/global/2396
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/global/2397
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/global/pg_control
Binary file not shown.
Binary file not shown.
Binary file modified dataconnect/.dataconnect/pgliteData/pg17/pg_xact/0000
Binary file not shown.
Loading
Loading