| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453 |
- 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';
- // 防止重复分析的标记
- private isAnalyzing: boolean = false;
- 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<any> {
- // 🔥 防止重复分析:如果正在分析中,直接拒绝
- if (this.isAnalyzing) {
- console.log('⚠️ [analyzeReferenceImages] 正在分析中,拒绝重复调用');
- return Promise.reject(new Error('正在分析中,请稍候'));
- }
- this.isAnalyzing = true;
- console.log('🔒 [analyzeReferenceImages] 设置分析锁');
- 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);
- }
- // 🔥 释放分析锁
- this.isAnalyzing = false;
- console.log('🔓 [analyzeReferenceImages] 释放分析锁(成功)');
- resolve(analysisData);
- } catch (err: any) {
- // 🔥 释放分析锁
- this.isAnalyzing = false;
- console.log('🔓 [analyzeReferenceImages] 释放分析锁(失败)');
- 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(' | ') : '暂无摘要';
- }
- /**
- * 生成客服标注(从AI分析结果提取关键信息)
- */
- generateCustomerServiceNotes(analysisData: any, customerRequirements?: string): string {
- 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') : '请根据分析内容补充具体要求';
- }
- /**
- * 🔥 新增:真正的AI对话功能(流式响应)
- * 参考ai.service.ts的实现方式,使用FmodeChatCompletion
- */
- async chatWithAI(options: {
- userMessage: string;
- conversationHistory: Array<{ role: string; content: string; images?: string[] }>;
- images?: string[];
- context?: any;
- onContentStream?: (content: string) => void;
- }): Promise<string> {
- console.log('💬 [chatWithAI] 开始AI对话');
- console.log('📝 [chatWithAI] 用户消息:', options.userMessage);
- console.log('📜 [chatWithAI] 对话历史数量:', options.conversationHistory.length);
- console.log('📸 [chatWithAI] 图片数量:', options.images?.length || 0);
- return new Promise((resolve, reject) => {
- try {
- // 构建消息列表(包含历史对话和当前消息)
- const messageList: any[] = [];
- // 🔥 如果有上下文(结构化分析结果),添加为系统提示
- if (options.context) {
- console.log('🧠 [chatWithAI] 添加结构化上下文');
- let contextContent = '';
-
- // 如果是完整的分析结果对象
- if (options.context.structuredData) {
- contextContent = JSON.stringify(options.context.structuredData);
- } else {
- contextContent = JSON.stringify(options.context);
- }
-
- // 限制上下文长度,避免超出token限制
- if (contextContent.length > 5000) {
- contextContent = contextContent.substring(0, 5000) + '...';
- }
-
- messageList.push({
- role: 'system',
- content: `这是当前设计的详细分析数据,请基于此回答用户问题:\n${contextContent}`
- });
- }
- // 添加历史对话(但不包含图片,只在首次分析时使用图片)
- options.conversationHistory.forEach(msg => {
- messageList.push({
- role: msg.role,
- content: msg.content
- });
- });
- // 添加当前用户消息
- const currentMessage: any = {
- role: 'user',
- content: options.userMessage
- };
- // 🔥 如果提供了图片,添加到当前消息
- if (options.images && options.images.length > 0) {
- currentMessage.images = options.images;
- console.log('📸 [chatWithAI] 当前消息包含图片:', options.images.length);
- }
- messageList.push(currentMessage);
- console.log('📤 [chatWithAI] 发送消息列表到AI,总计:', messageList.length, '条');
- // 使用FmodeChatCompletion进行流式对话
- 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;
- // 🔥 实时回调,更新UI
- options.onContentStream?.(content);
- console.log('📨 [chatWithAI] 流式内容更新,当前长度:', content.length);
- }
- // 🔥 对话完成
- if (message?.complete && fullContent) {
- console.log('✅ [chatWithAI] 对话完成,总长度:', fullContent.length);
- subscription.unsubscribe();
- resolve(fullContent);
- }
- },
- error: (error: any) => {
- console.error('❌ [chatWithAI] 对话失败:', error);
- subscription.unsubscribe();
- reject(new Error(`AI对话失败: ${error?.message || '未知错误'}`));
- }
- });
- // 🔥 超时保护(60秒)
- setTimeout(() => {
- if (fullContent) {
- console.log('⏰ [chatWithAI] 超时但已有内容,返回部分结果');
- subscription.unsubscribe();
- resolve(fullContent);
- } else {
- console.error('❌ [chatWithAI] 超时且无内容');
- subscription.unsubscribe();
- reject(new Error('AI响应超时'));
- }
- }, 60000);
- } catch (error: any) {
- console.error('❌ [chatWithAI] 初始化失败:', error);
- reject(new Error(`AI对话初始化失败: ${error?.message || '未知错误'}`));
- }
- });
- }
- /**
- * 生成客户报告(此方法保留以便后续使用)
- */
- async generateClientReport(options: {
- analysisData: any;
- spaceName: string;
- onContentChange?: (content: string) => void;
- loading?: any;
- }): Promise<string> {
- 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));
- }
- });
- }
- }
|