import { Injectable } from '@angular/core'; import { FmodeParse } from 'fmode-ng/core'; import { FmodeChatCompletion, completionJSON } from 'fmode-ng/core/agent/chat/completion'; const Parse = FmodeParse.with('nova'); /** * 室内设计AI分析服务 * 使用豆包1.6模型进行设计分析 */ @Injectable({ providedIn: 'root' }) export class DesignAnalysisAIService { // AI模型配置(豆包1.6) private readonly AI_MODEL = 'fmode-1.6-cn'; constructor() {} /** * 分析参考图片,识别场景类型和设计维度 */ async analyzeReferenceImages(options: { images: string[]; textDescription?: string; spaceType?: string; conversationHistory?: Array<{ role: string; content: string }>; deepThinking?: boolean; onProgressChange?: (progress: string) => void; onContentStream?: (content: string) => void; // 新增:流式内容回调 loading?: any; }): Promise { return new Promise(async (resolve, reject) => { try { // 构建详细的分析提示词 const prompt = this.buildAnalysisPrompt( options.spaceType, options.textDescription, options.conversationHistory, options.deepThinking ); options.onProgressChange?.('正在识别场景和分析设计维度...'); // 🔥 直接使用图片URL,不转base64(参考ai-k12-daofa的实现) console.log('📸 准备传入图片URL到AI模型...'); console.log('📸 图片URL列表:', options.images); // 日志输出,帮助调试 console.log('🤖 调用豆包1.6模型进行vision分析...'); console.log('📸 图片数量:', options.images.length); console.log('📝 提示词长度:', prompt.length, '字符'); console.log('🏠 空间类型:', options.spaceType); console.log('💬 对话历史:', options.conversationHistory?.length || 0, '条'); // 检查提示词长度(建议不超过10000字符) if (prompt.length > 10000) { console.warn('⚠️ 提示词过长,可能导致API调用失败'); } // 🔥 使用completionJSON进行vision分析(严格参考ai-k12-daofa的成功实现) console.log('🚀 开始调用completionJSON进行vision分析...'); // 定义JSON schema(与提示词中的JSON格式完全一致) const outputSchema = `{ "quickSummary": { "colorTone": "色彩基调(如: 暖色调、木色和暖灰色结合)", "mainMaterials": "主要材质(如: 软装以木作为主、黑色皮革沙发)", "atmosphere": "整体氛围(如: 温暖、舒适、生活气息浓厚)" }, "spaceType": "空间类型(如:客餐厅一体化、主卧、玄关等)", "spacePositioning": "空间定位与场景属性的详细分析", "layout": "空间布局与动线的详细分析", "hardDecoration": "硬装系统细节的详细分析(顶面、墙面、地面、门窗)", "colorAnalysis": "色调精准分析(主色调、辅助色、色调关系)", "materials": "材质应用解析(自然材质、现代材质、材质对比)", "form": "形体与比例分析(空间形体、家具形体、造型细节)", "style": "风格与氛围营造(风格识别、氛围手法)", "suggestions": "专业优化建议(居住适配、细节优化、落地可行性)", "summary": "简洁摘要(格式: 空间类型 | 风格 | 色调 | 氛围)" }`; // 流式内容累积 let streamContent = ''; try { // 🔥 关键:使用completionJSON + vision: true + images (URL数组) console.log('📤 发送给AI的提示词:', prompt); console.log('📤 JSON Schema:', outputSchema); console.log('📤 图片URL:', options.images); const result = await completionJSON( prompt, outputSchema, // 🔥 关键:提供JSON schema (content) => { // 流式回调(模拟) console.log('📥 AI流式响应:', typeof content, content); if (content && options.onContentStream) { streamContent = content; // 🔥 关键修复:将JSON转为易读的中文格式化文本 let displayText: string; let jsonObject: any = null; // 1. 尝试获取JSON对象 if (typeof content === 'string') { // 检查是否是JSON字符串 const trimmed = content.trim(); if (trimmed.startsWith('{') || trimmed.startsWith('[')) { console.log('🔍 检测到JSON格式字符串,尝试解析...'); try { jsonObject = JSON.parse(trimmed); console.log('✅ JSON解析成功,完整对象'); } catch (e) { // 🔥 关键修复:JSON不完整时,提取已完整的字段并格式化显示 console.log('⚠️ JSON解析失败,尝试提取部分字段...'); // 尝试从不完整的JSON中提取已完成的字段 jsonObject = this.extractPartialJSON(trimmed); if (jsonObject && Object.keys(jsonObject).length > 0) { console.log('✅ 成功提取部分字段:', Object.keys(jsonObject).join(', ')); } else { // 完全无法提取,显示提示 displayText = '🔄 正在生成分析结果...'; console.log('⚠️ 无法提取有效字段,等待更多数据'); } } } else { // 普通文本,直接显示 displayText = content; } } else if (typeof content === 'object') { jsonObject = content; console.log('📦 收到JSON对象'); } // 2. 如果是JSON对象,进行格式化 if (jsonObject) { console.log('🎨 开始格式化JSON对象...'); displayText = this.formatJSONToText(jsonObject); // 如果格式化失败或内容过短,使用后备方案 if (!displayText || displayText.trim().length < 50) { console.log('⚠️ formatJSONToText结果过短,使用fallback...'); displayText = this.fallbackFormatJSON(jsonObject); } // 最后兜底:美化JSON if (!displayText || displayText.trim().length < 20) { console.log('⚠️ fallback也失败,使用beautifyJSON...'); displayText = this.beautifyJSON(jsonObject); } console.log('✅ 格式化完成,长度:', displayText.length); } // 3. 如果还没有displayText,使用默认值 if (!displayText) { displayText = typeof content === 'string' ? content : JSON.stringify(content, null, 2); } options.onContentStream(displayText); options.onProgressChange?.(`正在分析,已接收 ${displayText.length} 字符...`); } }, 2, // 重试次数 { model: this.AI_MODEL, vision: true, // 🔥 关键:启用vision images: options.images, // 🔥 关键:直接传URL数组 max_tokens: 8000, temperature: 0.3 // 🔥 降低随机性,提高一致性(0.0-1.0,越低越确定) } ); console.log('📥 AI最终返回结果:', result); console.log('📥 返回结果类型:', typeof result); // 获取最终内容(result应该就是JSON对象) const analysisResult = result; console.log('✅ AI分析完成,返回JSON对象:', analysisResult); console.log('📝 AI返回JSON预览:', JSON.stringify(analysisResult).substring(0, 500)); // 验证返回的JSON结构 if (!analysisResult || typeof analysisResult !== 'object') { console.error('❌ AI返回格式错误,不是JSON对象'); reject(new Error('AI返回格式异常,请重试')); return; } // 检查必要字段 if (!analysisResult.spaceType || !analysisResult.spacePositioning) { console.error('❌ AI返回JSON缺少必要字段'); console.error('🔍 AI返回的完整对象:', analysisResult); reject(new Error('AI分析结果不完整,请重试')); return; } // 解析JSON结果 const analysisData = this.parseJSONAnalysis(analysisResult); console.log('📊 解析后的分析数据:', analysisData); // 🔥 关键:在最后发送完整的格式化内容 if (options.onContentStream && analysisData.formattedContent) { console.log('📤 发送最终格式化内容到UI...'); options.onContentStream(analysisData.formattedContent); } resolve(analysisData); } catch (err: any) { console.error('❌ completionJSON失败,详细错误:', err); console.error('❌ 错误类型:', err?.constructor?.name); console.error('❌ 错误消息:', err?.message); // 🔥 关键:如果completionJSON失败,尝试使用FmodeChatCompletion作为备选方案 if (err?.message?.includes('JSON') || err?.message?.includes('格式')) { console.warn('⚠️ completionJSON解析失败,尝试使用FmodeChatCompletion备选方案...'); try { // 使用FmodeChatCompletion获取纯文本响应 const textPrompt = this.buildTextAnalysisPrompt(options.spaceType, options.textDescription); const messageList = [{ role: 'user', content: textPrompt, images: options.images }]; const completion = new FmodeChatCompletion(messageList, { model: this.AI_MODEL, max_tokens: 8000 }); let fullContent = ''; const subscription = completion.sendCompletion({ isDirect: true, }).subscribe({ next: (message: any) => { const content = message?.content || ''; if (content) { fullContent = content; options.onContentStream?.(content); } if (message?.complete && fullContent) { console.log('✅ FmodeChatCompletion备选方案成功,内容长度:', fullContent.length); const analysisData = this.parseAnalysisContent(fullContent); resolve(analysisData); subscription?.unsubscribe(); } }, error: (err2: any) => { console.error('❌ FmodeChatCompletion备选方案也失败:', err2); reject(new Error('AI分析失败,请稍后重试')); subscription?.unsubscribe(); } }); return; // 使用备选方案,不继续执行下面的reject } catch (fallbackErr: any) { console.error('❌ 备选方案失败:', fallbackErr); } } // 根据错误类型提供更具体的错误信息 let errorMessage = 'AI分析失败'; if (err?.message?.includes('500')) { errorMessage = 'AI服务暂时不可用(服务器错误),请稍后重试'; } else if (err?.message?.includes('timeout')) { errorMessage = 'AI分析超时,请减少图片数量或简化需求后重试'; } else if (err?.message?.includes('token')) { errorMessage = '提示词过长,请简化描述或减少对话历史'; } else if (err?.message) { errorMessage = `AI分析失败: ${err.message}`; } reject(new Error(errorMessage)); } } catch (error: any) { reject(new Error('分析失败: ' + error.message)); } }); } /** * 构建纯文本分析提示词(用于FmodeChatCompletion备选方案) */ private buildTextAnalysisPrompt(spaceType?: string, textDescription?: string): string { let prompt = `请对图片中的室内设计进行专业分析,从以下8个维度详细展开: 一、空间定位与场景属性 二、空间布局与动线 三、硬装系统细节 四、色调精准分析 五、材质应用解析 六、形体与比例 七、风格与氛围营造 八、专业优化建议 要求: 1. 基于图片实际视觉内容进行分析 2. 每个维度2-4个段落,每段3-5行 3. 使用专业的室内设计术语 4. 不使用Markdown符号,使用纯文本格式`; if (spaceType) { prompt += `\n5. 空间类型参考: ${spaceType}`; } if (textDescription) { prompt += `\n6. 客户需求参考: ${textDescription}`; } return prompt; } /** * 检测用户是否在进行单维度询问(而非完整分析) */ private detectSingleDimensionQuery(userInput: string): boolean { const lowerInput = userInput.toLowerCase(); // 🔥 单维度询问关键词 const singleDimensionKeywords = [ // 询问词 '什么', '如何', '怎么', '哪些', '是否', '有没有', // 具体维度 '色彩', '色调', '颜色', '配色', '材质', '材料', '用料', '布局', '动线', '空间', '灯光', '照明', '风格', '氛围', '尺寸', '大小', '面积', '建议', '优化', '改进', // 疑问形式 '?', '?' ]; // 🔥 完整分析关键词(如果包含这些,说明要完整分析) const fullAnalysisKeywords = [ '完整', '全面', '详细分析', '整体分析', '重新分析', '再分析一次', '重新生成', '全部', '所有维度', '各个方面' ]; // 如果包含完整分析关键词,返回false(不是单维度) for (const keyword of fullAnalysisKeywords) { if (lowerInput.includes(keyword)) { return false; } } // 如果包含单维度关键词,返回true for (const keyword of singleDimensionKeywords) { if (lowerInput.includes(keyword)) { return true; } } // 🔥 如果用户输入很短(<20字),可能是简单询问 if (userInput.length < 20) { return true; } // 默认:如果用户输入很长(>50字),可能是要求完整分析 return userInput.length < 50; } /** * 构建AI分析提示词(JSON格式输出,兼容completionJSON) * 参考ai-k12-daofa的简洁提示词风格 */ private buildAnalysisPrompt(spaceType?: string, textDescription?: string, conversationHistory?: Array<{ role: string; content: string }>, deepThinking?: boolean): string { // 🔥 全面优化的提示词:支持多种风格,精准识别材质和色调,支持重新分析和单维度问答 const hasPreviousAnalysis = conversationHistory && conversationHistory.length > 0; // 🔥 检测用户是否在进行单维度询问 const isSingleDimensionQuery = hasPreviousAnalysis && textDescription && this.detectSingleDimensionQuery(textDescription); let prompt = `你是一位专业的室内设计分析师,请仔细观察图片中的室内设计细节`; // 🔥 如果是单维度询问,使用对话模式 if (isSingleDimensionQuery) { prompt += `,并针对用户的具体问题进行专业回答。 【重要说明 - 单维度问答模式】 • 用户正在进行特定维度的询问(如色彩、材质、布局等) • 请直接针对用户的问题进行详细、专业的文字回答 • 不需要输出完整的JSON结构分析报告 • 使用自然流畅的语言,就像设计师之间的专业交流 • 回答应该详细、准确,包含具体的色号、材质名称等专业术语 【用户当前问题】:${textDescription} 请针对这个问题,给出专业、详细的回答:`; return prompt; } // 🔥 否则,使用完整分析模式 prompt += `,并按以下JSON格式输出专业分析:`; // 🔥 如果有对话历史,说明这是用户提出修正意见后的重新分析 if (hasPreviousAnalysis) { prompt += ` 【重要说明 - 完整重新分析】 • 用户已经看过之前的分析,并提出了修正意见或新的要求 • 请基于用户的反馈,重新生成一份完整的、修正后的分析报告 • 不要继续之前的分析内容,而是输出一份全新的、完整的JSON分析结果 • 特别关注用户提到的色调、材质、风格等修正意见,确保新的分析符合用户期望 【分析要求】`; } prompt += ` { "quickSummary": { "colorTone": "色彩基调(如: 暖白法式偏女性向,象牙白护墙板+浅灰地面+米色木地板,软装点缀豆沙紫/湖蓝色)", "mainMaterials": "主要材质(如: 象牙白护墙板+线条装饰、大理石纹理台面、黑色雕刻家具、水晶灯、香槟金灯具、豆沙紫/湖蓝色软装)", "atmosphere": "整体氛围(如: 柔暖、精致、优雅、女性向、明亮通透)" }, "spaceType": "空间类型(如:客餐厅一体化、主卧、玄关等)", "spacePositioning": "空间定位与场景属性的详细分析", "layout": "空间布局与动线的详细分析", "hardDecoration": "硬装系统细节的详细分析(顶面、墙面、地面、门窗)", "colorAnalysis": "色调精准分析", "materials": "材质应用解析", "form": "形体与比例分析(空间形体、家具形体、造型细节)", "style": "风格与氛围营造", "suggestions": "专业优化建议(居住适配、细节优化、落地可行性)", "summary": "简洁摘要(格式: 空间类型 | 风格 | 色调 | 氛围)" } 【核心分析原则 - 保证一致性】 • 🔥 **客观描述优先**:基于图片中可见的元素进行描述,避免过度解读 • 🔥 **细节精准识别**:准确识别护墙板、线条、大理石纹理、金属材质等细节 • 🔥 **色调精准定位**:使用精确的色彩描述(淡奶灰、暖白、米色、木色等) • 🔥 **风格准确判断**:根据硬装+软装+色调综合判断(现代法式、侘寂、轻奢等) • 🔥 **保持分析一致性**:相同的视觉元素应该得出相同的结论 【关键分析要求】 0. **快速总结 (quickSummary)** - 🔥 优先级最高: • 色彩基调(colorTone):精准描述主色调组合 【现代法式女性向示例】 - "暖白法式偏女性向,象牙白护墙板+浅灰地面+米色木地板,软装点缀豆沙紫/湖蓝色" - "浅色调为主,暖白色/米色为基底,黑色家具+香槟金灯具点缀" - "暖白象牙色主导,大理石灰白褐纹呼应,彩色软装增添柔美" 【侘寂风格示例】 - "暖色调,木色和暖灰色结合" - "自然暖棕+柔和灰,低饱和度舒适系" 【轻奢风格示例】 - "高级灰+香槟金,大理石质感" - "冷灰主导+金属光泽,精致轻奢" ⚠️ 必须明确: - 冷暖倾向(暖白/冷白/中性) - 主色调名称(象牙白/暖白/淡奶灰) - 关键配色(软装彩色/黑色对比/金属点缀) • 主要材质(mainMaterials):按重要性列举关键材质 【现代法式女性向示例】 - "象牙白护墙板+线条装饰、大理石纹理台面、黑色雕刻家具、水晶灯、香槟金灯具" - "白色岩板、浅灰地砖、米色木地板、豆沙紫/湖蓝色软装、绿植装饰" 【侘寂风格示例】 - "木质软装为主、黑色皮革沙发、藤编家具、混凝土墙面" ⚠️ 优先识别: - 硬装:护墙板、线条、大理石/岩板、木地板、瓷砖 - 软装:家具材质(木质/皮革/布艺)、灯具(水晶/金属) - 装饰:花瓶、烛台、绿植、艺术品 • 整体氛围(atmosphere):3-5个关键词 【现代法式女性向示例】 - "柔暖、精致、优雅、女性向、浪漫" - "明亮通透、轻盈柔美、精致细腻" 【侘寂风格示例】 - "温暖、舒适、质朴、生活气息" ⚠️ 氛围词必须与色调、材质相呼应: - 暖白+浅色 = 明亮、通透、柔和 - 彩色软装 = 女性向、浪漫、柔美 - 黑色对比+金属 = 精致、优雅、轻奢感 1. **色调精准分析 (colorAnalysis)** - 🔥 核心重点: • 🎨 **硬装基础色精准识别**: - **暖白色/象牙白**:偏米色的白色,带微黄调(NCS S 0502-Y50R) * 应用:护墙板、墙面涂料、顶面 * 特征:柔和、温润、不刺眼 - **浅灰色系**: * 淡奶灰色:带微黄调/微粉调的浅灰色(NCS S 0502-Y、S 0502-R) * 灰蓝色:带微蓝调的浅灰色(如背景墙) * 浅灰地面:大理石/瓷砖的自然灰色 - **米色/奶咖色**: * 木地板:浅橡木色、枫木色 * 暖米色墙面:带黄调的浅米色 - **大理石纹理色**: * 白色基底+褐色/金色纹理(桌面、台面) * 白色基底+蓝绿色纹理(装饰性大理石) * 灰白色带细褐纹(地面) • 🎀 **软装点缀色精准识别** - 女性向关键: - **豆沙紫/紫罗兰色**:柔和的紫色调沙发/椅子 - **湖蓝色/Tiffany蓝**:清新的蓝绿色调椅子/装饰 - **香槟金/玫瑰金**:金属灯具、装饰件 - **黑色**:雕刻家具、烛台、电器(形成优雅对比) - **绿植色**:自然绿色,点缀生机 • 🔢 **色彩比例量化分析**: 【现代法式女性向示例】 - 主色调(70%):暖白色/象牙白护墙板+墙面 - 辅助色(20%):浅灰色地面+米色木地板 - 点缀色(10%):豆沙紫+湖蓝色软装 + 黑色家具 + 香槟金灯具 【侘寂风格示例】 - 主色调(60%):暖灰色墙面 - 辅助色(30%):木色家具 - 点缀色(10%):黑色皮革 • 🌡️ **冷暖定位精准判断**: - **暖色系**:木色、米色、暖灰、奶咖、淡粉、豆沙紫 - **中性偏暖**:暖白/象牙白、淡奶灰(带微黄调) - **清新偏冷**:湖蓝色、Tiffany蓝、灰蓝色 - **冷色系**:纯灰、冷白、深蓝灰 - **中性色**:黑色(优雅对比)、金色(精致点缀) • 🎯 **色调协调性分析** - 重要: - 大面积浅色(暖白+浅灰)营造明亮通透感 - 软装彩色(豆沙紫+湖蓝)增加女性柔美感 - 黑色家具形成优雅对比,提升精致度 - 金色饰品点缀轻奢感 - 自然绿植平衡色彩,增加生机 • ⚠️ **避免模糊词汇,使用精准描述**: ❌ "中性灰棕色系" → ✅ "暖白法式,浅色调为主" ❌ "浅色系" → ✅ "象牙白护墙板+浅灰地面+米色木地板" ❌ "彩色点缀" → ✅ "豆沙紫沙发+湖蓝色椅子+香槟金灯具" 2. **材质应用解析 (materials)** - 全面细致: • 🏗️ **硬装材质全面识别**: 【墙面系统】 * **护墙板**(法式关键特征): - 颜色:象牙白/暖白/淡奶灰 - 工艺:凸起线条装饰、方框造型、哑光质感 - 细节:线条宽度、阴影层次、接缝工艺 * **线条装饰**(法式精髓): - 顶角线/腰线/门框线 - 造型:简约直线/曲线/雕花 - 材质:石膏/PU/实木 * **涂料墙面**: - 暖白色/米色/浅灰色乳胶漆 - 质感:哑光/丝光 * **大理石/岩板背景墙**: - 白色岩板(电视墙/装饰墙) - 纹理大理石(背景装饰) 【地面系统】 * **大理石地砖**: - 浅灰色柔哑面地砖(主要区域) - 纹理:细密灰白纹理/大理石自然纹 - 拼接:直铺/对角铺/拼花 * **木地板**: - 浅色木地板(米色/浅橡木色) - 工艺:直拼/人字拼/鱼骨拼 - 质感:哑光/半哑光 【顶面系统】 * 石膏线装饰/隐藏式灯带 * 平顶+局部造型 * 无主灯设计(筒灯+吊灯组合) 【门窗系统】 * 拱门造型(法式经典元素) * 门框线条装饰 • 🛋️ **软装材质细节描述** - 女性向重点: 【家具材质】 * **桌子**: - 大理石圆桌(灰白褐纹/蓝绿纹理) - 黑色雕刻底座(手工雕花纹理) * **沙发/椅子**: - 豆沙紫布艺沙发(丝绒/天鹅绒质感) - 湖蓝色椅子(皮革/布艺) - 曲线造型、包裹感强 * **柜子**: - 白色浮雕柜(立体雕花装饰) - 储物功能+装饰性 【灯具材质】 * **水晶灯**: - 透明水晶台灯(切面反光) - 白色灯罩(布艺/丝绸质感) * **金属灯**: - 香槟金/玫瑰金树枝造型灯 - 艺术造型、雕塑感 【装饰品材质】 * **花瓶**: - 白色陶瓷花瓶(哑光质感) - 造型:圆润/曲线/传统 * **烛台**: - 黑色烛台(亮面烤漆/陶瓷) - 组合摆放,形成节奏感 * **绿植**: - 自然枝条(枯枝/绿叶) - 点缀生机、柔化空间 * **墙面装饰**: - 蝴蝶/鸟类装饰(白色/金色) - 艺术挂画 • 🔍 **材质质感精准描述** - 触觉与视觉: - **护墙板**:哑光漆面,肌理感,立体阴影 - **大理石**: * 柔哑面(不反光,温润触感) * 天然纹理(褐色/金色/蓝绿色不规则纹路) * 质感层次(深浅交错,自然过渡) - **木质**:自然木纹,温润触感,哑光/半哑光 - **金属**: * 黑色雕刻(手工痕迹,哑光质感) * 香槟金(微光泽,细腻拉丝) - **水晶/玻璃**:透明清澈,切面反光,精致感 - **布艺**:丝绒柔软,天鹅绒光泽,包裹感 - **陶瓷**:哑光白瓷,温润细腻,手工感 3. **风格与氛围营造 (style)** - 综合判断: • 🎭 **风格准确识别** - 基于硬装+软装+色调综合判断: 【现代法式】关键特征: - 硬装:淡奶灰/暖白/象牙白护墙板 + 线条装饰 + 大理石地面 + 拱门造型 - 软装:水晶灯/金属灯 + 精致家具 + 花艺绿植装饰 - 色调:暖白/象牙白为主 + 米色/浅灰辅助 + 黑色家具对比 - 氛围:柔暖、精致、优雅、明亮通透 【现代法式·女性向】附加特征: - 软装彩色:豆沙紫/湖蓝色/淡粉色沙发/椅子 - 装饰细节:蝴蝶/鸟类墙饰、曲线造型、浮雕柜 - 灯具选择:水晶台灯、香槟金/玫瑰金灯具 - 色彩搭配:浅色基底+柔和彩色点缀 - 氛围升级:女性向、浪漫、轻盈柔美、精致细腻 【温润侘寂】关键特征: - 硬装:暖灰色墙面 + 木地板 + 简约造型 - 软装:木质家具为主 + 藤编/皮革 + 自然装饰 - 色调:木色 + 暖灰色 + 低饱和度 - 氛围:温暖、舒适、质朴、生活气息 【现代轻奢】关键特征: - 硬装:大理石 + 金属线条 + 高级灰 - 软装:轻奢家具 + 金属饰品 + 艺术挂画 - 色调:高级灰 + 香槟金/玫瑰金 + 白色 - 氛围:精致、时尚、轻奢、品质感 • 💫 **氛围判断依据**: - 柔暖精致:淡奶灰+暖白+水晶灯+护墙板 - 温暖舒适:木色+暖灰+木质软装+自然光 - 清冷克制:纯灰+冷白+极简家具+留白 - 女性向/浪漫:淡粉色点缀+曲线造型+精致细节 • ⚠️ **避免混淆**: - 护墙板+水晶灯+大理石 = 法式,而非侘寂 - 木质+混凝土+暖灰 = 侘寂,而非法式 4. **专业优化建议 (suggestions)**: • 🏠 **居住适配** - 基于风格特征提建议: - 法式风格:补充淡粉色软装(女儿房)、台盆柜+梳妆台一体化设计 - 侘寂风格:木质模块化收纳、暖灰色地毯、生活化装饰 • 🔧 **细节优化**: - 材质统一性(护墙板色调协调、大理石纹理呼应) - 色彩过渡(淡奶灰→米色→木色的渐变) - 隐形工程(筒射灯预埋无边框、空调风口预埋无边框) • ✅ **落地可行性**: - 材料选择(护墙板材质、大理石品类、木地板工艺) - 施工注意事项(拼花对缝、线条安装、灯光预埋) 5. **基础要求 - 保证分析质量**: • ✅ 基于图片**实际可见元素**进行分析,严禁臆测或模板化 • ✅ 每个字段提供**详细描述**(200-400字) • ✅ 使用**专业室内设计术语**(护墙板、线条、大理石纹理、柔哑面等) • ✅ **不提及品牌**,仅描述材质、色调、形态、氛围 • ✅ **保持客观中立**,避免过度解读或情感化描述 • ✅ **同一视觉元素=同一结论**,确保分析一致性`; // 添加空间类型提示 if (spaceType) { prompt += `\n\n【空间类型参考】: ${spaceType}`; } // 添加客户需求提示 if (textDescription) { prompt += `\n\n【客户核心需求】: ${textDescription}\n请特别关注客户需求中提到的色调、材质、氛围要求,确保分析结果与需求高度契合`; } // 🔥 强化分析质量要求 if (hasPreviousAnalysis) { // 如果是重新分析,强调要结合用户反馈 prompt += `\n\n【重要提醒 - 重新分析要点】 • 仔细阅读用户的修正意见和反馈,理解用户的真实需求 • 重新审视图片,基于用户指出的方向进行调整 • 输出一份完整的、修正后的JSON分析报告 • 使用精准的专业术语(如"淡奶灰色护墙板"而非"灰色墙面") • 如果用户指出了色调、材质、风格的具体要求,必须在新报告中体现 • 保持分析的专业性和完整性,不要只修改某一部分`; } else { // 如果是首次分析,强调一致性 prompt += `\n\n【重要提醒 - 保证分析质量】 • 基于图片中客观可见的元素进行分析,避免主观臆测 • 使用精准的专业术语(如"淡奶灰色护墙板"而非"灰色墙面") • 材质、色调、风格的判断应该保持逻辑一致性 • 避免使用模糊或可变的描述词汇 • 确保分析的完整性和专业性`; } return prompt; } /** * 解析JSON格式的AI分析结果(新方法,处理completionJSON返回的JSON对象) */ private parseJSONAnalysis(jsonResult: any): any { console.log('📝 [parseJSONAnalysis] 开始解析JSON分析结果...'); console.log('🔍 [parseJSONAnalysis] JSON对象:', JSON.stringify(jsonResult).substring(0, 300)); // 将JSON字段转换为易读的格式化文本 let formattedContent = this.formatJSONToText(jsonResult); // 🔥 关键:如果formattedContent为空或过短,说明JSON可能没有标准字段 if (!formattedContent || formattedContent.trim().length < 50) { console.warn('⚠️ [parseJSONAnalysis] 格式化内容过短,尝试后备方案...'); formattedContent = this.fallbackFormatJSON(jsonResult); } // 🔥 最终校验:如果还是为空,使用原始JSON的美化版本 if (!formattedContent || formattedContent.trim().length < 20) { console.warn('⚠️ [parseJSONAnalysis] 后备方案也失败,使用JSON美化版本...'); formattedContent = this.beautifyJSON(jsonResult); } console.log('✅ [parseJSONAnalysis] 最终格式化内容长度:', formattedContent.length); console.log('📝 [parseJSONAnalysis] 内容预览:', formattedContent.substring(0, 200)); return { rawContent: JSON.stringify(jsonResult, null, 2), // 原始JSON formattedContent: formattedContent, // 格式化文本(确保有内容) structuredData: { quickSummary: jsonResult.quickSummary || null, // 🔥 快速总结 spacePositioning: jsonResult.spacePositioning || '', layout: jsonResult.layout || '', hardDecoration: jsonResult.hardDecoration || '', colorAnalysis: jsonResult.colorAnalysis || '', materials: jsonResult.materials || '', form: jsonResult.form || '', style: jsonResult.style || '', suggestions: jsonResult.suggestions || '' }, spaceType: jsonResult.spaceType || '', summary: jsonResult.summary || '', hasContent: true, timestamp: new Date().toISOString() }; } /** * 美化JSON显示(当所有格式化方法都失败时的最后手段) */ private beautifyJSON(jsonResult: any): string { const lines: string[] = []; for (const [key, value] of Object.entries(jsonResult)) { if (value && typeof value === 'string' && value.trim().length > 0) { // 将驼峰命名转换为中文标题 const chineseTitle = this.getChineseTitleForKey(key); lines.push(`【${chineseTitle}】\n${value}\n`); } } return lines.join('\n') || '分析结果为空,请重新分析'; } /** * 将JSON字段名转换为中文标题 */ private getChineseTitleForKey(key: string): string { const titleMap: { [key: string]: string } = { 'spaceType': '空间类型', 'spacePositioning': '空间定位与场景属性', 'layout': '空间布局与动线', 'hardDecoration': '硬装系统细节', 'colorAnalysis': '色调精准分析', 'materials': '材质应用解析', 'form': '形体与比例', 'style': '风格与氛围营造', 'suggestions': '专业优化建议', 'summary': '设计概要' }; return titleMap[key] || key; } /** * 将JSON结果转换为易读的文本格式 */ private formatJSONToText(jsonResult: any): string { console.log('🔄 [formatJSONToText] 开始格式化JSON结果...'); console.log('🔍 [formatJSONToText] JSON字段数量:', Object.keys(jsonResult).length); const sections = []; // 🔥 关键:确保每个字段都被处理,即使内容为空也显示标题 if (jsonResult.spacePositioning) { sections.push(`一、空间定位与场景属性\n\n${jsonResult.spacePositioning}\n`); } if (jsonResult.layout) { sections.push(`二、空间布局与动线\n\n${jsonResult.layout}\n`); } if (jsonResult.hardDecoration) { sections.push(`三、硬装系统细节\n\n${jsonResult.hardDecoration}\n`); } if (jsonResult.colorAnalysis) { sections.push(`四、色调精准分析\n\n${jsonResult.colorAnalysis}\n`); } if (jsonResult.materials) { sections.push(`五、材质应用解析\n\n${jsonResult.materials}\n`); } if (jsonResult.form) { sections.push(`六、形体与比例\n\n${jsonResult.form}\n`); } if (jsonResult.style) { sections.push(`七、风格与氛围营造\n\n${jsonResult.style}\n`); } if (jsonResult.suggestions) { sections.push(`八、专业优化建议\n\n${jsonResult.suggestions}\n`); } const formattedText = sections.join('\n'); console.log('✅ [formatJSONToText] 格式化完成,长度:', formattedText.length); console.log('📝 [formatJSONToText] 内容预览:', formattedText.substring(0, 200)); // 🔥 后备机制:如果格式化结果为空,尝试从JSON直接生成文本 if (!formattedText || formattedText.trim().length === 0) { console.warn('⚠️ [formatJSONToText] 格式化结果为空,使用后备方案...'); return this.fallbackFormatJSON(jsonResult); } return formattedText; } /** * 从不完整的JSON字符串中提取已完成的字段 * 🔥 流式传输专用:实时提取部分字段 */ private extractPartialJSON(jsonString: string): any { console.log('🔧 [extractPartialJSON] 开始提取部分JSON字段...'); const result: any = {}; // 定义所有可能的字段 const fields = [ 'spaceType', 'spacePositioning', 'layout', 'hardDecoration', 'colorAnalysis', 'materials', 'form', 'style', 'suggestions', 'summary' ]; // 使用正则表达式提取每个字段的完整值 for (const field of fields) { // 匹配 "fieldName": "value" 或 "fieldName": "value...(可能不完整) const regex = new RegExp(`"${field}"\\s*:\\s*"([^"]*(?:"[^"]*)*)"`, 'g'); const match = regex.exec(jsonString); if (match && match[1]) { // 提取到完整的字段值 result[field] = match[1]; console.log(`✅ 提取字段 ${field}:`, match[1].substring(0, 50) + '...'); } else { // 尝试提取不完整的值(到字符串末尾) const partialRegex = new RegExp(`"${field}"\\s*:\\s*"([^"]*?)(?:"|$)`, 's'); const partialMatch = partialRegex.exec(jsonString); if (partialMatch && partialMatch[1] && partialMatch[1].length > 20) { // 只有当值足够长时才提取(避免只有几个字符的情况) result[field] = partialMatch[1] + '...'; console.log(`⚠️ 提取不完整字段 ${field}:`, partialMatch[1].substring(0, 50) + '...'); } } } const extractedCount = Object.keys(result).length; console.log(`✅ [extractPartialJSON] 提取了 ${extractedCount} 个字段`); return extractedCount > 0 ? result : null; } /** * 后备格式化方法:当主要方法失败时使用 */ private fallbackFormatJSON(jsonResult: any): string { const lines: string[] = []; // 遍历JSON对象的所有字段 const fieldMap: { [key: string]: string } = { 'spaceType': '空间类型', 'spacePositioning': '一、空间定位与场景属性', 'layout': '二、空间布局与动线', 'hardDecoration': '三、硬装系统细节', 'colorAnalysis': '四、色调精准分析', 'materials': '五、材质应用解析', 'form': '六、形体与比例', 'style': '七、风格与氛围营造', 'suggestions': '八、专业优化建议', 'summary': '设计概要' }; for (const [key, title] of Object.entries(fieldMap)) { if (jsonResult[key] && typeof jsonResult[key] === 'string' && jsonResult[key].trim().length > 0) { if (key === 'spaceType' || key === 'summary') { lines.push(`${title}:${jsonResult[key]}\n`); } else { lines.push(`${title}\n\n${jsonResult[key]}\n`); } } } const result = lines.join('\n'); console.log('✅ [fallbackFormatJSON] 后备格式化完成,长度:', result.length); return result || '暂无分析内容'; } /** * 解析AI分析内容(优化版:格式化处理,确保结构清晰) * @deprecated 使用parseJSONAnalysis代替 */ private parseAnalysisContent(content: string): any { console.log('📝 AI返回的原始内容长度:', content.length); console.log('📝 AI返回的内容预览:', content.substring(0, 500)); if (!content || content.length < 50) { console.warn('⚠️ AI返回内容过短或为空'); return { rawContent: content, formattedContent: content, hasContent: false, timestamp: new Date().toISOString() }; } // 格式化处理:优化段落、间距、结构 const formattedContent = this.formatAnalysisContent(content); // 提取结构化信息 const structuredData = this.extractStructuredInfo(content); return { rawContent: content, // 原始AI输出 formattedContent: formattedContent, // 格式化后的内容 structuredData: structuredData, // 结构化数据(维度分段) hasContent: content.length > 50, timestamp: new Date().toISOString() }; } /** * 格式化分析内容:优化排版、段落、间距 */ private formatAnalysisContent(content: string): string { let formatted = content; // 1. 统一维度标题格式(确保维度标题前后有空行) const dimensionPattern = /([一二三四五六七八九十]、[^\n]+)/g; formatted = formatted.replace(dimensionPattern, '\n\n$1\n'); // 2. 处理过长段落:如果段落超过300字,尝试在句号处换行 const paragraphs = formatted.split('\n'); const processedParagraphs = paragraphs.map(para => { if (para.trim().length > 300) { // 在句号、问号、感叹号后添加换行,但保持在段落内 return para.replace(/([。!?])(?=[^。!?\n]{50,})/g, '$1\n'); } return para; }); formatted = processedParagraphs.join('\n'); // 3. 清理多余空行(超过2个连续空行压缩为2个) formatted = formatted.replace(/\n{3,}/g, '\n\n'); // 4. 确保维度之间有明确的空行分隔 formatted = formatted.replace(/(一、|二、|三、|四、|五、|六、|七、|八、)/g, '\n\n$1'); // 5. 移除开头和结尾的多余空行 formatted = formatted.trim(); // 6. 确保每个维度内部段落之间有适当间距 formatted = formatted.replace(/([。!?])\s*\n(?=[^\n一二三四五六七八])/g, '$1\n\n'); // 7. 最后清理:确保格式整洁 formatted = formatted.replace(/\n{3,}/g, '\n\n'); return formatted; } /** * 提取结构化信息:将内容按维度分段 */ private extractStructuredInfo(content: string): any { const dimensions: any = { spacePositioning: '', // 空间定位与场景属性 layout: '', // 空间布局与动线 hardDecoration: '', // 硬装系统细节 colorAnalysis: '', // 色调精准分析 materials: '', // 材质应用解析 form: '', // 形体与比例 style: '', // 风格与氛围营造 suggestions: '' // 专业优化建议 }; // 按维度标题分割内容 const dimensionRegex = /([一二三四五六七八]、[^\n]+)\n+([\s\S]*?)(?=\n[一二三四五六七八]、|$)/g; let match; while ((match = dimensionRegex.exec(content)) !== null) { const title = match[1].trim(); const contentText = match[2].trim(); // 根据标题关键词匹配到对应维度 if (title.includes('空间定位') || title.includes('场景属性')) { dimensions.spacePositioning = contentText; } else if (title.includes('布局') || title.includes('动线')) { dimensions.layout = contentText; } else if (title.includes('硬装') || title.includes('系统细节')) { dimensions.hardDecoration = contentText; } else if (title.includes('色调') || title.includes('色彩')) { dimensions.colorAnalysis = contentText; } else if (title.includes('材质')) { dimensions.materials = contentText; } else if (title.includes('形体') || title.includes('比例')) { dimensions.form = contentText; } else if (title.includes('风格') || title.includes('氛围')) { dimensions.style = contentText; } else if (title.includes('建议') || title.includes('优化')) { dimensions.suggestions = contentText; } } return dimensions; } /** * 生成简洁摘要:提取关键信息,适合客服和设计师快速查看 */ generateBriefSummary(analysisData: any): string { if (!analysisData || !analysisData.rawContent) { return '暂无分析内容'; } const content = analysisData.rawContent; const summary: string[] = []; // 1. 提取空间类型 const spaceTypeMatch = content.match(/(?:这是|空间为|属于).*?([^\n]{2,20}?(?:空间|客厅|餐厅|卧室|厨房|卫生间|玄关|书房))/); if (spaceTypeMatch) { summary.push(spaceTypeMatch[1].trim()); } // 2. 提取风格关键词(优化:区分温润vs清冷侘寂) const styleKeywords = ['现代', '法式', '简约', '极简', '轻奢', '新中式', '日式', '北欧', '工业风', '台式', '温润侘寂', '侘寂', '美式', '混搭']; const foundStyles: string[] = []; styleKeywords.forEach(keyword => { if (content.includes(keyword) && !foundStyles.includes(keyword)) { foundStyles.push(keyword); } }); if (foundStyles.length > 0) { summary.push(foundStyles.slice(0, 2).join('+')); } // 3. 提取色调关键词(优化:优先识别暖灰色、木色) const colorKeywords = [ '暖灰色', '暖灰', '木色', '木棕', '原木色', '暖棕', '胡桃木色', // 暖色调优先 '暖色系', '暖调', '米白', '奶白', '米色', '冷色系', '冷调', '高级灰', '纯灰' // 冷色调在后 ]; const foundColors: string[] = []; colorKeywords.forEach(keyword => { if (content.includes(keyword) && !foundColors.includes(keyword)) { foundColors.push(keyword); } }); if (foundColors.length > 0) { summary.push(foundColors.slice(0, 3).join('、')); } // 4. 提取氛围关键词(优化:优先识别温暖、舒适、生活气息) const moodKeywords = [ '温暖', '舒适', '生活气息', '温馨', '质朴', // 温暖氛围优先 '精致', '高级', '优雅', '松弛', '静谧', '时尚', '女性向', '男性向', '亲子', '清冷' // 清冷在后 ]; const foundMoods: string[] = []; moodKeywords.forEach(keyword => { if (content.includes(keyword) && !foundMoods.includes(keyword)) { foundMoods.push(keyword); } }); if (foundMoods.length > 0) { summary.push(foundMoods.slice(0, 3).join('、')); } // 5. 提取关键材质(优化:优先识别木材、皮革) const materialKeywords = [ '木材', '木质', '实木', '胡桃木', '橡木', '柚木', // 木材优先 '皮革', '黑色皮革', // 皮革 '大理石', '瓷砖', '混凝土', '护墙板', '布艺', '金属', '玻璃', '藤编' ]; const foundMaterials: string[] = []; materialKeywords.forEach(keyword => { if (content.includes(keyword) && !foundMaterials.includes(keyword)) { foundMaterials.push(keyword); } }); if (foundMaterials.length > 0) { summary.push('主要材质:' + foundMaterials.slice(0, 4).join('、')); } return summary.length > 0 ? summary.join(' | ') : '整体设计基于图片实际内容分析'; } /** * 生成客服标注格式:提取客户要求的关键点 */ generateCustomerServiceNotes(analysisData: any, customerRequirements?: string): string { if (!analysisData) { return '暂无标注内容'; } const notes: string[] = []; // 优先使用JSON格式的structuredData,否则使用rawContent const structuredData = analysisData.structuredData; const rawContent = analysisData.rawContent || ''; // 1. 客户要求(如果有) if (customerRequirements) { notes.push(`【客户要求】\n${customerRequirements}`); } // 2. 空间类型识别 let spaceType = ''; if (structuredData?.spaceType) { spaceType = structuredData.spaceType; } else { // 从rawContent提取 const spaceMatch = rawContent.match(/(?:这是|空间为|属于).*?([^\n]{2,20}?(?:空间|客厅|餐厅|卧室|厨房|卫生间|玄关|书房|客餐厅一体化|三室两厅|两室一厅))/); if (spaceMatch) spaceType = spaceMatch[1].trim(); } if (spaceType) { notes.push(`【空间类型】\n${spaceType}`); } // 3. 风格定位(从结构化数据或rawContent提取) let styleInfo = ''; if (structuredData?.style) { // 提取风格关键词 const styleKeywords = ['现代', '法式', '简约', '极简', '轻奢', '新中式', '日式', '北欧', '工业风', '侘寂', '美式', '台式']; const foundStyles: string[] = []; styleKeywords.forEach(keyword => { if (structuredData.style.includes(keyword) && !foundStyles.includes(keyword)) { foundStyles.push(keyword); } }); if (foundStyles.length > 0) { styleInfo = foundStyles.join('+') + '风格'; } // 提取氛围描述 const moodMatch = structuredData.style.match(/(?:氛围|营造|呈现).*?([^\n。]{5,30}?(?:温馨|舒适|精致|高级|松弛|静谧|优雅|时尚))/); if (moodMatch) { styleInfo += `,${moodMatch[1].trim()}`; } } else if (rawContent) { // 从rawContent提取风格 const styleMatch = rawContent.match(/(?:风格|呈现|属于).*?([^\n。]{5,40}?(?:风格|法式|现代|简约|极简))/); if (styleMatch) styleInfo = styleMatch[1].trim(); } if (styleInfo) { notes.push(`【风格定位】\n${styleInfo}`); } // 4. 色调要求(从色彩分析提取) let colorInfo = ''; if (structuredData?.colorAnalysis) { // 提取主色调 const mainColorMatch = structuredData.colorAnalysis.match(/主色调[::]\s*([^\n。]{5,50})/); if (mainColorMatch) { colorInfo = `主色调:${mainColorMatch[1].trim()}`; } // 提取辅助色 const subColorMatch = structuredData.colorAnalysis.match(/辅助色[::]\s*([^\n。]{5,50})/); if (subColorMatch) { colorInfo += `\n辅助色:${subColorMatch[1].trim()}`; } } else if (rawContent) { // 从rawContent提取色调 const colorMatch = rawContent.match(/(?:色调|色彩|主色)[::]\s*([^\n。]{5,50})/); if (colorMatch) colorInfo = colorMatch[1].trim(); } if (colorInfo) { notes.push(`【色调要求】\n${colorInfo}`); } // 5. 材质要求(从硬装和材质维度提取) const materials: string[] = []; if (structuredData?.hardDecoration) { // 提取地面材质 const floorMatch = structuredData.hardDecoration.match(/地面[::]\s*([^\n。]{5,40})/); if (floorMatch) materials.push(`地面:${floorMatch[1].trim()}`); // 提取墙面材质 const wallMatch = structuredData.hardDecoration.match(/墙面[::]\s*([^\n。]{5,40})/); if (wallMatch) materials.push(`墙面:${wallMatch[1].trim()}`); // 提取顶面材质 const ceilingMatch = structuredData.hardDecoration.match(/顶面[::]\s*([^\n。]{5,40})/); if (ceilingMatch) materials.push(`顶面:${ceilingMatch[1].trim()}`); } // 从材质维度补充 if (structuredData?.materials && materials.length < 2) { const materialMatch = structuredData.materials.match(/(?:主要材质|材质应用)[::]\s*([^\n。]{10,60})/); if (materialMatch) materials.push(materialMatch[1].trim()); } if (materials.length > 0) { notes.push(`【材质要求】\n${materials.join('\n')}`); } // 6. 空间布局要点 if (structuredData?.layout) { const layoutMatch = structuredData.layout.match(/(?:布局特点|空间关系)[::]\s*([^\n。]{10,60})/); if (layoutMatch) { notes.push(`【布局要点】\n${layoutMatch[1].trim()}`); } } // 7. 施工注意事项(从优化建议提取) if (structuredData?.suggestions) { const attentionPoints: string[] = []; // 提取落地可行性 const feasibilityMatch = structuredData.suggestions.match(/落地可行性[::]\s*([^\n。]{10,80})/); if (feasibilityMatch) attentionPoints.push(feasibilityMatch[1].trim()); // 提取细节优化 const detailMatch = structuredData.suggestions.match(/细节优化[::]\s*([^\n。]{10,80})/); if (detailMatch) attentionPoints.push(detailMatch[1].trim()); if (attentionPoints.length > 0) { notes.push(`【施工注意】\n${attentionPoints.join('\n')}`); } } // 8. 品质要求(固定添加) notes.push(`【品质要求】\n新客户,需严格把控施工品质和材料质量`); return notes.length > 0 ? notes.join('\n\n') : '请根据分析内容补充具体要求'; } /** * 生成客户报告(此方法保留以便后续使用) */ async generateClientReport(options: { analysisData: any; spaceName: string; onContentChange?: (content: string) => void; loading?: any; }): Promise { return new Promise(async (resolve, reject) => { try { // 使用格式化后的内容 const content = options.analysisData?.formattedContent || options.analysisData?.rawContent || '暂无报告内容'; resolve(content); } catch (error: any) { reject(new Error('生成报告失败: ' + error.message)); } }); } }