haixunMaster/lib/ai/replicate-viral.ts

73 lines
2.7 KiB
TypeScript
Raw Normal View History

2026-06-21 12:50:31 +00:00
import { z } from "zod";
import type { ProviderApiKeys } from "./keys";
import { withAgentSystem } from "./agent";
import { generateStructuredObject } from "./generate-structured";
import { HASHTAG_USER_REMINDER, HASHTAG_WRITING_RULES } from "./hashtag-rules";
import { buildPersonaPromptBlock } from "./persona";
import { getModel } from "./provider";
import { THREADS_MAX_CHARS } from "@/lib/utils";
import type { ViralAnalysis } from "@/lib/types/viral";
const replicaSchema = z.object({
angle: z.string(),
hook: z.string(),
text: z.string().max(THREADS_MAX_CHARS),
imageBrief: z.string(),
rationale: z.string(),
structureNotes: z.string(),
});
export interface ReplicateViralInput {
originalText: string;
authorName?: string | null;
viralAnalysis: ViralAnalysis;
topicLabel: string;
topicBrief?: string | null;
persona?: string | null;
aiProvider: string;
aiModel: string;
apiKeys?: ProviderApiKeys;
}
export async function replicateViralPost(input: ReplicateViralInput) {
const model = getModel(input.aiProvider, input.aiModel, input.apiKeys ?? {});
const object = await generateStructuredObject({
model,
provider: input.aiProvider,
modelId: input.aiModel,
schema: replicaSchema,
system: withAgentSystem(`你是 Threads 爆款複製策略師。根據爆款分析報告,為使用者撰寫「同結構、同節奏、但完全原創」的貼文。
${buildPersonaPromptBlock(input.persona)}
- hook
-
- text 500 #
${HASHTAG_WRITING_RULES}
- imageBrief `),
prompt: `主題:${input.topicLabel}
${input.topicBrief ? `Brief${input.topicBrief}` : ""}
@${input.authorName ?? "匿名"}
${input.originalText}
- ${input.viralAnalysis.whyViral.join("")}
- Hook ${input.viralAnalysis.hookPattern}
- ${input.viralAnalysis.structurePattern}
- ${input.viralAnalysis.emotionalTrigger}
- ${input.viralAnalysis.commentInsights.whyPeopleEngage}
- ${input.viralAnalysis.replicationStrategy}
- ${input.viralAnalysis.visualAnalysis.replicationTips.join("")}
- prompt ${input.viralAnalysis.visualAnalysis.imageGenPrompt}
+ ${HASHTAG_USER_REMINDER}`,
});
return {
...object,
text: object.text.slice(0, THREADS_MAX_CHARS),
};
}