162 lines
6.1 KiB
TypeScript
162 lines
6.1 KiB
TypeScript
import type { AgentThreadState } from "@/lib/ai/runtime/agent-thread-state";
|
||
import type { AgentWorkflowStatus } from "@/lib/ai/runtime/agent-workflow-state";
|
||
import type { PromptMemoryEntry } from "@/lib/ai/prompt-memory";
|
||
import type { StoryboardPreferenceMemory } from "@/lib/video-agent/storyboard-preference-memory";
|
||
|
||
type StoryboardTaskDetail = {
|
||
task: {
|
||
id: string;
|
||
status: string | null;
|
||
confirmationStatus?: string | null;
|
||
hdImageStageStatus?: string | null;
|
||
gridType?: string | null;
|
||
gridRows?: number | null;
|
||
gridCols?: number | null;
|
||
currentShot: number | null;
|
||
totalShots: number | null;
|
||
sourceImageUrl?: string | null;
|
||
providerType?: string | null;
|
||
normalizedScript?: unknown;
|
||
};
|
||
shots: Array<{
|
||
shotIndex: number | null;
|
||
prompt?: string | null;
|
||
status?: string | null;
|
||
tileImageUrl?: string | null;
|
||
videoUrl?: string | null;
|
||
}>;
|
||
};
|
||
|
||
function normalizeText(value: unknown, max = 240): string | undefined {
|
||
const normalized = String(value || "").trim();
|
||
if (!normalized) return undefined;
|
||
return normalized.slice(0, max);
|
||
}
|
||
|
||
function mapStoryboardTaskStatusToWorkflowStatus(
|
||
task: StoryboardTaskDetail["task"],
|
||
): AgentWorkflowStatus {
|
||
const rawConfirmationStatus = (task as Record<string, unknown>).confirmationStatus;
|
||
const confirmationStatus = normalizeText(rawConfirmationStatus, 40);
|
||
if (confirmationStatus === "awaiting_user_confirmation") {
|
||
return "awaiting_user_confirmation";
|
||
}
|
||
if (confirmationStatus === "confirmed") {
|
||
return "confirmed";
|
||
}
|
||
if (confirmationStatus === "rejected") {
|
||
return "rejected";
|
||
}
|
||
|
||
const status = normalizeText(task.status, 40);
|
||
switch (status) {
|
||
case "pending":
|
||
return "draft";
|
||
case "running":
|
||
return "running";
|
||
case "succeeded":
|
||
case "partial_success":
|
||
return "completed";
|
||
case "failed":
|
||
return "failed";
|
||
case "cancelled":
|
||
return "cancelled";
|
||
default:
|
||
return "draft";
|
||
}
|
||
}
|
||
|
||
export function buildAgentThreadStateFromStoryboardTask(
|
||
detail: StoryboardTaskDetail | null | undefined,
|
||
options?: {
|
||
storyboardPreferenceMemory?: StoryboardPreferenceMemory | null;
|
||
promptMemory?: PromptMemoryEntry | null;
|
||
},
|
||
): AgentThreadState | null {
|
||
if (!detail?.task?.id) {
|
||
if (!options?.storyboardPreferenceMemory && !options?.promptMemory) return null;
|
||
return {
|
||
workflowState: null,
|
||
memoryHints: [
|
||
options?.storyboardPreferenceMemory
|
||
? "当前会话里存在 storyboard 偏好记忆,可在分镜/宫格任务里优先参考用户常用版式和确认习惯。"
|
||
: "",
|
||
options?.promptMemory?.optimizedPrompt
|
||
? "当前会话里存在上一轮可复用提示词;如果用户明确要求沿用上一个提示词继续做图,应优先把它作为本轮生图提示词。"
|
||
: "",
|
||
].filter(Boolean),
|
||
metadata: {
|
||
workflowSource: "storyboard_preference_memory",
|
||
storyboardPreferenceMemory: options.storyboardPreferenceMemory,
|
||
promptMemory: options?.promptMemory || undefined,
|
||
},
|
||
};
|
||
}
|
||
|
||
const totalShots = Math.max(0, Number(detail.task.totalShots || detail.shots.length || 0));
|
||
const currentShot = Math.max(0, Number(detail.task.currentShot || 0));
|
||
const gridRows = Math.max(0, Number(detail.task.gridRows || 0));
|
||
const gridCols = Math.max(0, Number(detail.task.gridCols || 0));
|
||
const gridLabel =
|
||
gridRows > 0 && gridCols > 0 ? `${gridCols}x${gridRows}` : `${totalShots} 段`;
|
||
|
||
return {
|
||
workflowState: {
|
||
workflowId: detail.task.id,
|
||
type: "storyboard_video",
|
||
status: mapStoryboardTaskStatusToWorkflowStatus(detail.task),
|
||
title: `分镜视频任务(${gridLabel})`,
|
||
summary:
|
||
totalShots > 0
|
||
? `当前分镜任务进度:第 ${Math.min(currentShot, totalShots)}/${totalShots} 段。`
|
||
: "当前存在一个分镜视频任务。",
|
||
confirmPhrases: ["确认开始生成视频", "开始生成", "按这个分镜生成"],
|
||
revisionHints: ["修改第3镜头", "调整第5镜头动作", "重生第2张高清图"],
|
||
pendingItems: detail.shots.slice(0, 9).map((shot) => ({
|
||
id:
|
||
Number.isFinite(Number(shot.shotIndex)) && Number(shot.shotIndex) > 0
|
||
? `shot-${Math.round(Number(shot.shotIndex))}`
|
||
: undefined,
|
||
index:
|
||
Number.isFinite(Number(shot.shotIndex)) && Number(shot.shotIndex) > 0
|
||
? Math.round(Number(shot.shotIndex))
|
||
: undefined,
|
||
title:
|
||
Number.isFinite(Number(shot.shotIndex)) && Number(shot.shotIndex) > 0
|
||
? `镜头 ${Math.round(Number(shot.shotIndex))}`
|
||
: "镜头",
|
||
summary: normalizeText(shot.prompt, 120),
|
||
status: normalizeText(shot.status, 32),
|
||
})),
|
||
metadata: {
|
||
providerType: normalizeText(detail.task.providerType, 40),
|
||
gridType: normalizeText(detail.task.gridType, 32),
|
||
gridRows: gridRows || undefined,
|
||
gridCols: gridCols || undefined,
|
||
sourceImageUrl: normalizeText(detail.task.sourceImageUrl, 500),
|
||
normalizedScript:
|
||
detail.task.normalizedScript &&
|
||
typeof detail.task.normalizedScript === "object" &&
|
||
!Array.isArray(detail.task.normalizedScript)
|
||
? detail.task.normalizedScript
|
||
: undefined,
|
||
},
|
||
},
|
||
memoryHints: [
|
||
"如果当前对话是在继续一个分镜视频任务,优先理解为对当前分镜流程的确认、修改或取消。",
|
||
"如果用户提到第N镜头、重生某张图、调整某个镜头,优先解释为对当前 storyboard workflow 的修订。",
|
||
],
|
||
contextSummary:
|
||
totalShots > 0
|
||
? `当前 chat 存在一个分镜视频任务,共 ${totalShots} 段,当前进度第 ${Math.min(currentShot, totalShots)}/${totalShots} 段。`
|
||
: "当前 chat 存在一个分镜视频任务。",
|
||
metadata: {
|
||
workflowSource: "video_storyboard_task",
|
||
taskId: detail.task.id,
|
||
shotCount: totalShots,
|
||
storyboardPreferenceMemory: options?.storyboardPreferenceMemory || undefined,
|
||
promptMemory: options?.promptMemory || undefined,
|
||
},
|
||
};
|
||
}
|