Skip to content

Commit 3b6284f

Browse files
authored
Phase 2: Replace NotebookLM with Gemini Deep Research API
Phase 2: Replace NotebookLM with Gemini Deep Research API\n\n- SDK migration: @google/generative-ai → @google/genai\n- New gemini-research.ts: Interactions API client (submit/poll/parse)\n- New gemini-infographics.ts: Imagen 4 Fast infographic generation\n- Rewrite check-research route: Gemini polling + inline infographic generation\n- Update ingest route: submitResearch() replaces NotebookLM\n- All config from Sanity singletons via getConfigValue()\n- enableDeepResearch toggle for gradual rollout\n- Backward compatible with legacy NotebookLM docs
2 parents 7425042 + 54e765c commit 3b6284f

File tree

11 files changed

+1071
-358
lines changed

11 files changed

+1071
-358
lines changed

app/api/cron/check-research/route.ts

Lines changed: 166 additions & 291 deletions
Large diffs are not rendered by default.

app/api/cron/ingest/route.ts

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import { writeClient } from "@/lib/sanity-write-client";
77
import { getConfigValue } from "@/lib/config";
88
import { discoverTrends, type TrendResult } from "@/lib/services/trend-discovery";
99
import type { ResearchPayload } from "@/lib/services/research";
10-
import { NotebookLMClient } from "@/lib/services/notebooklm/client";
11-
import { initAuth } from "@/lib/services/notebooklm/auth";
10+
import { submitResearch } from "@/lib/services/gemini-research";
1211

1312
// ---------------------------------------------------------------------------
1413
// Types
@@ -469,11 +468,11 @@ async function createSanityDocuments(
469468
selectedTrend: TrendResult,
470469
qualityThreshold: number,
471470
research?: ResearchPayload,
472-
researchMeta?: { notebookId: string; taskId: string },
471+
researchInteractionId?: string,
473472
) {
474473
const isFlagged = criticResult.score < qualityThreshold;
475474
// When research is in-flight, status is "researching" (check-research cron will transition to script_ready)
476-
const isResearching = !!researchMeta?.notebookId;
475+
const isResearching = !!researchInteractionId;
477476
const status = isFlagged ? "flagged" : isResearching ? "researching" : "script_ready";
478477

479478
const contentIdea = await writeClient.create({
@@ -510,8 +509,7 @@ async function createSanityDocuments(
510509
}),
511510
trendScore: selectedTrend.score,
512511
trendSources: selectedTrend.signals.map(s => s.source).join(", "),
513-
researchNotebookId: researchMeta?.notebookId ?? research?.notebookId,
514-
...(researchMeta?.taskId && { researchTaskId: researchMeta.taskId }),
512+
researchInteractionId: researchInteractionId || undefined,
515513
});
516514

517515
console.log(`[CRON/ingest] Created automatedVideo: ${automatedVideo._id}`);
@@ -548,9 +546,9 @@ export async function GET(request: NextRequest) {
548546
"systemInstruction",
549547
SYSTEM_INSTRUCTION_FALLBACK,
550548
);
551-
const enableNotebookLmResearch = await getConfigValue(
549+
const enableDeepResearch = await getConfigValue(
552550
"pipeline_config",
553-
"enableNotebookLmResearch",
551+
"enableDeepResearch",
554552
false,
555553
);
556554
const qualityThreshold = await getConfigValue(
@@ -620,41 +618,21 @@ export async function GET(request: NextRequest) {
620618
console.log(`[CRON/ingest] Dedup: selected "${selectedTrend.topic}" (score: ${selectedTrend.score}, skipped ${skippedCount} topics)`);
621619

622620
// Step 2: Optional deep research on selected topic (fire-and-forget)
623-
// When research is enabled, we create a notebook and start research
621+
// When research is enabled, we submit to Gemini Deep Research
624622
// but DON'T wait for it — the check-research cron will poll and enrich later
625-
let researchMeta: { notebookId: string; taskId: string } | undefined;
626-
if (enableNotebookLmResearch) {
627-
console.log(`[CRON/ingest] Starting fire-and-forget research on: "${selectedTrend.topic}"...`);
623+
let researchInteractionId: string | undefined;
624+
if (enableDeepResearch) {
625+
console.log(`[CRON/ingest] Starting Gemini Deep Research on: "${selectedTrend.topic}"...`);
628626
try {
629-
const auth = await initAuth();
630-
const nbClient = new NotebookLMClient(auth);
631-
632-
// Create notebook
633-
const notebook = await nbClient.createNotebook(selectedTrend.topic);
634-
const notebookId = notebook.id;
635-
console.log(`[CRON/ingest] Created notebook: ${notebookId}`);
636-
637-
// Add source URLs from trend signals
638627
const sourceUrls = (selectedTrend.signals ?? [])
639628
.map((s: { url?: string }) => s.url)
640629
.filter((u): u is string => !!u && u.startsWith("http"))
641630
.slice(0, 5);
642631

643-
for (const url of sourceUrls) {
644-
await nbClient.addSource(notebookId, url).catch((err) => {
645-
console.warn(`[CRON/ingest] Failed to add source ${url}:`, err);
646-
});
647-
}
648-
console.log(`[CRON/ingest] Added ${sourceUrls.length} source URLs to notebook`);
649-
650-
// Start deep research (fire-and-forget — don't poll!)
651-
const researchTask = await nbClient.startResearch(notebookId, selectedTrend.topic, "deep");
652-
const researchTaskId = researchTask?.taskId ?? "";
653-
console.log(`[CRON/ingest] Research started — taskId: ${researchTaskId}. check-research cron will poll.`);
654-
655-
researchMeta = { notebookId, taskId: researchTaskId };
632+
researchInteractionId = await submitResearch(selectedTrend.topic, { sourceUrls });
633+
console.log(`[CRON/ingest] Deep Research submitted — interactionId: ${researchInteractionId}. check-research cron will poll.`);
656634
} catch (err) {
657-
console.warn("[CRON/ingest] Research start failed, continuing without:", err);
635+
console.warn("[CRON/ingest] Deep Research submission failed, continuing without:", err);
658636
}
659637
}
660638

@@ -692,7 +670,7 @@ export async function GET(request: NextRequest) {
692670
);
693671

694672
console.log("[CRON/ingest] Creating Sanity documents...");
695-
const result = await createSanityDocuments(script, criticResult, selectedTrend, qualityThreshold, undefined, researchMeta);
673+
const result = await createSanityDocuments(script, criticResult, selectedTrend, qualityThreshold, undefined, researchInteractionId);
696674

697675
console.log("[CRON/ingest] Done!", result);
698676

@@ -704,8 +682,8 @@ export async function GET(request: NextRequest) {
704682
trendCount: trends.length,
705683
trendScore: selectedTrend.score,
706684
skippedCount,
707-
researchStarted: !!researchMeta,
708-
researchNotebookId: researchMeta?.notebookId,
685+
researchStarted: !!researchInteractionId,
686+
researchInteractionId: researchInteractionId,
709687
});
710688
} catch (err) {
711689
console.error("[CRON/ingest] Unexpected error:", err);

lib/gemini.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
import { GoogleGenerativeAI } from "@google/generative-ai";
1+
import { GoogleGenAI } from "@google/genai";
22
import { getConfigValue } from "@/lib/config";
33

4-
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || "");
4+
let _ai: GoogleGenAI | null = null;
5+
6+
/** Lazy-initialize the GoogleGenAI client (avoids crash at import time if GEMINI_API_KEY is missing). */
7+
function getAI(): GoogleGenAI {
8+
if (!_ai) {
9+
const apiKey = process.env.GEMINI_API_KEY || "";
10+
_ai = new GoogleGenAI({ apiKey });
11+
}
12+
return _ai;
13+
}
514

615
/**
716
* Generate text content using Gemini Flash.
@@ -14,13 +23,13 @@ export async function generateWithGemini(
1423
systemInstruction?: string,
1524
): Promise<string> {
1625
const geminiModel = await getConfigValue("pipeline_config", "geminiModel", "gemini-2.0-flash");
17-
const model = genAI.getGenerativeModel({
26+
const ai = getAI();
27+
const response = await ai.models.generateContent({
1828
model: geminiModel,
19-
...(systemInstruction && { systemInstruction }),
29+
contents: prompt,
30+
...(systemInstruction && { config: { systemInstruction } }),
2031
});
21-
const result = await model.generateContent(prompt);
22-
const response = result.response;
23-
return response.text();
32+
return response.text ?? "";
2433
}
2534

2635
/**

0 commit comments

Comments
 (0)