685 lines
24 KiB
TypeScript
685 lines
24 KiB
TypeScript
/**
|
||
* 提示词优化器
|
||
* 将用户简单输入转换为专业的图片编辑提示词
|
||
*
|
||
* 主要应用场景:图片编辑(抠图、换背景、增强等)
|
||
*/
|
||
|
||
// 优化结果类型
|
||
export interface OptimizeResult {
|
||
originalPrompt: string;
|
||
optimizedPrompt: string;
|
||
negativePrompt: string;
|
||
recommendedImageParams?: {
|
||
aspectRatio?: string;
|
||
imageSize?: string;
|
||
imageCount?: number;
|
||
promptExtend?: boolean;
|
||
negativePrompt?: string;
|
||
};
|
||
friendlyMessage?: string; // 友好的对话消息
|
||
}
|
||
|
||
// 子意图的友好名称和回复模板
|
||
const FRIENDLY_MESSAGES: Record<string, { name: string; template: string }> = {
|
||
remove_background: {
|
||
name: "抠图",
|
||
template: "好的,我来帮你抠图,去除背景保留主体~",
|
||
},
|
||
replace_background: {
|
||
name: "换背景",
|
||
template: "收到,我来帮你把背景换成{background}~",
|
||
},
|
||
enhance: {
|
||
name: "图片增强",
|
||
template: "明白,我来帮你提升图片清晰度~",
|
||
},
|
||
extend: {
|
||
name: "图像拓展",
|
||
template: "好的,我来帮你扩展图片边界~",
|
||
},
|
||
inpaint: {
|
||
name: "局部修改",
|
||
template: "收到,我来帮你{action}{target}~",
|
||
},
|
||
style_transfer: {
|
||
name: "风格迁移",
|
||
template: "好的,我来帮你转换成{style}风格~",
|
||
},
|
||
resize: {
|
||
name: "尺寸调整",
|
||
template: "明白,我来帮你调整图片尺寸~",
|
||
},
|
||
color_adjust: {
|
||
name: "调色",
|
||
template: "收到,我来帮你调整图片颜色~",
|
||
},
|
||
};
|
||
|
||
// 图片编辑子意图的提示词模板
|
||
const EDIT_TEMPLATES: Record<
|
||
string,
|
||
{
|
||
template: string;
|
||
negative: string;
|
||
extractors: Record<string, RegExp>;
|
||
defaults: Record<string, string>;
|
||
useOriginalPrompt?: boolean; // 🆕 是否使用原始提示词
|
||
}
|
||
> = {
|
||
remove_background: {
|
||
template:
|
||
"精准抠图:去除图片背景,保留主体完整清晰,输出透明背景PNG格式。边缘处理要求:平滑自然无锯齿,保留精细细节(如发丝、毛发、边缘纹理)。主体保持原有清晰度和色彩。",
|
||
negative: "残留背景色块,边缘毛糙,锯齿明显,主体不完整,边缘模糊,细节丢失,颜色溢出",
|
||
extractors: {},
|
||
defaults: {},
|
||
},
|
||
|
||
replace_background: {
|
||
template:
|
||
"背景替换:将图片背景替换为{background}。要求:保持主体完整清晰不变形,边缘过渡自然平滑,光影效果与新背景协调一致。{extra}",
|
||
negative: "残留原背景,边缘不自然,透视错误,主体变形,光影不协调,明显P图痕迹,边缘光晕",
|
||
extractors: {
|
||
background: /(?:换成|改成|替换为|变成|换为|改为)(.+?)(?:背景|底|$)/,
|
||
},
|
||
defaults: {
|
||
background: "纯白色(#FFFFFF)",
|
||
extra: "保留主体自然阴影效果",
|
||
},
|
||
},
|
||
|
||
enhance: {
|
||
template:
|
||
"图片增强:提升图片整体清晰度和画质。要求:保持自然色彩不失真,智能去除噪点,增强细节层次,适度锐化边缘。{extra}",
|
||
negative: "过度锐化,色彩失真,噪点增加,伪影,画质下降,不自然的对比度,颜色过饱和",
|
||
extractors: {},
|
||
defaults: {
|
||
extra: "整体效果自然真实",
|
||
},
|
||
},
|
||
|
||
extend: {
|
||
template:
|
||
"图像拓展(Outpainting):向{direction}方向扩展图片约{amount}。要求:扩展区域的内容、风格、色调与原图保持高度一致,光影过渡自然流畅,无明显接缝痕迹,透视关系正确。",
|
||
negative: "风格不一致,明显接缝,透视错误,内容突变,色调不匹配,光影断裂",
|
||
extractors: {
|
||
direction: /向?(上|下|左|右|四周|周围)/,
|
||
amount: /(\d+%?|一倍|两倍|50%|100%)/,
|
||
},
|
||
defaults: {
|
||
direction: "四周",
|
||
amount: "50%",
|
||
},
|
||
},
|
||
|
||
inpaint: {
|
||
template:
|
||
"局部修改(Inpainting):{action}图片中的{target}。要求:使用周围环境自然填充修改区域,保持整体风格、色调、光影一致,无任何修复痕迹,过渡自然。",
|
||
negative: "明显修复痕迹,风格不一致,内容错误,边缘不自然,色差明显,纹理断裂",
|
||
extractors: {
|
||
target: /(?:去除|消除|擦掉|删除|移除|P掉|p掉)(.+?)(?:,|。|$| )/,
|
||
action: /(去除|消除|擦掉|删除|移除|修改)/,
|
||
},
|
||
defaults: {
|
||
target: "指定区域内的元素",
|
||
action: "去除",
|
||
},
|
||
},
|
||
|
||
style_transfer: {
|
||
template:
|
||
"风格迁移:将图片转换为{style}风格。要求:保持主体可识别性和基本特征,色彩风格协调统一,整体效果自然美观。",
|
||
negative: "主体变形严重,特征丢失,风格不统一,色彩异常,人物不可识别",
|
||
extractors: {
|
||
style: /(?:转换为|变成|改成|转成)(.+?)(?:风格|$)/,
|
||
},
|
||
defaults: {
|
||
style: "卡通动漫",
|
||
},
|
||
},
|
||
|
||
resize: {
|
||
template:
|
||
"尺寸调整:将图片{action}至{size}。要求:保持画质清晰,比例协调,细节不丢失。",
|
||
negative: "画质损失严重,比例失调,模糊,变形",
|
||
extractors: {
|
||
size: /(\d+[x×]\d+|\d+%)/,
|
||
action: /(放大|缩小|调整|裁剪)/,
|
||
},
|
||
defaults: {
|
||
size: "目标尺寸",
|
||
action: "调整",
|
||
},
|
||
},
|
||
|
||
color_adjust: {
|
||
template:
|
||
"颜色调整:{original_request}。要求:精准修改指定部分的颜色,保持其他区域不变,色彩过渡自然协调。",
|
||
negative: "色彩失真,过度调整,不自然,偏色严重,其他区域被误改",
|
||
extractors: {
|
||
// 匹配各种颜色修改表达方式
|
||
target_color: /(?:改成|换成|变成|改为|换为|变为|调成|调整为)(.+?)(?:色|$)/,
|
||
target_object: /(?:把|将)?(.+?)(?:的)?(?:颜色|色)/,
|
||
},
|
||
defaults: {
|
||
original_request: "进行色彩优化处理",
|
||
},
|
||
// 特殊标记:使用原始请求
|
||
useOriginalPrompt: true,
|
||
},
|
||
};
|
||
|
||
import { generateText } from "ai";
|
||
|
||
// 历史消息类型
|
||
interface HistoryMessage {
|
||
role: "user" | "assistant";
|
||
content: string;
|
||
}
|
||
|
||
// LLM 优化提示词的系统提示 - 专注于精准局部编辑
|
||
const LLM_OPTIMIZER_SYSTEM_PROMPT = `你是一个专业的AI图片编辑提示词优化专家。本系统主要用于产品图片编辑,需要精准控制编辑范围。
|
||
|
||
## 核心原则:精准局部编辑
|
||
1. **只修改用户指定的部分**:明确指出要修改的对象/区域
|
||
2. **保护其他元素**:强调保持其他所有元素完全不变
|
||
3. **细节要求**:包含颜色、材质、光影、位置等具体参数
|
||
4. **自然过渡**:修改区域与周围环境自然融合
|
||
|
||
## 图片标注处理(重要!)
|
||
如果用户消息中提到"图片中有标注"或"标注区域",说明用户已经在图片上画了标记(箭头、圆圈、文字等)来指示需要修改的区域:
|
||
- **优先处理标注区域**:将标注标记的区域作为主要编辑目标
|
||
- **理解标注意图**:箭头指向的位置、圆圈圈住的区域、文字说明的内容都是用户想要修改的部分
|
||
- **强调标注**:在提示词中明确提到"按照图片中的标注/标记指示进行修改"
|
||
- **保护非标注区域**:标注以外的区域应保持完全不变
|
||
|
||
## 输出格式(严格JSON)
|
||
{
|
||
"optimizedPrompt": "精准的图片编辑指令",
|
||
"negativePrompt": "需要避免的效果"
|
||
}
|
||
|
||
## 示例
|
||
|
||
用户请求:"把椅子颜色改成绿色"
|
||
{
|
||
"optimizedPrompt": "【精准编辑】将图片中椅子的颜色改为绿色。具体要求:1.只修改椅子颜色,保持椅子的形状、材质纹理、光影反射完全不变;2.背景、地面、其他物品保持原样不做任何改动;3.绿色需自然协调,与原有光照环境匹配",
|
||
"negativePrompt": "背景改变,其他物品变色,形状变形,材质改变,光影不自然,整体色调偏移"
|
||
}
|
||
|
||
用户请求:"把背景换成白色"
|
||
{
|
||
"optimizedPrompt": "【精准编辑】将图片背景替换为纯白色(#FFFFFF)。具体要求:1.只替换背景区域,主体产品保持完全不变;2.主体边缘精准抠取,无残留背景色;3.保留主体自然投影效果",
|
||
"negativePrompt": "主体被裁切,边缘锯齿,主体颜色改变,产品变形,细节丢失"
|
||
}
|
||
|
||
用户请求:"把产品放大一点"
|
||
{
|
||
"optimizedPrompt": "【精准编辑】将图片中的产品主体等比例放大约20%。具体要求:1.保持产品清晰度和细节不丢失;2.保持产品比例不变形;3.背景自然填充延伸",
|
||
"negativePrompt": "产品模糊,比例变形,画质下降,背景突兀"
|
||
}
|
||
|
||
用户历史说"抠图",现在说"再清晰一点"
|
||
{
|
||
"optimizedPrompt": "【精准编辑】提升图片清晰度。具体要求:1.增强主体产品的锐度和细节;2.保持透明背景不变;3.不改变产品颜色和形状",
|
||
"negativePrompt": "过度锐化,噪点增加,颜色失真,边缘光晕"
|
||
}
|
||
|
||
## 关键要求
|
||
- 必须使用【精准编辑】开头,明确这是局部编辑任务
|
||
- 必须列出"具体要求"说明要改什么、不改什么
|
||
- negativePrompt 必须包含"其他元素改变"相关描述
|
||
- 结合对话历史理解用户的连续编辑意图`;
|
||
|
||
const IMAGE_GEN_OPTIMIZER_SYSTEM_PROMPT = `你是一个专业的图片生成提示词优化专家,目标是把用户自然语言改写成更稳定、高质量、可迭代的生图提示词。
|
||
|
||
## 输出格式(严格 JSON)
|
||
{
|
||
"optimizedPrompt": "适合生图模型的中文提示词",
|
||
"negativePrompt": "需要避免的效果",
|
||
"suggestedParams": {
|
||
"aspectRatio": "1:1|2:3|3:2|3:4|4:3|4:5|5:4|9:16|16:9|21:9|auto",
|
||
"imageSize": "1K|2K|4K",
|
||
"imageCount": 1,
|
||
"promptExtend": true
|
||
}
|
||
}
|
||
|
||
## 优化原则
|
||
1. 保留用户核心诉求,不擅自改题。
|
||
2. 自动补齐:主体、场景、构图、材质、光线、风格、镜头感。
|
||
3. 对电商/家居场景优先强调:真实材质、空间比例合理、商业可用。
|
||
4. negativePrompt 要包含:畸形、模糊、水印、文字错乱、额外主体、比例错误等。
|
||
5. 输出简洁可执行,不要解释过程。
|
||
6. 参数建议必须服务于“首图命中率”:人物全身/竖构图偏 9:16,横向场景偏 16:9,未明确时可用 auto。
|
||
`;
|
||
|
||
function parseOptimizerJson(responseText: string): {
|
||
optimizedPrompt?: string;
|
||
negativePrompt?: string;
|
||
suggestedParams?: {
|
||
aspectRatio?: string;
|
||
imageSize?: string;
|
||
imageCount?: number;
|
||
promptExtend?: boolean;
|
||
};
|
||
} | null {
|
||
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
|
||
if (!jsonMatch) return null;
|
||
try {
|
||
return JSON.parse(jsonMatch[0]);
|
||
} catch (error) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function sanitizeSuggestedParams(input?: {
|
||
aspectRatio?: string;
|
||
imageSize?: string;
|
||
imageCount?: number;
|
||
promptExtend?: boolean;
|
||
}) {
|
||
if (!input) return undefined;
|
||
const allowedAspectRatios = new Set([
|
||
"1:1",
|
||
"2:3",
|
||
"3:2",
|
||
"3:4",
|
||
"4:3",
|
||
"4:5",
|
||
"5:4",
|
||
"9:16",
|
||
"16:9",
|
||
"21:9",
|
||
"auto",
|
||
]);
|
||
const allowedSizes = new Set(["1K", "2K", "4K"]);
|
||
const normalizedAspectRatio = input.aspectRatio?.trim() || undefined;
|
||
const normalizedSize = input.imageSize?.trim().toUpperCase() || undefined;
|
||
const normalizedCount =
|
||
typeof input.imageCount === "number" && Number.isFinite(input.imageCount)
|
||
? Math.min(Math.max(Math.round(input.imageCount), 1), 4)
|
||
: undefined;
|
||
const output: NonNullable<OptimizeResult["recommendedImageParams"]> = {};
|
||
if (normalizedAspectRatio && allowedAspectRatios.has(normalizedAspectRatio)) {
|
||
output.aspectRatio = normalizedAspectRatio;
|
||
}
|
||
if (normalizedSize && allowedSizes.has(normalizedSize)) {
|
||
output.imageSize = normalizedSize;
|
||
}
|
||
if (normalizedCount) {
|
||
output.imageCount = normalizedCount;
|
||
}
|
||
if (typeof input.promptExtend === "boolean") {
|
||
output.promptExtend = input.promptExtend;
|
||
}
|
||
return Object.keys(output).length > 0 ? output : undefined;
|
||
}
|
||
|
||
function shouldKeepBackgroundAsIs(originalPrompt: string, subIntent?: string): boolean {
|
||
if (subIntent === "replace_background" || subIntent === "remove_background") {
|
||
return false;
|
||
}
|
||
return !/(换背景|改背景|替换背景|背景改成|背景换成|白底|黑底|透明背景|场景换成|背景变成)/i.test(
|
||
originalPrompt
|
||
);
|
||
}
|
||
|
||
function shouldKeepCameraAsIs(originalPrompt: string): boolean {
|
||
return !/(角度|视角|机位|镜头|构图|俯拍|仰拍|侧拍|旋转|翻转|远景|近景|特写)/i.test(
|
||
originalPrompt
|
||
);
|
||
}
|
||
|
||
function enforceImageEditStability(params: {
|
||
optimizedPrompt: string;
|
||
originalPrompt: string;
|
||
subIntent?: string;
|
||
negativePrompt?: string;
|
||
}) {
|
||
const keepBackground = shouldKeepBackgroundAsIs(params.originalPrompt, params.subIntent);
|
||
const keepCamera = shouldKeepCameraAsIs(params.originalPrompt);
|
||
|
||
const stabilityRules = [
|
||
"只修改用户明确要求的内容,其他元素保持不变",
|
||
keepCamera ? "保持原图拍摄角度、主体朝向、构图和透视关系不变" : "",
|
||
keepBackground
|
||
? "保持原图背景颜色与明暗不变;若原图是白底,继续保持纯白背景(#FFFFFF)"
|
||
: "",
|
||
"保持主体形状比例、材质纹理和光影一致,禁止出现黑底/灰底污染",
|
||
].filter(Boolean);
|
||
|
||
const mergedNegativePrompt = [
|
||
params.negativePrompt || "",
|
||
keepCamera ? "角度变化,构图漂移,主体旋转" : "",
|
||
keepBackground ? "背景变黑,背景变灰,背景误改,白底丢失" : "",
|
||
"主体变形,比例错误,材质改变,边缘脏污",
|
||
]
|
||
.filter(Boolean)
|
||
.join(",");
|
||
|
||
const promptWithStability = `${params.optimizedPrompt.trim()}\n\n保真约束:${stabilityRules.join(";")}。`;
|
||
|
||
return {
|
||
optimizedPrompt: promptWithStability,
|
||
negativePrompt: mergedNegativePrompt,
|
||
};
|
||
}
|
||
|
||
function fallbackImageGenPrompt(originalPrompt: string): OptimizeResult {
|
||
const normalized = originalPrompt.replace(/\s+/g, " ").trim();
|
||
const optimizedPrompt = [
|
||
"高质量图像生成:",
|
||
normalized,
|
||
"要求:主体清晰,构图平衡,光影自然,材质真实,细节丰富,整体风格统一。",
|
||
].join(" ");
|
||
|
||
return {
|
||
originalPrompt,
|
||
optimizedPrompt,
|
||
negativePrompt: "低清晰度,构图混乱,比例畸形,重复主体,手部异常,文字错乱,水印,噪点,过曝,过度锐化",
|
||
recommendedImageParams: {
|
||
aspectRatio: "auto",
|
||
imageSize: "2K",
|
||
imageCount: 1,
|
||
promptExtend: true,
|
||
},
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 🆕 使用 LLM 深度优化提示词(基于历史对话上下文)
|
||
*/
|
||
export async function optimizePromptWithLLM(params: {
|
||
originalPrompt: string;
|
||
intent: string;
|
||
subIntent?: string;
|
||
historyMessages?: HistoryMessage[];
|
||
context?: string;
|
||
}): Promise<OptimizeResult> {
|
||
const { originalPrompt, intent, subIntent, historyMessages, context } = params;
|
||
|
||
console.log("[Prompt Optimizer LLM] Input:", {
|
||
originalPrompt,
|
||
intent,
|
||
subIntent,
|
||
historyCount: historyMessages?.length || 0
|
||
});
|
||
|
||
if (intent !== "image_edit" && intent !== "image_gen") {
|
||
return {
|
||
originalPrompt,
|
||
optimizedPrompt: originalPrompt,
|
||
negativePrompt: "模糊,低质量,变形,水印,失真",
|
||
};
|
||
}
|
||
|
||
try {
|
||
// 动态导入避免循环依赖
|
||
const {
|
||
getEnabledAiModelsWithProvider,
|
||
getAgentAiModelWithProvider,
|
||
} = await import("@/lib/db/queries");
|
||
const { getLanguageModelById } = await import("./dynamic-provider");
|
||
|
||
// 提示词优化优先使用 Agent 模型(建议智普),失败时回退
|
||
const allModels = await getEnabledAiModelsWithProvider();
|
||
const agentModel = await getAgentAiModelWithProvider();
|
||
const isReasoningCompatibleProvider = (providerType: string) =>
|
||
["zhipu", "alibaba", "openai", "custom", "local"].includes(providerType);
|
||
const usableAgentModel =
|
||
agentModel && isReasoningCompatibleProvider(agentModel.provider.type)
|
||
? agentModel
|
||
: undefined;
|
||
const zhipuModel = allModels.find(
|
||
(m) => m.provider.type === "zhipu" && m.model.isEnabled && !m.model.isSubModel
|
||
);
|
||
const qwenModel = allModels.find(
|
||
(m) => m.provider.type === "alibaba" && m.model.supportsVision
|
||
);
|
||
const selectedOptimizerModel = usableAgentModel || zhipuModel || qwenModel;
|
||
|
||
if (!selectedOptimizerModel) {
|
||
console.log("[Prompt Optimizer LLM] No optimizer model available, falling back to template");
|
||
return optimizePrompt({ originalPrompt, intent, subIntent });
|
||
}
|
||
|
||
const model = await getLanguageModelById(selectedOptimizerModel.model.id);
|
||
if (!model) {
|
||
console.log("[Prompt Optimizer LLM] Failed to get model, falling back to template");
|
||
return optimizePrompt({ originalPrompt, intent, subIntent });
|
||
}
|
||
|
||
// 构建包含上下文的消息
|
||
let contextMessage = "";
|
||
if (context && context.trim().length > 0) {
|
||
contextMessage = `## 画布上下文(仅用于理解,不需要在最终提示词中复述坐标):\n${context.trim()}\n\n`;
|
||
}
|
||
if (historyMessages && historyMessages.length > 0) {
|
||
const recentHistory = historyMessages.slice(-6); // 最近3轮对话
|
||
const historyText = recentHistory.map(msg =>
|
||
`${msg.role === 'user' ? '用户' : 'AI'}: ${msg.content.substring(0, 150)}`
|
||
).join('\n');
|
||
contextMessage += `## 对话历史:\n${historyText}\n\n`;
|
||
}
|
||
|
||
const optimizerSystemPrompt =
|
||
intent === "image_gen" ? IMAGE_GEN_OPTIMIZER_SYSTEM_PROMPT : LLM_OPTIMIZER_SYSTEM_PROMPT;
|
||
|
||
const intentLabel =
|
||
intent === "image_gen" ? "图片生成" : (subIntent || "图片编辑");
|
||
|
||
const finalUserMessage = `${contextMessage}## 当前用户请求:\n"${originalPrompt}"\n\n## 识别的意图:${intentLabel}\n\n请生成优化后的提示词:`;
|
||
|
||
console.log(
|
||
"[Prompt Optimizer LLM] Calling optimizer model:",
|
||
selectedOptimizerModel.model.displayName,
|
||
`(${selectedOptimizerModel.provider.type})`
|
||
);
|
||
|
||
const { text: responseText } = await generateText({
|
||
model,
|
||
system: optimizerSystemPrompt,
|
||
prompt: finalUserMessage,
|
||
temperature: 0.2,
|
||
maxTokens: 200,
|
||
} as any);
|
||
|
||
// 解析 JSON 响应
|
||
const parsedResult = parseOptimizerJson(responseText);
|
||
if (parsedResult) {
|
||
console.log("[Prompt Optimizer LLM] Result parsed");
|
||
const stabilized =
|
||
intent === "image_edit"
|
||
? enforceImageEditStability({
|
||
optimizedPrompt: parsedResult.optimizedPrompt || originalPrompt,
|
||
originalPrompt,
|
||
subIntent,
|
||
negativePrompt: parsedResult.negativePrompt || "模糊,低质量,变形,失真",
|
||
})
|
||
: {
|
||
optimizedPrompt: parsedResult.optimizedPrompt || originalPrompt,
|
||
negativePrompt: parsedResult.negativePrompt || "模糊,低质量,变形,失真",
|
||
};
|
||
return {
|
||
originalPrompt,
|
||
optimizedPrompt: stabilized.optimizedPrompt,
|
||
negativePrompt: stabilized.negativePrompt,
|
||
recommendedImageParams: sanitizeSuggestedParams(parsedResult.suggestedParams),
|
||
friendlyMessage: intent === "image_edit" ? FRIENDLY_MESSAGES[subIntent || ""]?.template : undefined,
|
||
};
|
||
}
|
||
|
||
// 解析失败,回退到模板优化
|
||
console.log("[Prompt Optimizer LLM] Parse failed, falling back to template");
|
||
return optimizePrompt({ originalPrompt, intent, subIntent });
|
||
|
||
} catch (error) {
|
||
console.error("[Prompt Optimizer LLM] Error:", error);
|
||
// 出错时回退到模板优化
|
||
return optimizePrompt({ originalPrompt, intent, subIntent });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 优化提示词(模板方式 - 作为回退)
|
||
*/
|
||
export function optimizePrompt(params: {
|
||
originalPrompt: string;
|
||
intent: string;
|
||
subIntent?: string;
|
||
}): OptimizeResult {
|
||
const { originalPrompt, intent, subIntent } = params;
|
||
|
||
console.log("[Prompt Optimizer] Input:", { originalPrompt, intent, subIntent });
|
||
|
||
if (intent === "image_gen") {
|
||
return fallbackImageGenPrompt(originalPrompt);
|
||
}
|
||
|
||
// 只对图片编辑类意图进行优化
|
||
if (intent !== "image_edit") {
|
||
return {
|
||
originalPrompt,
|
||
optimizedPrompt: originalPrompt,
|
||
negativePrompt: "模糊,低质量,变形,水印,失真",
|
||
};
|
||
}
|
||
|
||
// 如果没有子意图,返回通用优化
|
||
if (!subIntent) {
|
||
return {
|
||
originalPrompt,
|
||
optimizedPrompt: `图片编辑:${originalPrompt}。要求:效果自然,画质清晰,细节完整。`,
|
||
negativePrompt: "模糊,低质量,变形,水印,失真,不自然",
|
||
};
|
||
}
|
||
|
||
const template = EDIT_TEMPLATES[subIntent] as any;
|
||
if (!template) {
|
||
console.log("[Prompt Optimizer] No template for subIntent:", subIntent);
|
||
return {
|
||
originalPrompt,
|
||
optimizedPrompt: originalPrompt,
|
||
negativePrompt: "模糊,低质量,变形,水印,失真",
|
||
};
|
||
}
|
||
|
||
// 🆕 对于某些子意图,直接使用原始提示词(因为用户的描述更精准)
|
||
// 例如:color_adjust - "把椅子颜色改成绿色" 比模板更精准
|
||
if (template.useOriginalPrompt) {
|
||
console.log("[Prompt Optimizer] Using original prompt for", subIntent);
|
||
const optimizedPrompt = `${originalPrompt}。要求:精准执行用户要求,保持其他区域不变,效果自然协调。`;
|
||
return {
|
||
originalPrompt,
|
||
optimizedPrompt,
|
||
negativePrompt: template.negative,
|
||
friendlyMessage: FRIENDLY_MESSAGES[subIntent]?.template,
|
||
};
|
||
}
|
||
|
||
// 使用模板生成优化后的提示词
|
||
let optimizedPrompt = template.template;
|
||
|
||
// 从用户输入中提取参数并填充模板
|
||
for (const [key, regex] of Object.entries(template.extractors)) {
|
||
const match = originalPrompt.match(regex as RegExp);
|
||
let value = match ? match[1].trim() : template.defaults[key] || "";
|
||
|
||
// 清理提取的值
|
||
value = value.replace(/[,。!?,.!?]/g, "").trim();
|
||
|
||
if (!value) {
|
||
value = template.defaults[key] || "";
|
||
}
|
||
|
||
optimizedPrompt = optimizedPrompt.replace(`{${key}}`, value);
|
||
}
|
||
|
||
// 填充剩余的默认值
|
||
for (const [key, defaultValue] of Object.entries(template.defaults)) {
|
||
optimizedPrompt = optimizedPrompt.replace(`{${key}}`, defaultValue as string);
|
||
}
|
||
|
||
// 附加用户原始意图(如果优化后的提示词没有完全覆盖用户需求)
|
||
if (originalPrompt.length > 10 && !optimizedPrompt.includes(originalPrompt)) {
|
||
optimizedPrompt = `${optimizedPrompt}\n\n用户补充要求:${originalPrompt}`;
|
||
}
|
||
|
||
// 生成友好的对话消息
|
||
let friendlyMessage = "";
|
||
const friendlyTemplate = FRIENDLY_MESSAGES[subIntent];
|
||
if (friendlyTemplate) {
|
||
friendlyMessage = friendlyTemplate.template;
|
||
|
||
// 填充友好消息中的变量
|
||
for (const [key, regex] of Object.entries(template.extractors) as [string, RegExp][]) {
|
||
const match = originalPrompt.match(regex);
|
||
let value = match ? match[1].trim() : template.defaults[key] || "";
|
||
value = value.replace(/[,。!?,.!?]/g, "").trim();
|
||
if (!value) {
|
||
value = template.defaults[key] || "";
|
||
}
|
||
friendlyMessage = friendlyMessage.replace(`{${key}}`, value);
|
||
}
|
||
|
||
// 填充剩余的默认值
|
||
for (const [key, defaultValue] of Object.entries(template.defaults) as [string, string][]) {
|
||
friendlyMessage = friendlyMessage.replace(`{${key}}`, defaultValue);
|
||
}
|
||
}
|
||
|
||
const result: OptimizeResult = {
|
||
originalPrompt,
|
||
optimizedPrompt,
|
||
negativePrompt: template.negative,
|
||
friendlyMessage: friendlyMessage || `好的,我来帮你处理这张图片~`,
|
||
};
|
||
|
||
if (intent === "image_edit") {
|
||
const stabilized = enforceImageEditStability({
|
||
optimizedPrompt: result.optimizedPrompt,
|
||
originalPrompt,
|
||
subIntent,
|
||
negativePrompt: result.negativePrompt,
|
||
});
|
||
result.optimizedPrompt = stabilized.optimizedPrompt;
|
||
result.negativePrompt = stabilized.negativePrompt;
|
||
}
|
||
|
||
console.log("[Prompt Optimizer] Output:", result);
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 提取背景描述(辅助函数)
|
||
*/
|
||
export function extractBackgroundDescription(text: string): string {
|
||
// 尝试提取用户描述的背景
|
||
const patterns = [
|
||
/(?:换成|改成|替换为|变成|换为|改为)(.+?)(?:背景|底)/,
|
||
/(.+?)(?:背景|底)$/,
|
||
/背景(?:换成|改成|替换为|变成)(.+)/,
|
||
];
|
||
|
||
for (const pattern of patterns) {
|
||
const match = text.match(pattern);
|
||
if (match && match[1]) {
|
||
return match[1].trim();
|
||
}
|
||
}
|
||
|
||
return "纯白色";
|
||
}
|
||
|
||
/**
|
||
* 检测是否需要保留阴影
|
||
*/
|
||
export function shouldPreserveShadow(text: string): boolean {
|
||
// 产品图、商品图通常需要保留阴影
|
||
if (/产品|商品|电商|白底|纯白/.test(text)) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|