接入文章优化案例自动保存
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { requireApiAccess } from "../../../../../lib/api/auth";
|
||||
import { getRepositoryFromRuntime } from "../../../../../lib/db/repository";
|
||||
import { buildArticleCaseSummary, createProcessStep } from "../../../../../lib/cases/summaries";
|
||||
import { getRepositoryFromRuntime, type AppRepository } from "../../../../../lib/db/repository";
|
||||
import type { ArticleJob } from "../../../../../lib/db/repositories";
|
||||
import type { LlmAuditSummary } from "../../../../../lib/llm/audit";
|
||||
import { LlmValidationError } from "../../../../../lib/llm/client";
|
||||
import { getExportStoreFromRuntime } from "../../../../../lib/workflow/export-store";
|
||||
import { runOptimizationWorkflow } from "../../../../../lib/workflow/orchestrator";
|
||||
@@ -36,6 +39,8 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const requestStartedAt = Date.now();
|
||||
const caseId = await ensureArticleCase(repository, job);
|
||||
const llmAuditSummary: LlmAuditSummary[] = [];
|
||||
startWorkflowProgress(jobId);
|
||||
try {
|
||||
const result = await runOptimizationWorkflow({
|
||||
@@ -50,6 +55,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
onProgress: (event) => {
|
||||
recordWorkflowProgress(jobId, event);
|
||||
},
|
||||
onAuditSummary: (summary) => llmAuditSummary.push(summary),
|
||||
});
|
||||
const optimizedArticle = await repository.saveOptimizedArticle(
|
||||
jobId,
|
||||
@@ -70,8 +76,27 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
status: "optimized",
|
||||
export_paths: exportPaths,
|
||||
});
|
||||
const resultVersion = await repository.createOptimizationResultVersion({
|
||||
case_id: caseId,
|
||||
case_type: "article",
|
||||
status: "optimized",
|
||||
article_job_id: jobId,
|
||||
article_revision: optimizedArticle.revision ?? 1,
|
||||
result_summary: optimizedArticle.summary,
|
||||
payload: {
|
||||
article: optimizedArticle,
|
||||
qa_report: qaReport,
|
||||
export_paths: exportPaths,
|
||||
},
|
||||
process_summary: [],
|
||||
llm_audit_summary: llmAuditSummary,
|
||||
error_stage: null,
|
||||
error_summary: null,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
case: { id: caseId, case_type: "article" },
|
||||
resultVersion: { id: resultVersion.id, version: resultVersion.version },
|
||||
optimizedArticle,
|
||||
qaReport,
|
||||
exportPaths,
|
||||
@@ -81,6 +106,28 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "LLM optimization failed";
|
||||
await repository.createOptimizationResultVersion({
|
||||
case_id: caseId,
|
||||
case_type: "article",
|
||||
status: "failed",
|
||||
article_job_id: jobId,
|
||||
article_revision: null,
|
||||
result_summary: "",
|
||||
payload: null,
|
||||
process_summary: [
|
||||
createProcessStep({
|
||||
stage: "optimize",
|
||||
startedAt: requestStartedAt,
|
||||
endedAt: Date.now(),
|
||||
status: "failed",
|
||||
errorSummary: message,
|
||||
producedResultVersion: false,
|
||||
}),
|
||||
],
|
||||
llm_audit_summary: llmAuditSummary,
|
||||
error_stage: "optimize",
|
||||
error_summary: message,
|
||||
});
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: message,
|
||||
@@ -94,6 +141,36 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureArticleCase(
|
||||
repository: AppRepository,
|
||||
job: ArticleJob,
|
||||
) {
|
||||
if (job.case_id) return job.case_id;
|
||||
|
||||
const caseSummary = buildArticleCaseSummary({
|
||||
source_title: job.source_title,
|
||||
source_body: job.source_body,
|
||||
publish_platform: job.publish_platform,
|
||||
});
|
||||
const optimizationCase = await repository.createOptimizationCase({
|
||||
case_type: "article",
|
||||
...caseSummary,
|
||||
});
|
||||
await repository.saveCaseInput({
|
||||
case_id: optimizationCase.id,
|
||||
case_type: "article",
|
||||
article_job_id: job.id,
|
||||
payload: {
|
||||
source_title: job.source_title,
|
||||
source_body: job.source_body,
|
||||
image_inputs: job.image_inputs,
|
||||
publish_platform: job.publish_platform,
|
||||
user_instructions: job.user_instructions,
|
||||
},
|
||||
});
|
||||
return optimizationCase.id;
|
||||
}
|
||||
|
||||
function getErrorStatus(error: unknown) {
|
||||
if (error instanceof LlmValidationError) return 502;
|
||||
if (error instanceof Error && /^LLM\b|provider/i.test(error.message)) return 502;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { requireApiAccess } from "../../../../lib/api/auth";
|
||||
import type { ProcessSummaryStep } from "../../../../lib/cases/types";
|
||||
import { buildArticleCaseSummary, createProcessStep } from "../../../../lib/cases/summaries";
|
||||
import { getRepositoryFromRuntime } from "../../../../lib/db/repository";
|
||||
import { 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";
|
||||
@@ -56,11 +59,26 @@ export async function POST(request: Request) {
|
||||
};
|
||||
|
||||
let jobId: string | undefined;
|
||||
let caseId: string | undefined;
|
||||
let stage: OptimizationStreamStage = "job";
|
||||
const llmAuditSummary: LlmAuditSummary[] = [];
|
||||
const processSummary: ProcessSummaryStep[] = [];
|
||||
const requestStartedAt = Date.now();
|
||||
|
||||
try {
|
||||
const repository = getRepositoryFromRuntime();
|
||||
const caseSummary = buildArticleCaseSummary({
|
||||
source_title: normalized.articleInput.title,
|
||||
source_body: normalized.articleInput.body,
|
||||
publish_platform: normalized.articleInput.platform,
|
||||
});
|
||||
const optimizationCase = await repository.createOptimizationCase({
|
||||
case_type: "article",
|
||||
...caseSummary,
|
||||
});
|
||||
caseId = optimizationCase.id;
|
||||
const job = await repository.createArticleJob({
|
||||
case_id: optimizationCase.id,
|
||||
source_title: normalized.articleInput.title,
|
||||
source_body: normalized.articleInput.body,
|
||||
image_inputs: normalized.articleInput.images,
|
||||
@@ -68,12 +86,40 @@ export async function POST(request: Request) {
|
||||
user_instructions: normalized.articleInput.user_instructions,
|
||||
});
|
||||
jobId = job.id;
|
||||
send({ type: "job_created", job: { id: job.id } });
|
||||
await repository.saveCaseInput({
|
||||
case_id: optimizationCase.id,
|
||||
case_type: "article",
|
||||
article_job_id: job.id,
|
||||
payload: {
|
||||
source_title: normalized.articleInput.title,
|
||||
source_body: normalized.articleInput.body,
|
||||
image_inputs: normalized.articleInput.images,
|
||||
publish_platform: normalized.articleInput.platform,
|
||||
user_instructions: normalized.articleInput.user_instructions,
|
||||
},
|
||||
});
|
||||
send({
|
||||
type: "job_created",
|
||||
job: { id: job.id },
|
||||
case: { id: optimizationCase.id, case_type: "article" },
|
||||
});
|
||||
|
||||
stage = "fact_card";
|
||||
const factCardStartedAt = Date.now();
|
||||
const factCard = optimizationFactCardSchema.parse(
|
||||
payload.fact_card ??
|
||||
(await extractCandidateFactCard(normalized.articleInput)),
|
||||
(await extractCandidateFactCard(normalized.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);
|
||||
send({
|
||||
@@ -90,6 +136,7 @@ export async function POST(request: Request) {
|
||||
stage = stageForEvent(event, stage);
|
||||
send(event);
|
||||
},
|
||||
onAuditSummary: (summary) => llmAuditSummary.push(summary),
|
||||
});
|
||||
|
||||
stage = "final";
|
||||
@@ -112,19 +159,75 @@ export async function POST(request: Request) {
|
||||
status: "optimized",
|
||||
export_paths: exportPaths,
|
||||
});
|
||||
const resultVersion = await repository.createOptimizationResultVersion({
|
||||
case_id: optimizationCase.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, ...result.processSummary],
|
||||
llm_audit_summary: llmAuditSummary,
|
||||
error_stage: null,
|
||||
error_summary: null,
|
||||
});
|
||||
send({
|
||||
type: "final_ready",
|
||||
job_id: job.id,
|
||||
case: { id: optimizationCase.id, case_type: "article" },
|
||||
result_version: { id: resultVersion.id, version: resultVersion.version },
|
||||
optimized_article: optimizedArticle,
|
||||
qa_report: qaReport,
|
||||
export_paths: exportPaths,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "优化失败";
|
||||
let failedVersion:
|
||||
| { id: string; version: number }
|
||||
| undefined;
|
||||
if (caseId) {
|
||||
try {
|
||||
const repository = getRepositoryFromRuntime();
|
||||
const version = await repository.createOptimizationResultVersion({
|
||||
case_id: caseId,
|
||||
case_type: "article",
|
||||
status: "failed",
|
||||
article_job_id: jobId ?? null,
|
||||
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,
|
||||
});
|
||||
failedVersion = { id: version.id, version: version.version };
|
||||
} catch {
|
||||
failedVersion = undefined;
|
||||
}
|
||||
}
|
||||
send({
|
||||
type: "failed",
|
||||
job_id: jobId,
|
||||
case: caseId ? { id: caseId, case_type: "article" } : undefined,
|
||||
result_version: failedVersion,
|
||||
stage,
|
||||
error: error instanceof Error ? error.message : "优化失败",
|
||||
error: message,
|
||||
});
|
||||
} finally {
|
||||
controller.close();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { requireApiAccess } from "../../../lib/api/auth";
|
||||
import { buildArticleCaseSummary, createProcessStep } from "../../../lib/cases/summaries";
|
||||
import { getRepositoryFromRuntime } from "../../../lib/db/repository";
|
||||
import type { LlmAuditSummary } from "../../../lib/llm/audit";
|
||||
import { LlmValidationError } from "../../../lib/llm/client";
|
||||
import { extractCandidateFactCard } from "../../../lib/workflow/fact-extractor";
|
||||
import { normalizeInput, type RawArticleInput } from "../../../lib/workflow/input-normalizer";
|
||||
@@ -16,29 +18,78 @@ export async function POST(request: Request) {
|
||||
const payload = (await request.json()) as RawArticleInput;
|
||||
const normalized = normalizeInput(payload);
|
||||
const repository = getRepositoryFromRuntime();
|
||||
const caseSummary = buildArticleCaseSummary({
|
||||
source_title: normalized.articleInput.title,
|
||||
source_body: normalized.articleInput.body,
|
||||
publish_platform: normalized.articleInput.platform,
|
||||
});
|
||||
const optimizationCase = await repository.createOptimizationCase({
|
||||
case_type: "article",
|
||||
...caseSummary,
|
||||
});
|
||||
const job = await repository.createArticleJob({
|
||||
case_id: optimizationCase.id,
|
||||
source_title: normalized.articleInput.title,
|
||||
source_body: normalized.articleInput.body,
|
||||
image_inputs: normalized.articleInput.images,
|
||||
publish_platform: normalized.articleInput.platform,
|
||||
user_instructions: normalized.articleInput.user_instructions,
|
||||
});
|
||||
await repository.saveCaseInput({
|
||||
case_id: optimizationCase.id,
|
||||
case_type: "article",
|
||||
article_job_id: job.id,
|
||||
payload: {
|
||||
source_title: normalized.articleInput.title,
|
||||
source_body: normalized.articleInput.body,
|
||||
image_inputs: normalized.articleInput.images,
|
||||
publish_platform: normalized.articleInput.platform,
|
||||
user_instructions: normalized.articleInput.user_instructions,
|
||||
},
|
||||
});
|
||||
const factStartedAt = Date.now();
|
||||
const llmAuditSummary: LlmAuditSummary[] = [];
|
||||
const timing = {
|
||||
total_ms: 0,
|
||||
steps: [] as Array<{ label: string; duration_ms: number }>,
|
||||
};
|
||||
try {
|
||||
const candidateFactCard = await extractCandidateFactCard(normalized.articleInput);
|
||||
const candidateFactCard = await extractCandidateFactCard(
|
||||
normalized.articleInput,
|
||||
{ onAuditSummary: (summary) => llmAuditSummary.push(summary) },
|
||||
);
|
||||
const duration = Date.now() - factStartedAt;
|
||||
timing.total_ms = duration;
|
||||
timing.steps.push({ label: "事实卡提取", duration_ms: duration });
|
||||
|
||||
return NextResponse.json({ job, candidateFactCard, timing }, { status: 201 });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Request failed";
|
||||
const duration = Date.now() - factStartedAt;
|
||||
timing.total_ms = duration;
|
||||
timing.steps.push({ label: "事实卡提取", duration_ms: duration });
|
||||
await repository.createOptimizationResultVersion({
|
||||
case_id: optimizationCase.id,
|
||||
case_type: "article",
|
||||
status: "failed",
|
||||
article_job_id: job.id,
|
||||
article_revision: null,
|
||||
result_summary: "",
|
||||
payload: null,
|
||||
process_summary: [
|
||||
createProcessStep({
|
||||
stage: "fact_card",
|
||||
startedAt: factStartedAt,
|
||||
endedAt: Date.now(),
|
||||
status: "failed",
|
||||
errorSummary: message,
|
||||
producedResultVersion: false,
|
||||
}),
|
||||
],
|
||||
llm_audit_summary: llmAuditSummary,
|
||||
error_stage: "fact_card",
|
||||
error_summary: message,
|
||||
});
|
||||
return jsonError(error, getErrorStatus(error), timing);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user