import { NextResponse } from "next/server"; import { z } from "zod"; import { requireApiAccess } from "../../../../../lib/api/auth"; import type { ArticleCaseInputPayload, OptimizationCaseDetail, ProcessSummaryStep, } from "../../../../../lib/cases/types"; import { createProcessStep, excerpt } from "../../../../../lib/cases/summaries"; import { getRepositoryFromRuntime, type AppRepository, } from "../../../../../lib/db/repository"; import { articleInputSchema, copyOptimizationRequestSchema, optimizationFactCardSchema, } from "../../../../../lib/domain/validation"; import type { LlmAuditSummary } from "../../../../../lib/llm/audit"; import { LlmValidationError } from "../../../../../lib/llm/client"; import { getExportStoreFromRuntime } from "../../../../../lib/workflow/export-store"; import { extractCandidateFactCard } from "../../../../../lib/workflow/fact-extractor"; import { runOptimizationWorkflow } from "../../../../../lib/workflow/orchestrator"; import { optimizeRenweiCopy } from "../../../../../lib/workflow/renwei-copy-optimizer"; interface RouteContext { params: Promise<{ caseId: string }>; } export async function POST(request: Request, context: RouteContext) { const access = requireApiAccess(request); if (!access.ok) { return access.response; } const { caseId } = await context.params; const repository = getRepositoryFromRuntime(); const detail = await repository.getOptimizationCaseDetail(caseId); if (!detail?.input) { return NextResponse.json({ error: "Case not found" }, { status: 404 }); } const caseDetail = { ...detail, input: detail.input } satisfies CaseDetailWithInput; if (detail.case.case_type === "article") { return rerunArticleCase(repository, caseDetail); } if (detail.case.case_type === "human_copy") { return rerunHumanCopyCase(repository, caseDetail); } return NextResponse.json({ error: "Unsupported case type" }, { status: 400 }); } type CaseDetailWithInput = OptimizationCaseDetail & { input: NonNullable; }; async function rerunHumanCopyCase( repository: AppRepository, detail: CaseDetailWithInput, ) { const payload = copyOptimizationRequestSchema.parse(detail.input.payload); const startedAt = Date.now(); const llmAuditSummary: LlmAuditSummary[] = []; try { const result = await optimizeRenweiCopy(payload, { onAuditSummary: (summary) => { llmAuditSummary.push(summary); }, }); const resultVersion = await repository.createOptimizationResultVersion({ case_id: detail.case.id, case_type: "human_copy", status: "optimized", article_job_id: null, article_revision: null, result_summary: excerpt(result.optimized_text), payload: result, process_summary: [ createProcessStep({ stage: "human_copy_optimize", startedAt, endedAt: Date.now(), status: "success", producedResultVersion: true, }), ], llm_audit_summary: llmAuditSummary, error_stage: null, error_summary: null, }); return NextResponse.json( { case: { id: detail.case.id, case_type: "human_copy" }, result_version: { id: resultVersion.id, version: resultVersion.version }, result, }, { status: 201 }, ); } catch (error) { const message = errorMessage(error); const resultVersion = await repository.createOptimizationResultVersion({ case_id: detail.case.id, case_type: "human_copy", status: "failed", article_job_id: null, article_revision: null, result_summary: "", payload: null, process_summary: [ createProcessStep({ stage: "human_copy_optimize", startedAt, endedAt: Date.now(), status: "failed", errorSummary: message, producedResultVersion: false, }), ], llm_audit_summary: llmAuditSummary, error_stage: "human_copy_optimize", error_summary: message, }); return NextResponse.json( { error: message, case: { id: detail.case.id, case_type: "human_copy" }, result_version: { id: resultVersion.id, version: resultVersion.version }, }, { status: getErrorStatus(error) }, ); } } function errorMessage(error: unknown) { return error instanceof z.ZodError ? "请输入需要优化的文案" : error instanceof Error ? error.message : "文案优化失败"; } function getErrorStatus(error: unknown) { if (error instanceof LlmValidationError) return 502; if (error instanceof Error && /^LLM\b|provider/i.test(error.message)) return 502; return 400; } async function rerunArticleCase( repository: AppRepository, detail: CaseDetailWithInput, ) { const payload = detail.input.payload as ArticleCaseInputPayload; const articleInput = articleInputSchema.parse({ title: payload.source_title, body: payload.source_body, images: payload.image_inputs, platform: payload.publish_platform, user_instructions: payload.user_instructions, }); const llmAuditSummary: LlmAuditSummary[] = []; const processSummary: ProcessSummaryStep[] = []; const requestStartedAt = Date.now(); let jobId: string | null = null; let stage = "job"; try { const job = await repository.createArticleJob({ case_id: detail.case.id, source_title: articleInput.title, source_body: articleInput.body, image_inputs: articleInput.images, publish_platform: articleInput.platform, user_instructions: articleInput.user_instructions, }); jobId = job.id; stage = "fact_card"; const factCardStartedAt = Date.now(); const existingFactCard = detail.input.article_job_id ? await repository.getFactCard(detail.input.article_job_id) : null; const factCard = optimizationFactCardSchema.parse( payload.fact_card ?? existingFactCard ?? (await extractCandidateFactCard(articleInput, { onAuditSummary: (summary) => { llmAuditSummary.push(summary); }, })), ); processSummary.push( createProcessStep({ stage: "fact_card", startedAt: factCardStartedAt, endedAt: Date.now(), status: "success", producedResultVersion: false, }), ); const savedFactCard = await repository.saveFactCard(job.id, factCard); stage = "optimize"; const result = await runOptimizationWorkflow({ input: articleInput, factCard: savedFactCard, onAuditSummary: (summary) => { llmAuditSummary.push(summary); }, }); const optimizedArticle = await repository.saveOptimizedArticle( job.id, result.article, ); const qaReport = await repository.saveQaReport( job.id, optimizedArticle.revision ?? 1, result.qaReport, ); const exportStore = getExportStoreFromRuntime(); const exportPaths = await exportStore.writeJobExports({ jobId: job.id, article: optimizedArticle, qaReport, }); await repository.updateArticleJob(job.id, { status: "optimized", export_paths: exportPaths, }); const resultVersion = await repository.createOptimizationResultVersion({ case_id: detail.case.id, case_type: "article", status: "optimized", article_job_id: job.id, article_revision: optimizedArticle.revision ?? 1, result_summary: optimizedArticle.summary, payload: { article: optimizedArticle, qa_report: qaReport, export_paths: exportPaths, }, process_summary: processSummary, llm_audit_summary: llmAuditSummary, error_stage: null, error_summary: null, }); return NextResponse.json( { case: { id: detail.case.id, case_type: "article" }, result_version: { id: resultVersion.id, version: resultVersion.version }, optimizedArticle, qaReport, exportPaths, rewriteRounds: result.rewrite_rounds, stoppedAfterMaxRewrites: result.stopped_after_max_rewrites, timing: result.timing, }, { status: 201 }, ); } catch (error) { const message = error instanceof Error ? error.message : "文章优化失败"; const resultVersion = await repository.createOptimizationResultVersion({ case_id: detail.case.id, case_type: "article", status: "failed", article_job_id: jobId, article_revision: null, result_summary: "", payload: null, process_summary: [ ...processSummary, createProcessStep({ stage, startedAt: requestStartedAt, endedAt: Date.now(), status: "failed", errorSummary: message, producedResultVersion: false, }), ], llm_audit_summary: llmAuditSummary, error_stage: stage, error_summary: message, }); if (jobId) { await repository.updateArticleJob(jobId, { status: "failed" }); } return NextResponse.json( { error: message, case: { id: detail.case.id, case_type: "article" }, result_version: { id: resultVersion.id, version: resultVersion.version }, }, { status: getErrorStatus(error) }, ); } }