haixunMaster/lib/services/style-analysis.ts

79 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import "server-only";
import { analyzeStyle8D, evaluateBenchmarkEngagement, serialize8DPersona } from "@/lib/ai/analyze-style-8d";
import { parseProviderApiKeys } from "@/lib/ai/keys";
import { prisma } from "@/lib/db";
import { setTaskStatus } from "@/lib/jobs/progress";
import { updateJobProgress } from "@/lib/jobs/progress-server";
import type { JobProgressDetail } from "@/lib/jobs/types";
import { ensureActiveSession, getProfilePosts } from "@/lib/threads-browser";
import { consumeCrawlerPageQuota } from "@/lib/threads-browser/safety";
import { getOrCreateSettings } from "@/lib/user-settings";
import { assertJobNotCancelled } from "@/lib/jobs/cancel";
export async function runStyle8DAnalysis(accountId: string, benchmarkUsername: string, jobId: string) {
const username = benchmarkUsername.replace(/^@/, "").trim();
const detail: JobProgressDetail = {
summary: "確認 Threads 連線…",
phase: "session",
tasks: [
{ id: "session", label: "確認 Extension 連線", status: "running", startedAt: Date.now() },
{ id: "samples", label: "抓取近期貼文", status: "pending" },
{ id: "style", label: "AI 8D 分析", status: "pending" },
{ id: "store", label: "儲存帳號策略", status: "pending" },
],
};
await updateJobProgress(jobId, detail);
await assertJobNotCancelled(jobId);
const session = await ensureActiveSession();
consumeCrawlerPageQuota(session.id);
setTaskStatus(detail, "session", { status: "done" });
setTaskStatus(detail, "samples", { status: "running", startedAt: Date.now() });
detail.phase = "samples";
detail.summary = `正在抓取 @${username} 的近期貼文…`;
await updateJobProgress(jobId, detail);
const posts = await getProfilePosts(session.storageState, username, 12);
await assertJobNotCancelled(jobId);
if (posts.length < 4) throw new Error("可讀取的近期貼文不足 4 篇,無法做可靠的 8D 分析");
setTaskStatus(detail, "samples", { status: "done", found: posts.length });
setTaskStatus(detail, "style", { status: "running", startedAt: Date.now() });
detail.phase = "style";
detail.summary = `已取得 ${posts.length}AI 正在分析 D1D8通常 13 分鐘)…`;
await updateJobProgress(jobId, detail);
const settings = await getOrCreateSettings();
const analysis = await analyzeStyle8D({
username,
posts,
aiProvider: settings.researchAiProvider ?? settings.aiProvider,
aiModel: settings.researchAiModel ?? settings.aiModel,
apiKeys: parseProviderApiKeys(settings.providerApiKeys),
});
await assertJobNotCancelled(jobId);
setTaskStatus(detail, "style", { status: "done" });
setTaskStatus(detail, "store", { status: "running", startedAt: Date.now() });
detail.phase = "store";
detail.summary = "8D 分析完成,正在寫入帳號策略…";
await updateJobProgress(jobId, detail);
const profile = {
username,
analyzedAt: new Date().toISOString(),
postCount: posts.length,
engagement: evaluateBenchmarkEngagement(posts),
analysis,
personaDraft: serialize8DPersona(analysis.personaDraft),
};
await assertJobNotCancelled(jobId);
await prisma.account.update({
where: { id: accountId },
data: { styleProfile: JSON.stringify(profile), styleBenchmark: username },
});
setTaskStatus(detail, "store", { status: "done" });
detail.summary = `完成 · @${username} · ${posts.length} 篇樣本 · 8D 策略已套用`;
await updateJobProgress(jobId, detail);
return profile;
}