179 lines
5.5 KiB
TypeScript
179 lines
5.5 KiB
TypeScript
import { z } from "zod";
|
|
import type { CanvasOperationPlan } from "@/types/canvas/agent-protocol";
|
|
|
|
export const canvasAgentOperationPlanSchema = z
|
|
.object({
|
|
version: z.literal(1),
|
|
operationId: z.string().min(1).max(160),
|
|
operation: z.enum([
|
|
"create_agent_slot",
|
|
"fill_slot",
|
|
"insert_beside",
|
|
"append_revision",
|
|
"replace_node_media",
|
|
"add_annotation",
|
|
"link_nodes",
|
|
"move_nodes",
|
|
"delete_nodes",
|
|
"rename_node",
|
|
"update_text",
|
|
]),
|
|
projectId: z.string().uuid().optional(),
|
|
baseCanvasRevision: z.string().max(160).optional(),
|
|
baseCanvasUpdatedAt: z.string().max(160).optional(),
|
|
sourceNodeIds: z.array(z.string().min(1).max(160)).max(20).optional(),
|
|
targetNodeIds: z.array(z.string().min(1).max(160)).max(20).optional(),
|
|
targetSlotId: z.string().min(1).max(160).optional(),
|
|
anchorNodeId: z.string().min(1).max(160).optional(),
|
|
edgeType: z.enum(["default", "reference", "conversation"]).optional(),
|
|
textContent: z.string().min(1).max(4000).optional(),
|
|
confirmed: z.boolean().optional(),
|
|
layoutStrategy: z.enum(["row", "column", "grid"]).optional(),
|
|
spacing: z.number().positive().max(240).optional(),
|
|
targetPosition: z
|
|
.object({
|
|
x: z.number(),
|
|
y: z.number(),
|
|
})
|
|
.optional(),
|
|
slot: z
|
|
.object({
|
|
slotType: z.enum(["image", "video", "ui-screen"]),
|
|
label: z.string().max(120).optional(),
|
|
aspectRatio: z.string().max(32).optional(),
|
|
fillPolicy: z
|
|
.enum(["replace-empty", "append-version", "replace-existing"])
|
|
.optional(),
|
|
acceptedSources: z
|
|
.array(z.enum(["text", "image", "selection", "annotation"]))
|
|
.max(4)
|
|
.optional(),
|
|
})
|
|
.optional(),
|
|
annotations: z
|
|
.array(
|
|
z.object({
|
|
id: z.string().min(1).max(128).optional(),
|
|
sourceNodeId: z.string().min(1).max(160).optional(),
|
|
type: z.enum(["text-note", "arrow-note", "box", "freehand", "crop-box"]),
|
|
text: z.string().max(1000).optional(),
|
|
bounds: z
|
|
.object({
|
|
x: z.number(),
|
|
y: z.number(),
|
|
width: z.number(),
|
|
height: z.number(),
|
|
})
|
|
.optional(),
|
|
targetPoint: z
|
|
.object({
|
|
x: z.number(),
|
|
y: z.number(),
|
|
})
|
|
.optional(),
|
|
color: z.string().max(64).optional(),
|
|
createdAt: z.string().max(80).optional(),
|
|
})
|
|
)
|
|
.max(20)
|
|
.optional(),
|
|
placement: z
|
|
.enum(["right", "left", "below", "above", "viewport-center"])
|
|
.optional(),
|
|
placementStrategy: z
|
|
.enum(["single", "row", "grid", "overlay", "beside_each_source"])
|
|
.optional(),
|
|
preserveOriginal: z.boolean().optional(),
|
|
media: z
|
|
.object({
|
|
type: z.enum(["image", "video"]),
|
|
url: z.string().url(),
|
|
assetId: z.string().uuid().optional(),
|
|
width: z.number().positive().optional(),
|
|
height: z.number().positive().optional(),
|
|
title: z.string().max(256).optional(),
|
|
prompt: z.string().max(2000).optional(),
|
|
sourceNodeIds: z.array(z.string().min(1).max(160)).max(20).optional(),
|
|
metadata: z.record(z.unknown()).optional(),
|
|
})
|
|
.optional(),
|
|
mediaItems: z
|
|
.array(
|
|
z.object({
|
|
mediaItemId: z.string().min(1).max(160),
|
|
type: z.enum(["image", "video"]),
|
|
url: z.string().url(),
|
|
assetId: z.string().uuid().optional(),
|
|
width: z.number().positive().optional(),
|
|
height: z.number().positive().optional(),
|
|
title: z.string().max(256).optional(),
|
|
prompt: z.string().max(2000).optional(),
|
|
sourceNodeIds: z.array(z.string().min(1).max(160)).max(20).optional(),
|
|
metadata: z.record(z.unknown()).optional(),
|
|
})
|
|
)
|
|
.max(20)
|
|
.optional(),
|
|
metadata: z.record(z.unknown()).optional(),
|
|
})
|
|
.refine(
|
|
(value) =>
|
|
value.operation === "create_agent_slot" ||
|
|
value.operation === "link_nodes" ||
|
|
value.operation === "update_text" ||
|
|
value.operation === "add_annotation" ||
|
|
value.operation === "move_nodes" ||
|
|
value.operation === "delete_nodes" ||
|
|
value.operation === "rename_node" ||
|
|
value.media ||
|
|
(value.mediaItems?.length ?? 0) > 0,
|
|
{
|
|
message: "缺少媒体结果",
|
|
path: ["mediaItems"],
|
|
}
|
|
)
|
|
.refine(
|
|
(value) =>
|
|
value.operation !== "add_annotation" ||
|
|
Boolean(String(value.textContent || "").trim()) ||
|
|
(value.annotations?.length ?? 0) > 0,
|
|
{
|
|
message: "缺少批注内容",
|
|
path: ["annotations"],
|
|
}
|
|
)
|
|
.refine(
|
|
(value) =>
|
|
value.operation !== "update_text" ||
|
|
Boolean(String(value.textContent || "").trim()),
|
|
{
|
|
message: "缺少文本内容",
|
|
path: ["textContent"],
|
|
}
|
|
)
|
|
.refine(
|
|
(value) =>
|
|
value.operation !== "rename_node" ||
|
|
Boolean(String(value.textContent || "").trim()),
|
|
{
|
|
message: "缺少节点标题",
|
|
path: ["textContent"],
|
|
}
|
|
);
|
|
|
|
export const canvasAgentOperationRequestSchema = z.object({
|
|
operationPlan: canvasAgentOperationPlanSchema,
|
|
});
|
|
|
|
export type CanvasAgentOperationRequestBody = {
|
|
operationPlan: CanvasOperationPlan;
|
|
};
|
|
|
|
export function parseCanvasAgentOperationRequestBody(
|
|
value: unknown
|
|
): CanvasAgentOperationRequestBody {
|
|
return canvasAgentOperationRequestSchema.parse(
|
|
value
|
|
) as CanvasAgentOperationRequestBody;
|
|
}
|