import { z } from "zod"; import type { ProviderApiKeys } from "./keys"; import { withAgentSystem } from "./agent"; import { generateStructuredObject } from "./generate-structured"; import { getModel } from "./provider"; import { describePostImages } from "./vision"; import type { ViralAnalysis } from "@/lib/types/viral"; const viralSchema = z.object({ whyViral: z.array(z.string()).min(2).max(6), hookPattern: z.string(), structurePattern: z.string(), emotionalTrigger: z.string(), timingAngle: z.string(), commentInsights: z.object({ themes: z.array(z.string()), sentiment: z.string(), topReactions: z.array(z.string()), audienceQuestions: z.array(z.string()), whyPeopleEngage: z.string(), }), visualAnalysis: z.object({ hasImages: z.boolean(), layout: z.string(), colorMood: z.string(), textOnImage: z.boolean(), typographyStyle: z.string(), visualHook: z.string(), replicationTips: z.array(z.string()), imageGenPrompt: z.string(), }), replicationStrategy: z.string(), keyTakeaways: z.array(z.string()), }); export interface AnalyzeViralInput { postText: string; authorName?: string | null; likeCount?: number | null; replyCount?: number | null; searchTag?: string | null; mediaUrls?: string[]; mediaType?: string | null; topicLabel: string; topicBrief?: string | null; persona?: string | null; aiProvider: string; aiModel: string; apiKeys?: ProviderApiKeys; replies: Array<{ text: string; authorName?: string | null; likeCount?: number | null }>; } export async function analyzeViralPost(input: AnalyzeViralInput): Promise { const model = getModel(input.aiProvider, input.aiModel, input.apiKeys ?? {}); let visualDescription: string | null = null; if (input.mediaUrls && input.mediaUrls.length > 0) { visualDescription = await describePostImages( input.mediaUrls, input.postText, input.apiKeys ?? {} ); } const repliesBlock = input.replies .slice(0, 15) .map( (r, i) => `${i + 1}. @${r.authorName ?? "匿名"}(${r.likeCount ?? 0}讚):${r.text.slice(0, 200)}` ) .join("\n"); const visualBlock = visualDescription ? `\n\nAI 圖文視覺分析:\n${visualDescription}` : input.mediaUrls && input.mediaUrls.length > 0 ? `\n\n(附圖 ${input.mediaUrls.length} 張,未能進行視覺辨識,請依貼文文字推測圖文策略)` : "\n\n(純文字貼文,無附圖)"; const object = await generateStructuredObject({ model, provider: input.aiProvider, modelId: input.aiModel, schema: viralSchema, system: withAgentSystem(`你是 Threads 爆款內容分析師。任務是拆解「為什麼這篇會紅」,並從留言中理解受眾為什麼買單。 分析重點: - 開頭 hook 用了什麼手法(反差、提問、數字、故事、痛點) - 內文結構(幾段、節奏、轉折、CTA) - 留言反映的共鳴點(不是摘要留言,是找「為什麼大家願意互動」) - 若有圖文,分析圖文如何配合文字放大傳播 - imageGenPrompt 要寫成可直接給 AI 繪圖的英文 prompt,描述類似風格的圖 除 imageGenPrompt 外,全部繁體中文台灣用語。`), prompt: `主題:${input.topicLabel} ${input.topicBrief ? `Brief:${input.topicBrief}` : ""} ${input.persona ? `創作者人設:${input.persona}` : ""} 搜尋標籤:${input.searchTag ?? "—"} 媒體類型:${input.mediaType ?? "unknown"} 爆款貼文: @${input.authorName ?? "匿名"} · ${input.likeCount ?? 0} 讚 · ${input.replyCount ?? 0} 留言 ${input.postText} ${visualBlock} 熱門留言(理解受眾為什麼互動): ${repliesBlock || "(無留言資料)"} 請深度分析這篇為什麼會爆款,以及如何複製其成功模式。`, }); return { ...object, visualAnalysis: { ...object.visualAnalysis, hasImages: (input.mediaUrls?.length ?? 0) > 0, }, analyzedAt: new Date().toISOString(), }; }