新增优化流事件契约
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
encodeOptimizationStreamEvent,
|
||||||
|
parseOptimizationStreamChunk,
|
||||||
|
type OptimizationStreamEvent,
|
||||||
|
} from "../stream-events";
|
||||||
|
|
||||||
|
describe("optimization stream events", () => {
|
||||||
|
it("encodes each event as one JSON line", () => {
|
||||||
|
const event: OptimizationStreamEvent = {
|
||||||
|
type: "draft_started",
|
||||||
|
job_id: "job_123",
|
||||||
|
message: "正在生成优化草稿",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(encodeOptimizationStreamEvent(event)).toBe(
|
||||||
|
'{"type":"draft_started","job_id":"job_123","message":"正在生成优化草稿"}\n',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses chunked NDJSON while preserving incomplete lines", () => {
|
||||||
|
const first = parseOptimizationStreamChunk(
|
||||||
|
"",
|
||||||
|
'{"type":"job_created","job":{"id":"job_',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(first.events).toEqual([]);
|
||||||
|
expect(first.remainder).toBe('{"type":"job_created","job":{"id":"job_');
|
||||||
|
|
||||||
|
const second = parseOptimizationStreamChunk(
|
||||||
|
first.remainder,
|
||||||
|
'123"}}\n{"type":"draft_started","job_id":"job_123","message":"正在生成"}\n{"type":"qa_started"',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(second.events).toEqual([
|
||||||
|
{ type: "job_created", job: { id: "job_123" } },
|
||||||
|
{ type: "draft_started", job_id: "job_123", message: "正在生成" },
|
||||||
|
]);
|
||||||
|
expect(second.remainder).toBe('{"type":"qa_started"');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import type {
|
||||||
|
OptimizationFactCard,
|
||||||
|
OptimizedArticle,
|
||||||
|
QaReport,
|
||||||
|
} from "../domain/types";
|
||||||
|
|
||||||
|
export type OptimizationStreamStage =
|
||||||
|
| "input"
|
||||||
|
| "job"
|
||||||
|
| "fact_card"
|
||||||
|
| "draft"
|
||||||
|
| "qa"
|
||||||
|
| "rewrite"
|
||||||
|
| "final";
|
||||||
|
|
||||||
|
export type OptimizationStreamEvent =
|
||||||
|
| { type: "job_created"; job: { id: string } }
|
||||||
|
| {
|
||||||
|
type: "fact_card_ready";
|
||||||
|
job_id: string;
|
||||||
|
fact_card: OptimizationFactCard;
|
||||||
|
}
|
||||||
|
| { type: "draft_started"; job_id: string; message: string }
|
||||||
|
| { type: "draft_ready"; job_id: string; article: OptimizedArticle }
|
||||||
|
| { type: "qa_started"; job_id: string; message: string }
|
||||||
|
| { type: "qa_ready"; job_id: string; qa_report: QaReport }
|
||||||
|
| { type: "rewrite_started"; job_id: string; round: number }
|
||||||
|
| {
|
||||||
|
type: "rewrite_ready";
|
||||||
|
job_id: string;
|
||||||
|
round: number;
|
||||||
|
article: OptimizedArticle;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "final_ready";
|
||||||
|
job_id: string;
|
||||||
|
optimized_article: OptimizedArticle;
|
||||||
|
qa_report: QaReport;
|
||||||
|
export_paths: Record<string, string>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "failed";
|
||||||
|
job_id?: string;
|
||||||
|
stage: OptimizationStreamStage;
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function encodeOptimizationStreamEvent(
|
||||||
|
event: OptimizationStreamEvent,
|
||||||
|
) {
|
||||||
|
return `${JSON.stringify(event)}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseOptimizationStreamChunk(
|
||||||
|
previousRemainder: string,
|
||||||
|
chunk: string,
|
||||||
|
) {
|
||||||
|
const text = previousRemainder + chunk;
|
||||||
|
const lines = text.split(/\n/);
|
||||||
|
const remainder = lines.pop() ?? "";
|
||||||
|
const events = lines
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((line) => JSON.parse(line) as OptimizationStreamEvent);
|
||||||
|
|
||||||
|
return { events, remainder };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user