design-analysis-ai.service.ts 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317
  1. import { Injectable } from '@angular/core';
  2. import { FmodeParse } from 'fmode-ng/core';
  3. import { FmodeChatCompletion, completionJSON } from 'fmode-ng/core/agent/chat/completion';
  4. const Parse = FmodeParse.with('nova');
  5. /**
  6. * 室内设计AI分析服务
  7. * 使用豆包1.6模型进行设计分析
  8. */
  9. @Injectable({
  10. providedIn: 'root'
  11. })
  12. export class DesignAnalysisAIService {
  13. // AI模型配置(豆包1.6)
  14. private readonly AI_MODEL = 'fmode-1.6-cn';
  15. constructor() {}
  16. /**
  17. * 分析参考图片,识别场景类型和设计维度
  18. */
  19. async analyzeReferenceImages(options: {
  20. images: string[];
  21. textDescription?: string;
  22. spaceType?: string;
  23. conversationHistory?: Array<{ role: string; content: string }>;
  24. deepThinking?: boolean;
  25. onProgressChange?: (progress: string) => void;
  26. onContentStream?: (content: string) => void; // 新增:流式内容回调
  27. loading?: any;
  28. }): Promise<any> {
  29. return new Promise(async (resolve, reject) => {
  30. try {
  31. // 构建详细的分析提示词
  32. const prompt = this.buildAnalysisPrompt(
  33. options.spaceType,
  34. options.textDescription,
  35. options.conversationHistory,
  36. options.deepThinking
  37. );
  38. options.onProgressChange?.('正在识别场景和分析设计维度...');
  39. // 🔥 直接使用图片URL,不转base64(参考ai-k12-daofa的实现)
  40. console.log('📸 准备传入图片URL到AI模型...');
  41. console.log('📸 图片URL列表:', options.images);
  42. // 日志输出,帮助调试
  43. console.log('🤖 调用豆包1.6模型进行vision分析...');
  44. console.log('📸 图片数量:', options.images.length);
  45. console.log('📝 提示词长度:', prompt.length, '字符');
  46. console.log('🏠 空间类型:', options.spaceType);
  47. console.log('💬 对话历史:', options.conversationHistory?.length || 0, '条');
  48. // 检查提示词长度(建议不超过10000字符)
  49. if (prompt.length > 10000) {
  50. console.warn('⚠️ 提示词过长,可能导致API调用失败');
  51. }
  52. // 🔥 使用completionJSON进行vision分析(严格参考ai-k12-daofa的成功实现)
  53. console.log('🚀 开始调用completionJSON进行vision分析...');
  54. // 定义JSON schema(与提示词中的JSON格式完全一致)
  55. const outputSchema = `{
  56. "quickSummary": {
  57. "colorTone": "色彩基调(如: 暖色调、木色和暖灰色结合)",
  58. "mainMaterials": "主要材质(如: 软装以木作为主、黑色皮革沙发)",
  59. "atmosphere": "整体氛围(如: 温暖、舒适、生活气息浓厚)"
  60. },
  61. "spaceType": "空间类型(如:客餐厅一体化、主卧、玄关等)",
  62. "spacePositioning": "空间定位与场景属性的详细分析",
  63. "layout": "空间布局与动线的详细分析",
  64. "hardDecoration": "硬装系统细节的详细分析(顶面、墙面、地面、门窗)",
  65. "colorAnalysis": "色调精准分析(主色调、辅助色、色调关系)",
  66. "materials": "材质应用解析(自然材质、现代材质、材质对比)",
  67. "form": "形体与比例分析(空间形体、家具形体、造型细节)",
  68. "style": "风格与氛围营造(风格识别、氛围手法)",
  69. "suggestions": "专业优化建议(居住适配、细节优化、落地可行性)",
  70. "summary": "简洁摘要(格式: 空间类型 | 风格 | 色调 | 氛围)"
  71. }`;
  72. // 流式内容累积
  73. let streamContent = '';
  74. try {
  75. // 🔥 关键:使用completionJSON + vision: true + images (URL数组)
  76. console.log('📤 发送给AI的提示词:', prompt);
  77. console.log('📤 JSON Schema:', outputSchema);
  78. console.log('📤 图片URL:', options.images);
  79. const result = await completionJSON(
  80. prompt,
  81. outputSchema, // 🔥 关键:提供JSON schema
  82. (content) => {
  83. // 流式回调(模拟)
  84. console.log('📥 AI流式响应:', typeof content, content);
  85. if (content && options.onContentStream) {
  86. streamContent = content;
  87. // 🔥 关键修复:将JSON转为易读的中文格式化文本
  88. let displayText: string;
  89. let jsonObject: any = null;
  90. // 1. 尝试获取JSON对象
  91. if (typeof content === 'string') {
  92. // 检查是否是JSON字符串
  93. const trimmed = content.trim();
  94. if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
  95. console.log('🔍 检测到JSON格式字符串,尝试解析...');
  96. try {
  97. jsonObject = JSON.parse(trimmed);
  98. console.log('✅ JSON解析成功,完整对象');
  99. } catch (e) {
  100. // 🔥 关键修复:JSON不完整时,提取已完整的字段并格式化显示
  101. console.log('⚠️ JSON解析失败,尝试提取部分字段...');
  102. // 尝试从不完整的JSON中提取已完成的字段
  103. jsonObject = this.extractPartialJSON(trimmed);
  104. if (jsonObject && Object.keys(jsonObject).length > 0) {
  105. console.log('✅ 成功提取部分字段:', Object.keys(jsonObject).join(', '));
  106. } else {
  107. // 完全无法提取,显示提示
  108. displayText = '🔄 正在生成分析结果...';
  109. console.log('⚠️ 无法提取有效字段,等待更多数据');
  110. }
  111. }
  112. } else {
  113. // 普通文本,直接显示
  114. displayText = content;
  115. }
  116. } else if (typeof content === 'object') {
  117. jsonObject = content;
  118. console.log('📦 收到JSON对象');
  119. }
  120. // 2. 如果是JSON对象,进行格式化
  121. if (jsonObject) {
  122. console.log('🎨 开始格式化JSON对象...');
  123. displayText = this.formatJSONToText(jsonObject);
  124. // 如果格式化失败或内容过短,使用后备方案
  125. if (!displayText || displayText.trim().length < 50) {
  126. console.log('⚠️ formatJSONToText结果过短,使用fallback...');
  127. displayText = this.fallbackFormatJSON(jsonObject);
  128. }
  129. // 最后兜底:美化JSON
  130. if (!displayText || displayText.trim().length < 20) {
  131. console.log('⚠️ fallback也失败,使用beautifyJSON...');
  132. displayText = this.beautifyJSON(jsonObject);
  133. }
  134. console.log('✅ 格式化完成,长度:', displayText.length);
  135. }
  136. // 3. 如果还没有displayText,使用默认值
  137. if (!displayText) {
  138. displayText = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
  139. }
  140. options.onContentStream(displayText);
  141. options.onProgressChange?.(`正在分析,已接收 ${displayText.length} 字符...`);
  142. }
  143. },
  144. 2, // 重试次数
  145. {
  146. model: this.AI_MODEL,
  147. vision: true, // 🔥 关键:启用vision
  148. images: options.images, // 🔥 关键:直接传URL数组
  149. max_tokens: 8000,
  150. temperature: 0.3 // 🔥 降低随机性,提高一致性(0.0-1.0,越低越确定)
  151. }
  152. );
  153. console.log('📥 AI最终返回结果:', result);
  154. console.log('📥 返回结果类型:', typeof result);
  155. // 获取最终内容(result应该就是JSON对象)
  156. const analysisResult = result;
  157. console.log('✅ AI分析完成,返回JSON对象:', analysisResult);
  158. console.log('📝 AI返回JSON预览:', JSON.stringify(analysisResult).substring(0, 500));
  159. // 验证返回的JSON结构
  160. if (!analysisResult || typeof analysisResult !== 'object') {
  161. console.error('❌ AI返回格式错误,不是JSON对象');
  162. reject(new Error('AI返回格式异常,请重试'));
  163. return;
  164. }
  165. // 检查必要字段
  166. if (!analysisResult.spaceType || !analysisResult.spacePositioning) {
  167. console.error('❌ AI返回JSON缺少必要字段');
  168. console.error('🔍 AI返回的完整对象:', analysisResult);
  169. reject(new Error('AI分析结果不完整,请重试'));
  170. return;
  171. }
  172. // 解析JSON结果
  173. const analysisData = this.parseJSONAnalysis(analysisResult);
  174. console.log('📊 解析后的分析数据:', analysisData);
  175. // 🔥 关键:在最后发送完整的格式化内容
  176. if (options.onContentStream && analysisData.formattedContent) {
  177. console.log('📤 发送最终格式化内容到UI...');
  178. options.onContentStream(analysisData.formattedContent);
  179. }
  180. resolve(analysisData);
  181. } catch (err: any) {
  182. console.error('❌ completionJSON失败,详细错误:', err);
  183. console.error('❌ 错误类型:', err?.constructor?.name);
  184. console.error('❌ 错误消息:', err?.message);
  185. // 🔥 关键:如果completionJSON失败,尝试使用FmodeChatCompletion作为备选方案
  186. if (err?.message?.includes('JSON') || err?.message?.includes('格式')) {
  187. console.warn('⚠️ completionJSON解析失败,尝试使用FmodeChatCompletion备选方案...');
  188. try {
  189. // 使用FmodeChatCompletion获取纯文本响应
  190. const textPrompt = this.buildTextAnalysisPrompt(options.spaceType, options.textDescription);
  191. const messageList = [{
  192. role: 'user',
  193. content: textPrompt,
  194. images: options.images
  195. }];
  196. const completion = new FmodeChatCompletion(messageList, {
  197. model: this.AI_MODEL,
  198. max_tokens: 8000
  199. });
  200. let fullContent = '';
  201. const subscription = completion.sendCompletion({
  202. isDirect: true,
  203. }).subscribe({
  204. next: (message: any) => {
  205. const content = message?.content || '';
  206. if (content) {
  207. fullContent = content;
  208. options.onContentStream?.(content);
  209. }
  210. if (message?.complete && fullContent) {
  211. console.log('✅ FmodeChatCompletion备选方案成功,内容长度:', fullContent.length);
  212. const analysisData = this.parseAnalysisContent(fullContent);
  213. resolve(analysisData);
  214. subscription?.unsubscribe();
  215. }
  216. },
  217. error: (err2: any) => {
  218. console.error('❌ FmodeChatCompletion备选方案也失败:', err2);
  219. reject(new Error('AI分析失败,请稍后重试'));
  220. subscription?.unsubscribe();
  221. }
  222. });
  223. return; // 使用备选方案,不继续执行下面的reject
  224. } catch (fallbackErr: any) {
  225. console.error('❌ 备选方案失败:', fallbackErr);
  226. }
  227. }
  228. // 根据错误类型提供更具体的错误信息
  229. let errorMessage = 'AI分析失败';
  230. if (err?.message?.includes('500')) {
  231. errorMessage = 'AI服务暂时不可用(服务器错误),请稍后重试';
  232. } else if (err?.message?.includes('timeout')) {
  233. errorMessage = 'AI分析超时,请减少图片数量或简化需求后重试';
  234. } else if (err?.message?.includes('token')) {
  235. errorMessage = '提示词过长,请简化描述或减少对话历史';
  236. } else if (err?.message) {
  237. errorMessage = `AI分析失败: ${err.message}`;
  238. }
  239. reject(new Error(errorMessage));
  240. }
  241. } catch (error: any) {
  242. reject(new Error('分析失败: ' + error.message));
  243. }
  244. });
  245. }
  246. /**
  247. * 构建纯文本分析提示词(用于FmodeChatCompletion备选方案)
  248. */
  249. private buildTextAnalysisPrompt(spaceType?: string, textDescription?: string): string {
  250. let prompt = `请对图片中的室内设计进行专业分析,从以下8个维度详细展开:
  251. 一、空间定位与场景属性
  252. 二、空间布局与动线
  253. 三、硬装系统细节
  254. 四、色调精准分析
  255. 五、材质应用解析
  256. 六、形体与比例
  257. 七、风格与氛围营造
  258. 八、专业优化建议
  259. 要求:
  260. 1. 基于图片实际视觉内容进行分析
  261. 2. 每个维度2-4个段落,每段3-5行
  262. 3. 使用专业的室内设计术语
  263. 4. 不使用Markdown符号,使用纯文本格式`;
  264. if (spaceType) {
  265. prompt += `\n5. 空间类型参考: ${spaceType}`;
  266. }
  267. if (textDescription) {
  268. prompt += `\n6. 客户需求参考: ${textDescription}`;
  269. }
  270. return prompt;
  271. }
  272. /**
  273. * 检测用户是否在进行单维度询问(而非完整分析)
  274. */
  275. private detectSingleDimensionQuery(userInput: string): boolean {
  276. const lowerInput = userInput.toLowerCase();
  277. // 🔥 单维度询问关键词
  278. const singleDimensionKeywords = [
  279. // 询问词
  280. '什么', '如何', '怎么', '哪些', '是否', '有没有',
  281. // 具体维度
  282. '色彩', '色调', '颜色', '配色',
  283. '材质', '材料', '用料',
  284. '布局', '动线', '空间',
  285. '灯光', '照明',
  286. '风格', '氛围',
  287. '尺寸', '大小', '面积',
  288. '建议', '优化', '改进',
  289. // 疑问形式
  290. '?', '?'
  291. ];
  292. // 🔥 完整分析关键词(如果包含这些,说明要完整分析)
  293. const fullAnalysisKeywords = [
  294. '完整', '全面', '详细分析', '整体分析',
  295. '重新分析', '再分析一次', '重新生成',
  296. '全部', '所有维度', '各个方面'
  297. ];
  298. // 如果包含完整分析关键词,返回false(不是单维度)
  299. for (const keyword of fullAnalysisKeywords) {
  300. if (lowerInput.includes(keyword)) {
  301. return false;
  302. }
  303. }
  304. // 如果包含单维度关键词,返回true
  305. for (const keyword of singleDimensionKeywords) {
  306. if (lowerInput.includes(keyword)) {
  307. return true;
  308. }
  309. }
  310. // 🔥 如果用户输入很短(<20字),可能是简单询问
  311. if (userInput.length < 20) {
  312. return true;
  313. }
  314. // 默认:如果用户输入很长(>50字),可能是要求完整分析
  315. return userInput.length < 50;
  316. }
  317. /**
  318. * 构建AI分析提示词(JSON格式输出,兼容completionJSON)
  319. * 参考ai-k12-daofa的简洁提示词风格
  320. */
  321. private buildAnalysisPrompt(spaceType?: string, textDescription?: string, conversationHistory?: Array<{ role: string; content: string }>, deepThinking?: boolean): string {
  322. // 🔥 全面优化的提示词:支持多种风格,精准识别材质和色调,支持重新分析和单维度问答
  323. const hasPreviousAnalysis = conversationHistory && conversationHistory.length > 0;
  324. // 🔥 检测用户是否在进行单维度询问
  325. const isSingleDimensionQuery = hasPreviousAnalysis && textDescription && this.detectSingleDimensionQuery(textDescription);
  326. let prompt = `你是一位专业的室内设计分析师,请仔细观察图片中的室内设计细节`;
  327. // 🔥 如果是单维度询问,使用对话模式
  328. if (isSingleDimensionQuery) {
  329. prompt += `,并针对用户的具体问题进行专业回答。
  330. 【重要说明 - 单维度问答模式】
  331. • 用户正在进行特定维度的询问(如色彩、材质、布局等)
  332. • 请直接针对用户的问题进行详细、专业的文字回答
  333. • 不需要输出完整的JSON结构分析报告
  334. • 使用自然流畅的语言,就像设计师之间的专业交流
  335. • 回答应该详细、准确,包含具体的色号、材质名称等专业术语
  336. 【用户当前问题】:${textDescription}
  337. 请针对这个问题,给出专业、详细的回答:`;
  338. return prompt;
  339. }
  340. // 🔥 否则,使用完整分析模式
  341. prompt += `,并按以下JSON格式输出专业分析:`;
  342. // 🔥 如果有对话历史,说明这是用户提出修正意见后的重新分析
  343. if (hasPreviousAnalysis) {
  344. prompt += `
  345. 【重要说明 - 完整重新分析】
  346. • 用户已经看过之前的分析,并提出了修正意见或新的要求
  347. • 请基于用户的反馈,重新生成一份完整的、修正后的分析报告
  348. • 不要继续之前的分析内容,而是输出一份全新的、完整的JSON分析结果
  349. • 特别关注用户提到的色调、材质、风格等修正意见,确保新的分析符合用户期望
  350. 【分析要求】`;
  351. }
  352. prompt += `
  353. {
  354. "quickSummary": {
  355. "colorTone": "色彩基调(如: 暖白法式偏女性向,象牙白护墙板+浅灰地面+米色木地板,软装点缀豆沙紫/湖蓝色)",
  356. "mainMaterials": "主要材质(如: 象牙白护墙板+线条装饰、大理石纹理台面、黑色雕刻家具、水晶灯、香槟金灯具、豆沙紫/湖蓝色软装)",
  357. "atmosphere": "整体氛围(如: 柔暖、精致、优雅、女性向、明亮通透)"
  358. },
  359. "spaceType": "空间类型(如:客餐厅一体化、主卧、玄关等)",
  360. "spacePositioning": "空间定位与场景属性的详细分析",
  361. "layout": "空间布局与动线的详细分析",
  362. "hardDecoration": "硬装系统细节的详细分析(顶面、墙面、地面、门窗)",
  363. "colorAnalysis": "色调精准分析",
  364. "materials": "材质应用解析",
  365. "form": "形体与比例分析(空间形体、家具形体、造型细节)",
  366. "style": "风格与氛围营造",
  367. "suggestions": "专业优化建议(居住适配、细节优化、落地可行性)",
  368. "summary": "简洁摘要(格式: 空间类型 | 风格 | 色调 | 氛围)"
  369. }
  370. 【核心分析原则 - 保证一致性】
  371. • 🔥 **客观描述优先**:基于图片中可见的元素进行描述,避免过度解读
  372. • 🔥 **细节精准识别**:准确识别护墙板、线条、大理石纹理、金属材质等细节
  373. • 🔥 **色调精准定位**:使用精确的色彩描述(淡奶灰、暖白、米色、木色等)
  374. • 🔥 **风格准确判断**:根据硬装+软装+色调综合判断(现代法式、侘寂、轻奢等)
  375. • 🔥 **保持分析一致性**:相同的视觉元素应该得出相同的结论
  376. 【关键分析要求】
  377. 0. **快速总结 (quickSummary)** - 🔥 优先级最高:
  378. • 色彩基调(colorTone):精准描述主色调组合
  379. 【现代法式女性向示例】
  380. - "暖白法式偏女性向,象牙白护墙板+浅灰地面+米色木地板,软装点缀豆沙紫/湖蓝色"
  381. - "浅色调为主,暖白色/米色为基底,黑色家具+香槟金灯具点缀"
  382. - "暖白象牙色主导,大理石灰白褐纹呼应,彩色软装增添柔美"
  383. 【侘寂风格示例】
  384. - "暖色调,木色和暖灰色结合"
  385. - "自然暖棕+柔和灰,低饱和度舒适系"
  386. 【轻奢风格示例】
  387. - "高级灰+香槟金,大理石质感"
  388. - "冷灰主导+金属光泽,精致轻奢"
  389. ⚠️ 必须明确:
  390. - 冷暖倾向(暖白/冷白/中性)
  391. - 主色调名称(象牙白/暖白/淡奶灰)
  392. - 关键配色(软装彩色/黑色对比/金属点缀)
  393. • 主要材质(mainMaterials):按重要性列举关键材质
  394. 【现代法式女性向示例】
  395. - "象牙白护墙板+线条装饰、大理石纹理台面、黑色雕刻家具、水晶灯、香槟金灯具"
  396. - "白色岩板、浅灰地砖、米色木地板、豆沙紫/湖蓝色软装、绿植装饰"
  397. 【侘寂风格示例】
  398. - "木质软装为主、黑色皮革沙发、藤编家具、混凝土墙面"
  399. ⚠️ 优先识别:
  400. - 硬装:护墙板、线条、大理石/岩板、木地板、瓷砖
  401. - 软装:家具材质(木质/皮革/布艺)、灯具(水晶/金属)
  402. - 装饰:花瓶、烛台、绿植、艺术品
  403. • 整体氛围(atmosphere):3-5个关键词
  404. 【现代法式女性向示例】
  405. - "柔暖、精致、优雅、女性向、浪漫"
  406. - "明亮通透、轻盈柔美、精致细腻"
  407. 【侘寂风格示例】
  408. - "温暖、舒适、质朴、生活气息"
  409. ⚠️ 氛围词必须与色调、材质相呼应:
  410. - 暖白+浅色 = 明亮、通透、柔和
  411. - 彩色软装 = 女性向、浪漫、柔美
  412. - 黑色对比+金属 = 精致、优雅、轻奢感
  413. 1. **色调精准分析 (colorAnalysis)** - 🔥 核心重点:
  414. • 🎨 **硬装基础色精准识别**:
  415. - **暖白色/象牙白**:偏米色的白色,带微黄调(NCS S 0502-Y50R)
  416. * 应用:护墙板、墙面涂料、顶面
  417. * 特征:柔和、温润、不刺眼
  418. - **浅灰色系**:
  419. * 淡奶灰色:带微黄调/微粉调的浅灰色(NCS S 0502-Y、S 0502-R)
  420. * 灰蓝色:带微蓝调的浅灰色(如背景墙)
  421. * 浅灰地面:大理石/瓷砖的自然灰色
  422. - **米色/奶咖色**:
  423. * 木地板:浅橡木色、枫木色
  424. * 暖米色墙面:带黄调的浅米色
  425. - **大理石纹理色**:
  426. * 白色基底+褐色/金色纹理(桌面、台面)
  427. * 白色基底+蓝绿色纹理(装饰性大理石)
  428. * 灰白色带细褐纹(地面)
  429. • 🎀 **软装点缀色精准识别** - 女性向关键:
  430. - **豆沙紫/紫罗兰色**:柔和的紫色调沙发/椅子
  431. - **湖蓝色/Tiffany蓝**:清新的蓝绿色调椅子/装饰
  432. - **香槟金/玫瑰金**:金属灯具、装饰件
  433. - **黑色**:雕刻家具、烛台、电器(形成优雅对比)
  434. - **绿植色**:自然绿色,点缀生机
  435. • 🔢 **色彩比例量化分析**:
  436. 【现代法式女性向示例】
  437. - 主色调(70%):暖白色/象牙白护墙板+墙面
  438. - 辅助色(20%):浅灰色地面+米色木地板
  439. - 点缀色(10%):豆沙紫+湖蓝色软装 + 黑色家具 + 香槟金灯具
  440. 【侘寂风格示例】
  441. - 主色调(60%):暖灰色墙面
  442. - 辅助色(30%):木色家具
  443. - 点缀色(10%):黑色皮革
  444. • 🌡️ **冷暖定位精准判断**:
  445. - **暖色系**:木色、米色、暖灰、奶咖、淡粉、豆沙紫
  446. - **中性偏暖**:暖白/象牙白、淡奶灰(带微黄调)
  447. - **清新偏冷**:湖蓝色、Tiffany蓝、灰蓝色
  448. - **冷色系**:纯灰、冷白、深蓝灰
  449. - **中性色**:黑色(优雅对比)、金色(精致点缀)
  450. • 🎯 **色调协调性分析** - 重要:
  451. - 大面积浅色(暖白+浅灰)营造明亮通透感
  452. - 软装彩色(豆沙紫+湖蓝)增加女性柔美感
  453. - 黑色家具形成优雅对比,提升精致度
  454. - 金色饰品点缀轻奢感
  455. - 自然绿植平衡色彩,增加生机
  456. • ⚠️ **避免模糊词汇,使用精准描述**:
  457. ❌ "中性灰棕色系" → ✅ "暖白法式,浅色调为主"
  458. ❌ "浅色系" → ✅ "象牙白护墙板+浅灰地面+米色木地板"
  459. ❌ "彩色点缀" → ✅ "豆沙紫沙发+湖蓝色椅子+香槟金灯具"
  460. 2. **材质应用解析 (materials)** - 全面细致:
  461. • 🏗️ **硬装材质全面识别**:
  462. 【墙面系统】
  463. * **护墙板**(法式关键特征):
  464. - 颜色:象牙白/暖白/淡奶灰
  465. - 工艺:凸起线条装饰、方框造型、哑光质感
  466. - 细节:线条宽度、阴影层次、接缝工艺
  467. * **线条装饰**(法式精髓):
  468. - 顶角线/腰线/门框线
  469. - 造型:简约直线/曲线/雕花
  470. - 材质:石膏/PU/实木
  471. * **涂料墙面**:
  472. - 暖白色/米色/浅灰色乳胶漆
  473. - 质感:哑光/丝光
  474. * **大理石/岩板背景墙**:
  475. - 白色岩板(电视墙/装饰墙)
  476. - 纹理大理石(背景装饰)
  477. 【地面系统】
  478. * **大理石地砖**:
  479. - 浅灰色柔哑面地砖(主要区域)
  480. - 纹理:细密灰白纹理/大理石自然纹
  481. - 拼接:直铺/对角铺/拼花
  482. * **木地板**:
  483. - 浅色木地板(米色/浅橡木色)
  484. - 工艺:直拼/人字拼/鱼骨拼
  485. - 质感:哑光/半哑光
  486. 【顶面系统】
  487. * 石膏线装饰/隐藏式灯带
  488. * 平顶+局部造型
  489. * 无主灯设计(筒灯+吊灯组合)
  490. 【门窗系统】
  491. * 拱门造型(法式经典元素)
  492. * 门框线条装饰
  493. • 🛋️ **软装材质细节描述** - 女性向重点:
  494. 【家具材质】
  495. * **桌子**:
  496. - 大理石圆桌(灰白褐纹/蓝绿纹理)
  497. - 黑色雕刻底座(手工雕花纹理)
  498. * **沙发/椅子**:
  499. - 豆沙紫布艺沙发(丝绒/天鹅绒质感)
  500. - 湖蓝色椅子(皮革/布艺)
  501. - 曲线造型、包裹感强
  502. * **柜子**:
  503. - 白色浮雕柜(立体雕花装饰)
  504. - 储物功能+装饰性
  505. 【灯具材质】
  506. * **水晶灯**:
  507. - 透明水晶台灯(切面反光)
  508. - 白色灯罩(布艺/丝绸质感)
  509. * **金属灯**:
  510. - 香槟金/玫瑰金树枝造型灯
  511. - 艺术造型、雕塑感
  512. 【装饰品材质】
  513. * **花瓶**:
  514. - 白色陶瓷花瓶(哑光质感)
  515. - 造型:圆润/曲线/传统
  516. * **烛台**:
  517. - 黑色烛台(亮面烤漆/陶瓷)
  518. - 组合摆放,形成节奏感
  519. * **绿植**:
  520. - 自然枝条(枯枝/绿叶)
  521. - 点缀生机、柔化空间
  522. * **墙面装饰**:
  523. - 蝴蝶/鸟类装饰(白色/金色)
  524. - 艺术挂画
  525. • 🔍 **材质质感精准描述** - 触觉与视觉:
  526. - **护墙板**:哑光漆面,肌理感,立体阴影
  527. - **大理石**:
  528. * 柔哑面(不反光,温润触感)
  529. * 天然纹理(褐色/金色/蓝绿色不规则纹路)
  530. * 质感层次(深浅交错,自然过渡)
  531. - **木质**:自然木纹,温润触感,哑光/半哑光
  532. - **金属**:
  533. * 黑色雕刻(手工痕迹,哑光质感)
  534. * 香槟金(微光泽,细腻拉丝)
  535. - **水晶/玻璃**:透明清澈,切面反光,精致感
  536. - **布艺**:丝绒柔软,天鹅绒光泽,包裹感
  537. - **陶瓷**:哑光白瓷,温润细腻,手工感
  538. 3. **风格与氛围营造 (style)** - 综合判断:
  539. • 🎭 **风格准确识别** - 基于硬装+软装+色调综合判断:
  540. 【现代法式】关键特征:
  541. - 硬装:淡奶灰/暖白/象牙白护墙板 + 线条装饰 + 大理石地面 + 拱门造型
  542. - 软装:水晶灯/金属灯 + 精致家具 + 花艺绿植装饰
  543. - 色调:暖白/象牙白为主 + 米色/浅灰辅助 + 黑色家具对比
  544. - 氛围:柔暖、精致、优雅、明亮通透
  545. 【现代法式·女性向】附加特征:
  546. - 软装彩色:豆沙紫/湖蓝色/淡粉色沙发/椅子
  547. - 装饰细节:蝴蝶/鸟类墙饰、曲线造型、浮雕柜
  548. - 灯具选择:水晶台灯、香槟金/玫瑰金灯具
  549. - 色彩搭配:浅色基底+柔和彩色点缀
  550. - 氛围升级:女性向、浪漫、轻盈柔美、精致细腻
  551. 【温润侘寂】关键特征:
  552. - 硬装:暖灰色墙面 + 木地板 + 简约造型
  553. - 软装:木质家具为主 + 藤编/皮革 + 自然装饰
  554. - 色调:木色 + 暖灰色 + 低饱和度
  555. - 氛围:温暖、舒适、质朴、生活气息
  556. 【现代轻奢】关键特征:
  557. - 硬装:大理石 + 金属线条 + 高级灰
  558. - 软装:轻奢家具 + 金属饰品 + 艺术挂画
  559. - 色调:高级灰 + 香槟金/玫瑰金 + 白色
  560. - 氛围:精致、时尚、轻奢、品质感
  561. • 💫 **氛围判断依据**:
  562. - 柔暖精致:淡奶灰+暖白+水晶灯+护墙板
  563. - 温暖舒适:木色+暖灰+木质软装+自然光
  564. - 清冷克制:纯灰+冷白+极简家具+留白
  565. - 女性向/浪漫:淡粉色点缀+曲线造型+精致细节
  566. • ⚠️ **避免混淆**:
  567. - 护墙板+水晶灯+大理石 = 法式,而非侘寂
  568. - 木质+混凝土+暖灰 = 侘寂,而非法式
  569. 4. **专业优化建议 (suggestions)**:
  570. • 🏠 **居住适配** - 基于风格特征提建议:
  571. - 法式风格:补充淡粉色软装(女儿房)、台盆柜+梳妆台一体化设计
  572. - 侘寂风格:木质模块化收纳、暖灰色地毯、生活化装饰
  573. • 🔧 **细节优化**:
  574. - 材质统一性(护墙板色调协调、大理石纹理呼应)
  575. - 色彩过渡(淡奶灰→米色→木色的渐变)
  576. - 隐形工程(筒射灯预埋无边框、空调风口预埋无边框)
  577. • ✅ **落地可行性**:
  578. - 材料选择(护墙板材质、大理石品类、木地板工艺)
  579. - 施工注意事项(拼花对缝、线条安装、灯光预埋)
  580. 5. **基础要求 - 保证分析质量**:
  581. • ✅ 基于图片**实际可见元素**进行分析,严禁臆测或模板化
  582. • ✅ 每个字段提供**详细描述**(200-400字)
  583. • ✅ 使用**专业室内设计术语**(护墙板、线条、大理石纹理、柔哑面等)
  584. • ✅ **不提及品牌**,仅描述材质、色调、形态、氛围
  585. • ✅ **保持客观中立**,避免过度解读或情感化描述
  586. • ✅ **同一视觉元素=同一结论**,确保分析一致性`;
  587. // 添加空间类型提示
  588. if (spaceType) {
  589. prompt += `\n\n【空间类型参考】: ${spaceType}`;
  590. }
  591. // 添加客户需求提示
  592. if (textDescription) {
  593. prompt += `\n\n【客户核心需求】: ${textDescription}\n请特别关注客户需求中提到的色调、材质、氛围要求,确保分析结果与需求高度契合`;
  594. }
  595. // 🔥 强化分析质量要求
  596. if (hasPreviousAnalysis) {
  597. // 如果是重新分析,强调要结合用户反馈
  598. prompt += `\n\n【重要提醒 - 重新分析要点】
  599. • 仔细阅读用户的修正意见和反馈,理解用户的真实需求
  600. • 重新审视图片,基于用户指出的方向进行调整
  601. • 输出一份完整的、修正后的JSON分析报告
  602. • 使用精准的专业术语(如"淡奶灰色护墙板"而非"灰色墙面")
  603. • 如果用户指出了色调、材质、风格的具体要求,必须在新报告中体现
  604. • 保持分析的专业性和完整性,不要只修改某一部分`;
  605. } else {
  606. // 如果是首次分析,强调一致性
  607. prompt += `\n\n【重要提醒 - 保证分析质量】
  608. • 基于图片中客观可见的元素进行分析,避免主观臆测
  609. • 使用精准的专业术语(如"淡奶灰色护墙板"而非"灰色墙面")
  610. • 材质、色调、风格的判断应该保持逻辑一致性
  611. • 避免使用模糊或可变的描述词汇
  612. • 确保分析的完整性和专业性`;
  613. }
  614. return prompt;
  615. }
  616. /**
  617. * 解析JSON格式的AI分析结果(新方法,处理completionJSON返回的JSON对象)
  618. */
  619. private parseJSONAnalysis(jsonResult: any): any {
  620. console.log('📝 [parseJSONAnalysis] 开始解析JSON分析结果...');
  621. console.log('🔍 [parseJSONAnalysis] JSON对象:', JSON.stringify(jsonResult).substring(0, 300));
  622. // 将JSON字段转换为易读的格式化文本
  623. let formattedContent = this.formatJSONToText(jsonResult);
  624. // 🔥 关键:如果formattedContent为空或过短,说明JSON可能没有标准字段
  625. if (!formattedContent || formattedContent.trim().length < 50) {
  626. console.warn('⚠️ [parseJSONAnalysis] 格式化内容过短,尝试后备方案...');
  627. formattedContent = this.fallbackFormatJSON(jsonResult);
  628. }
  629. // 🔥 最终校验:如果还是为空,使用原始JSON的美化版本
  630. if (!formattedContent || formattedContent.trim().length < 20) {
  631. console.warn('⚠️ [parseJSONAnalysis] 后备方案也失败,使用JSON美化版本...');
  632. formattedContent = this.beautifyJSON(jsonResult);
  633. }
  634. console.log('✅ [parseJSONAnalysis] 最终格式化内容长度:', formattedContent.length);
  635. console.log('📝 [parseJSONAnalysis] 内容预览:', formattedContent.substring(0, 200));
  636. return {
  637. rawContent: JSON.stringify(jsonResult, null, 2), // 原始JSON
  638. formattedContent: formattedContent, // 格式化文本(确保有内容)
  639. structuredData: {
  640. quickSummary: jsonResult.quickSummary || null, // 🔥 快速总结
  641. spacePositioning: jsonResult.spacePositioning || '',
  642. layout: jsonResult.layout || '',
  643. hardDecoration: jsonResult.hardDecoration || '',
  644. colorAnalysis: jsonResult.colorAnalysis || '',
  645. materials: jsonResult.materials || '',
  646. form: jsonResult.form || '',
  647. style: jsonResult.style || '',
  648. suggestions: jsonResult.suggestions || ''
  649. },
  650. spaceType: jsonResult.spaceType || '',
  651. summary: jsonResult.summary || '',
  652. hasContent: true,
  653. timestamp: new Date().toISOString()
  654. };
  655. }
  656. /**
  657. * 美化JSON显示(当所有格式化方法都失败时的最后手段)
  658. */
  659. private beautifyJSON(jsonResult: any): string {
  660. const lines: string[] = [];
  661. for (const [key, value] of Object.entries(jsonResult)) {
  662. if (value && typeof value === 'string' && value.trim().length > 0) {
  663. // 将驼峰命名转换为中文标题
  664. const chineseTitle = this.getChineseTitleForKey(key);
  665. lines.push(`【${chineseTitle}】\n${value}\n`);
  666. }
  667. }
  668. return lines.join('\n') || '分析结果为空,请重新分析';
  669. }
  670. /**
  671. * 将JSON字段名转换为中文标题
  672. */
  673. private getChineseTitleForKey(key: string): string {
  674. const titleMap: { [key: string]: string } = {
  675. 'spaceType': '空间类型',
  676. 'spacePositioning': '空间定位与场景属性',
  677. 'layout': '空间布局与动线',
  678. 'hardDecoration': '硬装系统细节',
  679. 'colorAnalysis': '色调精准分析',
  680. 'materials': '材质应用解析',
  681. 'form': '形体与比例',
  682. 'style': '风格与氛围营造',
  683. 'suggestions': '专业优化建议',
  684. 'summary': '设计概要'
  685. };
  686. return titleMap[key] || key;
  687. }
  688. /**
  689. * 将JSON结果转换为易读的文本格式
  690. */
  691. private formatJSONToText(jsonResult: any): string {
  692. console.log('🔄 [formatJSONToText] 开始格式化JSON结果...');
  693. console.log('🔍 [formatJSONToText] JSON字段数量:', Object.keys(jsonResult).length);
  694. const sections = [];
  695. // 🔥 关键:确保每个字段都被处理,即使内容为空也显示标题
  696. if (jsonResult.spacePositioning) {
  697. sections.push(`一、空间定位与场景属性\n\n${jsonResult.spacePositioning}\n`);
  698. }
  699. if (jsonResult.layout) {
  700. sections.push(`二、空间布局与动线\n\n${jsonResult.layout}\n`);
  701. }
  702. if (jsonResult.hardDecoration) {
  703. sections.push(`三、硬装系统细节\n\n${jsonResult.hardDecoration}\n`);
  704. }
  705. if (jsonResult.colorAnalysis) {
  706. sections.push(`四、色调精准分析\n\n${jsonResult.colorAnalysis}\n`);
  707. }
  708. if (jsonResult.materials) {
  709. sections.push(`五、材质应用解析\n\n${jsonResult.materials}\n`);
  710. }
  711. if (jsonResult.form) {
  712. sections.push(`六、形体与比例\n\n${jsonResult.form}\n`);
  713. }
  714. if (jsonResult.style) {
  715. sections.push(`七、风格与氛围营造\n\n${jsonResult.style}\n`);
  716. }
  717. if (jsonResult.suggestions) {
  718. sections.push(`八、专业优化建议\n\n${jsonResult.suggestions}\n`);
  719. }
  720. const formattedText = sections.join('\n');
  721. console.log('✅ [formatJSONToText] 格式化完成,长度:', formattedText.length);
  722. console.log('📝 [formatJSONToText] 内容预览:', formattedText.substring(0, 200));
  723. // 🔥 后备机制:如果格式化结果为空,尝试从JSON直接生成文本
  724. if (!formattedText || formattedText.trim().length === 0) {
  725. console.warn('⚠️ [formatJSONToText] 格式化结果为空,使用后备方案...');
  726. return this.fallbackFormatJSON(jsonResult);
  727. }
  728. return formattedText;
  729. }
  730. /**
  731. * 从不完整的JSON字符串中提取已完成的字段
  732. * 🔥 流式传输专用:实时提取部分字段
  733. */
  734. private extractPartialJSON(jsonString: string): any {
  735. console.log('🔧 [extractPartialJSON] 开始提取部分JSON字段...');
  736. const result: any = {};
  737. // 定义所有可能的字段
  738. const fields = [
  739. 'spaceType', 'spacePositioning', 'layout', 'hardDecoration',
  740. 'colorAnalysis', 'materials', 'form', 'style', 'suggestions', 'summary'
  741. ];
  742. // 使用正则表达式提取每个字段的完整值
  743. for (const field of fields) {
  744. // 匹配 "fieldName": "value" 或 "fieldName": "value...(可能不完整)
  745. const regex = new RegExp(`"${field}"\\s*:\\s*"([^"]*(?:"[^"]*)*)"`, 'g');
  746. const match = regex.exec(jsonString);
  747. if (match && match[1]) {
  748. // 提取到完整的字段值
  749. result[field] = match[1];
  750. console.log(`✅ 提取字段 ${field}:`, match[1].substring(0, 50) + '...');
  751. } else {
  752. // 尝试提取不完整的值(到字符串末尾)
  753. const partialRegex = new RegExp(`"${field}"\\s*:\\s*"([^"]*?)(?:"|$)`, 's');
  754. const partialMatch = partialRegex.exec(jsonString);
  755. if (partialMatch && partialMatch[1] && partialMatch[1].length > 20) {
  756. // 只有当值足够长时才提取(避免只有几个字符的情况)
  757. result[field] = partialMatch[1] + '...';
  758. console.log(`⚠️ 提取不完整字段 ${field}:`, partialMatch[1].substring(0, 50) + '...');
  759. }
  760. }
  761. }
  762. const extractedCount = Object.keys(result).length;
  763. console.log(`✅ [extractPartialJSON] 提取了 ${extractedCount} 个字段`);
  764. return extractedCount > 0 ? result : null;
  765. }
  766. /**
  767. * 后备格式化方法:当主要方法失败时使用
  768. */
  769. private fallbackFormatJSON(jsonResult: any): string {
  770. const lines: string[] = [];
  771. // 遍历JSON对象的所有字段
  772. const fieldMap: { [key: string]: string } = {
  773. 'spaceType': '空间类型',
  774. 'spacePositioning': '一、空间定位与场景属性',
  775. 'layout': '二、空间布局与动线',
  776. 'hardDecoration': '三、硬装系统细节',
  777. 'colorAnalysis': '四、色调精准分析',
  778. 'materials': '五、材质应用解析',
  779. 'form': '六、形体与比例',
  780. 'style': '七、风格与氛围营造',
  781. 'suggestions': '八、专业优化建议',
  782. 'summary': '设计概要'
  783. };
  784. for (const [key, title] of Object.entries(fieldMap)) {
  785. if (jsonResult[key] && typeof jsonResult[key] === 'string' && jsonResult[key].trim().length > 0) {
  786. if (key === 'spaceType' || key === 'summary') {
  787. lines.push(`${title}:${jsonResult[key]}\n`);
  788. } else {
  789. lines.push(`${title}\n\n${jsonResult[key]}\n`);
  790. }
  791. }
  792. }
  793. const result = lines.join('\n');
  794. console.log('✅ [fallbackFormatJSON] 后备格式化完成,长度:', result.length);
  795. return result || '暂无分析内容';
  796. }
  797. /**
  798. * 解析AI分析内容(优化版:格式化处理,确保结构清晰)
  799. * @deprecated 使用parseJSONAnalysis代替
  800. */
  801. private parseAnalysisContent(content: string): any {
  802. console.log('📝 AI返回的原始内容长度:', content.length);
  803. console.log('📝 AI返回的内容预览:', content.substring(0, 500));
  804. if (!content || content.length < 50) {
  805. console.warn('⚠️ AI返回内容过短或为空');
  806. return {
  807. rawContent: content,
  808. formattedContent: content,
  809. hasContent: false,
  810. timestamp: new Date().toISOString()
  811. };
  812. }
  813. // 格式化处理:优化段落、间距、结构
  814. const formattedContent = this.formatAnalysisContent(content);
  815. // 提取结构化信息
  816. const structuredData = this.extractStructuredInfo(content);
  817. return {
  818. rawContent: content, // 原始AI输出
  819. formattedContent: formattedContent, // 格式化后的内容
  820. structuredData: structuredData, // 结构化数据(维度分段)
  821. hasContent: content.length > 50,
  822. timestamp: new Date().toISOString()
  823. };
  824. }
  825. /**
  826. * 格式化分析内容:优化排版、段落、间距
  827. */
  828. private formatAnalysisContent(content: string): string {
  829. let formatted = content;
  830. // 1. 统一维度标题格式(确保维度标题前后有空行)
  831. const dimensionPattern = /([一二三四五六七八九十]、[^\n]+)/g;
  832. formatted = formatted.replace(dimensionPattern, '\n\n$1\n');
  833. // 2. 处理过长段落:如果段落超过300字,尝试在句号处换行
  834. const paragraphs = formatted.split('\n');
  835. const processedParagraphs = paragraphs.map(para => {
  836. if (para.trim().length > 300) {
  837. // 在句号、问号、感叹号后添加换行,但保持在段落内
  838. return para.replace(/([。!?])(?=[^。!?\n]{50,})/g, '$1\n');
  839. }
  840. return para;
  841. });
  842. formatted = processedParagraphs.join('\n');
  843. // 3. 清理多余空行(超过2个连续空行压缩为2个)
  844. formatted = formatted.replace(/\n{3,}/g, '\n\n');
  845. // 4. 确保维度之间有明确的空行分隔
  846. formatted = formatted.replace(/(一、|二、|三、|四、|五、|六、|七、|八、)/g, '\n\n$1');
  847. // 5. 移除开头和结尾的多余空行
  848. formatted = formatted.trim();
  849. // 6. 确保每个维度内部段落之间有适当间距
  850. formatted = formatted.replace(/([。!?])\s*\n(?=[^\n一二三四五六七八])/g, '$1\n\n');
  851. // 7. 最后清理:确保格式整洁
  852. formatted = formatted.replace(/\n{3,}/g, '\n\n');
  853. return formatted;
  854. }
  855. /**
  856. * 提取结构化信息:将内容按维度分段
  857. */
  858. private extractStructuredInfo(content: string): any {
  859. const dimensions: any = {
  860. spacePositioning: '', // 空间定位与场景属性
  861. layout: '', // 空间布局与动线
  862. hardDecoration: '', // 硬装系统细节
  863. colorAnalysis: '', // 色调精准分析
  864. materials: '', // 材质应用解析
  865. form: '', // 形体与比例
  866. style: '', // 风格与氛围营造
  867. suggestions: '' // 专业优化建议
  868. };
  869. // 按维度标题分割内容
  870. const dimensionRegex = /([一二三四五六七八]、[^\n]+)\n+([\s\S]*?)(?=\n[一二三四五六七八]、|$)/g;
  871. let match;
  872. while ((match = dimensionRegex.exec(content)) !== null) {
  873. const title = match[1].trim();
  874. const contentText = match[2].trim();
  875. // 根据标题关键词匹配到对应维度
  876. if (title.includes('空间定位') || title.includes('场景属性')) {
  877. dimensions.spacePositioning = contentText;
  878. } else if (title.includes('布局') || title.includes('动线')) {
  879. dimensions.layout = contentText;
  880. } else if (title.includes('硬装') || title.includes('系统细节')) {
  881. dimensions.hardDecoration = contentText;
  882. } else if (title.includes('色调') || title.includes('色彩')) {
  883. dimensions.colorAnalysis = contentText;
  884. } else if (title.includes('材质')) {
  885. dimensions.materials = contentText;
  886. } else if (title.includes('形体') || title.includes('比例')) {
  887. dimensions.form = contentText;
  888. } else if (title.includes('风格') || title.includes('氛围')) {
  889. dimensions.style = contentText;
  890. } else if (title.includes('建议') || title.includes('优化')) {
  891. dimensions.suggestions = contentText;
  892. }
  893. }
  894. return dimensions;
  895. }
  896. /**
  897. * 生成简洁摘要:提取关键信息,适合客服和设计师快速查看
  898. */
  899. generateBriefSummary(analysisData: any): string {
  900. if (!analysisData || !analysisData.rawContent) {
  901. return '暂无分析内容';
  902. }
  903. const content = analysisData.rawContent;
  904. const summary: string[] = [];
  905. // 1. 提取空间类型
  906. const spaceTypeMatch = content.match(/(?:这是|空间为|属于).*?([^\n]{2,20}?(?:空间|客厅|餐厅|卧室|厨房|卫生间|玄关|书房))/);
  907. if (spaceTypeMatch) {
  908. summary.push(spaceTypeMatch[1].trim());
  909. }
  910. // 2. 提取风格关键词(优化:区分温润vs清冷侘寂)
  911. const styleKeywords = ['现代', '法式', '简约', '极简', '轻奢', '新中式', '日式', '北欧', '工业风', '台式', '温润侘寂', '侘寂', '美式', '混搭'];
  912. const foundStyles: string[] = [];
  913. styleKeywords.forEach(keyword => {
  914. if (content.includes(keyword) && !foundStyles.includes(keyword)) {
  915. foundStyles.push(keyword);
  916. }
  917. });
  918. if (foundStyles.length > 0) {
  919. summary.push(foundStyles.slice(0, 2).join('+'));
  920. }
  921. // 3. 提取色调关键词(优化:优先识别暖灰色、木色)
  922. const colorKeywords = [
  923. '暖灰色', '暖灰', '木色', '木棕', '原木色', '暖棕', '胡桃木色', // 暖色调优先
  924. '暖色系', '暖调', '米白', '奶白', '米色',
  925. '冷色系', '冷调', '高级灰', '纯灰' // 冷色调在后
  926. ];
  927. const foundColors: string[] = [];
  928. colorKeywords.forEach(keyword => {
  929. if (content.includes(keyword) && !foundColors.includes(keyword)) {
  930. foundColors.push(keyword);
  931. }
  932. });
  933. if (foundColors.length > 0) {
  934. summary.push(foundColors.slice(0, 3).join('、'));
  935. }
  936. // 4. 提取氛围关键词(优化:优先识别温暖、舒适、生活气息)
  937. const moodKeywords = [
  938. '温暖', '舒适', '生活气息', '温馨', '质朴', // 温暖氛围优先
  939. '精致', '高级', '优雅', '松弛', '静谧', '时尚',
  940. '女性向', '男性向', '亲子', '清冷' // 清冷在后
  941. ];
  942. const foundMoods: string[] = [];
  943. moodKeywords.forEach(keyword => {
  944. if (content.includes(keyword) && !foundMoods.includes(keyword)) {
  945. foundMoods.push(keyword);
  946. }
  947. });
  948. if (foundMoods.length > 0) {
  949. summary.push(foundMoods.slice(0, 3).join('、'));
  950. }
  951. // 5. 提取关键材质(优化:优先识别木材、皮革)
  952. const materialKeywords = [
  953. '木材', '木质', '实木', '胡桃木', '橡木', '柚木', // 木材优先
  954. '皮革', '黑色皮革', // 皮革
  955. '大理石', '瓷砖', '混凝土', '护墙板', '布艺', '金属', '玻璃', '藤编'
  956. ];
  957. const foundMaterials: string[] = [];
  958. materialKeywords.forEach(keyword => {
  959. if (content.includes(keyword) && !foundMaterials.includes(keyword)) {
  960. foundMaterials.push(keyword);
  961. }
  962. });
  963. if (foundMaterials.length > 0) {
  964. summary.push('主要材质:' + foundMaterials.slice(0, 4).join('、'));
  965. }
  966. return summary.length > 0 ? summary.join(' | ') : '整体设计基于图片实际内容分析';
  967. }
  968. /**
  969. * 生成客服标注格式:提取客户要求的关键点
  970. */
  971. generateCustomerServiceNotes(analysisData: any, customerRequirements?: string): string {
  972. if (!analysisData) {
  973. return '暂无标注内容';
  974. }
  975. const notes: string[] = [];
  976. // 优先使用JSON格式的structuredData,否则使用rawContent
  977. const structuredData = analysisData.structuredData;
  978. const rawContent = analysisData.rawContent || '';
  979. // 1. 客户要求(如果有)
  980. if (customerRequirements) {
  981. notes.push(`【客户要求】\n${customerRequirements}`);
  982. }
  983. // 2. 空间类型识别
  984. let spaceType = '';
  985. if (structuredData?.spaceType) {
  986. spaceType = structuredData.spaceType;
  987. } else {
  988. // 从rawContent提取
  989. const spaceMatch = rawContent.match(/(?:这是|空间为|属于).*?([^\n]{2,20}?(?:空间|客厅|餐厅|卧室|厨房|卫生间|玄关|书房|客餐厅一体化|三室两厅|两室一厅))/);
  990. if (spaceMatch) spaceType = spaceMatch[1].trim();
  991. }
  992. if (spaceType) {
  993. notes.push(`【空间类型】\n${spaceType}`);
  994. }
  995. // 3. 风格定位(从结构化数据或rawContent提取)
  996. let styleInfo = '';
  997. if (structuredData?.style) {
  998. // 提取风格关键词
  999. const styleKeywords = ['现代', '法式', '简约', '极简', '轻奢', '新中式', '日式', '北欧', '工业风', '侘寂', '美式', '台式'];
  1000. const foundStyles: string[] = [];
  1001. styleKeywords.forEach(keyword => {
  1002. if (structuredData.style.includes(keyword) && !foundStyles.includes(keyword)) {
  1003. foundStyles.push(keyword);
  1004. }
  1005. });
  1006. if (foundStyles.length > 0) {
  1007. styleInfo = foundStyles.join('+') + '风格';
  1008. }
  1009. // 提取氛围描述
  1010. const moodMatch = structuredData.style.match(/(?:氛围|营造|呈现).*?([^\n。]{5,30}?(?:温馨|舒适|精致|高级|松弛|静谧|优雅|时尚))/);
  1011. if (moodMatch) {
  1012. styleInfo += `,${moodMatch[1].trim()}`;
  1013. }
  1014. } else if (rawContent) {
  1015. // 从rawContent提取风格
  1016. const styleMatch = rawContent.match(/(?:风格|呈现|属于).*?([^\n。]{5,40}?(?:风格|法式|现代|简约|极简))/);
  1017. if (styleMatch) styleInfo = styleMatch[1].trim();
  1018. }
  1019. if (styleInfo) {
  1020. notes.push(`【风格定位】\n${styleInfo}`);
  1021. }
  1022. // 4. 色调要求(从色彩分析提取)
  1023. let colorInfo = '';
  1024. if (structuredData?.colorAnalysis) {
  1025. // 提取主色调
  1026. const mainColorMatch = structuredData.colorAnalysis.match(/主色调[::]\s*([^\n。]{5,50})/);
  1027. if (mainColorMatch) {
  1028. colorInfo = `主色调:${mainColorMatch[1].trim()}`;
  1029. }
  1030. // 提取辅助色
  1031. const subColorMatch = structuredData.colorAnalysis.match(/辅助色[::]\s*([^\n。]{5,50})/);
  1032. if (subColorMatch) {
  1033. colorInfo += `\n辅助色:${subColorMatch[1].trim()}`;
  1034. }
  1035. } else if (rawContent) {
  1036. // 从rawContent提取色调
  1037. const colorMatch = rawContent.match(/(?:色调|色彩|主色)[::]\s*([^\n。]{5,50})/);
  1038. if (colorMatch) colorInfo = colorMatch[1].trim();
  1039. }
  1040. if (colorInfo) {
  1041. notes.push(`【色调要求】\n${colorInfo}`);
  1042. }
  1043. // 5. 材质要求(从硬装和材质维度提取)
  1044. const materials: string[] = [];
  1045. if (structuredData?.hardDecoration) {
  1046. // 提取地面材质
  1047. const floorMatch = structuredData.hardDecoration.match(/地面[::]\s*([^\n。]{5,40})/);
  1048. if (floorMatch) materials.push(`地面:${floorMatch[1].trim()}`);
  1049. // 提取墙面材质
  1050. const wallMatch = structuredData.hardDecoration.match(/墙面[::]\s*([^\n。]{5,40})/);
  1051. if (wallMatch) materials.push(`墙面:${wallMatch[1].trim()}`);
  1052. // 提取顶面材质
  1053. const ceilingMatch = structuredData.hardDecoration.match(/顶面[::]\s*([^\n。]{5,40})/);
  1054. if (ceilingMatch) materials.push(`顶面:${ceilingMatch[1].trim()}`);
  1055. }
  1056. // 从材质维度补充
  1057. if (structuredData?.materials && materials.length < 2) {
  1058. const materialMatch = structuredData.materials.match(/(?:主要材质|材质应用)[::]\s*([^\n。]{10,60})/);
  1059. if (materialMatch) materials.push(materialMatch[1].trim());
  1060. }
  1061. if (materials.length > 0) {
  1062. notes.push(`【材质要求】\n${materials.join('\n')}`);
  1063. }
  1064. // 6. 空间布局要点
  1065. if (structuredData?.layout) {
  1066. const layoutMatch = structuredData.layout.match(/(?:布局特点|空间关系)[::]\s*([^\n。]{10,60})/);
  1067. if (layoutMatch) {
  1068. notes.push(`【布局要点】\n${layoutMatch[1].trim()}`);
  1069. }
  1070. }
  1071. // 7. 施工注意事项(从优化建议提取)
  1072. if (structuredData?.suggestions) {
  1073. const attentionPoints: string[] = [];
  1074. // 提取落地可行性
  1075. const feasibilityMatch = structuredData.suggestions.match(/落地可行性[::]\s*([^\n。]{10,80})/);
  1076. if (feasibilityMatch) attentionPoints.push(feasibilityMatch[1].trim());
  1077. // 提取细节优化
  1078. const detailMatch = structuredData.suggestions.match(/细节优化[::]\s*([^\n。]{10,80})/);
  1079. if (detailMatch) attentionPoints.push(detailMatch[1].trim());
  1080. if (attentionPoints.length > 0) {
  1081. notes.push(`【施工注意】\n${attentionPoints.join('\n')}`);
  1082. }
  1083. }
  1084. // 8. 品质要求(固定添加)
  1085. notes.push(`【品质要求】\n新客户,需严格把控施工品质和材料质量`);
  1086. return notes.length > 0 ? notes.join('\n\n') : '请根据分析内容补充具体要求';
  1087. }
  1088. /**
  1089. * 生成客户报告(此方法保留以便后续使用)
  1090. */
  1091. async generateClientReport(options: {
  1092. analysisData: any;
  1093. spaceName: string;
  1094. onContentChange?: (content: string) => void;
  1095. loading?: any;
  1096. }): Promise<string> {
  1097. return new Promise(async (resolve, reject) => {
  1098. try {
  1099. // 使用格式化后的内容
  1100. const content = options.analysisData?.formattedContent || options.analysisData?.rawContent || '暂无报告内容';
  1101. resolve(content);
  1102. } catch (error: any) {
  1103. reject(new Error('生成报告失败: ' + error.message));
  1104. }
  1105. });
  1106. }
  1107. }