79 lines
3.4 KiB
TypeScript
79 lines
3.4 KiB
TypeScript
|
|
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 正在分析 D1–D8(通常 1~3 分鐘)…`;
|
|||
|
|
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;
|
|||
|
|
}
|