新增优化流事件契约
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