fix agent video runtime dependencies
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-13 10:40:45 +08:00
parent 6f0e49bfb9
commit 37db7e2d3a
3 changed files with 350 additions and 0 deletions

View File

@ -0,0 +1,41 @@
import {
normalizeAgentWorkflowState,
type AgentWorkflowState,
} from "@/lib/ai/runtime/agent-workflow-state";
export interface AgentThreadState {
workflowState: AgentWorkflowState | null;
memoryHints: string[];
contextSummary?: string;
metadata?: Record<string, unknown>;
}
function normalizeText(value: unknown, max = 300): string | undefined {
const normalized = String(value || "").trim();
if (!normalized) return undefined;
return normalized.slice(0, max);
}
function normalizeMemoryHints(value: unknown): string[] {
if (!Array.isArray(value)) return [];
return value
.map((item) => normalizeText(item, 120))
.filter((item): item is string => Boolean(item));
}
export function normalizeAgentThreadState(
value?: Partial<AgentThreadState> | null,
): AgentThreadState | null {
if (!value || typeof value !== "object") return null;
return {
workflowState: normalizeAgentWorkflowState(value.workflowState),
memoryHints: normalizeMemoryHints(value.memoryHints),
contextSummary: normalizeText(value.contextSummary, 400),
metadata:
value.metadata && typeof value.metadata === "object" && !Array.isArray(value.metadata)
? (value.metadata as Record<string, unknown>)
: undefined,
};
}

View File

@ -0,0 +1,114 @@
export type AgentWorkflowType =
| "storyboard_video"
| "high_cost_video"
| "image_batch"
| "custom";
export type AgentWorkflowStatus =
| "draft"
| "awaiting_user_confirmation"
| "confirmed"
| "rejected"
| "running"
| "completed"
| "failed"
| "cancelled";
export interface AgentWorkflowPendingItem {
id?: string;
index?: number;
title?: string;
summary?: string;
status?: string;
}
export interface AgentWorkflowState {
workflowId: string;
type: AgentWorkflowType;
status: AgentWorkflowStatus;
title?: string;
summary?: string;
confirmPhrases: string[];
revisionHints: string[];
pendingItems: AgentWorkflowPendingItem[];
metadata?: Record<string, unknown>;
}
function normalizeText(value: unknown, max = 200): string | undefined {
const normalized = String(value || "").trim();
if (!normalized) return undefined;
return normalized.slice(0, max);
}
function normalizeList(value: unknown, fallback: string[]): string[] {
const raw = Array.isArray(value) ? value : fallback;
const normalized = raw
.map((item) => String(item || "").trim())
.filter(Boolean);
return normalized.length > 0 ? Array.from(new Set(normalized)) : fallback;
}
function normalizePendingItems(value: unknown): AgentWorkflowPendingItem[] {
if (!Array.isArray(value)) return [];
return value
.map((item) => {
if (!item || typeof item !== "object") return null;
const record = item as Record<string, unknown>;
const normalized: AgentWorkflowPendingItem = {
id: normalizeText(record.id, 80),
title: normalizeText(record.title, 80),
summary: normalizeText(record.summary, 160),
status: normalizeText(record.status, 32),
};
const rawIndex = Number(record.index);
if (Number.isFinite(rawIndex) && rawIndex > 0) {
normalized.index = Math.round(rawIndex);
}
return normalized;
})
.filter((item): item is AgentWorkflowPendingItem => Boolean(item));
}
export function normalizeAgentWorkflowState(
value?: Partial<AgentWorkflowState> | null,
): AgentWorkflowState | null {
if (!value || typeof value !== "object") return null;
const workflowId = normalizeText(value.workflowId, 120);
const type = normalizeText(value.type, 32) as AgentWorkflowType | undefined;
const status = normalizeText(value.status, 40) as AgentWorkflowStatus | undefined;
if (!workflowId || !type || !status) {
return null;
}
return {
workflowId,
type,
status,
title: normalizeText(value.title, 120),
summary: normalizeText(value.summary, 240),
confirmPhrases: normalizeList(value.confirmPhrases, [
"确认开始生成视频",
"开始生成",
"按这个分镜生成",
]),
revisionHints: normalizeList(value.revisionHints, [
"修改第3镜头",
"调整第5镜头动作",
"重生第2张高清图",
]),
pendingItems: normalizePendingItems(value.pendingItems),
metadata:
value.metadata && typeof value.metadata === "object" && !Array.isArray(value.metadata)
? (value.metadata as Record<string, unknown>)
: undefined,
};
}
export function isAwaitingUserConfirmationWorkflow(
workflowState?: AgentWorkflowState | null,
): boolean {
return workflowState?.status === "awaiting_user_confirmation";
}

View File

@ -0,0 +1,195 @@
import {
isAwaitingUserConfirmationWorkflow,
type AgentWorkflowState,
type AgentWorkflowStatus,
type AgentWorkflowType,
} from "@/lib/ai/runtime/agent-workflow-state";
import type { AgentThreadState } from "@/lib/ai/runtime/agent-thread-state";
export type HumanConfirmationIntent =
| "none"
| "confirm"
| "revise"
| "cancel"
| "unclear";
export type HumanConfirmationGateAction =
| "pass"
| "awaiting_confirmation"
| "confirmed"
| "revision_requested"
| "cancel_requested";
export interface HumanConfirmationGateDecision {
action: HumanConfirmationGateAction;
confirmationIntent: HumanConfirmationIntent;
workflowId?: string;
workflowType?: AgentWorkflowType;
nextWorkflowStatus?: AgentWorkflowStatus;
allowWorkflowExecution: boolean;
shouldBypassPlanner: boolean;
friendlyMessage?: string;
}
const CONFIRM_PATTERNS = [
/确认开始生成视频/,
/确认开始生成/,
/按这个分镜生成/,
/开始生成视频/,
/开始生成$/,
/可以开始了/,
/可以执行了/,
/就按这个/,
/没问题开始/,
/确认无误/,
];
const REVISE_PATTERNS = [
/修改第\d+镜头/,
/第\d+镜头/,
/调整第\d+镜头/,
/重生第\d+/,
/重做第\d+/,
/改一下/,
/修改/,
/调整/,
/不要这样/,
/不对/,
/不行/,
/换成/,
];
const CANCEL_PATTERNS = [
/取消/,
/先不要/,
/暂停/,
/停止/,
/先别生成/,
/不用生成了/,
];
function normalizeCompactText(text: string) {
return String(text || "").toLowerCase().replace(/\s+/g, "").trim();
}
export function detectHumanConfirmationIntent(text: string): HumanConfirmationIntent {
const normalized = normalizeCompactText(text);
if (!normalized) return "unclear";
if (CANCEL_PATTERNS.some((pattern) => pattern.test(normalized))) {
return "cancel";
}
if (REVISE_PATTERNS.some((pattern) => pattern.test(normalized))) {
return "revise";
}
if (CONFIRM_PATTERNS.some((pattern) => pattern.test(normalized))) {
return "confirm";
}
return "unclear";
}
function buildAwaitingConfirmationMessage(workflowState: AgentWorkflowState): string {
const title = workflowState.title || "当前工作流";
const summary = workflowState.summary
? `\n当前待确认内容:${workflowState.summary}`
: "";
const pendingItemsPreview = workflowState.pendingItems
.slice(0, 3)
.map((item) => {
const indexText = item.index ? `镜头${item.index}` : item.title || "待确认项";
const summaryText = item.summary ? `:${item.summary}` : "";
return `- ${indexText}${summaryText}`;
})
.join("\n");
const previewBlock = pendingItemsPreview ? `\n${pendingItemsPreview}` : "";
const confirmExample = workflowState.confirmPhrases[0] || "确认开始生成视频";
const reviseExample = workflowState.revisionHints[0] || "修改第3镜头";
return [
`当前有一个待确认流程:${title}。`,
summary ? summary.trim() : "",
previewBlock.trim(),
`如果确认无误,请回复“${confirmExample}”。`,
`如果需要修改,请直接回复类似“${reviseExample}”。`,
]
.filter(Boolean)
.join("\n");
}
function buildConfirmMessage(workflowState: AgentWorkflowState): string {
const title = workflowState.title || "当前流程";
return `已收到确认:${title} 可以进入下一步执行。我会按已确认内容继续处理。`;
}
function buildRevisionMessage(workflowState: AgentWorkflowState): string {
const title = workflowState.title || "当前流程";
return `收到修改意见。我会先更新 ${title} 的草案内容,并在重新确认前暂停执行。`;
}
function buildCancelMessage(workflowState: AgentWorkflowState): string {
const title = workflowState.title || "当前流程";
return `已收到取消指令。${title} 会继续停留在人工确认前状态,不会继续自动执行。`;
}
export function runHumanConfirmationGate(params: {
text: string;
threadState?: AgentThreadState | null;
}): HumanConfirmationGateDecision {
const workflowState = params.threadState?.workflowState;
if (!workflowState || !isAwaitingUserConfirmationWorkflow(workflowState)) {
return {
action: "pass",
confirmationIntent: "none",
allowWorkflowExecution: true,
shouldBypassPlanner: false,
};
}
const confirmationIntent = detectHumanConfirmationIntent(params.text);
const baseDecision = {
workflowId: workflowState.workflowId,
workflowType: workflowState.type,
shouldBypassPlanner: true,
};
switch (confirmationIntent) {
case "confirm":
return {
...baseDecision,
action: "confirmed",
confirmationIntent,
nextWorkflowStatus: "confirmed",
allowWorkflowExecution: true,
friendlyMessage: buildConfirmMessage(workflowState),
};
case "revise":
return {
...baseDecision,
action: "revision_requested",
confirmationIntent,
nextWorkflowStatus: "draft",
allowWorkflowExecution: false,
friendlyMessage: buildRevisionMessage(workflowState),
};
case "cancel":
return {
...baseDecision,
action: "cancel_requested",
confirmationIntent,
nextWorkflowStatus: "cancelled",
allowWorkflowExecution: false,
friendlyMessage: buildCancelMessage(workflowState),
};
case "unclear":
case "none":
default:
return {
...baseDecision,
action: "awaiting_confirmation",
confirmationIntent: "unclear",
nextWorkflowStatus: workflowState.status,
allowWorkflowExecution: false,
friendlyMessage: buildAwaitingConfirmationMessage(workflowState),
};
}
}