Files
Jyotisha/frontend/src/lib/personal-report-longform-appendix.ts
T
jesse-uxandGrok (agent loop) d2296c2654 把前后端共用的请求契约和小工具收成一份,避免再各抄一份。
报告创建请求、校正入口和同名类型改从 contracts 推导;同义的对象判断与 sha256 改走共享函数。调用处用 import 别名保留原名,使 diff 低于 1500 行。行为、哈希和状态字符串不变。

Co-Authored-By: Grok (agent loop)
2026-10-15 22:22:44 +08:00

92 lines
2.9 KiB
TypeScript

import { sha256Hex } from "./hash";
export const LONGFORM_APPENDIX_TABLE = "personal_report_longform_appendices";
export const LONGFORM_APPENDIX_MAX_ATTEMPTS = 2;
export type LongformAppendixStatus = "pending" | "ready" | "unavailable";
export type LongformAppendixRow = Readonly<{
reportId: string;
userId: string;
requestId: string;
status: LongformAppendixStatus;
markdown: string | null;
contentSha256: string | null;
attemptCount: number;
lastErrorCode: string | null;
}>;
export type LongformAppendixWrite = Readonly<{
status: LongformAppendixStatus;
attemptCount: number;
markdown: string | null;
contentSha256: string | null;
lastErrorCode: string | null;
}>;
export function hashLongformMarkdown(markdown: string): string {
return sha256Hex(markdown);
}
export function parseLongformAppendixRow(value: unknown): LongformAppendixRow | null {
if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
const row = value as Record<string, unknown>;
const reportId = text(row.report_id ?? row.reportId);
const userId = text(row.user_id ?? row.userId);
const requestId = text(row.request_id ?? row.requestId);
const status = row.status;
if (!reportId || !userId || !requestId) return null;
if (status !== "pending" && status !== "ready" && status !== "unavailable") return null;
const markdown = typeof row.markdown === "string" && row.markdown.trim() ? row.markdown : null;
const contentSha256 = text(row.content_sha256 ?? row.contentSha256);
const attemptCount = typeof row.attempt_count === "number" && Number.isFinite(row.attempt_count)
? row.attempt_count
: typeof row.attemptCount === "number" && Number.isFinite(row.attemptCount)
? row.attemptCount
: 0;
return {
reportId,
userId,
requestId,
status,
markdown,
contentSha256,
attemptCount,
lastErrorCode: text(row.last_error_code ?? row.lastErrorCode),
};
}
export function nextLongformAppendixState(input: Readonly<{
current: LongformAppendixRow | null;
successMarkdown?: string | null;
errorCode?: string | null;
}>): LongformAppendixWrite {
const successMarkdown = input.successMarkdown?.trim() ? input.successMarkdown : null;
if (successMarkdown) {
return {
status: "ready",
attemptCount: input.current?.attemptCount ?? 0,
markdown: successMarkdown,
contentSha256: hashLongformMarkdown(successMarkdown),
lastErrorCode: null,
};
}
const attemptCount = Math.min(
(input.current?.attemptCount ?? 0) + 1,
LONGFORM_APPENDIX_MAX_ATTEMPTS,
);
return {
status: attemptCount >= LONGFORM_APPENDIX_MAX_ATTEMPTS ? "unavailable" : "pending",
attemptCount,
markdown: null,
contentSha256: null,
lastErrorCode: input.errorCode ?? "generation_failed",
};
}
function text(value: unknown): string | null {
if (typeof value !== "string") return null;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
}