Skip to content
457 changes: 166 additions & 291 deletions app/api/cron/check-research/route.ts

Large diffs are not rendered by default.

54 changes: 16 additions & 38 deletions app/api/cron/ingest/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import { writeClient } from "@/lib/sanity-write-client";
import { getConfigValue } from "@/lib/config";
import { discoverTrends, type TrendResult } from "@/lib/services/trend-discovery";
import type { ResearchPayload } from "@/lib/services/research";
import { NotebookLMClient } from "@/lib/services/notebooklm/client";
import { initAuth } from "@/lib/services/notebooklm/auth";
import { submitResearch } from "@/lib/services/gemini-research";

// ---------------------------------------------------------------------------
// Types
Expand Down Expand Up @@ -469,11 +468,11 @@ async function createSanityDocuments(
selectedTrend: TrendResult,
qualityThreshold: number,
research?: ResearchPayload,
researchMeta?: { notebookId: string; taskId: string },
researchInteractionId?: string,
) {
const isFlagged = criticResult.score < qualityThreshold;
// When research is in-flight, status is "researching" (check-research cron will transition to script_ready)
const isResearching = !!researchMeta?.notebookId;
const isResearching = !!researchInteractionId;
const status = isFlagged ? "flagged" : isResearching ? "researching" : "script_ready";

const contentIdea = await writeClient.create({
Expand Down Expand Up @@ -510,8 +509,7 @@ async function createSanityDocuments(
}),
trendScore: selectedTrend.score,
trendSources: selectedTrend.signals.map(s => s.source).join(", "),
researchNotebookId: researchMeta?.notebookId ?? research?.notebookId,
...(researchMeta?.taskId && { researchTaskId: researchMeta.taskId }),
researchInteractionId: researchInteractionId || undefined,
});

console.log(`[CRON/ingest] Created automatedVideo: ${automatedVideo._id}`);
Expand Down Expand Up @@ -548,9 +546,9 @@ export async function GET(request: NextRequest) {
"systemInstruction",
SYSTEM_INSTRUCTION_FALLBACK,
);
const enableNotebookLmResearch = await getConfigValue(
const enableDeepResearch = await getConfigValue(
"pipeline_config",
"enableNotebookLmResearch",
"enableDeepResearch",
false,
);
const qualityThreshold = await getConfigValue(
Expand Down Expand Up @@ -620,41 +618,21 @@ export async function GET(request: NextRequest) {
console.log(`[CRON/ingest] Dedup: selected "${selectedTrend.topic}" (score: ${selectedTrend.score}, skipped ${skippedCount} topics)`);

// Step 2: Optional deep research on selected topic (fire-and-forget)
// When research is enabled, we create a notebook and start research
// When research is enabled, we submit to Gemini Deep Research
// but DON'T wait for it — the check-research cron will poll and enrich later
let researchMeta: { notebookId: string; taskId: string } | undefined;
if (enableNotebookLmResearch) {
console.log(`[CRON/ingest] Starting fire-and-forget research on: "${selectedTrend.topic}"...`);
let researchInteractionId: string | undefined;
if (enableDeepResearch) {
console.log(`[CRON/ingest] Starting Gemini Deep Research on: "${selectedTrend.topic}"...`);
try {
const auth = await initAuth();
const nbClient = new NotebookLMClient(auth);

// Create notebook
const notebook = await nbClient.createNotebook(selectedTrend.topic);
const notebookId = notebook.id;
console.log(`[CRON/ingest] Created notebook: ${notebookId}`);

// Add source URLs from trend signals
const sourceUrls = (selectedTrend.signals ?? [])
.map((s: { url?: string }) => s.url)
.filter((u): u is string => !!u && u.startsWith("http"))
.slice(0, 5);

for (const url of sourceUrls) {
await nbClient.addSource(notebookId, url).catch((err) => {
console.warn(`[CRON/ingest] Failed to add source ${url}:`, err);
});
}
console.log(`[CRON/ingest] Added ${sourceUrls.length} source URLs to notebook`);

// Start deep research (fire-and-forget — don't poll!)
const researchTask = await nbClient.startResearch(notebookId, selectedTrend.topic, "deep");
const researchTaskId = researchTask?.taskId ?? "";
console.log(`[CRON/ingest] Research started — taskId: ${researchTaskId}. check-research cron will poll.`);

researchMeta = { notebookId, taskId: researchTaskId };
researchInteractionId = await submitResearch(selectedTrend.topic, { sourceUrls });
console.log(`[CRON/ingest] Deep Research submitted — interactionId: ${researchInteractionId}. check-research cron will poll.`);
} catch (err) {
console.warn("[CRON/ingest] Research start failed, continuing without:", err);
console.warn("[CRON/ingest] Deep Research submission failed, continuing without:", err);
}
}

Expand Down Expand Up @@ -692,7 +670,7 @@ export async function GET(request: NextRequest) {
);

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

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

Expand All @@ -704,8 +682,8 @@ export async function GET(request: NextRequest) {
trendCount: trends.length,
trendScore: selectedTrend.score,
skippedCount,
researchStarted: !!researchMeta,
researchNotebookId: researchMeta?.notebookId,
researchStarted: !!researchInteractionId,
researchInteractionId: researchInteractionId,
});
} catch (err) {
console.error("[CRON/ingest] Unexpected error:", err);
Expand Down
23 changes: 16 additions & 7 deletions lib/gemini.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { GoogleGenerativeAI } from "@google/generative-ai";
import { GoogleGenAI } from "@google/genai";
import { getConfigValue } from "@/lib/config";

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || "");
let _ai: GoogleGenAI | null = null;

/** Lazy-initialize the GoogleGenAI client (avoids crash at import time if GEMINI_API_KEY is missing). */
function getAI(): GoogleGenAI {
if (!_ai) {
const apiKey = process.env.GEMINI_API_KEY || "";
_ai = new GoogleGenAI({ apiKey });
}
return _ai;
}

/**
* Generate text content using Gemini Flash.
Expand All @@ -14,13 +23,13 @@ export async function generateWithGemini(
systemInstruction?: string,
): Promise<string> {
const geminiModel = await getConfigValue("pipeline_config", "geminiModel", "gemini-2.0-flash");
const model = genAI.getGenerativeModel({
const ai = getAI();
const response = await ai.models.generateContent({
model: geminiModel,
...(systemInstruction && { systemInstruction }),
contents: prompt,
...(systemInstruction && { config: { systemInstruction } }),
});
const result = await model.generateContent(prompt);
const response = result.response;
return response.text();
return response.text ?? "";
}

/**
Expand Down
Loading
Loading