/** * 智能意图分析器 * 分析用户输入,识别图片编辑意图 * * 主要应用场景:图片编辑(抠图、换背景、增强等) */ import { generateText } from "ai"; // 使用动态导入避免循环依赖问题 // import { getLanguageModelById } from "./dynamic-provider"; // import { getEnabledAiModelsWithProvider } from "@/lib/db/queries"; // 分析结果类型 export interface AnalysisResult { intent: "image_edit" | "vision" | "image_gen" | "video_gen" | "chat"; subIntent?: string; confidence: number; optimizedPrompt?: string; negativePrompt?: string; suggestedModel?: string; needsClarification?: boolean; clarifyingQuestions?: string[]; conversationalReply?: string; // 🆕 自然对话回复 } // 系统提示词 - 针对图片编辑场景优化,生成自然对话 const ANALYZER_SYSTEM_PROMPT = `你是一个友好的AI图片编辑助手。用户会给你发送图片和编辑请求,你需要: 1. 理解用户想要对图片做什么 2. 用自然、友好的语气回复用户,告诉他你理解了他的需求并会帮他处理 3. 同时输出结构化的意图分析结果 ## 意图分类 ### image_edit - 图片编辑(用户上传了图片并想编辑) 子意图: - remove_background: 抠图、去背景、透明背景 - replace_background: 换背景、改背景、白底 - enhance: 变清晰、高清、锐化、降噪 - extend: 扩展、拓展、延伸画布 - inpaint: 去除某物、消除、擦掉、P掉、局部修改 - style_transfer: 风格化、卡通化、动漫风、改风格 - color_adjust: 调色、调整颜色、亮度、对比度 - redo: 用户不满意之前的结果,想重新处理 ### vision - 图片理解(用户想让你看图说话) ### image_gen - 图片生成(用户想生成新图片) ### video_gen - 视频生成 ### chat - 普通对话 ## 特殊场景:连续对话(用户没有发送新图片,系统自动使用历史图片) 当用户继续对话但没有发送新图片时,系统会自动找到之前的图片。这时候: - 用户可能在继续描述想要的效果(如:"换成蓝色"、"再清晰一点"、"把背景去掉") - 用户可能在修改之前的结果(如:"不满意"、"再调整"、"换个风格") - 用户可能在追问(如:"还可以再亮一点吗"、"能不能换个颜色") 处理方式: - intent 通常是 image_edit - subIntent 根据用户的具体要求判断 - conversationalReply 要自然地回应用户,不需要特别强调"找到了之前的图片" ## 输出格式(严格JSON) { "intent": "image_edit", "subIntent": "replace_background", "confidence": 0.95, "conversationalReply": "好的,我来帮你把背景换成白色!这样产品会更突出~", "needsClarification": false, "clarifyingQuestions": [] } ## 回复风格要求 - conversationalReply 要自然、友好、简洁 - 可以加一些语气词让对话更亲切,如"好的"、"没问题"、"~"等 - 说明你理解了什么需求,让用户知道你在做什么 - 如果意图不明确,礼貌地询问用户想要什么效果 - 如果提供了画布上下文,仅用于理解,不要在 conversationalReply 中复述坐标或技术细节 ## 示例 用户:"帮我把背景换成白色" { "intent": "image_edit", "subIntent": "replace_background", "confidence": 0.95, "conversationalReply": "好的,我来帮你把背景换成纯白色,这样图片会更干净专业~", "needsClarification": false } 用户:"把这张图变清晰一点" { "intent": "image_edit", "subIntent": "enhance", "confidence": 0.9, "conversationalReply": "收到!我来帮你提升图片清晰度,稍等一下~", "needsClarification": false } 用户发了图片但没说要做什么: { "intent": "image_edit", "confidence": 0.5, "conversationalReply": "我看到你发了一张图片,你想对它做什么处理呢?比如抠图、换背景、变清晰?", "needsClarification": true } 连续对话场景 - 用户说"换成蓝色"(系统自动使用历史图片): { "intent": "image_edit", "subIntent": "color_adjust", "confidence": 0.9, "conversationalReply": "好的,我来帮你调整成蓝色~", "needsClarification": false } 连续对话场景 - 用户说"不满意,再调整一下": { "intent": "image_edit", "subIntent": "redo", "confidence": 0.8, "conversationalReply": "没问题,你想怎么调整呢?比如换个颜色、调整风格?", "needsClarification": true, "clarifyingQuestions": ["你具体想调整哪方面呢?"] } 连续对话场景 - 用户说"再清晰一点": { "intent": "image_edit", "subIntent": "enhance", "confidence": 0.9, "conversationalReply": "好的,我再帮你提升一下清晰度~", "needsClarification": false }`; // 历史消息类型 interface HistoryMessage { role: "user" | "assistant"; content: string; } /** * 分析用户意图 */ export async function analyzeIntent(params: { text: string; hasImage: boolean; imageUrls?: string[]; imageFromHistory?: boolean; // 🆕 图片是否来自历史记录 historyMessages?: HistoryMessage[]; // 🆕 历史对话消息(用于上下文理解) context?: string; // 🆕 画布上下文(仅用于理解) }): Promise { const { text, hasImage, imageUrls, imageFromHistory, historyMessages, context } = params; console.log("[Smart Analyzer] Analyzing intent:", { text, hasImage, imageCount: imageUrls?.length, imageFromHistory, historyCount: historyMessages?.length || 0 }); // 先尝试关键词快速匹配(高置信度场景直接返回,节省LLM调用) const quickResult = quickKeywordMatch(text, hasImage); if (quickResult.confidence >= 0.9) { console.log("[Smart Analyzer] Quick match result:", quickResult); return quickResult; } // 尝试使用 Qwen3 VL 进行深度分析(传入历史对话) try { const llmResult = await analyzWithLLM(text, hasImage, imageUrls, historyMessages, context); if (llmResult) { console.log("[Smart Analyzer] LLM analysis result:", llmResult); return llmResult; } } catch (error) { console.error("[Smart Analyzer] LLM analysis failed:", error); } // 回退到完整关键词分析 const fallbackResult = fallbackKeywordAnalysis(text, hasImage); console.log("[Smart Analyzer] Fallback result:", fallbackResult); return fallbackResult; } /** * 快速关键词匹配(高置信度场景) * 🆕 现在返回低置信度,强制走LLM分析以获取自然对话回复 */ function quickKeywordMatch(text: string, hasImage: boolean): AnalysisResult { // 返回低置信度,强制走LLM分析以获取自然对话回复 // 只有LLM不可用时才会使用fallback return { intent: "chat", confidence: 0.3, }; } /** * 使用 LLM 进行深度分析 */ async function analyzWithLLM( text: string, hasImage: boolean, imageUrls?: string[], historyMessages?: HistoryMessage[], context?: string ): Promise { // 使用动态导入避免循环依赖问题 const { getEnabledAiModelsWithProvider, getAgentAiModelWithProvider, } = await import("@/lib/db/queries"); const { getLanguageModelById } = await import("./dynamic-provider"); // 获取千问3 VL 模型 const allModels = await getEnabledAiModelsWithProvider(); const agentModel = await getAgentAiModelWithProvider(); const qwenModel = allModels.find( (m) => m.provider.type === "alibaba" && m.model.supportsVision ); const selectedAnalyzerModel = (agentModel && (!hasImage || agentModel.model.supportsVision) ? agentModel : undefined) || qwenModel; if (!selectedAnalyzerModel) { console.log("[Smart Analyzer] No Qwen3 VL model found"); return null; } const model = await getLanguageModelById(selectedAnalyzerModel.model.id); if (!model) { console.log("[Smart Analyzer] Failed to get language model"); return null; } // 🆕 构建包含历史上下文的消息 let contextMessage = ""; // 🆕 画布上下文(仅用于理解,不要复述) if (context && context.trim().length > 0) { contextMessage = `## 画布上下文(仅用于理解,不要在对话回复中复述):\n${context.trim()}\n\n`; } // 添加历史对话上下文(最多取最近5轮对话) if (historyMessages && historyMessages.length > 0) { const recentHistory = historyMessages.slice(-10); // 最多10条消息(约5轮对话) const historyText = recentHistory.map(msg => `${msg.role === 'user' ? '用户' : 'AI'}: ${msg.content.substring(0, 200)}` ).join('\n'); contextMessage += `## 对话历史(最近的对话上下文): ${historyText} ## 当前请求: `; } // 构建用户消息 const userMessage = hasImage ? `${contextMessage}用户上传了图片,请求内容:"${text}"` : `${contextMessage}用户请求(无图片):"${text}"`; console.log("[Smart Analyzer] Calling Qwen3 VL for analysis with", historyMessages?.length || 0, "history messages"); const visionImages = (imageUrls || []) .filter((url) => /^https?:\/\//i.test(url)) .slice(0, 4) .map((url) => ({ type: "image" as const, image: url })); // 调用模型分析(有图时携带图片,确保真实视觉分析) const requestPayload = hasImage && visionImages.length > 0 ? { model, system: ANALYZER_SYSTEM_PROMPT, messages: [ { role: "user" as const, content: [ { type: "text" as const, text: userMessage }, ...visionImages, ], }, ], temperature: 0.1, maxTokens: 360, } : { model, system: ANALYZER_SYSTEM_PROMPT, prompt: userMessage, temperature: 0.1, maxTokens: 300, }; const { text: responseText } = await generateText(requestPayload as any); // 解析JSON响应 const jsonMatch = responseText.match(/\{[\s\S]*\}/); if (jsonMatch) { try { const result = JSON.parse(jsonMatch[0]) as AnalysisResult; return result; } catch (e) { console.error("[Smart Analyzer] Failed to parse LLM response:", e); } } return null; } /** * 回退方案:完整关键词匹配(当LLM不可用时使用) * 🆕 添加了自然对话回复 */ export function fallbackKeywordAnalysis( text: string, hasImage: boolean ): AnalysisResult { const lowerText = text.toLowerCase(); // === 图片编辑场景(需要有图片) === if (hasImage) { // 抠图/去背景 if (/抠图|去背景|透明背景|去背|扣图|移除背景/.test(lowerText)) { return { intent: "image_edit", subIntent: "remove_background", confidence: 0.9, conversationalReply: "好的,我来帮你抠图,去除背景保留主体~", }; } // 换背景 if (/换背景|白底|改背景|背景换成|纯色背景|换成.*背景|替换.*背景/.test(lowerText)) { // 提取背景颜色 const bgMatch = text.match(/(?:换成|改成|替换为|变成)(.+?)(?:背景|底|$)/); const bg = bgMatch ? bgMatch[1].trim() : "白色"; return { intent: "image_edit", subIntent: "replace_background", confidence: 0.9, conversationalReply: `好的,我来帮你把背景换成${bg}~`, }; } // 图片增强 if (/变清晰|高清|锐化|降噪|提升质量|修复|清晰化|变清楚|画质/.test(lowerText)) { return { intent: "image_edit", subIntent: "enhance", confidence: 0.9, conversationalReply: "收到,我来帮你提升图片清晰度~", }; } // 图像拓展 if (/扩展|拓展|延伸|扩图|outpainting|拓宽|扩大/.test(lowerText)) { return { intent: "image_edit", subIntent: "extend", confidence: 0.9, conversationalReply: "好的,我来帮你扩展图片边界~", }; } // 局部修改/去除 if (/去除|消除|擦掉|P掉|p掉|删除|修改.*部分|移除.*(?:人|物|水印|logo|文字)/.test(lowerText)) { return { intent: "image_edit", subIntent: "inpaint", confidence: 0.9, conversationalReply: "明白,我来帮你处理这个区域~", }; } // 风格迁移 if (/风格|卡通化|动漫风|油画风|转.*风格|变.*风格|改.*风格/.test(lowerText)) { const styleMatch = text.match(/(?:转换为|变成|改成|转成)(.+?)(?:风格|$)/); const style = styleMatch ? styleMatch[1].trim() : "新"; return { intent: "image_edit", subIntent: "style_transfer", confidence: 0.9, conversationalReply: `好的,我来帮你转换成${style}风格~`, }; } // 调色/改颜色 if (/调色|调整颜色|亮度|对比度|饱和度|色调|曝光|滤镜|改成.*色|换成.*色|变成.*色|颜色改成|颜色换成|颜色变成/.test(lowerText)) { // 提取目标颜色 const colorMatch = text.match(/(?:改成|换成|变成)(.+?)(?:色|$)/); const color = colorMatch ? colorMatch[1].trim() + "色" : ""; return { intent: "image_edit", subIntent: "color_adjust", confidence: 0.9, conversationalReply: color ? `好的,我来帮你把颜色改成${color}~` : "收到,我来帮你调整图片色彩~", }; } // 尺寸调整 if (/缩放|放大|缩小|调整尺寸|裁剪|resize|crop/.test(lowerText)) { return { intent: "image_edit", subIntent: "resize", confidence: 0.9, conversationalReply: "好的,我来帮你调整图片尺寸~", }; } // 通用编辑关键词 if (/修图|P图|改图|调图|处理.*图|编辑.*图|优化.*图/.test(lowerText)) { return { intent: "image_edit", confidence: 0.8, needsClarification: true, conversationalReply: "我看到你想编辑这张图片,你具体想要做什么处理呢?比如抠图、换背景、变清晰?", }; } // 视觉理解 if (/看|识别|分析|描述|这是什么|有什么|tell me|what is|看看|帮我看/.test(lowerText)) { return { intent: "vision", confidence: 0.85, conversationalReply: "让我来看看这张图片~", }; } // 有图但意图不明确 return { intent: "image_edit", confidence: 0.5, needsClarification: true, conversationalReply: "我看到你发了一张图片,你想对它做什么处理呢?比如抠图、换背景、变清晰?", }; } // === 无图片场景 === // 图片生成 if (/画|绘制|生成图|创建.*图|设计.*图|画一个|生成一张|做一张/.test(lowerText)) { return { intent: "image_gen", confidence: 0.85, conversationalReply: "好的,我来帮你生成这张图片~", }; } // 视频生成 if (/视频|动画|动起来|生成视频|做视频|视频生成/.test(lowerText)) { return { intent: "video_gen", confidence: 0.85, conversationalReply: "好的,我来帮你生成视频~", }; } // 需要图片但没有上传 if (/抠图|去背景|换背景|修图|P图|编辑图/.test(lowerText)) { return { intent: "image_edit", confidence: 0.7, needsClarification: true, conversationalReply: "你想编辑图片的话,需要先上传一张图片哦~", }; } // 默认对话 return { intent: "chat", confidence: 0.7 }; }