111 lines
3.6 KiB
TypeScript
111 lines
3.6 KiB
TypeScript
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;
|
|
}
|