import { prisma, resolvePersona } from "@/lib/db"; import { getOrCreateSettings } from "@/lib/user-settings"; import { getActiveAccountProfile } from "@/lib/account-context"; import { generateOutreachCommentDrafts } from "@/lib/ai/generate-replies"; import { parseProviderApiKeys } from "@/lib/ai/keys"; import { resolveProductContextForPlacement } from "@/lib/services/product-catalog"; import { formatProductContextForPrompt, getProductCta } from "@/lib/types/product-context"; import { isPlacementGoal } from "@/lib/types/topic-goal"; export async function generateOutreachForScanItem( scanItemId: string, options?: { productBrief?: string | null } ) { const item = await prisma.scanItem.findUnique({ where: { id: scanItemId }, include: { replies: { orderBy: { likeCount: "desc" }, take: 5 }, outreachTargets: { include: { drafts: true } }, scan: { include: { topic: true } }, }, }); if (!item) throw new Error("找不到貼文"); const topic = item.scan.topic; if (!isPlacementGoal(item.scan.scanGoal ?? topic.topicGoal)) { throw new Error("只有找 TA 任務可以生成主動互動留言"); } const settings = await getOrCreateSettings(); const account = await getActiveAccountProfile(); const apiKeys = parseProviderApiKeys(settings.providerApiKeys); const resolvedContext = options?.productBrief?.trim() || (await resolveProductContextForPlacement({ brandProfileId: topic.brandProfileId, productProfileId: topic.productProfileId, fallbackContext: topic.productContext, searchTag: item.searchTag, })) || topic.productContext?.trim() || ""; const rawContext = resolvedContext; const productBrief = (rawContext ? formatProductContextForPrompt(rawContext) : null) || topic.brief?.trim() || account?.productBrief?.trim() || null; const { ctaType, ctaUrl } = getProductCta(rawContext); const generated = await generateOutreachCommentDrafts({ persona: resolvePersona(settings, account), aiProvider: settings.aiProvider, aiModel: settings.aiModel, apiKeys, topicLabel: topic.label, audienceBrief: topic.brief, productBrief, placementReason: item.placementReason ?? item.qualityReason, ctaType, ctaUrl, targetText: item.text, authorName: item.authorName, replies: item.replies, count: 2, }); const existing = item.outreachTargets[0]; if (existing) { await prisma.outreachDraft.deleteMany({ where: { outreachTargetId: existing.id } }); const target = await prisma.outreachTarget.update({ where: { id: existing.id }, data: { relevance: generated.relevance, reason: generated.reason, status: generated.relevance >= 0.55 ? "DRAFTED" : "LOW_RELEVANCE", drafts: { create: generated.drafts.map((draft) => ({ text: draft.text, angle: draft.angle, rationale: draft.rationale, })), }, }, include: { drafts: true, scanItem: { include: { scan: { include: { topic: true } } } } }, }); return target; } const target = await prisma.outreachTarget.create({ data: { scanItemId: item.id, relevance: generated.relevance, reason: generated.reason, status: generated.relevance >= 0.55 ? "DRAFTED" : "LOW_RELEVANCE", drafts: { create: generated.drafts.map((draft) => ({ text: draft.text, angle: draft.angle, rationale: draft.rationale, })), }, }, include: { drafts: true, scanItem: { include: { scan: { include: { topic: true } } } } }, }); return target; }