feat: show backend optimization progress

This commit is contained in:
Codex
2026-06-21 23:43:03 +08:00
parent 5a627bcaa8
commit 21aa75d240
15 changed files with 549 additions and 43 deletions
+41
View File
@@ -21,6 +21,7 @@ vi.mock("../../../lib/llm/client", async () => {
import { POST as confirmFactCard } from "../jobs/[jobId]/confirm-fact-card/route";
import { GET as downloadExport } from "../jobs/[jobId]/exports/[fileName]/route";
import { POST as optimizeJob } from "../jobs/[jobId]/optimize/route";
import { GET as getJobProgress } from "../jobs/[jobId]/progress/route";
import { POST as createJob } from "../jobs/route";
const validFactCard = {
@@ -236,10 +237,50 @@ describe("job API routes", () => {
expect(body.timing.steps.map((step) => step.label)).toEqual([
"生成优化稿",
"质量检查",
"整理结果",
]);
expect(body.timing.steps.every((step) => step.duration_ms >= 0)).toBe(true);
});
it("returns backend-visible optimization progress after a job runs", async () => {
const { job } = await createJobFixture();
await confirmFactCard(
request(validFactCard),
params<{ jobId: string }>({ jobId: job.id }),
);
llmMocks.generateValidatedJson
.mockResolvedValueOnce({
title: "API LLM Optimized GEO Article",
summary:
"A official site article for Marketing teams about GEO optimization.",
body_markdown:
"Example Technology Co., Ltd. has 8 years of GEO optimization experience.",
image_suggestions: [],
changed_sections: ["title", "body"],
requires_user_confirmation: [],
})
.mockResolvedValueOnce({ checks: [] });
await optimizeJob(request({}), params<{ jobId: string }>({ jobId: job.id }));
const response = await getJobProgress(
request({}),
params<{ jobId: string }>({ jobId: job.id }),
);
const body = (await response.json()) as {
progress: { status: string; steps: Array<{ label: string; status: string }> };
};
expect(response.status).toBe(200);
expect(body.progress.status).toBe("completed");
expect(body.progress.steps.map((step) => step.label)).toEqual([
"生成优化稿",
"质量检查",
"整理结果",
]);
});
it("uses mocked LLM article output during optimize route", async () => {
const { job } = await createJobFixture();
await confirmFactCard(
@@ -5,6 +5,10 @@ import { getRepositoryFromRuntime } from "../../../../../lib/db/repository";
import { LlmValidationError } from "../../../../../lib/llm/client";
import { getExportStoreFromRuntime } from "../../../../../lib/workflow/export-store";
import { runOptimizationWorkflow } from "../../../../../lib/workflow/orchestrator";
import {
recordWorkflowProgress,
startWorkflowProgress,
} from "../../../../../lib/workflow/progress-store";
interface RouteContext {
params: Promise<{ jobId: string }>;
@@ -32,6 +36,7 @@ export async function POST(request: Request, context: RouteContext) {
}
const requestStartedAt = Date.now();
startWorkflowProgress(jobId);
try {
const result = await runOptimizationWorkflow({
input: {
@@ -42,6 +47,9 @@ export async function POST(request: Request, context: RouteContext) {
user_instructions: job.user_instructions,
},
factCard: factCardRecord,
onProgress: (event) => {
recordWorkflowProgress(jobId, event);
},
});
const optimizedArticle = await repository.saveOptimizedArticle(
jobId,
@@ -0,0 +1,18 @@
import { NextResponse } from "next/server";
import { requireApiAccess } from "../../../../../lib/api/auth";
import { getWorkflowProgress } from "../../../../../lib/workflow/progress-store";
interface RouteContext {
params: Promise<{ jobId: string }>;
}
export async function GET(request: Request, context: RouteContext) {
const access = requireApiAccess(request);
if (!access.ok) {
return access.response;
}
const { jobId } = await context.params;
return NextResponse.json({ progress: getWorkflowProgress(jobId) });
}