|
@@ -10,10 +10,17 @@ import {
|
|
|
* output is unaffected by the refactor.
|
|
* output is unaffected by the refactor.
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
-/** Appended on the retry attempt when the first LLM JSON failed to parse. */
|
|
|
|
|
|
|
+/** Appended on the retry attempt when the first LLM output failed to parse. */
|
|
|
export const JSON_TRUNCATION_NUDGE =
|
|
export const JSON_TRUNCATION_NUDGE =
|
|
|
"\n\n[重要] 你上一次的 JSON 输出因超出输出长度上限被截断,导致解析失败。请重新输出一份更精简但结构完整的 JSON:适当减少 scenes 数量、缩短每个场景的 narration 与详细描述字段,务必确保整个 JSON(含所有闭合括号)在输出上限内完整结束。";
|
|
"\n\n[重要] 你上一次的 JSON 输出因超出输出长度上限被截断,导致解析失败。请重新输出一份更精简但结构完整的 JSON:适当减少 scenes 数量、缩短每个场景的 narration 与详细描述字段,务必确保整个 JSON(含所有闭合括号)在输出上限内完整结束。";
|
|
|
|
|
|
|
|
|
|
+/** Appended on the retry attempt when the LLM output parsed as JSON but NO scene
|
|
|
|
|
+ * carried the structured github object. github templates render every repo
|
|
|
|
|
+ * scene from its `github` block; assembly drops scenes without one, so such an
|
|
|
|
|
+ * output would silently render a cover-only (~4s) video. */
|
|
|
|
|
+export const GITHUB_STRUCTURE_NUDGE =
|
|
|
|
|
+ "\n\n[重要] 你上一次的输出中没有任何场景包含必需的 github 结构化对象(github.repo / highlights / intro / review)。请重新输出完整 JSON:github 模板下每个仓库场景都必须包含完整 github 对象(github.repo 从输入的 <!-- repo-meta --> 注释块原样复制,不得改写字段或数值),不要输出缺少 github 对象的概览型场景。";
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Clip LLM-generated text fields to their schema-enforced maximums before
|
|
* Clip LLM-generated text fields to their schema-enforced maximums before
|
|
|
* validation. Clips at a sentence/clause boundary when possible so the result
|
|
* validation. Clips at a sentence/clause boundary when possible so the result
|
|
@@ -32,6 +39,20 @@ const isGithubTemplate = (t: string) =>
|
|
|
* (prompt asks for 160-230 chars; 260 leaves clip headroom, schema caps at 280). */
|
|
* (prompt asks for 160-230 chars; 260 leaves clip headroom, schema caps at 280). */
|
|
|
const narrationMax = (t: string) => (t === "github-daily-pick" ? 260 : 200);
|
|
const narrationMax = (t: string) => (t === "github-daily-pick" ? 260 : 200);
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * True when NO scene carries the structured github object. github board
|
|
|
|
|
+ * templates render EVERY content scene from its structured github block
|
|
|
|
|
+ * (extension). An LLM output where no scene carries one (e.g. it produced only
|
|
|
|
|
+ * overview scenes) would leave the document cover-only after assembly —
|
|
|
|
|
+ * checked by the generate layer so it can retry with a structure nudge.
|
|
|
|
|
+ */
|
|
|
|
|
+export function isMissingGithubScenes(input: unknown): boolean {
|
|
|
|
|
+ if (!input || typeof input !== "object") return true;
|
|
|
|
|
+ const scenes = (input as any).scenes;
|
|
|
|
|
+ if (!Array.isArray(scenes)) return true;
|
|
|
|
|
+ return !scenes.some((s: any) => s && typeof s === "object" && s.github && typeof s.github === "object");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export function applyLengthLimits(input: unknown, template: string): unknown {
|
|
export function applyLengthLimits(input: unknown, template: string): unknown {
|
|
|
if (!input || typeof input !== "object") return input;
|
|
if (!input || typeof input !== "object") return input;
|
|
|
const root = (input as any).scenes && Array.isArray((input as any).scenes)
|
|
const root = (input as any).scenes && Array.isArray((input as any).scenes)
|
|
@@ -39,9 +60,15 @@ export function applyLengthLimits(input: unknown, template: string): unknown {
|
|
|
: input;
|
|
: input;
|
|
|
if (!Array.isArray((root as any).scenes)) return input;
|
|
if (!Array.isArray((root as any).scenes)) return input;
|
|
|
|
|
|
|
|
- (root as any).scenes = (root as any).scenes.map((scene: any) => {
|
|
|
|
|
|
|
+ (root as any).scenes = (root as any).scenes.map((scene: any, index: number) => {
|
|
|
if (!scene || typeof scene !== "object") return scene;
|
|
if (!scene || typeof scene !== "object") return scene;
|
|
|
const next: any = { ...scene };
|
|
const next: any = { ...scene };
|
|
|
|
|
+ // Some compatible LLMs omit the mechanically-required scene id even though
|
|
|
|
|
+ // the prompt includes it. Keep the contract strict while repairing this
|
|
|
|
|
+ // deterministic field before schema validation.
|
|
|
|
|
+ if (typeof next.id !== "string" || next.id.trim() === "") {
|
|
|
|
|
+ next.id = `scene-${index + 1}`;
|
|
|
|
|
+ }
|
|
|
const narration = isGithubTemplate(template)
|
|
const narration = isGithubTemplate(template)
|
|
|
? stripLeadingGreeting(scene.narration)
|
|
? stripLeadingGreeting(scene.narration)
|
|
|
: scene.narration;
|
|
: scene.narration;
|