ai-chatbot/lib/canvas/agent-confirmation.ts

149 lines
4.8 KiB
TypeScript

import type {
CanvasOperationPlan,
CanvasViewOperationPlan,
} from "@/types/canvas/agent-protocol";
export type PendingCanvasOperation = {
version: 1;
pendingId: string;
projectId: string;
operationId: string;
operation: string;
riskOperation: string;
targetNodeIds: string[];
operationPlan?: CanvasOperationPlan;
viewPlan?: CanvasViewOperationPlan;
prompt: string;
reply: string;
requestedAt: string;
requestedByMessageId: string;
};
function trimString(value: unknown, maxLength: number) {
return typeof value === "string" ? value.trim().slice(0, maxLength) : "";
}
function normalizeStringArray(value: unknown, maxItems: number) {
if (!Array.isArray(value)) return [];
return value
.map((item) => trimString(item, 160))
.filter(Boolean)
.slice(0, maxItems);
}
function normalizeOperationPlan(value: unknown): CanvasOperationPlan | undefined {
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
const raw = value as CanvasOperationPlan;
if (raw.version !== 1 || !raw.operationId || !raw.operation || !raw.projectId) {
return undefined;
}
return raw;
}
function normalizeViewPlan(value: unknown): CanvasViewOperationPlan | undefined {
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
const raw = value as CanvasViewOperationPlan;
if (raw.version !== 1 || !raw.viewOperationId || !raw.operation) {
return undefined;
}
return raw;
}
export function extractPendingCanvasOperationFromWorkingState(
workingState?: { lastTurnSummary?: unknown } | null
): PendingCanvasOperation | null {
const summary = workingState?.lastTurnSummary;
if (!summary || typeof summary !== "object" || Array.isArray(summary)) return null;
const pending = (summary as Record<string, unknown>).pendingCanvasOperation;
if (!pending || typeof pending !== "object" || Array.isArray(pending)) return null;
const record = pending as Record<string, unknown>;
const projectId = trimString(record.projectId, 160);
const operationId = trimString(record.operationId, 180);
const operation = trimString(record.operation, 80);
const riskOperation = trimString(record.riskOperation, 80);
const requestedByMessageId = trimString(record.requestedByMessageId, 160);
const prompt = trimString(record.prompt, 1000);
const reply = trimString(record.reply, 500);
if (!projectId || !operationId || !operation || !riskOperation || !requestedByMessageId) {
return null;
}
return {
version: 1,
pendingId: trimString(record.pendingId, 180) || operationId,
projectId,
operationId,
operation,
riskOperation,
targetNodeIds: normalizeStringArray(record.targetNodeIds, 20),
operationPlan: normalizeOperationPlan(record.operationPlan),
viewPlan: normalizeViewPlan(record.viewPlan),
prompt,
reply,
requestedAt: trimString(record.requestedAt, 80) || new Date(0).toISOString(),
requestedByMessageId,
};
}
export function isCanvasOperationConfirmText(text: string) {
const normalized = String(text || "").toLowerCase().replace(/\s+/g, "");
if (!normalized) return false;
return /确认|同意|可以执行|执行吧|就这样|没问题|按这个|开始执行|确认执行/.test(
normalized
);
}
export function isCanvasOperationCancelText(text: string) {
const normalized = String(text || "").toLowerCase().replace(/\s+/g, "");
if (!normalized) return false;
return /取消|先不|不要|别执行|不执行|算了|停止/.test(normalized);
}
export function buildPendingCanvasOperation(params: {
projectId: string;
messageId: string;
userText: string;
riskOperation: string;
targetNodeIds: string[];
reply: string;
operationPlan?: CanvasOperationPlan;
viewPlan?: CanvasViewOperationPlan;
}): PendingCanvasOperation {
const operationId =
params.operationPlan?.operationId || `${params.messageId}:pending-canvas-operation`;
return {
version: 1,
pendingId: operationId,
projectId: params.projectId,
operationId,
operation: params.operationPlan?.operation || params.riskOperation,
riskOperation: params.riskOperation,
targetNodeIds: params.targetNodeIds.slice(0, 20),
operationPlan: params.operationPlan,
viewPlan: params.viewPlan,
prompt: params.userText.slice(0, 1000),
reply: params.reply.slice(0, 500),
requestedAt: new Date().toISOString(),
requestedByMessageId: params.messageId,
};
}
export function withConfirmedCanvasOperationPlan(
pending: PendingCanvasOperation,
confirmingMessageId: string
): CanvasOperationPlan | null {
if (!pending.operationPlan) return null;
return {
...pending.operationPlan,
confirmed: true,
metadata: {
...(pending.operationPlan.metadata || {}),
confirmedByUser: true,
confirmedByMessageId: confirmingMessageId,
confirmedAt: new Date().toISOString(),
pendingOperationId: pending.pendingId,
},
};
}