fix agent video runtime dependencies
This commit is contained in:
parent
6f0e49bfb9
commit
37db7e2d3a
41
lib/ai/runtime/agent-thread-state.ts
Normal file
41
lib/ai/runtime/agent-thread-state.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
|
||||
114
lib/ai/runtime/agent-workflow-state.ts
Normal file
114
lib/ai/runtime/agent-workflow-state.ts
Normal 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";
|
||||
}
|
||||
|
||||
195
lib/ai/runtime/middlewares/human-confirmation-gate.ts
Normal file
195
lib/ai/runtime/middlewares/human-confirmation-gate.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user