ai-chatbot/lib/ai/runtime/agent-planning-brief.ts
2026-07-08 14:48:29 +08:00

303 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

export type AgentPlanningBriefIntent =
| "image_edit"
| "vision"
| "image_gen"
| "video_gen"
| "chat";
export type AgentPlanningBriefAction =
| "respond"
| "clarify"
| "analyze_image"
| "generate_image"
| "edit_image"
| "generate_video";
export type AgentPlanningBriefTargetSource =
| "explicit_selection"
| "current_upload"
| "text_reference"
| "working_memory"
| "latest_result"
| "clarification"
| "none";
export type AgentPlanningBriefCapability =
| "respond"
| "analyze"
| "generic_edit"
| "remove_background"
| "remove_watermark"
| "enhance"
| "extend"
| "resize"
| "angle_variant"
| "local_edit"
| "box_cutout"
| "generate_image"
| "generate_video"
| "publish_canvas";
export interface AgentPlanningBriefInput {
userText: string;
intent: AgentPlanningBriefIntent;
subIntent?: string;
nextAction?: AgentPlanningBriefAction;
preferredCapability?: AgentPlanningBriefCapability;
targetSource?: AgentPlanningBriefTargetSource;
hasImage?: boolean;
imageFromHistory?: boolean;
selectedImageCount?: number;
currentUploadImageCount?: number;
historyImageCount?: number;
imageCount?: number;
aspectRatio?: string;
imageSize?: string;
planGoal?: string;
}
export interface AgentTaskGroup {
label: string;
count?: number;
description: string;
}
const CHINESE_NUMBER_VALUES: Record<string, number> = {
一: 1,
二: 2,
两: 2,
三: 3,
四: 4,
五: 5,
六: 6,
七: 7,
八: 8,
九: 9,
十: 10,
};
function compactLine(value: string, maxLength: number) {
const normalized = String(value || "").replace(/\s+/g, " ").trim();
if (normalized.length <= maxLength) return normalized;
return `${normalized.slice(0, maxLength)}...`;
}
function parseCountToken(token?: string | null) {
const value = String(token || "").trim();
if (!value) return undefined;
if (/^\d+$/.test(value)) {
const numeric = Number(value);
return Number.isFinite(numeric) && numeric > 0 ? Math.round(numeric) : undefined;
}
return CHINESE_NUMBER_VALUES[value];
}
function countFromText(text: string) {
const match = String(text || "").match(/([1-9]\d?|一|二|两|三|四|五|六|七|八|九|十)\s*张/);
return parseCountToken(match?.[1]);
}
function normalizeGroupDescription(description: string) {
return String(description || "")
.replace(/^[为是::,,\s]+/, "")
.replace(/[。;;,,]\s*$/, "")
.trim();
}
export function extractAgentTaskGroups(text: string): AgentTaskGroup[] {
const normalized = String(text || "").replace(/\s+/g, " ").trim();
if (!normalized) return [];
const groups: AgentTaskGroup[] = [];
const groupPattern =
/第([一二两三四五六七八九十]|\d{1,2})组(?:为|是|:|:)?([\s\S]*?)(?=(?:第[一二两三四五六七八九十\d]{1,2}组)|[。;;]|$)/g;
let match: RegExpExecArray | null;
while ((match = groupPattern.exec(normalized)) !== null) {
const labelToken = match[1];
const description = normalizeGroupDescription(match[2] || "");
if (!description) continue;
const labelNumber = parseCountToken(labelToken);
groups.push({
label: `第${labelNumber || labelToken}组`,
count: countFromText(description),
description: compactLine(description, 96),
});
}
return groups.slice(0, 8);
}
function getTargetSourceLabel(source?: AgentPlanningBriefTargetSource) {
switch (source) {
case "explicit_selection":
return "画布选中图片";
case "current_upload":
return "当前上传图片";
case "text_reference":
return "文本指定图片";
case "working_memory":
return "会话工作记忆图片";
case "latest_result":
return "最近生成结果";
case "clarification":
return "待确认目标";
case "none":
default:
return "无需现有图片";
}
}
function getCapabilityLabel(capability?: AgentPlanningBriefCapability, subIntent?: string) {
switch (capability || subIntent) {
case "remove_background":
return "主体抠图/去背景";
case "remove_watermark":
return "去水印";
case "enhance":
return "清晰度增强";
case "extend":
return "扩图/画幅延展";
case "resize":
return "尺寸调整";
case "angle_variant":
case "generate_angle_variant":
return "多视角变体";
case "local_edit":
case "inpaint_region":
return "局部编辑";
case "box_cutout":
return "框选抠图";
case "generate_video":
return "视频生成";
case "generate_image":
return "图片生成";
case "analyze":
return "图片分析";
default:
return "图片编辑";
}
}
function inferTotalCount(params: AgentPlanningBriefInput, groups: AgentTaskGroup[]) {
if (typeof params.imageCount === "number" && Number.isFinite(params.imageCount)) {
return Math.max(1, Math.round(params.imageCount));
}
const groupedTotal = groups.reduce((sum, group) => sum + (group.count || 0), 0);
return groupedTotal > 0 ? groupedTotal : undefined;
}
function buildIntentLine(params: AgentPlanningBriefInput, groups: AgentTaskGroup[]) {
const target = getTargetSourceLabel(params.targetSource);
const capability = getCapabilityLabel(params.preferredCapability, params.subIntent);
const totalCount = inferTotalCount(params, groups);
const demand = compactLine(params.planGoal || params.userText || "", 120);
if (params.intent === "image_gen") {
const countText = totalCount ? `,共 ${totalCount} 张` : "";
const groupText = groups.length > 0 ? `,拆成 ${groups.length} 组` : "";
const referenceText = params.hasImage ? ",并保持参考图主体核心特征一致" : "";
return `用户需要生成新图片${groupText}${countText}${referenceText};核心目标是:${demand || "完成本轮生图任务"}。`;
}
if (params.intent === "image_edit") {
return `用户需要基于${target}执行${capability};核心目标是:${demand || "完成本轮图片处理"}。`;
}
if (params.intent === "video_gen") {
return `用户需要生成视频;先拆解镜头、主体连续性和运动节奏,再提交视频生成。`;
}
if (params.intent === "vision") {
return `用户需要理解或分析图片内容;先观察主体、场景、构图和潜在问题,再给出结论。`;
}
return `用户当前主要是在对话或咨询;先回答问题,不主动触发生图或修图工具。`;
}
function buildImageGenerationPlan(params: AgentPlanningBriefInput, groups: AgentTaskGroup[]) {
if (groups.length > 0) {
return groups.map((group, index) => {
const countText = group.count ? `${group.count}张` : "一组";
return `${index + 1}. ${group.label}:生成${countText}${group.description.replace(/^\d+\s*张/, "")},保持主体识别一致。`;
});
}
const count = inferTotalCount(params, groups) || 1;
return [
`1. 锁定主体、场景、风格和构图约束,整理成可执行提示词。`,
`2. 生成 ${count} 张候选图,优先保证主体一致性和画面完成度。`,
`3. 输出结果并保留可继续追改的上下文。`,
];
}
function buildImageEditPlan(params: AgentPlanningBriefInput) {
const target = getTargetSourceLabel(params.targetSource);
const capability = getCapabilityLabel(params.preferredCapability, params.subIntent);
return [
`1. 先锁定${target}作为本轮处理目标,避免误处理其他图片。`,
`2. 调用${capability}能力执行核心改动,同时尽量保留主体结构和画质。`,
`3. 输出处理结果,并保留原图用于对比或继续微调。`,
];
}
function buildGenericPlan(params: AgentPlanningBriefInput) {
if (params.intent === "video_gen") {
return [
"1. 拆解视频主体、场景、动作节奏和镜头关系。",
"2. 生成脚本与分镜,并整理可直接提交的视频提示词。",
"3. 按规划提交视频生成,返回结果后支持继续调整。",
];
}
if (params.intent === "vision") {
return [
"1. 先识别图片主体、背景、构图和关键信息。",
"2. 结合用户问题输出判断、建议或可执行修改方向。",
];
}
return ["1. 先直接回答用户问题;如果需要执行工具,再根据明确指令进入下一步。"];
}
function buildOutputSettings(params: AgentPlanningBriefInput, groups: AgentTaskGroup[]) {
const lines: string[] = [];
const totalCount = inferTotalCount(params, groups);
if (params.intent === "image_gen" || params.intent === "image_edit") {
lines.push(`- 输出数量:${params.intent === "image_edit" ? "1 张处理结果" : `${totalCount || 1} 张`}`);
lines.push(`- 图片比例:${params.aspectRatio && params.aspectRatio !== "auto" ? params.aspectRatio : "按需求自适应"}`);
lines.push(`- 清晰度:${params.imageSize || (params.intent === "image_edit" ? "4K优先" : "2K优先")}`);
if (params.intent === "image_edit") {
lines.push(`- 目标来源:${getTargetSourceLabel(params.targetSource)}`);
}
} else if (params.intent === "video_gen") {
lines.push("- 输出形式:视频结果 + 可继续追改的分镜上下文");
} else {
lines.push("- 输出形式:直接回答,不主动触发生成工具");
}
return lines;
}
export function buildAgentPlanningBriefMarkdown(params: AgentPlanningBriefInput) {
const groups = extractAgentTaskGroups(params.userText);
const planLines =
params.intent === "image_gen"
? buildImageGenerationPlan(params, groups)
: params.intent === "image_edit"
? buildImageEditPlan(params)
: buildGenericPlan(params);
const outputSettings = buildOutputSettings(params, groups);
return [
"### 意图分析",
`- ${buildIntentLine(params, groups)}`,
"### 任务规划",
...planLines.map((line) => (line.startsWith("- ") ? line : `- ${line}`)),
"### 输出设置",
...outputSettings,
].join("\n");
}
export function isAgentPlanningBriefMarkdown(text: string) {
const normalized = String(text || "");
return /###\s*意图分析|【\s*意图分析\s*】/.test(normalized) &&
/###\s*任务规划|【\s*任务规划\s*】/.test(normalized);
}