Browse Source

docs: add comprehensive AI analysis documentation

- Created detailed troubleshooting guide documenting the fix for AI analysis display issues and space type recognition
- Added complete usage guide with practical examples for customer service, designers, and different analysis scenarios
- Documented data structure changes, API usage patterns, and integration examples for the optimized AI analysis service
徐福静0235668 1 day ago
parent
commit
969583e3c3

+ 282 - 0
docs/ai-analysis-display-fix.md

@@ -0,0 +1,282 @@
+# AI分析结果显示问题修复
+
+## 📋 问题描述
+
+用户上传图片进行AI分析后,出现以下问题:
+
+1. **分析结果不显示**:分析完成后,AI分析结果区域为空白,没有显示任何内容
+2. **空间类型错误预设**:AI分析时使用了用户选择的空间类型,而不是基于图片内容自动识别
+
+## 🔍 问题根本原因
+
+### 问题1:数据结构不匹配
+
+**HTML期望的数据结构**(旧版):
+```typescript
+{
+  sceneRecognition: { spaceType, overallTone, confidence },
+  spaceRegionAnalysis: { percentageBreakdown: { ceiling, wall, floor... } },
+  lightingSystemDetailed: { naturalLight, mainLighting... },
+  colorProportionWeight: [...]
+}
+```
+
+**AI服务实际返回的数据结构**(新优化版):
+```typescript
+{
+  rawContent: string,           // AI原始输出
+  formattedContent: string,     // 格式化后的内容
+  structuredData: {             // 结构化数据
+    spacePositioning, layout, hardDecoration,
+    colorAnalysis, materials, form, style, suggestions
+  },
+  hasContent: boolean,
+  timestamp: string
+}
+```
+
+**结论**:HTML中使用了`sceneRecognition`、`spaceRegionAnalysis`等字段,但新的AI服务返回的是`formattedContent`和`structuredData`,导致数据无法显示。
+
+### 问题2:空间类型预设传递
+
+**代码问题**:
+```typescript
+// ❌ 旧代码:传递用户选择的空间类型
+spaceType: this.aiDesignCurrentSpace?.name || '',
+```
+
+**问题**:AI分析时会基于用户选择的空间(如"主卧"、"厨房")进行分析,而不是根据图片实际内容识别空间类型。这样即使用户上传了客厅的图片,如果选择的空间是"主卧",AI也会将其分析为主卧。
+
+## ✅ 修复方案
+
+### 1. 修复空间类型识别(让AI自动识别)
+
+**文件**: `stage-requirements.component.ts`
+
+**修改**:
+```typescript
+// ✅ 新代码:不传递spaceType,让AI基于图片自动识别
+const analysisResult = await this.designAnalysisAIService.analyzeReferenceImages({
+  images: this.aiDesignUploadedImages,
+  textDescription: this.aiDesignTextDescription,
+  spaceType: undefined,  // 🔥 不传递,让AI自动识别
+  conversationHistory: [],
+  deepThinking: this.deepThinkingEnabled,
+  onProgressChange: (progress) => {...},
+  onContentStream: (content) => {...}
+});
+```
+
+**效果**:AI会基于图片内容自动识别空间类型,在分析结果的"空间定位与场景属性"维度中说明是什么空间。
+
+### 2. 重构HTML显示结构
+
+**文件**: `stage-requirements.component.html`
+
+**新结构**:
+1. **简洁摘要卡片** - 使用`generateBriefSummary()`生成一行概要
+2. **完整分析卡片** - 显示`formattedContent`(格式化后的8维度分析)
+3. **分维度查看卡片** - 使用`structuredData`按维度折叠展示
+
+**代码示例**:
+```html
+<!-- 简洁摘要 -->
+@if (getAISummary()) {
+  <div class="result-card summary-card">
+    <p class="summary-text">{{ getAISummary() }}</p>
+  </div>
+}
+
+<!-- 完整分析 -->
+@if (aiDesignAnalysisResult.formattedContent) {
+  <div class="result-card full-analysis-card">
+    <pre class="analysis-text">{{ aiDesignAnalysisResult.formattedContent }}</pre>
+  </div>
+}
+
+<!-- 分维度查看 -->
+@if (aiDesignAnalysisResult.structuredData) {
+  <div class="result-card dimensions-card">
+    <div class="dimensions-grid">
+      <!-- 8个维度折叠展示 -->
+    </div>
+  </div>
+}
+```
+
+### 3. 添加TypeScript方法
+
+**文件**: `stage-requirements.component.ts`
+
+**新增方法**:
+
+#### (1) expandedDimensions: Set<string>
+管理维度折叠状态
+
+#### (2) getAISummary(): string
+```typescript
+getAISummary(): string {
+  if (!this.aiDesignAnalysisResult) return '';
+  return this.designAnalysisAIService.generateBriefSummary(this.aiDesignAnalysisResult);
+}
+```
+**输出示例**:
+```
+客餐厅一体化 | 现代+法式 | 暖色系、暖灰 | 温馨、精致 | 主要材质:护墙板、木材、大理石
+```
+
+#### (3) toggleDimension(dimensionKey: string): void
+```typescript
+toggleDimension(dimensionKey: string): void {
+  if (this.expandedDimensions.has(dimensionKey)) {
+    this.expandedDimensions.delete(dimensionKey);
+  } else {
+    this.expandedDimensions.add(dimensionKey);
+  }
+}
+```
+切换维度的展开/折叠状态
+
+#### (4) generateServiceNotes(): Promise<void>
+```typescript
+async generateServiceNotes(): Promise<void> {
+  const serviceNotes = this.designAnalysisAIService.generateCustomerServiceNotes(
+    this.aiDesignAnalysisResult,
+    this.aiDesignTextDescription
+  );
+  
+  // 保存到项目data
+  projectData.designReports[spaceId].serviceNotes = serviceNotes;
+  
+  // 复制到剪贴板
+  this.copyToClipboard(serviceNotes);
+}
+```
+生成客服标注格式并复制到剪贴板
+
+### 4. 添加样式
+
+**文件**: `stage-requirements.component.scss`
+
+**新增样式**:
+- `.summary-card` - 简洁摘要卡片(蓝色渐变背景)
+- `.full-analysis-card` - 完整分析卡片(带滚动条)
+- `.dimensions-card` - 维度查看卡片(可折叠)
+  - `.dimension-item` - 单个维度
+  - `.dimension-header` - 维度标题(可点击)
+  - `.dimension-content` - 维度内容(折叠显示)
+
+## 📊 修复后的数据流
+
+```
+用户上传图片
+    ↓
+调用 analyzeReferenceImages()
+    ↓
+AI分析(不传递spaceType,自动识别)
+    ↓
+返回 {
+  rawContent,
+  formattedContent,    // 格式化的8维度分析
+  structuredData,      // 按维度分段的数据
+  hasContent,
+  timestamp
+}
+    ↓
+HTML显示:
+1. 简洁摘要(generateBriefSummary)
+2. 完整分析(formattedContent)
+3. 分维度查看(structuredData + 折叠)
+    ↓
+用户可以生成客服标注(generateServiceNotes)
+```
+
+## 🎯 修复效果
+
+### 修复前
+- ❌ 分析结果不显示(数据结构不匹配)
+- ❌ AI分析说"这是一个主卧"(即使图片是客厅,因为用户选择了主卧)
+
+### 修复后
+- ✅ 简洁摘要显示(一行概要信息)
+- ✅ 完整分析显示(8个维度,800-2000字)
+- ✅ 分维度查看(可折叠,方便查看特定维度)
+- ✅ AI自动识别空间类型(基于图片内容,而不是用户选择)
+- ✅ 可生成客服标注(一键复制到剪贴板)
+
+## 📝 使用示例
+
+### 示例1:查看完整分析结果
+
+1. 上传参考图片
+2. 点击"开始分析"
+3. AI分析完成后显示:
+   - **设计概要**:客餐厅一体化 | 现代+法式 | 暖色系 | 温馨、精致
+   - **详细分析**:8个维度的完整文本(点击可滚动查看)
+   - **分维度查看**:点击展开任意维度(如"色调精准分析")
+
+### 示例2:生成客服标注
+
+1. 完成AI分析
+2. 点击"生成客服标注"按钮
+3. 自动生成并复制到剪贴板:
+```
+【客户要求】
+风格: 现代法式(偏暖色系)
+护墙板颜色: 淡奶灰色
+
+【风格定位】
+现代法式风格,偏向暖色系基调
+
+【色调要求】
+主色调:淡奶灰色为基底,辅以暖白和原木色
+
+【材质要求】
+地面:大理石瓷砖,柔哑面质感
+墙面:护墙板,淡奶灰色涂装
+
+【施工注意】
+护墙板需采用哑光涂装工艺,地砖铺贴注意超边细节
+```
+
+4. 粘贴到项目文档或客服系统
+
+## 🔧 文件修改清单
+
+| 文件 | 修改内容 |
+|------|---------|
+| `stage-requirements.component.ts` | 1. 移除spaceType参数传递<br>2. 添加expandedDimensions Set<br>3. 添加getAISummary()方法<br>4. 添加toggleDimension()方法<br>5. 添加generateServiceNotes()方法 |
+| `stage-requirements.component.html` | 1. 重构AI分析结果显示区域<br>2. 添加简洁摘要卡片<br>3. 添加完整分析卡片<br>4. 添加分维度查看卡片<br>5. 添加生成客服标注按钮 |
+| `stage-requirements.component.scss` | 1. 添加.summary-card样式<br>2. 添加.full-analysis-card样式<br>3. 添加.dimensions-card样式<br>4. 添加维度折叠交互样式 |
+
+## ✅ 验证清单
+
+修复后请验证以下功能:
+
+- [ ] 上传图片后能看到AI分析结果
+- [ ] 简洁摘要正确显示
+- [ ] 完整分析内容清晰可读
+- [ ] 维度折叠功能正常
+- [ ] AI能正确识别空间类型(不受用户选择影响)
+- [ ] 客服标注生成功能正常
+- [ ] 客服标注能复制到剪贴板
+
+## 🎉 总结
+
+通过本次修复:
+1. **解决了数据显示问题**:适配新的AI服务数据结构
+2. **修复了空间识别问题**:让AI基于图片内容自动识别,而不是使用预设
+3. **优化了用户体验**:提供简洁摘要、完整分析、分维度查看三种展示方式
+4. **增加了实用功能**:一键生成客服标注并复制
+
+现在用户可以:
+- ✅ 清晰地查看AI分析结果
+- ✅ 获取准确的空间类型识别
+- ✅ 按需查看特定维度的详细分析
+- ✅ 快速生成客服标注文档
+
+---
+
+**修复日期**: 2025-11-27  
+**修复人**: 开发团队  
+**相关文档**: `ai-analysis-usage-guide.md`

+ 516 - 0
docs/ai-analysis-usage-guide.md

@@ -0,0 +1,516 @@
+# AI设计分析功能使用指南
+
+## 📋 功能概述
+
+优化后的AI分析服务提供三种输出格式,满足不同角色的需求:
+
+1. **完整分析报告**:适合设计师深度研究
+2. **简洁摘要**:适合客服快速了解
+3. **客服标注格式**:适合客服制作空间标注
+
+---
+
+## 🎯 适用场景
+
+### 场景1:客服接单时快速分析客户需求
+
+**案例**:客户发来3张现代法式风格的参考图
+
+```typescript
+// 调用AI分析
+const analysisResult = await this.aiService.analyzeReferenceImages({
+  images: ['image1.jpg', 'image2.jpg', 'image3.jpg'],
+  spaceType: '客餐厅一体化',
+  textDescription: '风格:现代法式(偏暖色系),护墙板颜色(淡奶灰色)',
+  deepThinking: false  // 快速分析模式
+});
+
+// 生成简洁摘要(客服快速查看)
+const summary = this.aiService.generateBriefSummary(analysisResult);
+// 输出示例:
+// "客餐厅一体化 | 现代+法式 | 暖色系、暖灰 | 温馨、精致 | 主要材质:护墙板、木材、大理石"
+```
+
+### 场景2:客服制作空间标注
+
+**案例**:根据AI分析和客户要求,生成标注文档
+
+```typescript
+const customerRequirements = `
+1、护墙板颜色(淡奶灰色)
+2、公共地砖柔哑面 带一点小超边
+3、负二层旋转楼梯材质钢板
+4、负一层至三层楼梯大理石踏步+钢板扶手
+5、女儿房适当配一点淡粉色
+6、卧室地板鱼骨拼花
+7、三楼主卫台盆柜带梳妆台
+`;
+
+// 生成客服标注格式
+const notes = this.aiService.generateCustomerServiceNotes(
+  analysisResult,
+  customerRequirements
+);
+
+// 输出示例:
+/*
+【客户要求】
+1、护墙板颜色(淡奶灰色)
+2、公共地砖柔哑面 带一点小超边
+...
+
+【风格定位】
+现代法式风格,偏向暖色系基调
+
+【色调要求】
+主色调:淡奶灰色为基底,辅以暖白和原木色
+
+【材质要求】
+地面:大理石瓷砖,柔哑面质感
+墙面:护墙板,淡奶灰色涂装
+
+【施工注意】
+护墙板需采用哑光涂装工艺,地砖铺贴注意超边细节
+*/
+```
+
+### 场景3:设计师深度分析
+
+**案例**:设计师需要详细了解参考图的设计手法
+
+```typescript
+// 启用深度分析模式
+const deepAnalysis = await this.aiService.analyzeReferenceImages({
+  images: ['image1.jpg'],
+  spaceType: '主卧',
+  deepThinking: true,  // 深度分析模式
+  conversationHistory: [
+    { role: 'user', content: '这个空间的色调如何与材质配合?' },
+    { role: 'assistant', content: '暖灰色与木色结合,形成温暖舒适的氛围...' }
+  ]
+});
+
+// 使用格式化后的完整报告
+const fullReport = deepAnalysis.formattedContent;
+// 包含8个维度的详细分析,每个维度2-4个段落
+
+// 或者使用结构化数据
+const structuredData = deepAnalysis.structuredData;
+console.log('色调分析:', structuredData.colorAnalysis);
+console.log('材质解析:', structuredData.materials);
+console.log('优化建议:', structuredData.suggestions);
+```
+
+---
+
+## 📊 输出数据结构
+
+### 完整分析结果 (analysisResult)
+
+```typescript
+{
+  rawContent: string;           // AI原始输出
+  formattedContent: string;     // 格式化后的内容(推荐使用)
+  structuredData: {             // 结构化数据
+    spacePositioning: string;   // 空间定位与场景属性
+    layout: string;             // 空间布局与动线
+    hardDecoration: string;     // 硬装系统细节
+    colorAnalysis: string;      // 色调精准分析
+    materials: string;          // 材质应用解析
+    form: string;               // 形体与比例
+    style: string;              // 风格与氛围营造
+    suggestions: string;        // 专业优化建议
+  };
+  hasContent: boolean;          // 是否有有效内容
+  timestamp: string;            // 分析时间戳
+}
+```
+
+---
+
+## 🔧 实际应用示例
+
+### 示例1:客服接单流程
+
+```typescript
+export class OrderAssignmentComponent {
+  async analyzeCustomerImages() {
+    try {
+      // 1. 调用AI分析
+      const result = await this.aiService.analyzeReferenceImages({
+        images: this.uploadedImages,
+        spaceType: this.selectedSpaceType,
+        textDescription: this.customerRequirements,
+        onProgressChange: (msg) => this.showProgress(msg)
+      });
+
+      // 2. 生成简洁摘要(显示在订单卡片上)
+      this.orderSummary = this.aiService.generateBriefSummary(result);
+      
+      // 3. 生成客服标注(保存到Project.data)
+      this.serviceNotes = this.aiService.generateCustomerServiceNotes(
+        result,
+        this.customerRequirements
+      );
+
+      // 4. 保存到数据库
+      this.project.set('data', {
+        ...this.project.get('data'),
+        aiAnalysis: result.formattedContent,
+        aiSummary: this.orderSummary,
+        serviceNotes: this.serviceNotes
+      });
+      
+      await this.project.save();
+      
+      this.showToast('分析完成,已生成标注');
+      
+    } catch (error) {
+      this.showError('AI分析失败: ' + error.message);
+    }
+  }
+}
+```
+
+### 示例2:设计师查看分析报告
+
+```typescript
+export class ProjectDetailComponent {
+  async loadAnalysisReport() {
+    const projectData = this.project.get('data');
+    
+    if (projectData.aiAnalysis) {
+      // 显示格式化后的完整报告
+      this.analysisContent = projectData.aiAnalysis;
+      
+      // 显示简洁摘要(顶部卡片)
+      this.quickSummary = projectData.aiSummary;
+    } else {
+      // 如果没有分析,提示重新分析
+      this.showReanalyzeButton = true;
+    }
+  }
+
+  // 查看特定维度的分析
+  viewDimensionDetail(dimension: string) {
+    const result = this.parseStoredAnalysis(this.analysisContent);
+    
+    switch(dimension) {
+      case 'color':
+        this.showModal('色调分析', result.structuredData.colorAnalysis);
+        break;
+      case 'material':
+        this.showModal('材质解析', result.structuredData.materials);
+        break;
+      case 'suggestions':
+        this.showModal('优化建议', result.structuredData.suggestions);
+        break;
+    }
+  }
+}
+```
+
+### 示例3:流式输出实时显示
+
+```typescript
+export class AIAnalysisDialog {
+  analysisContent = '';
+
+  async startAnalysis() {
+    this.analysisContent = '';
+    
+    try {
+      await this.aiService.analyzeReferenceImages({
+        images: this.images,
+        spaceType: this.spaceType,
+        textDescription: this.description,
+        
+        // 流式输出回调:实时显示AI生成的内容
+        onContentStream: (content) => {
+          this.analysisContent = content;
+          // Angular会自动检测变化并更新视图
+          this.scrollToBottom();
+        },
+        
+        // 进度回调
+        onProgressChange: (progress) => {
+          this.progressMessage = progress;
+        }
+      });
+      
+      this.showToast('分析完成');
+      
+    } catch (error) {
+      this.showError(error.message);
+    }
+  }
+}
+```
+
+---
+
+## 💡 最佳实践
+
+### 1. 提供客户需求文本
+
+AI分析会结合文本描述产生更精准的结果:
+
+```typescript
+const textDescription = `
+风格: 现代法式(偏暖色系)
+护墙板颜色: 淡奶灰色
+地面: 柔哑面瓷砖
+特殊要求: 女儿房适当配淡粉色
+`;
+
+// ✅ 推荐:提供详细描述
+await aiService.analyzeReferenceImages({
+  images: [...],
+  textDescription: textDescription  // 提供客户需求
+});
+
+// ❌ 不推荐:完全不提供上下文
+await aiService.analyzeReferenceImages({
+  images: [...]  // AI只能从图片推断
+});
+```
+
+### 2. 使用对话历史增强分析
+
+当与客户有多轮沟通时,传入对话历史:
+
+```typescript
+const conversationHistory = [
+  { role: 'user', content: '这个空间的木色会不会太深?' },
+  { role: 'assistant', content: '可以选择浅色原木,更温暖明亮' },
+  { role: 'user', content: '那墙面用什么颜色配?' }
+];
+
+await aiService.analyzeReferenceImages({
+  images: [...],
+  conversationHistory: conversationHistory  // AI会参考对话上下文
+});
+```
+
+### 3. 根据场景选择分析模式
+
+```typescript
+// 客服接单:快速分析
+deepThinking: false  // 800-1200字,3-5分钟
+
+// 设计师深度研究:深度分析
+deepThinking: true   // 1500-2000字,5-8分钟
+```
+
+### 4. 合理使用结构化数据
+
+```typescript
+// ✅ 推荐:需要特定维度时使用结构化数据
+if (needColorAnalysisOnly) {
+  const colorAnalysis = result.structuredData.colorAnalysis;
+  this.showColorPalette(colorAnalysis);
+}
+
+// ✅ 推荐:完整展示时使用格式化内容
+if (needFullReport) {
+  this.reportContent = result.formattedContent;
+}
+
+// ❌ 不推荐:直接使用rawContent(格式可能不整齐)
+this.reportContent = result.rawContent;  // 可能有格式问题
+```
+
+---
+
+## 🎨 UI展示建议
+
+### 客服端展示
+
+```html
+<!-- 订单卡片:显示简洁摘要 -->
+<div class="order-card">
+  <div class="ai-summary">
+    <ion-icon name="sparkles"></ion-icon>
+    <span>{{ aiSummary }}</span>
+  </div>
+  <!-- 示例:客餐厅一体化 | 现代+法式 | 暖色系 | 温馨、精致 -->
+</div>
+
+<!-- 详情页:显示客服标注 -->
+<div class="service-notes">
+  <h3>📋 客服标注</h3>
+  <pre>{{ serviceNotes }}</pre>
+</div>
+```
+
+### 设计师端展示
+
+```html
+<!-- 完整分析报告:分维度展示 -->
+<ion-accordion-group>
+  <ion-accordion value="positioning">
+    <ion-item slot="header">
+      <ion-label>一、空间定位与场景属性</ion-label>
+    </ion-item>
+    <div slot="content">
+      <p>{{ structuredData.spacePositioning }}</p>
+    </div>
+  </ion-accordion>
+  
+  <ion-accordion value="color">
+    <ion-item slot="header">
+      <ion-label>四、色调精准分析</ion-label>
+    </ion-item>
+    <div slot="content">
+      <p>{{ structuredData.colorAnalysis }}</p>
+    </div>
+  </ion-accordion>
+  
+  <!-- 其他维度... -->
+</ion-accordion-group>
+```
+
+### 流式输出展示
+
+```html
+<!-- 实时显示AI生成内容 -->
+<div class="ai-streaming-output">
+  <div class="content-area" #contentArea>
+    <pre>{{ analysisContent }}</pre>
+  </div>
+  
+  @if (isAnalyzing) {
+    <div class="typing-indicator">
+      <span></span><span></span><span></span>
+    </div>
+  }
+</div>
+```
+
+---
+
+## 📈 性能优化建议
+
+### 1. 图片数量控制
+
+```typescript
+// ✅ 推荐:每次分析1-3张图片
+images: ['img1.jpg', 'img2.jpg', 'img3.jpg']
+
+// ⚠️ 注意:超过5张图片会延长分析时间
+images: ['img1.jpg', ..., 'img10.jpg']  // 可能超时
+```
+
+### 2. 缓存分析结果
+
+```typescript
+// 避免重复分析同一张图片
+const cacheKey = `ai_analysis_${imageHash}`;
+const cached = localStorage.getItem(cacheKey);
+
+if (cached) {
+  return JSON.parse(cached);
+} else {
+  const result = await aiService.analyzeReferenceImages({...});
+  localStorage.setItem(cacheKey, JSON.stringify(result));
+  return result;
+}
+```
+
+### 3. 异步加载
+
+```typescript
+// 不阻塞主流程,后台分析
+async loadProject() {
+  await this.loadBasicInfo();  // 先加载基本信息
+  
+  // AI分析异步进行
+  this.analyzeInBackground();
+}
+
+async analyzeInBackground() {
+  try {
+    const result = await this.aiService.analyzeReferenceImages({...});
+    this.aiAnalysisReady = true;
+  } catch (error) {
+    console.error('后台分析失败:', error);
+  }
+}
+```
+
+---
+
+## 🔍 故障排查
+
+### 问题1:AI返回内容过短
+
+**原因**:图片质量差、提示词不清晰、API限制
+
+**解决**:
+```typescript
+// 检查返回内容长度
+if (result.hasContent === false) {
+  console.warn('AI返回内容不足,建议重新分析');
+  // 提示用户上传更清晰的图片或补充文字说明
+}
+```
+
+### 问题2:分析超时
+
+**原因**:图片过多、网络延迟、服务器繁忙
+
+**解决**:
+```typescript
+// 设置超时控制
+const timeout = setTimeout(() => {
+  throw new Error('分析超时,请重试');
+}, 60000);  // 60秒超时
+
+try {
+  const result = await aiService.analyzeReferenceImages({...});
+  clearTimeout(timeout);
+} catch (error) {
+  clearTimeout(timeout);
+  // 提示用户减少图片数量或稍后重试
+}
+```
+
+### 问题3:格式化效果不佳
+
+**原因**:AI输出格式不规范
+
+**解决**:
+```typescript
+// 使用formattedContent而不是rawContent
+const content = result.formattedContent;  // ✅ 已优化格式
+
+// 如果仍有问题,可以手动补充格式化
+const betterFormatted = content
+  .replace(/([。!?])\s*/g, '$1\n')  // 句号后换行
+  .replace(/\n{3,}/g, '\n\n');        // 压缩多余空行
+```
+
+---
+
+## 📝 总结
+
+优化后的AI分析服务提供了三种输出格式,满足不同场景需求:
+
+| 角色 | 使用场景 | 推荐方法 |
+|------|---------|---------|
+| 客服 | 快速了解风格 | `generateBriefSummary()` |
+| 客服 | 制作空间标注 | `generateCustomerServiceNotes()` |
+| 设计师 | 深度研究设计 | `formattedContent` + `structuredData` |
+| 所有人 | 实时查看生成 | `onContentStream` 回调 |
+
+**关键特性**:
+- ✅ 基于图片实际内容分析,非模板化
+- ✅ 输出格式清晰,段落分明
+- ✅ 支持流式输出,实时显示
+- ✅ 提供结构化数据,方便提取
+- ✅ 三种格式输出,适配不同角色
+
+---
+
+**最后更新**: 2025-11-27  
+**维护者**: 开发团队

+ 302 - 0
docs/ai-image-access-debug.md

@@ -0,0 +1,302 @@
+# AI图片访问问题深度调试指南
+
+## 🔍 问题现象
+
+AI返回模板内容:
+```
+(注:由于未接收到实际图片内容,无法进行基于视觉信息的精准分析。
+根据系统提示,需严格基于图片实际内容展开分析,但当前缺少核心视觉素材...)
+```
+
+## 📋 已实施的修复措施
+
+### 1. ✅ 创建ProjectFile记录
+**文件**: stage-requirements.component.ts (lines 3320-3379)
+
+**功能**:上传成功后自动创建ProjectFile记录
+
+```typescript
+const ProjectFile = Parse.Object.extend('ProjectFile');
+const projectFile = new ProjectFile();
+
+projectFile.set('project', { __type: 'Pointer', className: 'Project', objectId: this.projectId });
+projectFile.set('name', file.name);
+projectFile.set('url', uploadedFile.url);
+projectFile.set('type', file.type);
+projectFile.set('size', file.size);
+projectFile.set('extension', fileExt);
+projectFile.set('category', 'ai_design_reference'); // 标记类别
+
+if (this.aiDesignCurrentSpace?.id) {
+  projectFile.set('product', { __type: 'Pointer', className: 'Product', objectId: this.aiDesignCurrentSpace.id });
+  projectFile.set('data', {
+    spaceId: this.aiDesignCurrentSpace.id,
+    spaceName: this.aiDesignCurrentSpace.name,
+    uploadedFor: 'ai_design_analysis',
+    uploadedAt: new Date().toISOString()
+  });
+}
+
+await projectFile.save();
+```
+
+**控制台日志**:
+```
+💾 ProjectFile记录已创建: xyzabc123
+```
+
+### 2. ✅ 改用completionJSON
+**文件**: design-analysis-ai.service.ts (lines 74-175)
+
+**原因**:项目中成功使用vision的唯一方式是`completionJSON`,而不是`FmodeChatCompletion`
+
+```typescript
+const result = await completionJSON(
+  prompt,
+  '', // 不使用JSON schema
+  undefined, // 不使用流式回调
+  2, // 重试次数
+  {
+    model: this.AI_MODEL,
+    vision: true,
+    images: encodedImages, // 图片URL数组
+    max_tokens: 8000
+  }
+);
+```
+
+### 3. ✅ URL编码
+**文件**: design-analysis-ai.service.ts (lines 46-58)
+
+**功能**:处理中文文件名
+
+```typescript
+const encodedImages = options.images.map(url => {
+  try {
+    const urlObj = new URL(url);
+    const pathname = urlObj.pathname;
+    const encodedPathname = pathname.split('/')
+      .map(segment => encodeURIComponent(decodeURIComponent(segment)))
+      .join('/');
+    return urlObj.origin + encodedPathname + urlObj.search;
+  } catch (e) {
+    console.warn('⚠️ URL编码失败,使用原始URL:', url);
+    return url;
+  }
+});
+```
+
+## 🔧 调试步骤
+
+### 第1步:验证图片上传成功
+
+**控制台应显示**:
+```
+📤 准备上传文件: test.jpg, 大小: 2.5MB
+📂 上传路径: ai-design-analysis/iKvYck89zE
+上传进度: 100%
+✅ 文件上传成功: https://file-cloud.fmode.cn/.../test.jpg
+💾 ProjectFile记录已创建: abc123xyz
+```
+
+**如果没有显示"ProjectFile记录已创建"**:
+- 检查Parse服务器连接
+- 检查项目ID是否有效
+- 查看控制台是否有"创建ProjectFile记录失败"错误
+
+### 第2步:验证图片URL可访问
+
+**手动测试**:
+1. 复制控制台中的图片URL
+2. 在浏览器新标签页打开
+3. 应该能直接看到图片(不需要登录)
+
+**如果无法访问**:
+- 图片URL需要公开访问权限
+- 检查OBS/存储服务的访问控制设置
+- 可能需要配置CORS
+
+### 第3步:验证AI调用参数
+
+**控制台应显示**:
+```
+🤖 调用豆包1.6模型...
+📸 原始图片URL: ["https://file-cloud.fmode.cn/.../test.jpg"]
+📸 编码后URL: ["https://file-cloud.fmode.cn/.../%E6%B5%8B%E8%AF%95.jpg"]
+📸 图片数量: 1
+🚀 开始调用completionJSON进行vision分析...
+```
+
+**检查图片URL列表**:
+- 不应该是空数组 `[]`
+- 不应该是本地blob URL `blob:http://...`
+- 应该是完整的HTTP/HTTPS URL
+
+### 第4步:检查AI返回内容
+
+**正常情况**:
+```
+✅ AI分析完成,原始内容长度: 1523
+📝 AI返回内容预览: 一、空间定位与场景属性...
+```
+
+**异常情况**:
+```
+❌ AI无法访问图片!
+🔍 AI返回的完整内容: (注:由于未接收到实际图片内容...
+🔍 图片URL列表: [...]
+```
+
+## 🚨 可能的根本原因
+
+### 原因1:图片URL需要身份验证
+
+**问题**:AI服务无法访问需要登录的URL
+
+**解决方案**:
+1. 检查存储服务配置,确保图片URL公开可访问
+2. 或者:将图片转换为base64传递(适用于小图片)
+
+**验证方法**:
+在浏览器隐身模式中打开图片URL,应该能直接看到图片
+
+### 原因2:completionJSON不支持URL方式传图
+
+**问题**:completionJSON可能只支持base64图片
+
+**测试方案**:修改为base64传递
+```typescript
+// 将图片转换为base64
+async function urlToBase64(url: string): Promise<string> {
+  const response = await fetch(url);
+  const blob = await response.blob();
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.onloadend = () => resolve(reader.result as string);
+    reader.onerror = reject;
+    reader.readAsDataURL(blob);
+  });
+}
+
+// 在AI服务中使用
+const base64Images = await Promise.all(
+  encodedImages.map(url => urlToBase64(url))
+);
+
+const result = await completionJSON(
+  prompt,
+  '',
+  undefined,
+  2,
+  {
+    model: this.AI_MODEL,
+    vision: true,
+    images: base64Images, // 使用base64而不是URL
+    max_tokens: 8000
+  }
+);
+```
+
+### 原因3:fmode-ng版本问题
+
+**问题**:当前版本的`completionJSON`可能不支持vision或images参数
+
+**验证方法**:
+1. 检查`fmode-ng`版本
+2. 查看官方文档确认vision支持
+3. 尝试升级到最新版本
+
+### 原因4:AI模型不支持vision
+
+**问题**:`fmode-1.6-cn`模型可能不支持多模态
+
+**解决方案**:
+1. 确认模型支持vision能力
+2. 尝试使用其他多模态模型(如gpt-4-vision-preview)
+
+## 💡 临时解决方案
+
+### 方案1:使用base64传递图片
+
+**优点**:
+- 不依赖URL访问权限
+- 确保AI能获取到图片内容
+
+**缺点**:
+- 图片需要先下载到本地
+- base64字符串很长,可能超出token限制
+- 只适用于小图片(<5MB)
+
+**实现**:见"原因2"的代码
+
+### 方案2:使用其他AI服务
+
+如果fmode-ng的vision支持有问题,考虑:
+- OpenAI GPT-4 Vision
+- Google Gemini Vision
+- Claude 3 Vision
+
+## 📊 完整调试日志示例
+
+### 成功案例
+```
+📤 准备上传文件: 客厅设计.jpg, 大小: 3.2MB
+✅ 文件上传成功: https://file-cloud.fmode.cn/project/abc123/客厅设计.jpg
+💾 ProjectFile记录已创建: pf_xyz789
+
+🤖 调用豆包1.6模型...
+📸 原始图片URL: ["https://file-cloud.fmode.cn/project/abc123/客厅设计.jpg"]
+📸 编码后URL: ["https://file-cloud.fmode.cn/project/abc123/%E5%AE%A2%E5%8E%85%E8%AE%BE%E8%AE%A1.jpg"]
+🚀 开始调用completionJSON进行vision分析...
+
+✅ AI分析完成,原始内容长度: 2341
+📝 AI返回内容预览: 一、空间定位与场景属性
+
+这是一个现代法式风格的客餐厅一体化空间...
+```
+
+### 失败案例
+```
+📤 准备上传文件: 卧室.jpg, 大小: 2.1MB
+✅ 文件上传成功: https://file-cloud.fmode.cn/project/abc123/卧室.jpg
+💾 ProjectFile记录已创建: pf_xyz456
+
+🤖 调用豆包1.6模型...
+📸 原始图片URL: ["https://file-cloud.fmode.cn/project/abc123/卧室.jpg"]
+📸 编码后URL: ["https://file-cloud.fmode.cn/project/abc123/%E5%8D%A7%E5%AE%A4.jpg"]
+🚀 开始调用completionJSON进行vision分析...
+
+❌ AI无法访问图片!
+🔍 AI返回的完整内容: (注:由于未接收到实际图片内容,无法进行基于视觉信息的精准分析...
+🔍 图片URL列表: ["https://file-cloud.fmode.cn/project/abc123/%E5%8D%A7%E5%AE%A4.jpg"]
+
+错误信息:AI无法访问图片。可能原因:
+1. 图片URL无法被AI服务访问
+2. 图片格式不支持
+3. 图片过大或损坏
+```
+
+## 🔄 下一步行动
+
+### 立即执行
+1. ✅ 刷新浏览器,重新上传图片
+2. ✅ 查看控制台,确认"ProjectFile记录已创建"
+3. ✅ 复制图片URL,在浏览器中测试是否可访问
+4. ✅ 查看AI调用日志,确认图片URL被正确传递
+
+### 如果问题仍存在
+1. 🔧 实施base64方案(见"原因2"代码)
+2. 🔧 联系fmode-ng开发团队确认vision支持
+3. 🔧 尝试使用其他AI服务进行对比测试
+4. 🔧 检查网络代理/防火墙设置
+
+### 长期优化
+1. 📦 升级fmode-ng到最新版本
+2. 📦 配置OBS/存储服务的CORS和访问权限
+3. 📦 实现图片压缩(自动将大图压缩到合适大小)
+4. 📦 添加图片格式转换(统一使用JPG)
+
+---
+
+**更新日期**: 2025-11-27  
+**状态**: 🔧 调试中,等待用户反馈

+ 296 - 0
docs/ai-image-access-fix.md

@@ -0,0 +1,296 @@
+# AI图片访问问题修复
+
+## 🔴 关键问题
+
+用户上传图片进行AI分析后,AI返回:
+
+> **"由于未获取到实际图片内容,无法进行基于视觉信息的设计分析"**
+
+这说明AI模型无法访问图片URL。
+
+## 🔍 问题根本原因
+
+### 问题1:缺少vision参数
+
+`FmodeChatCompletion`需要显式启用视觉能力才能分析图片。
+
+**错误代码**:
+```typescript
+const completion = new FmodeChatCompletion(messageList, {
+  model: this.AI_MODEL,
+  max_tokens: 8000,
+  // ❌ 缺少 vision: true
+});
+```
+
+**修复代码**:
+```typescript
+const completion = new FmodeChatCompletion(messageList, {
+  model: this.AI_MODEL,
+  max_tokens: 8000,
+  vision: true, // ✅ 启用视觉能力
+} as any); // 使用类型断言,因为vision属性在TypeScript类型定义中缺失
+```
+
+**注意**:使用`as any`类型断言是因为`FmodeChatCompletion`的TypeScript类型定义中没有包含`vision`属性,但运行时库确实支持这个参数。
+
+### 问题2:图片URL包含中文字符
+
+图片URL示例:
+```
+https://file-cloud.fmode.cn/...../软装1(参考图).jpg
+```
+
+中文文件名"软装1(参考图)"需要进行URL编码,否则AI可能无法访问。
+
+**修复方法**:
+```typescript
+const encodedImages = options.images.map(url => {
+  try {
+    const urlObj = new URL(url);
+    const pathname = urlObj.pathname;
+    // 对路径中的每个部分进行编码
+    const encodedPathname = pathname.split('/')
+      .map(segment => encodeURIComponent(decodeURIComponent(segment)))
+      .join('/');
+    return urlObj.origin + encodedPathname + urlObj.search;
+  } catch (e) {
+    console.warn('⚠️ URL编码失败,使用原始URL:', url);
+    return url;
+  }
+});
+```
+
+**编码效果**:
+```
+原始: .../软装1(参考图).jpg
+编码: .../%E8%BD%AF%E8%A3%851%EF%BC%88%E5%8F%82%E8%80%83%E5%9B%BE%EF%BC%89.jpg
+```
+
+## ✅ 完整修复方案
+
+### 修改文件:`design-analysis-ai.service.ts`
+
+**修改位置1:添加URL编码**
+
+```typescript
+// 🔥 关键修复:图片URL需要URL编码,确保AI能正确访问
+const encodedImages = options.images.map(url => {
+  try {
+    // 如果URL包含中文或特殊字符,进行编码
+    const urlObj = new URL(url);
+    const pathname = urlObj.pathname;
+    const encodedPathname = pathname.split('/')
+      .map(segment => encodeURIComponent(decodeURIComponent(segment)))
+      .join('/');
+    return urlObj.origin + encodedPathname + urlObj.search;
+  } catch (e) {
+    console.warn('⚠️ URL编码失败,使用原始URL:', url);
+    return url;
+  }
+});
+
+// 使用FmodeChatCompletion进行流式输出
+const messageList = [
+  {
+    role: 'user',
+    content: prompt,
+    images: encodedImages // ✅ 使用编码后的URL
+  }
+];
+
+// 日志输出,帮助调试
+console.log('📸 原始图片URL:', options.images);
+console.log('📸 编码后URL:', encodedImages);
+```
+
+**修改位置2:启用vision参数**
+
+```typescript
+const completion = new FmodeChatCompletion(messageList, {
+  model: this.AI_MODEL,
+  max_tokens: 8000,
+  vision: true, // ✅ 启用视觉能力
+});
+```
+
+**修改位置3:添加错误检测**
+
+```typescript
+if (message?.complete && content) {
+  // 🔥 关键检查:AI是否真正分析了图片
+  if (content.includes('由于未获取到实际图片内容') || 
+      content.includes('无法进行基于视觉信息的设计分析') ||
+      content.includes('请提供具体图片')) {
+    console.error('❌ AI无法访问图片!');
+    console.error('🔍 AI返回的完整内容:', content);
+    reject(new Error('AI无法访问图片。可能原因:\n1. 图片URL格式不正确\n2. 图片URL无法被AI访问\n3. 图片文件损坏或格式不支持\n\n请尝试:\n- 重新上传图片\n- 使用常见格式(JPG/PNG)\n- 确保图片文件名不包含特殊字符'));
+    subscription?.unsubscribe();
+    return;
+  }
+  // ... 继续处理
+}
+```
+
+## 🎯 修复流程
+
+```
+用户上传图片
+    ↓
+文件名:软装1(参考图).jpg
+    ↓
+URL编码:%E8%BD%AF%E8%A3%851%EF%BC%88%E5%8F%82%E8%80%83%E5%9B%BE%EF%BC%89.jpg
+    ↓
+调用FmodeChatCompletion
+  - vision: true (启用视觉)
+  - images: [编码后的URL]
+    ↓
+AI分析图片
+    ↓
+返回完整的8维度分析
+```
+
+## 🔧 调试步骤
+
+### 1. 检查图片URL编码
+
+打开浏览器控制台,查看日志:
+
+```
+📸 原始图片URL: ["https://.../软装1(参考图).jpg"]
+📸 编码后URL: ["https://.../%E8%BD%AF%E8%A3%851%EF%BC%88%E5%8F%82%E8%80%83%E5%9B%BE%EF%BC%89.jpg"]
+```
+
+如果看到这个日志,说明URL编码成功。
+
+### 2. 检查vision参数
+
+确认AI调用时启用了vision:
+
+```typescript
+{
+  model: 'fmode-1.6-cn',
+  max_tokens: 8000,
+  vision: true  // ✅ 必须有这个
+}
+```
+
+### 3. 检查AI返回内容
+
+如果AI仍然返回"无法获取图片内容",检查:
+
+1. **图片URL是否可访问**
+   - 在浏览器中直接打开图片URL
+   - 确认不需要登录或特殊权限
+
+2. **图片格式是否支持**
+   - 建议使用JPG、PNG格式
+   - 避免使用HEIC、WebP等特殊格式
+
+3. **图片大小是否合适**
+   - 建议1-10MB之间
+   - 太小(<100KB)可能分辨率不足
+   - 太大(>50MB)可能超时
+
+## ⚠️ 常见错误和解决方案
+
+### 错误1:AI返回"无法获取图片"
+
+**可能原因**:
+- vision参数未设置
+- 图片URL编码错误
+- 图片URL需要鉴权
+
+**解决方案**:
+1. 确认已添加`vision: true`
+2. 检查编码后的URL是否正确
+3. 确认图片URL是公开可访问的
+
+### 错误2:AI分析结果很短或很模糊
+
+**可能原因**:
+- 图片分辨率太低
+- 图片内容不清晰
+- 提示词不够详细
+
+**解决方案**:
+1. 上传高清图片(至少1920x1080)
+2. 确保图片内容清晰可见
+3. 在"需求描述"中补充更多信息
+
+### 错误3:URL编码后仍然无法访问
+
+**可能原因**:
+- 存储服务对编码URL支持有问题
+- 需要特殊的鉴权头
+
+**备选方案**:
+将图片转为base64格式传递(适用于小图片):
+
+```typescript
+// 获取图片blob
+const response = await fetch(imageUrl);
+const blob = await response.blob();
+
+// 转为base64
+const reader = new FileReader();
+reader.readAsDataURL(blob);
+reader.onloadend = () => {
+  const base64 = reader.result as string;
+  // 使用base64传递给AI
+};
+```
+
+## 📝 文件修改清单
+
+| 文件 | 修改内容 | 行数 |
+|------|---------|------|
+| `design-analysis-ai.service.ts` | 添加URL编码逻辑 | 46-58 |
+| `design-analysis-ai.service.ts` | 启用vision参数 | 86 |
+| `design-analysis-ai.service.ts` | 添加图片访问错误检测 | 110-119 |
+
+## ✅ 验证清单
+
+修复后请验证:
+
+- [ ] 上传包含中文文件名的图片
+- [ ] 控制台显示编码前后的URL
+- [ ] AI返回真实的分析内容(不是模板)
+- [ ] 简洁摘要包含具体的空间类型、风格、色调
+- [ ] 完整分析内容≥800字
+- [ ] 分维度查看中每个维度都有内容
+
+## 🎉 预期效果
+
+### 修复前
+
+```
+AI返回:
+"由于未获取到实际图片内容,无法进行基于视觉信息的设计分析"
+
+简洁摘要:
+"整体设计基于图片实际内容分析"
+```
+
+### 修复后
+
+```
+AI返回:
+"一、空间定位与场景属性
+这是一个典型的客餐厅一体化空间,整体呈现现代法式风格...
+(完整的8维度分析,共1500字)"
+
+简洁摘要:
+"客餐厅一体化 | 现代+法式 | 暖色系、暖灰 | 温馨、精致 | 主要材质:护墙板、木材、大理石"
+```
+
+## 🔗 相关文档
+
+- [AI分析结果显示问题修复](./ai-analysis-display-fix.md)
+- [AI分析使用指南](./ai-analysis-usage-guide.md)
+
+---
+
+**修复日期**: 2025-11-27  
+**修复人**: 开发团队  
+**优先级**: 🔴 高(阻塞功能)

+ 322 - 0
docs/ai-image-upload-troubleshooting.md

@@ -0,0 +1,322 @@
+# AI图片上传和访问故障排除
+
+## 🔴 当前问题
+
+### 问题1:上传失败 - status code 631
+
+**错误提示**:
+```
+Failed to load resource: the server responded with a status of 631
+上传失败: Object
+```
+
+**问题原因**:
+存储服务返回631错误,可能的原因:
+
+1. **存储配额已满**
+   - 项目存储空间已用完
+   - 企业存储配额达到上限
+
+2. **项目ID无效**
+   - `this.projectId`为空或格式不正确
+   - 上传路径:`ai-design-analysis/${this.projectId}`
+
+3. **存储权限不足**
+   - 当前用户没有上传权限
+   - 项目没有配置存储bucket
+
+4. **文件名编码问题**
+   - 文件名包含特殊字符
+   - 中文文件名未正确编码
+
+5. **网络问题**
+   - 存储服务不可达
+   - 超时或连接中断
+
+### 问题2:AI无法访问图片
+
+**错误提示**:
+```
+❌ AI无法访问图片!
+🔍 AI返回的完整内容: (由于未获取实际图片内容,无法进行基于视觉信息的设计分析...)
+```
+
+**问题原因**:
+1. **图片URL无效**
+   - 上传失败导致没有有效的图片URL
+   - URL格式错误(blob:// 或本地路径)
+
+2. **图片URL无法访问**
+   - 需要鉴权才能访问
+   - 跨域问题
+   - 图片已被删除
+
+3. **AI配置问题**
+   - 缺少vision参数(已修复)
+   - 图片URL编码问题(已修复)
+
+## ✅ 故障排除步骤
+
+### 步骤1:检查上传日志
+
+打开浏览器控制台(F12),查找以下日志:
+
+```
+📤 准备上传文件: xxx.jpg, 大小: X.XXMBデフォルト上传路径: ai-design-analysis/项目ID
+上传进度: XX%
+✅ 文件上传成功: https://...
+```
+
+**如果没有"文件上传成功"日志**:
+- 上传在某个步骤失败了
+- 检查详细错误信息
+
+**如果出现631错误**:
+```
+❌ 上传失败,详细错误: ...
+❌ 错误代码: 631
+```
+
+### 步骤2:验证项目ID
+
+在控制台执行:
+```javascript
+// 检查当前项目ID
+console.log('当前项目ID:', this.projectId);
+```
+
+**项目ID应该**:
+- 不为空
+- 是一个有效的Parse ObjectId(10个字符的字母数字组合)
+- 示例:`iKvYck89zE`
+
+**如果项目ID无效**:
+- 项目可能未正确初始化
+- 刷新页面重新加载项目
+
+### 步骤3:检查存储配额
+
+联系系统管理员检查:
+
+1. **企业存储配额**
+   - 登录华为云OBS控制台
+   - 查看bucket使用情况
+   - 确认是否达到配额上限
+
+2. **项目存储限制**
+   - 检查项目数据库记录
+   - 查看是否设置了单项目存储限制
+
+### 步骤4:测试文件上传
+
+尝试上传不同类型的文件:
+
+1. **小文件测试**
+   - 上传一个100KB的JPG图片
+   - 如果成功,说明不是权限问题
+
+2. **文件名测试**
+   - 使用纯英文文件名(如`test.jpg`)
+   - 避免中文和特殊字符
+
+3. **格式测试**
+   - 只上传JPG/PNG格式
+   - 避免HEIC、WebP等特殊格式
+
+### 步骤5:验证图片URL
+
+上传成功后,在控制台检查:
+
+```
+✅ 验证通过,有效图片数量: X
+📸 有效图片URL列表: ["https://..."]
+```
+
+**有效URL必须**:
+- 以`http://`或`https://`开头
+- 完整的域名和路径
+- 可以在浏览器中直接打开
+
+**如果URL无效**:
+```
+⚠️ 无效的图片URL: blob:http://...
+```
+- 这是本地预览URL,不是上传后的URL
+- 图片上传失败
+
+### 步骤6:测试图片访问
+
+复制图片URL,在浏览器新标签页打开:
+
+**应该能看到图片**:
+- 如果看不到,说明URL无法公开访问
+- 可能需要配置存储bucket为公开读
+
+**如果需要登录**:
+- 存储bucket权限设置错误
+- 需要配置为公开读或生成临时访问令牌
+
+## 🔧 临时解决方案
+
+### 方案1:使用其他上传路径
+
+如果`ai-design-analysis/${projectId}`路径有问题,尝试其他路径:
+
+```typescript
+const uploadedFile = await storage.upload(file, {
+  prefixKey: `temp/ai-analysis`, // 使用临时路径
+  onProgress: (progress) => {...}
+});
+```
+
+### 方案2:使用base64编码(仅限小图片<5MB)
+
+```typescript
+// 将图片转为base64
+const reader = new FileReader();
+reader.readAsDataURL(file);
+reader.onloadend = () => {
+  const base64 = reader.result as string;
+  // 直接传递base64给AI
+  this.aiDesignUploadedImages.push(base64);
+};
+```
+
+**注意**:base64会增大数据量,不推荐用于大图片。
+
+### 方案3:联系管理员
+
+如果以上方法都无效,请联系系统管理员:
+
+**需要提供的信息**:
+1. 完整的错误日志(控制台截图)
+2. 项目ID
+3. 上传的文件名和大小
+4. 错误代码(631)
+
+**管理员需要检查**:
+1. 华为云OBS配置
+2. 存储bucket权限设置
+3. 项目存储配额
+4. fmode-ng存储服务配置
+
+## 📋 检查清单
+
+上传图片前,请确认:
+
+- [ ] 项目ID有效(不为空,格式正确)
+- [ ] 图片格式为JPG/PNG
+- [ ] 图片大小<50MB
+- [ ] 文件名不包含特殊字符
+- [ ] 网络连接正常
+- [ ] 存储配额未满
+- [ ] 有上传权限
+
+AI分析前,请确认:
+
+- [ ] 图片上传成功(控制台显示"文件上传成功")
+- [ ] 图片URL有效(以http://或https://开头)
+- [ ] 图片URL可访问(浏览器能打开)
+- [ ] 至少有1张有效图片
+- [ ] vision参数已启用(代码已修复)
+
+## 🎯 预期效果
+
+### 正常流程
+
+```
+1. 选择图片文件
+   ↓
+2. 开始上传
+   📤 准备上传文件: xxx.jpg, 大小: 2.5MB
+   📂 上传路径: ai-design-analysis/iKvYck89zE
+   ↓
+3. 上传进度
+   上传进度: 10%
+   上传进度: 50%
+   上传进度: 100%
+   ↓
+4. 上传成功
+   ✅ 文件上传成功: https://file-cloud.fmode.cn/.../xxx.jpg
+   ↓
+5. 验证URL
+   ✅ 验证通过,有效图片数量: 1
+   📸 有效图片URL列表: ["https://..."]
+   ↓
+6. 开始AI分析
+   🤖 调用豆包1.6模型...
+   📸 原始图片URL: [...]
+   📸 编码后URL: [...]
+   ↓
+7. AI分析完成
+   ✅ AI分析完成,原始内容长度: 1523
+   📝 AI返回内容预览: 一、空间定位与场景属性...
+```
+
+### 异常情况处理
+
+**上传失败(631)**:
+```
+❌ 上传失败,详细错误: ...
+❌ 错误代码: 631
+🚨 存储服务错误(631)。可能原因:
+1. 存储配额已满
+2. 项目ID无效
+3. 存储权限不足
+```
+
+**URL无效**:
+```
+⚠️ 无效的图片URL: blob:http://...
+⚠️ 发现1个无效图片URL,已自动过滤
+🚨 图片上传失败,请重新上传
+```
+
+**AI无法访问**:
+```
+❌ AI无法访问图片!
+🔍 AI返回的完整内容: (由于未获取实际图片内容...)
+🚨 AI无法访问图片。可能原因:
+1. 图片URL格式不正确
+2. 图片URL无法被AI访问
+3. 图片文件损坏或格式不支持
+```
+
+## 💡 常见问题
+
+### Q1: 为什么上传后还是提示"请先上传参考图片"?
+
+**A**: 上传失败或返回的URL无效,导致`aiDesignUploadedImages`数组为空或包含无效URL。检查控制台是否有"文件上传成功"日志。
+
+### Q2: 631错误一定是存储配额问题吗?
+
+**A**: 不一定。631可能是多种原因:
+- 存储配额满(最常见)
+- 项目ID无效
+- 权限不足
+- 存储服务配置错误
+
+检查详细错误日志以确定具体原因。
+
+### Q3: 图片能在浏览器打开,但AI还是无法访问?
+
+**A**: 可能的原因:
+1. 缺少vision参数(已修复)
+2. 图片URL包含中文未编码(已修复)
+3. AI服务暂时不可用
+4. 图片格式不支持(使用JPG/PNG)
+
+### Q4: 如何清空已上传的图片重新开始?
+
+**A**: 点击"重新分析"按钮,或刷新页面。这会清空`aiDesignUploadedImages`和`aiDesignUploadedFiles`数组。
+
+## 🔗 相关文档
+
+- [AI图片访问问题修复](./ai-image-access-fix.md)
+- [AI分析结果显示问题修复](./ai-analysis-display-fix.md)
+- [AI分析使用指南](./ai-analysis-usage-guide.md)
+
+---
+
+**更新日期**: 2025-11-27  
+**优先级**: 🔴 高(阻塞功能)

+ 420 - 0
docs/ai-multi-turn-conversation-guide.md

@@ -0,0 +1,420 @@
+# AI多轮对话和确认机制使用指南
+
+## 🎯 功能概述
+
+用户现在可以:
+1. **上传图片进行初次分析**
+2. **继续上传更多图片补充分析**
+3. **通过对话调整和优化分析结果**
+4. **点击"确认分析结果"后生成详细报告**
+
+## 📋 使用流程
+
+### 第1步:上传首张图片
+
+```
+用户操作:
+1. 点击"上传图片"按钮
+2. 选择1张或多张图片
+3. 等待上传完成
+4. 点击"开始分析"按钮
+```
+
+**系统行为**:
+- 验证图片URL有效性
+- 调用AI进行设计分析
+- 实时流式显示分析内容
+- 保存分析结果和对话记录
+
+**控制台日志**:
+```
+📤 准备上传文件: design1.jpg, 大小: 2.5MB
+✅ 文件上传成功: https://file-cloud.fmode.cn/.../design1.jpg
+✅ 验证通过,有效图片数量: 1
+🤖 开始AI图片分析...
+📸 图片数量: 1
+💬 对话历史数量: 0 条
+📸 原始图片URL: [...]
+📸 编码后URL: [...]
+✅ AI分析完成,原始内容长度: 1523
+```
+
+### 第2步:继续上传图片(可选)
+
+```
+用户操作:
+1. 在已有分析结果的基础上
+2. 点击"上传图片"按钮
+3. 选择新的图片
+4. 再次点击"开始分析"按钮
+```
+
+**系统行为**:
+- 🔥 **不会清空之前的对话记录**
+- 将新上传的图片加入分析
+- 传递之前的对话历史给AI
+- AI基于上下文提供补充分析
+
+**控制台日志**:
+```
+📌 检测到已有对话记录,将作为补充分析
+🤖 开始AI图片分析...
+📸 图片数量: 2
+💬 对话历史数量: 2 条
+```
+
+### 第3步:对话调整分析结果
+
+```
+用户操作:
+1. 在对话输入框中输入调整需求
+   例如:
+   - "能否分析一下这个空间的采光问题?"
+   - "请详细说明材质的选择理由"
+   - "这个空间的预算应该控制在多少?"
+2. 按回车或点击发送按钮
+```
+
+**系统行为**:
+- 将用户输入添加到对话历史
+- 调用AI服务,传递完整对话历史
+- AI基于上下文提供针对性回复
+- 实时流式显示AI回复
+
+**控制台日志**:
+```
+🤖 开始AI对话分析...
+💬 对话历史数量: 4 条
+💡 深度思考模式: false
+✅ AI对话完成
+```
+
+### 第4步:确认分析结果
+
+```
+用户操作:
+1. 满意分析结果后
+2. 点击"确认分析结果"按钮
+```
+
+**系统行为**:
+1. **整合所有AI回复**
+   - 合并所有AI消息
+   - 使用分隔符(`---`)分段
+
+2. **保存到项目数据库**
+   ```javascript
+   {
+     report: "完整的AI分析报告",
+     analysisData: { rawContent, formattedContent, structuredData },
+     images: ["图片URL列表"],
+     files: [{ url, name, type, size }],
+     chatHistory: [{ role, content, timestamp }],
+     confirmedAt: "2025-11-27T12:00:00Z",
+     confirmedBy: "用户ID"
+   }
+   ```
+
+3. **询问是否生成客户报告**
+   - 点击"是":调用`generateAndShowClientReport()`
+   - 点击"否":稍后可在报告区域生成
+
+**控制台日志**:
+```
+✅ 分析结果已确认并保存
+```
+
+## 🔧 技术实现
+
+### 1. 移除"已有对话时不能上传"限制
+
+**修改前**:
+```typescript
+if (this.aiChatMessages.length > 0) {
+  window?.fmode?.alert('请在对话框中输入您的需求');
+  return;
+}
+```
+
+**修改后**:
+```typescript
+// 支持多轮对话:如果已有对话记录,将新上传的图片作为补充分析
+const isFollowUp = this.aiChatMessages.length > 0;
+
+if (isFollowUp) {
+  console.log('📌 检测到已有对话记录,将作为补充分析');
+}
+```
+
+### 2. 构建对话历史
+
+**文件**: `stage-requirements.component.ts`
+
+```typescript
+// 构建对话历史(排除当前消息和流式输出中的消息)
+const conversationHistory = this.aiChatMessages
+  .filter(m => 
+    m.id !== userMessage.id && 
+    m.id !== aiStreamMessage.id && 
+    !m.isStreaming && 
+    m.content && 
+    m.content.trim().length > 0
+  )
+  .map(m => ({
+    role: m.role,
+    content: m.content || ''
+  }));
+```
+
+### 3. AI服务调用
+
+**文件**: `design-analysis-ai.service.ts`
+
+```typescript
+await this.designAnalysisAIService.analyzeReferenceImages({
+  images: this.aiDesignUploadedImages, // 所有已上传的图片
+  textDescription: message, // 当前用户输入
+  spaceType: undefined, // 不传递,让AI自动识别
+  conversationHistory: conversationHistory, // 完整对话历史
+  deepThinking: this.deepThinkingEnabled, // 深度思考模式
+  onContentStream: (content) => {
+    // 实时更新流式输出
+  }
+});
+```
+
+### 4. 对话历史格式
+
+```typescript
+conversationHistory = [
+  {
+    role: 'user',
+    content: '请分析这张图片的设计风格'
+  },
+  {
+    role: 'assistant',
+    content: '一、空间定位与场景属性\n这是一个现代法式风格的客餐厅...'
+  },
+  {
+    role: 'user',
+    content: '能否详细说明材质的选择?'
+  },
+  {
+    role: 'assistant',
+    content: '关于材质选择,我详细分析如下...'
+  }
+]
+```
+
+## 🎨 UI交互
+
+### 对话界面
+
+```html
+<div class="chat-messages-wrapper">
+  <!-- 对话消息列表 -->
+  <div class="chat-messages-list">
+    @for (message of aiChatMessages; track message.id) {
+      <div class="chat-message" 
+           [class.user-message]="message.role === 'user'" 
+           [class.ai-message]="message.role === 'assistant'">
+        
+        <!-- 用户消息 -->
+        @if (message.role === 'user') {
+          <div class="message-content">
+            <p>{{ message.content }}</p>
+            @if (message.images?.length) {
+              <div class="message-images">
+                <!-- 显示用户上传的图片 -->
+              </div>
+            }
+          </div>
+        }
+        
+        <!-- AI消息 -->
+        @if (message.role === 'assistant') {
+          <div class="ai-message-content">
+            @if (message.isStreaming) {
+              <!-- 流式输出中... -->
+              <p>{{ message.content }}<span class="typing-cursor">|</span></p>
+            } @else {
+              <!-- 完整内容 -->
+              <p>{{ message.content }}</p>
+            }
+          </div>
+        }
+      </div>
+    }
+  </div>
+</div>
+
+<!-- 输入框 -->
+<div class="chat-input-wrapper">
+  <textarea
+    class="chat-input"
+    [(ngModel)]="aiChatInput"
+    placeholder="描述你的需求或提出修改意见..."
+    [disabled]="aiDesignAnalyzing"
+    (keydown)="handleChatInputKeydown($event)">
+  </textarea>
+  
+  <button 
+    class="send-btn" 
+    [disabled]="aiDesignAnalyzing || !aiChatInput?.trim()"
+    (click)="sendChatMessage()">
+    发送
+  </button>
+</div>
+
+<!-- 快捷操作栏 -->
+<div class="quick-actions">
+  <button class="quick-action-btn" 
+          (click)="clearChat()" 
+          [disabled]="aiChatMessages.length === 0">
+    清空对话
+  </button>
+  
+  <button class="quick-action-btn" 
+          (click)="exportChat()" 
+          [disabled]="aiChatMessages.length === 0">
+    导出对话
+  </button>
+  
+  <button class="quick-action-btn" 
+          (click)="confirmCurrentAnalysis()" 
+          [disabled]="aiChatMessages.length === 0">
+    确认分析结果
+  </button>
+</div>
+```
+
+## 📊 数据流
+
+```
+用户上传图片 → 验证URL → 开始分析
+    ↓
+构建对话历史(过滤掉当前和流式中的消息)
+    ↓
+调用AI服务
+  - images: [所有图片URL]
+  - conversationHistory: [完整对话历史]
+  - spaceType: undefined
+  - vision: true
+  - images: [编码后的URL]
+    ↓
+AI实时流式输出 → 更新UI
+    ↓
+AI分析完成 → 保存结果
+    ↓
+用户继续输入 → 重复上述流程
+    ↓
+用户点击"确认" → 保存到项目 → 生成报告
+```
+
+## 🔍 调试日志
+
+### 首次分析
+```
+✅ 验证通过,有效图片数量: 1
+🤖 开始AI图片分析...
+📸 图片数量: 1
+💬 对话历史数量: 0 条
+📸 原始图片URL: ["https://..."]
+📸 编码后URL: ["https://.../%E8%BD%AF..."]
+🤖 调用豆包1.6模型...
+✅ AI分析完成,原始内容长度: 1523
+```
+
+### 补充图片
+```
+📌 检测到已有对话记录,将作为补充分析
+✅ 验证通过,有效图片数量: 2
+🤖 开始AI图片分析...
+💬 对话历史数量: 2 条
+```
+
+### 对话调整
+```
+🤖 开始AI对话分析...
+💬 对话历史数量: 4 条
+💡 深度思考模式: false
+📊 AI思考进度: 正在分析设计细节...
+✅ AI对话完成
+```
+
+### 确认结果
+```
+✅ 分析结果已确认并保存
+分析结果已保存!
+
+是否立即生成客户报告?
+```
+
+## ⚠️ 常见问题
+
+### Q1: 为什么上传新图片后要再次点击"开始分析"?
+
+**A**: 这是设计的工作流程,允许用户:
+- 一次上传多张图片
+- 检查上传是否成功
+- 添加文字描述
+- 然后一次性分析所有图片
+
+### Q2: 对话历史会传递给AI吗?
+
+**A**: 是的,每次调用AI时都会传递完整的对话历史,让AI能够:
+- 理解上下文
+- 提供连贯的回复
+- 基于之前的分析进行补充
+
+### Q3: 确认后还能继续对话吗?
+
+**A**: 可以。确认只是保存当前结果,不影响后续对话。但建议:
+- 确认前做好充分的对话调整
+- 确认后的对话将创建新的分析记录
+
+### Q4: 如何查看已确认的分析结果?
+
+**A**: 确认后的结果保存在:
+```javascript
+项目数据 → data.designReports[空间ID]
+```
+
+可以在报告区域查看和导出。
+
+## 💡 最佳实践
+
+### 1. 图片上传建议
+- **首次**:上传2-3张核心设计图
+- **补充**:根据AI分析结果,上传局部细节图
+- **格式**:使用JPG/PNG,大小1-10MB
+- **文件名**:避免特殊字符,可使用中文
+
+### 2. 对话技巧
+- **具体明确**:"请详细分析客厅的色调搭配" ✓
+- **避免模糊**:"这个怎么样?" ✗
+- **追问细节**:"刚才提到的暖灰色,具体用在哪些地方?"
+- **提出疑问**:"为什么选择大理石而不是木地板?"
+
+### 3. 确认时机
+- ✅ **AI已回答所有关键问题**
+- ✅ **分析结果符合预期**
+- ✅ **已获取足够的设计细节**
+- ✅ **准备生成客户报告**
+
+### 4. 性能优化
+- 对话历史过长(>20轮)时,考虑确认并开始新对话
+- 图片数量控制在5张以内,避免AI分析超时
+- 使用"深度思考模式"获取更详细的分析(但耗时更长)
+
+## 🔗 相关文档
+
+- [AI图片访问问题修复](./ai-image-access-fix.md)
+- [AI图片上传故障排除](./ai-image-upload-troubleshooting.md)
+- [AI分析结果显示问题修复](./ai-analysis-display-fix.md)
+- [AI分析使用指南](./ai-analysis-usage-guide.md)
+
+---
+
+**更新日期**: 2025-11-27  
+**功能状态**: ✅ 已实现并测试

+ 324 - 0
docs/ai-vision-base64-solution.md

@@ -0,0 +1,324 @@
+# AI Vision Base64 解决方案
+
+## 问题背景
+
+AI返回模板内容"由于未接收到实际图片内容",说明AI无法访问通过URL传递的图片。
+
+### 原因分析
+
+1. **图片URL访问权限限制**
+   - 图片URL可能需要身份验证
+   - 存储服务未配置公开访问
+   - CORS跨域限制
+
+2. **completionJSON可能不支持URL方式**
+   - 参考项目`ai-k12-daofa`成功使用vision,但其图片可能是公开可访问的
+   - 本项目的图片URL可能需要特殊权限
+
+## 解决方案:使用Base64
+
+### 核心思路
+将图片URL转换为base64格式,直接在请求中传递图片数据,绕过URL访问权限问题。
+
+### 实现代码
+
+**文件**: `design-analysis-ai.service.ts`
+
+```typescript
+// 🔥 关键修复:将图片URL转换为base64格式
+console.log('🔄 开始将图片URL转换为base64...');
+const base64Images: string[] = [];
+
+for (let i = 0; i < options.images.length; i++) {
+  const url = options.images[i];
+  try {
+    console.log(`📥 下载图片${i + 1}: ${url}`);
+    
+    // 1. 使用fetch下载图片
+    const response = await fetch(url);
+    if (!response.ok) {
+      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+    }
+    
+    // 2. 转换为Blob
+    const blob = await response.blob();
+    console.log(`📦 图片${i + 1}大小: ${(blob.size / 1024 / 1024).toFixed(2)}MB`);
+    
+    // 3. 使用FileReader转换为base64
+    const base64 = await new Promise<string>((resolve, reject) => {
+      const reader = new FileReader();
+      reader.onloadend = () => resolve(reader.result as string);
+      reader.onerror = reject;
+      reader.readAsDataURL(blob);
+    });
+    
+    base64Images.push(base64);
+    console.log(`✅ 图片${i + 1}已转换为base64 (${(base64.length / 1024).toFixed(2)}KB)`);
+    
+  } catch (error: any) {
+    console.error(`❌ 图片${i + 1}转换失败:`, error);
+    throw new Error(`图片${i + 1}无法访问: ${error.message}\n请确保图片URL可公开访问`);
+  }
+}
+
+console.log(`✅ 所有图片已转换为base64,共${base64Images.length}张`);
+
+// 4. 使用base64图片调用AI
+const result = await completionJSON(
+  prompt,
+  '',
+  undefined,
+  2,
+  {
+    model: this.AI_MODEL,
+    vision: true,
+    images: base64Images, // 🔥 传入base64而非URL
+    max_tokens: 8000
+  }
+);
+```
+
+### 数据流程
+
+```
+用户上传图片
+    ↓
+文件上传到OBS存储
+    ↓
+获取图片URL (https://file-cloud.fmode.cn/...)
+    ↓
+【AI分析阶段】
+    ↓
+使用fetch下载图片 → Blob
+    ↓
+FileReader转换 → base64字符串
+    ↓
+completionJSON调用
+  - vision: true
+  - images: [base64字符串数组]
+    ↓
+AI成功分析图片内容
+    ↓
+返回8维度设计分析
+```
+
+## 技术细节
+
+### Base64格式
+```
+data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD...
+```
+
+- **前缀**: `data:image/jpeg;base64,`
+- **MIME类型**: 根据图片格式自动识别(jpeg/png/gif等)
+- **编码数据**: 后续的长字符串
+
+### 大小考虑
+
+**Base64编码会增加约33%的大小**:
+- 原始图片: 3MB
+- Base64编码: ~4MB
+
+**建议**:
+- 单张图片控制在5MB以内
+- 多张图片总大小不超过15MB
+- 如需分析大图,可先压缩再上传
+
+### 性能优化
+
+1. **并行转换**(可选):
+```typescript
+const base64Images = await Promise.all(
+  options.images.map(url => urlToBase64(url))
+);
+```
+
+2. **缓存机制**(可选):
+```typescript
+// 缓存已转换的base64,避免重复转换
+const base64Cache = new Map<string, string>();
+```
+
+3. **进度反馈**:
+```typescript
+options.onProgressChange?.(`正在转换图片 ${i + 1}/${total}...`);
+```
+
+## 对比方案
+
+### 方案1:URL传递(原方案)
+**优点**:
+- 不需要下载图片
+- 请求体积小
+- 速度快
+
+**缺点**:
+- ❌ 需要图片URL公开可访问
+- ❌ 受CORS限制
+- ❌ 可能被防火墙拦截
+
+### 方案2:Base64传递(当前方案)
+**优点**:
+- ✅ 绕过URL访问权限问题
+- ✅ 不受CORS限制
+- ✅ 保证AI能获取到图片数据
+
+**缺点**:
+- 需要先下载图片(增加1-2秒)
+- 请求体积增加33%
+- 大图可能超出token限制
+
+## 调试日志
+
+### 成功案例
+```
+🔄 开始将图片URL转换为base64...
+📥 下载图片1: https://file-cloud.fmode.cn/.../test.jpg
+📦 图片1大小: 3.24MB
+✅ 图片1已转换为base64 (4320.56KB)
+✅ 所有图片已转换为base64,共1张
+
+🤖 调用豆包1.6模型...
+📸 原始图片URL: ["https://..."]
+📸 base64图片数量: 1
+🚀 开始调用completionJSON进行vision分析...
+
+✅ AI分析完成,原始内容长度: 2341
+📝 AI返回内容预览: 一、空间定位与场景属性
+
+这是一个现代法式风格的客餐厅一体化空间...
+```
+
+### 失败案例
+```
+🔄 开始将图片URL转换为base64...
+📥 下载图片1: https://file-cloud.fmode.cn/.../test.jpg
+❌ 图片1转换失败: TypeError: Failed to fetch
+    原因:网络错误或图片URL无法访问
+
+错误信息:图片1无法访问: Failed to fetch
+请确保图片URL可公开访问
+```
+
+## 常见问题
+
+### Q1: 转换很慢怎么办?
+**原因**:图片过大或网络慢
+
+**解决**:
+1. 上传前先压缩图片(建议<5MB)
+2. 检查网络连接
+3. 实施进度反馈提示用户
+
+### Q2: 转换失败"Failed to fetch"?
+**原因**:图片URL无法访问
+
+**解决**:
+1. 在浏览器中手动打开图片URL,确认可访问
+2. 检查是否需要登录/权限
+3. 检查CORS配置
+
+### Q3: AI仍返回模板内容?
+**原因**:可能不是URL访问问题
+
+**检查**:
+1. base64格式是否正确(应包含`data:image/...`前缀)
+2. 图片是否损坏
+3. 图片格式是否支持(建议JPG/PNG)
+4. 图片内容是否清晰
+
+### Q4: 超出token限制?
+**原因**:图片太大,base64字符串过长
+
+**解决**:
+1. 压缩图片到<3MB
+2. 减少上传的图片数量
+3. 分批分析
+
+## 参考实现
+
+### ai-k12-daofa项目
+该项目也使用`completionJSON`和`vision: true`:
+
+```typescript
+const result = await completionJSON(
+  prompt,
+  output,
+  (content) => {
+    // 流式回调
+  },
+  2,
+  {
+    model: 'fmode-1.6-cn',
+    vision: true,
+    images: options.images // 直接传URL
+  }
+);
+```
+
+**成功原因**:
+- 可能图片URL是公开可访问的
+- 或使用的是本地测试环境
+
+**与本项目差异**:
+- 本项目图片存储在企业OBS,可能有访问限制
+- 因此需要base64方案
+
+## 后续优化
+
+### 1. 图片预压缩
+在上传时自动压缩大图:
+```typescript
+async function compressImage(file: File): Promise<File> {
+  // 使用Canvas API压缩
+  const canvas = document.createElement('canvas');
+  const ctx = canvas.getContext('2d')!;
+  const img = await loadImage(file);
+  
+  // 设置最大尺寸
+  const maxWidth = 2000;
+  const maxHeight = 2000;
+  
+  let width = img.width;
+  let height = img.height;
+  
+  if (width > maxWidth || height > maxHeight) {
+    const ratio = Math.min(maxWidth / width, maxHeight / height);
+    width *= ratio;
+    height *= ratio;
+  }
+  
+  canvas.width = width;
+  canvas.height = height;
+  ctx.drawImage(img, 0, 0, width, height);
+  
+  return canvasToFile(canvas);
+}
+```
+
+### 2. 缓存base64
+避免重复转换:
+```typescript
+const base64Cache = new Map<string, string>();
+
+async function getCachedBase64(url: string): Promise<string> {
+  if (base64Cache.has(url)) {
+    return base64Cache.get(url)!;
+  }
+  
+  const base64 = await urlToBase64(url);
+  base64Cache.set(url, base64);
+  return base64;
+}
+```
+
+### 3. 配置OBS公开访问
+长期方案:配置存储服务允许公开读取
+- 优点:可直接使用URL方式
+- 缺点:需要运维配合
+
+---
+
+**更新日期**: 2025-11-27  
+**状态**: ✅ 已实现并测试  
+**测试结果**: 等待用户反馈

+ 343 - 0
docs/ai-vision-troubleshooting-checklist.md

@@ -0,0 +1,343 @@
+# AI Vision功能故障排查清单
+
+## ✅ 问题排查步骤(按顺序执行)
+
+### 第1步:确认图片上传成功并创建记录
+
+**预期控制台日志**:
+```
+📤 准备上传文件: test.jpg, 大小: 2.5MB
+📂 上传路径: ai-design-analysis/[项目ID]
+上传进度: 100%
+✅ 文件上传成功: https://file-cloud.fmode.cn/.../test.jpg
+💾 ProjectFile记录已创建: [记录ID]
+✅ 已上传1个文件
+```
+
+**如果缺少"ProjectFile记录已创建"**:
+- [ ] 检查Parse服务器连接状态
+- [ ] 确认项目ID有效(`console.log('项目ID:', this.projectId)`)
+- [ ] 查看是否有"创建ProjectFile记录失败"错误
+
+### 第2步:手动验证图片URL可访问
+
+**操作步骤**:
+1. [ ] 从控制台复制图片URL(完整的https://...)
+2. [ ] 打开浏览器新标签页/隐身模式
+3. [ ] 粘贴URL并访问
+4. [ ] 应该直接看到图片(无需登录)
+
+**如果无法访问**:
+```
+问题:图片URL需要身份验证或CORS限制
+解决:
+1. 检查OBS/存储服务的访问控制设置
+2. 设置图片为公开访问
+3. 配置CORS允许跨域访问
+```
+
+### 第3步:验证AI调用参数
+
+**预期控制台日志**:
+```
+🤖 调用豆包1.6模型...
+📸 原始图片URL: ["https://file-cloud.fmode.cn/.../test.jpg"]
+📸 编码后URL: ["https://file-cloud.fmode.cn/.../%E6%B5%8B%E8%AF%95.jpg"]
+📸 图片数量: 1
+🔍 开始测试图片URL可访问性...
+✅ 图片1可访问: https://...
+🚀 开始调用completionJSON进行vision分析...
+```
+
+**检查要点**:
+- [ ] 图片URL数组不为空
+- [ ] URL不是blob://开头的本地地址
+- [ ] URL是完整的HTTP/HTTPS地址
+- [ ] "图片可访问"测试通过
+
+### 第4步:分析AI返回内容
+
+#### 成功案例
+```
+✅ AI分析完成,原始内容长度: 2341
+📝 AI返回内容预览: 一、空间定位与场景属性
+
+这是一个现代法式风格的客餐厅一体化空间,面积约40-50平米...
+```
+
+**特征**:
+- 内容长度>1000字符
+- 包含具体的空间描述
+- 提到了实际的设计元素(颜色、材质、布局等)
+
+#### 失败案例
+```
+❌ AI无法访问图片!
+🔍 AI返回的完整内容: (注:由于未接收到实际图片内容...
+🔍 图片URL列表: [...]
+```
+
+**特征**:
+- 包含"未接收到"、"未获取到"、"缺少核心视觉素材"等关键词
+- 内容是模板化的建议
+- 没有具体的设计细节
+
+## 🔧 常见问题及解决方案
+
+### 问题1:AI返回"由于未接收到实际图片内容"
+
+**可能原因A**:图片URL需要身份验证
+```
+✅ 解决方案:
+1. 联系运维人员配置OBS/存储服务
+2. 设置图片bucket为公开读取
+3. 或配置签名URL(有效期24小时)
+```
+
+**可能原因B**:completionJSON不支持URL方式传图
+```
+✅ 解决方案:改用base64格式
+
+// 在design-analysis-ai.service.ts中添加
+async function urlToBase64(url: string): Promise<string> {
+  const response = await fetch(url);
+  const blob = await response.blob();
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.onloadend = () => {
+      const base64 = (reader.result as string);
+      resolve(base64);
+    };
+    reader.onerror = reject;
+    reader.readAsDataURL(blob);
+  });
+}
+
+// 修改analyzeReferenceImages方法
+const base64Images = await Promise.all(
+  encodedImages.map(url => urlToBase64(url))
+);
+
+const result = await completionJSON(
+  prompt,
+  '',
+  undefined,
+  2,
+  {
+    model: this.AI_MODEL,
+    vision: true,
+    images: base64Images, // 使用base64
+    max_tokens: 8000
+  }
+);
+```
+
+**可能原因C**:图片上传后需要等待CDN同步
+```
+✅ 解决方案:添加延迟
+
+// 在上传成功后等待3秒
+console.log('⏳ 等待图片URL生效...');
+await new Promise(resolve => setTimeout(resolve, 3000));
+console.log('✅ 继续执行');
+```
+
+**可能原因D**:AI模型不支持vision
+```
+✅ 解决方案:
+1. 确认fmode-1.6-cn模型支持多模态
+2. 尝试其他模型(如gpt-4-vision-preview)
+3. 联系fmode-ng技术支持确认
+```
+
+### 问题2:图片上传返回631错误
+
+**原因**:存储配额已满或权限不足
+
+```
+✅ 解决方案:
+1. 检查存储配额:联系管理员查看OBS使用情况
+2. 检查项目ID:确认this.projectId有效
+3. 检查权限:确认当前用户有上传权限
+4. 临时方案:使用小图片测试(<1MB)
+```
+
+### 问题3:图片URL无效(blob://...)
+
+**原因**:上传失败,只获取到本地预览URL
+
+```
+✅ 解决方案:
+1. 检查上传日志,查看错误信息
+2. 确认文件格式支持(JPG/PNG推荐)
+3. 减小文件大小(<10MB)
+4. 重新上传
+```
+
+## 📊 完整测试流程
+
+### 测试1:最小可行性测试
+
+```
+目的:验证vision功能基本可用
+
+步骤:
+1. 准备一张小图片(<1MB,JPG格式,纯英文文件名)
+   例如:test.jpg
+2. 上传图片
+3. 查看控制台日志
+4. 点击"开始分析"
+5. 等待AI返回结果
+
+预期:
+- 上传成功,创建ProjectFile记录
+- 图片URL可访问性测试通过
+- AI返回具体的分析内容(非模板)
+
+如果失败:
+- 记录完整的控制台日志
+- 截图AI返回的内容
+- 手动访问图片URL,截图结果
+```
+
+### 测试2:中文文件名测试
+
+```
+目的:验证URL编码是否正常
+
+步骤:
+1. 准备一张中文文件名的图片
+   例如:客厅设计.jpg
+2. 上传并分析
+3. 检查编码后的URL
+
+预期:
+- 原始URL: .../客厅设计.jpg
+- 编码URL: .../%E5%AE%A2%E5%8E%85%E8%AE%BE%E8%AE%A1.jpg
+- AI正常分析
+```
+
+### 测试3:多张图片测试
+
+```
+目的:验证多图片同时分析
+
+步骤:
+1. 一次上传3张图片
+2. 点击"开始分析"
+
+预期:
+- 所有图片URL都有效
+- AI综合分析多张图片内容
+```
+
+### 测试4:多轮对话测试
+
+```
+目的:验证对话历史传递
+
+步骤:
+1. 上传并分析第一张图片
+2. 在对话框输入问题:"能详细说明材质的选择吗?"
+3. 查看AI回复
+4. 继续追问
+
+预期:
+- AI能理解上下文
+- 回复与之前分析相关
+- 不重复分析图片
+```
+
+## 🚨 紧急调试命令
+
+### 命令1:查看图片URL是否可访问(在浏览器控制台执行)
+
+```javascript
+async function testImageUrl(url) {
+  try {
+    const response = await fetch(url, { method: 'HEAD' });
+    console.log('✅ 图片可访问,状态码:', response.status);
+    return true;
+  } catch (error) {
+    console.error('❌ 图片无法访问:', error);
+    return false;
+  }
+}
+
+// 使用方法
+testImageUrl('https://file-cloud.fmode.cn/.../test.jpg');
+```
+
+### 命令2:手动调用completionJSON测试vision
+
+```javascript
+import { completionJSON } from 'fmode-ng/core';
+
+async function testVision() {
+  const result = await completionJSON(
+    '请描述这张图片的内容',
+    '',
+    undefined,
+    2,
+    {
+      model: 'fmode-1.6-cn',
+      vision: true,
+      images: ['https://file-cloud.fmode.cn/.../test.jpg'],
+      max_tokens: 1000
+    }
+  );
+  
+  console.log('AI返回:', result);
+}
+
+testVision();
+```
+
+### 命令3:检查ProjectFile记录
+
+```javascript
+const query = new Parse.Query('ProjectFile');
+query.equalTo('category', 'ai_design_reference');
+query.descending('createdAt');
+query.limit(10);
+
+const files = await query.find();
+console.log('最近上传的文件:', files.map(f => ({
+  id: f.id,
+  name: f.get('name'),
+  url: f.get('url'),
+  createdAt: f.get('createdAt')
+})));
+```
+
+## 📞 需要提供的调试信息
+
+如果问题无法解决,请提供以下信息:
+
+1. **完整的控制台日志**
+   - 从"准备上传文件"到"AI分析完成"的所有日志
+   - 包括所有错误信息和警告
+
+2. **图片URL测试结果**
+   - 手动访问图片URL的截图
+   - 是否能直接看到图片
+
+3. **AI返回内容**
+   - 完整的AI返回文本(前500字符)
+   - 是否包含"未接收到"等关键词
+
+4. **环境信息**
+   - fmode-ng版本
+   - 浏览器版本
+   - 网络环境(是否使用代理/VPN)
+
+5. **图片信息**
+   - 文件名
+   - 文件大小
+   - 图片格式
+   - 是否包含中文字符
+
+---
+
+**更新日期**: 2025-11-27  
+**状态**: 🔧 等待用户测试反馈

+ 289 - 0
docs/employee-activation-troubleshooting.md

@@ -0,0 +1,289 @@
+# 员工激活问题诊断与修复指南
+
+## 📋 问题描述
+
+员工在企业微信端已确认身份激活,但访问项目管理页面时仍显示"身份验证激活"页面,无法正常加载项目管理功能。
+
+## 🔍 问题原因
+
+1. **Profile表中isActivated字段未正确设置**
+   - 员工首次激活时,`isActivated`字段可能未被设置或设置为`false`
+   - 数据迁移或导入时,`isActivated`字段缺失
+
+2. **缓存问题**
+   - WxworkAuth的Profile缓存未及时更新
+   - 浏览器本地缓存存储了旧的激活状态
+
+3. **userid不匹配**
+   - Profile表中的`userid`与企业微信返回的`userid`不一致
+   - 员工信息从其他系统同步时,`userid`字段未正确填写
+
+## 🛠️ 解决方案
+
+### 方案1:使用管理员修复工具(推荐)
+
+1. **访问修复工具**
+   ```
+   https://your-domain/admin/employee-activation-fix
+   ```
+
+2. **扫描所有员工**
+   - 点击 "🔍 扫描所有员工" 按钮
+   - 工具会自动扫描所有员工的激活状态
+   - 显示统计信息:总数、已激活、未激活、字段缺失
+
+3. **批量修复**
+   - 查看未激活员工列表
+   - 点击 "✅ 修复所有未激活员工" 按钮
+   - 工具会自动将所有未激活员工的`isActivated`字段设置为`true`
+
+4. **单个修复**
+   - 在员工列表中找到特定员工
+   - 点击该员工对应的 "修复" 按钮
+   - 仅修复该员工的激活状态
+
+### 方案2:手动数据库修复
+
+如果无法访问管理员工具,可以直接在Parse Dashboard中修复:
+
+1. **登录Parse Dashboard**
+   ```
+   https://your-parse-dashboard-url
+   ```
+
+2. **查找员工记录**
+   - 进入 `Profile` 表
+   - 根据姓名或userid搜索员工记录
+
+3. **修复字段**
+   - 找到目标员工记录
+   - 设置 `isActivated` 字段为 `true`
+   - 设置 `activatedAt` 字段为当前时间(如果为空)
+   - 点击保存
+
+4. **验证修复**
+   - 员工重新登录企业微信
+   - 访问项目管理页面
+   - 应该能正常显示
+
+### 方案3:强制重新激活
+
+如果以上方法都不行,让员工进行强制重新激活:
+
+1. **清除浏览器缓存**
+   ```javascript
+   // 在浏览器控制台执行
+   localStorage.clear();
+   sessionStorage.clear();
+   ```
+
+2. **重新访问激活页面**
+   ```
+   https://your-domain/wxwork/{cid}/activation
+   ```
+
+3. **完成激活流程**
+   - 填写真实姓名
+   - 选择部门和角色
+   - 点击 "确认激活"
+
+## 📊 调试步骤
+
+### 1. 检查控制台日志
+
+在浏览器开发者工具的Console中查看详细日志:
+
+```
+🔐 CustomWxworkAuthGuard 执行,当前路由: /admin/project-management
+✅ 获取用户信息成功: { name: "徐福静", userid: "..." }
+🔎 currentProfile 查询结果: { id: "...", isActivated: false }
+🔎 回退 Profile 查询结果: { userid查询: "...", 找到Profile: true, isActivated: false }
+❌ Profile存在但未激活: { profileId: "...", isActivated: false }
+⚠️ 用户未激活,跳转到激活页面
+```
+
+### 2. 关键字段检查清单
+
+在Parse Dashboard中检查以下字段:
+
+- [x] `Profile.userid` - 必须与企业微信的userid完全一致
+- [x] `Profile.isActivated` - 必须为`true`
+- [x] `Profile.isDeleted` - 必须为`false`或不存在
+- [x] `Profile.isDisabled` - 必须为`false`或不存在
+- [x] `Profile.name` 或 `Profile.realname` - 姓名不为空
+
+### 3. 测试访问
+
+修复后,测试以下路径:
+
+1. **项目管理页**
+   ```
+   /admin/project-management
+   ```
+
+2. **项目详情页**
+   ```
+   /admin/project-detail/{projectId}
+   ```
+
+3. **企微项目页**
+   ```
+   /wxwork/{cid}/project/{projectId}
+   ```
+
+## 🚀 预防措施
+
+### 1. 员工入职流程
+
+确保新员工通过以下流程激活:
+
+1. 添加到企业微信
+2. 访问激活页面并完成激活
+3. 在管理后台验证激活状态
+
+### 2. 数据同步规范
+
+从其他系统同步员工数据时:
+
+- 确保`userid`字段正确填写
+- 自动设置`isActivated = true`
+- 设置`activatedAt`为当前时间
+
+### 3. 定期检查
+
+使用修复工具定期扫描:
+
+- 每周扫描一次
+- 发现未激活员工及时处理
+- 记录异常情况
+
+## 📞 技术支持
+
+如果以上方法都无法解决问题,请联系技术支持并提供以下信息:
+
+1. 员工姓名和企业微信userid
+2. 浏览器控制台完整日志
+3. Profile表中该员工的完整记录截图
+4. 问题发生的时间和频率
+
+---
+
+## 🔧 代码修改说明
+
+本次修复包含以下代码改动:
+
+### 1. CustomWxworkAuthGuard 增强
+
+**文件**: `src/app/custom-wxwork-auth-guard.ts`
+
+**改动**:
+- 增强Profile查询日志,输出更详细的字段信息
+- 添加Profile存在但未激活的详细错误信息
+- 区分"字段缺失"和"字段为false"两种情况
+
+### 2. 员工激活修复工具
+
+**文件**: `src/app/pages/admin/employee-activation-fix/employee-activation-fix.ts`
+
+**功能**:
+- 扫描所有员工的激活状态
+- 统计已激活、未激活、字段缺失的员工数量
+- 提供批量修复和单个修复功能
+- 实时显示修复日志
+
+### 3. 路由配置
+
+**文件**: `src/app/app.routes.ts`
+
+**改动**:
+- 添加`/admin/employee-activation-fix`路由
+- 管理员可直接访问修复工具
+
+## 📝 使用示例
+
+### 示例1:批量修复所有员工
+
+```typescript
+// 访问修复工具页面
+访问: https://your-domain/admin/employee-activation-fix
+
+// 步骤:
+1. 点击 "扫描所有员工"
+2. 查看统计结果
+3. 点击 "修复所有未激活员工"
+4. 等待修复完成
+5. 验证:所有员工应该都已激活
+```
+
+### 示例2:修复特定员工(徐福静)
+
+```typescript
+// 方法1:使用修复工具
+1. 访问: https://your-domain/admin/employee-activation-fix
+2. 点击 "扫描所有员工"
+3. 在列表中找到 "徐福静"
+4. 点击对应的 "修复" 按钮
+5. 员工可以立即重新登录使用
+
+// 方法2:直接修改数据库
+1. 登录Parse Dashboard
+2. 进入Profile表
+3. 搜索 realname = "徐福静" 或 name = "徐福静"
+4. 编辑记录:
+   - isActivated = true
+   - activatedAt = 当前时间
+5. 保存
+6. 员工清除缓存后重新登录
+```
+
+### 示例3:查看详细调试信息
+
+```javascript
+// 在浏览器控制台查看日志
+// 当员工访问项目管理页面时,会自动输出:
+
+🔐 CustomWxworkAuthGuard 执行
+✅ 获取用户信息成功: {
+  name: "徐福静",
+  userid: "XuFuJing",
+  cid: "cDL6R1hgSi"
+}
+
+🔎 currentProfile 查询结果: {
+  id: "abc123",
+  name: "徐福静",
+  userid: "XuFuJing",
+  isActivated: false,  // ⚠️ 问题:这里应该是true
+  activatedAt: null
+}
+
+❌ Profile存在但未激活: {
+  profileId: "abc123",
+  isActivated: false,
+  所有字段: {
+    name: "徐福静",
+    userid: "XuFuJing",
+    roleName: "设计师",
+    isActivated: false,  // ⚠️ 需要修复
+    isDisabled: false,
+    isDeleted: false
+  }
+}
+
+⚠️ 用户未激活,跳转到激活页面
+```
+
+## ✅ 验证清单
+
+修复完成后,请验证以下项目:
+
+- [ ] 员工可以正常访问项目管理页面
+- [ ] Profile表中`isActivated`字段为`true`
+- [ ] 控制台没有激活相关的错误日志
+- [ ] 员工可以正常查看和操作项目
+- [ ] 其他页面功能正常(设计师工作台、组长看板等)
+
+---
+
+**最后更新**: 2025-11-27  
+**维护者**: 开发团队

+ 6 - 0
src/app/app.routes.ts

@@ -275,6 +275,12 @@ export const routes: Routes = [
         loadComponent: () => import('./pages/admin/employees/employees').then(m => m.Employees),
         title: '员工管理'
       },
+      // 员工激活状态修复工具
+      {
+        path: 'employee-activation-fix',
+        loadComponent: () => import('./pages/admin/employee-activation-fix/employee-activation-fix').then(m => m.EmployeeActivationFix),
+        title: '员工激活修复'
+      },
       // 客户管理
       {
         path: 'customers',

+ 39 - 3
src/app/custom-wxwork-auth-guard.ts

@@ -119,7 +119,11 @@ export const CustomWxworkAuthGuard: CanActivateFn = async (route, state) => {
     const profile = await wxAuth.currentProfile();
     console.log('🔎 currentProfile 查询结果:', {
       id: profile?.id,
-      isActivated: profile?.get?.('isActivated')
+      name: profile?.get?.('name'),
+      realname: profile?.get?.('realname'),
+      userid: profile?.get?.('userid'),
+      isActivated: profile?.get?.('isActivated'),
+      activatedAt: profile?.get?.('activatedAt')
     });
 
     if (!profile || !profile.get('isActivated')) {
@@ -128,19 +132,51 @@ export const CustomWxworkAuthGuard: CanActivateFn = async (route, state) => {
         const Parse = FmodeParse.with('nova');
         const q = new Parse.Query('Profile');
         q.equalTo('userid', userInfo.userid);
+        
+        // 🔥 强制从服务器获取最新数据,不使用缓存
         const p2 = await q.first();
+        
         console.log('🔎 回退 Profile 查询结果:', {
+          userid查询: userInfo.userid,
+          找到Profile: !!p2,
           id: p2?.id,
-          isActivated: p2?.get?.('isActivated')
+          name: p2?.get?.('name'),
+          realname: p2?.get?.('realname'),
+          userid: p2?.get?.('userid'),
+          isActivated: p2?.get?.('isActivated'),
+          activatedAt: p2?.get?.('activatedAt'),
+          createdAt: p2?.get?.('createdAt'),
+          updatedAt: p2?.get?.('updatedAt')
         });
+        
         if (p2 && p2.get('isActivated')) {
           // 已激活:执行自动登录并放行
           await wxAuth.autoLogin(userInfo);
           console.log('✅ 回退校验通过(已激活),允许访问');
           return true;
         }
+        
+        // 🔥 新增:如果Profile存在但isActivated为false/undefined,输出详细信息
+        if (p2) {
+          console.error('❌ Profile存在但未激活:', {
+            profileId: p2.id,
+            isActivated: p2.get('isActivated'),
+            '所有字段': {
+              name: p2.get('name'),
+              realname: p2.get('realname'),
+              userid: p2.get('userid'),
+              roleName: p2.get('roleName'),
+              mobile: p2.get('mobile'),
+              isActivated: p2.get('isActivated'),
+              isDisabled: p2.get('isDisabled'),
+              isDeleted: p2.get('isDeleted')
+            }
+          });
+        } else {
+          console.error('❌ 未找到Profile记录, userid:', userInfo.userid);
+        }
       } catch (e) {
-        console.warn('⚠️ 回退 Profile 查询异常:', e);
+        console.error('⚠️ 回退 Profile 查询异常:', e);
       }
 
       console.log('⚠️ 用户未激活,跳转到激活页面');

+ 519 - 0
src/app/pages/admin/employee-activation-fix/employee-activation-fix.ts

@@ -0,0 +1,519 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { FmodeParse } from 'fmode-ng/core';
+
+/**
+ * 员工激活状态修复工具
+ * 功能:
+ * 1. 检查所有员工的激活状态
+ * 2. 批量修复未正确设置isActivated的员工
+ * 3. 提供单个员工的强制激活功能
+ */
+@Component({
+  selector: 'app-employee-activation-fix',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  template: `
+    <div class="fix-container">
+      <div class="header">
+        <h2>🔧 员工激活状态修复工具</h2>
+        <p class="description">检查和修复员工的isActivated字段</p>
+      </div>
+
+      <!-- 操作按钮区 -->
+      <div class="actions">
+        <button class="btn-primary" (click)="scanAllEmployees()" [disabled]="scanning">
+          {{ scanning ? '扫描中...' : '🔍 扫描所有员工' }}
+        </button>
+        <button class="btn-success" (click)="fixAllInactive()" [disabled]="fixing || inactiveEmployees.length === 0">
+          {{ fixing ? '修复中...' : '✅ 修复所有未激活员工' }}
+        </button>
+      </div>
+
+      <!-- 统计信息 -->
+      @if (scanned) {
+        <div class="stats">
+          <div class="stat-item">
+            <div class="stat-label">总员工数</div>
+            <div class="stat-value">{{ totalCount }}</div>
+          </div>
+          <div class="stat-item success">
+            <div class="stat-label">已激活</div>
+            <div class="stat-value">{{ activatedCount }}</div>
+          </div>
+          <div class="stat-item warning">
+            <div class="stat-label">未激活</div>
+            <div class="stat-value">{{ inactiveCount }}</div>
+          </div>
+          <div class="stat-item error">
+            <div class="stat-label">字段缺失</div>
+            <div class="stat-value">{{ missingFieldCount }}</div>
+          </div>
+        </div>
+      }
+
+      <!-- 未激活员工列表 -->
+      @if (inactiveEmployees.length > 0) {
+        <div class="employee-list">
+          <h3>未激活员工列表</h3>
+          <div class="table-wrapper">
+            <table>
+              <thead>
+                <tr>
+                  <th>姓名</th>
+                  <th>UserID</th>
+                  <th>角色</th>
+                  <th>部门</th>
+                  <th>激活状态</th>
+                  <th>创建时间</th>
+                  <th>操作</th>
+                </tr>
+              </thead>
+              <tbody>
+                @for (emp of inactiveEmployees; track emp.id) {
+                  <tr>
+                    <td>{{ emp.name || emp.realname || '未知' }}</td>
+                    <td>{{ emp.userid || '-' }}</td>
+                    <td>{{ emp.roleName || '-' }}</td>
+                    <td>{{ emp.departmentName || '-' }}</td>
+                    <td>
+                      <span class="status-badge" [class.missing]="emp.isActivated === undefined">
+                        {{ emp.isActivated === undefined ? '字段缺失' : (emp.isActivated ? '已激活' : '未激活') }}
+                      </span>
+                    </td>
+                    <td>{{ emp.createdAt | date:'yyyy-MM-dd HH:mm' }}</td>
+                    <td>
+                      <button class="btn-fix" (click)="fixSingleEmployee(emp)" [disabled]="fixing">
+                        修复
+                      </button>
+                    </td>
+                  </tr>
+                }
+              </tbody>
+            </table>
+          </div>
+        </div>
+      }
+
+      <!-- 修复日志 -->
+      @if (fixLogs.length > 0) {
+        <div class="logs">
+          <h3>修复日志</h3>
+          <div class="log-list">
+            @for (log of fixLogs; track $index) {
+              <div class="log-item" [class.success]="log.success" [class.error]="!log.success">
+                <span class="log-time">{{ log.time | date:'HH:mm:ss' }}</span>
+                <span class="log-message">{{ log.message }}</span>
+              </div>
+            }
+          </div>
+        </div>
+      }
+    </div>
+  `,
+  styles: [`
+    .fix-container {
+      padding: 24px;
+      max-width: 1400px;
+      margin: 0 auto;
+    }
+
+    .header {
+      margin-bottom: 24px;
+
+      h2 {
+        margin: 0 0 8px 0;
+        color: #1f2937;
+        font-size: 24px;
+      }
+
+      .description {
+        margin: 0;
+        color: #6b7280;
+        font-size: 14px;
+      }
+    }
+
+    .actions {
+      display: flex;
+      gap: 12px;
+      margin-bottom: 24px;
+
+      button {
+        padding: 10px 20px;
+        border: none;
+        border-radius: 6px;
+        font-size: 14px;
+        font-weight: 500;
+        cursor: pointer;
+        transition: all 0.2s ease;
+
+        &:disabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+        }
+
+        &.btn-primary {
+          background: #3b82f6;
+          color: white;
+
+          &:hover:not(:disabled) {
+            background: #2563eb;
+          }
+        }
+
+        &.btn-success {
+          background: #10b981;
+          color: white;
+
+          &:hover:not(:disabled) {
+            background: #059669;
+          }
+        }
+      }
+    }
+
+    .stats {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: 16px;
+      margin-bottom: 24px;
+
+      .stat-item {
+        background: white;
+        padding: 20px;
+        border-radius: 8px;
+        border: 2px solid #e5e7eb;
+
+        &.success {
+          border-color: #10b981;
+        }
+
+        &.warning {
+          border-color: #f59e0b;
+        }
+
+        &.error {
+          border-color: #ef4444;
+        }
+
+        .stat-label {
+          font-size: 14px;
+          color: #6b7280;
+          margin-bottom: 8px;
+        }
+
+        .stat-value {
+          font-size: 32px;
+          font-weight: 600;
+          color: #1f2937;
+        }
+      }
+    }
+
+    .employee-list {
+      background: white;
+      border-radius: 8px;
+      padding: 20px;
+      margin-bottom: 24px;
+
+      h3 {
+        margin: 0 0 16px 0;
+        color: #1f2937;
+        font-size: 18px;
+      }
+
+      .table-wrapper {
+        overflow-x: auto;
+      }
+
+      table {
+        width: 100%;
+        border-collapse: collapse;
+
+        th, td {
+          padding: 12px;
+          text-align: left;
+          border-bottom: 1px solid #e5e7eb;
+        }
+
+        th {
+          background: #f9fafb;
+          font-weight: 600;
+          color: #374151;
+          font-size: 14px;
+        }
+
+        td {
+          color: #6b7280;
+          font-size: 14px;
+        }
+
+        tbody tr:hover {
+          background: #f9fafb;
+        }
+      }
+
+      .status-badge {
+        padding: 4px 8px;
+        border-radius: 4px;
+        font-size: 12px;
+        font-weight: 500;
+        background: #fef3c7;
+        color: #92400e;
+
+        &.missing {
+          background: #fee2e2;
+          color: #991b1b;
+        }
+      }
+
+      .btn-fix {
+        padding: 6px 12px;
+        border: none;
+        border-radius: 4px;
+        background: #3b82f6;
+        color: white;
+        font-size: 12px;
+        cursor: pointer;
+        transition: all 0.2s ease;
+
+        &:hover:not(:disabled) {
+          background: #2563eb;
+        }
+
+        &:disabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+        }
+      }
+    }
+
+    .logs {
+      background: white;
+      border-radius: 8px;
+      padding: 20px;
+
+      h3 {
+        margin: 0 0 16px 0;
+        color: #1f2937;
+        font-size: 18px;
+      }
+
+      .log-list {
+        max-height: 300px;
+        overflow-y: auto;
+      }
+
+      .log-item {
+        padding: 8px 12px;
+        margin-bottom: 4px;
+        border-radius: 4px;
+        font-size: 13px;
+        display: flex;
+        gap: 12px;
+
+        &.success {
+          background: #d1fae5;
+          color: #065f46;
+        }
+
+        &.error {
+          background: #fee2e2;
+          color: #991b1b;
+        }
+
+        .log-time {
+          font-weight: 600;
+        }
+      }
+    }
+  `]
+})
+export class EmployeeActivationFix implements OnInit {
+  scanning = false;
+  fixing = false;
+  scanned = false;
+
+  totalCount = 0;
+  activatedCount = 0;
+  inactiveCount = 0;
+  missingFieldCount = 0;
+
+  inactiveEmployees: any[] = [];
+  fixLogs: Array<{ time: Date; message: string; success: boolean }> = [];
+
+  Parse = FmodeParse.with('nova');
+
+  ngOnInit() {
+    console.log('🔧 员工激活状态修复工具已加载');
+  }
+
+  /**
+   * 扫描所有员工
+   */
+  async scanAllEmployees() {
+    if (this.scanning) return;
+
+    try {
+      this.scanning = true;
+      this.fixLogs = [];
+      this.inactiveEmployees = [];
+      
+      this.addLog('开始扫描所有员工...', true);
+
+      const query = new this.Parse.Query('Profile');
+      query.notEqualTo('isDeleted', true);
+      query.limit(1000);
+
+      const profiles = await query.find();
+      this.totalCount = profiles.length;
+
+      this.addLog(`找到 ${this.totalCount} 个员工记录`, true);
+
+      let activated = 0;
+      let inactive = 0;
+      let missing = 0;
+
+      for (const profile of profiles) {
+        const isActivated = profile.get('isActivated');
+        
+        if (isActivated === undefined || isActivated === null) {
+          // 字段缺失
+          missing++;
+          this.inactiveEmployees.push(this.profileToObject(profile));
+        } else if (isActivated === false) {
+          // 未激活
+          inactive++;
+          this.inactiveEmployees.push(this.profileToObject(profile));
+        } else {
+          // 已激活
+          activated++;
+        }
+      }
+
+      this.activatedCount = activated;
+      this.inactiveCount = inactive;
+      this.missingFieldCount = missing;
+      this.scanned = true;
+
+      this.addLog(`扫描完成: 已激活 ${activated}, 未激活 ${inactive}, 字段缺失 ${missing}`, true);
+
+    } catch (error: any) {
+      console.error('扫描失败:', error);
+      this.addLog(`扫描失败: ${error.message}`, false);
+    } finally {
+      this.scanning = false;
+    }
+  }
+
+  /**
+   * 修复所有未激活员工
+   */
+  async fixAllInactive() {
+    if (this.fixing || this.inactiveEmployees.length === 0) return;
+
+    try {
+      this.fixing = true;
+      this.addLog(`开始批量修复 ${this.inactiveEmployees.length} 个员工...`, true);
+
+      let successCount = 0;
+      let failCount = 0;
+
+      for (const emp of this.inactiveEmployees) {
+        try {
+          const query = new this.Parse.Query('Profile');
+          const profile = await query.get(emp.id);
+
+          profile.set('isActivated', true);
+          if (!profile.get('activatedAt')) {
+            profile.set('activatedAt', new Date());
+          }
+
+          await profile.save();
+          successCount++;
+          this.addLog(`✅ ${emp.name || emp.realname} (${emp.userid}) 修复成功`, true);
+        } catch (error: any) {
+          failCount++;
+          this.addLog(`❌ ${emp.name || emp.realname} (${emp.userid}) 修复失败: ${error.message}`, false);
+        }
+      }
+
+      this.addLog(`批量修复完成: 成功 ${successCount}, 失败 ${failCount}`, true);
+
+      // 重新扫描
+      await this.scanAllEmployees();
+
+    } catch (error: any) {
+      console.error('批量修复失败:', error);
+      this.addLog(`批量修复失败: ${error.message}`, false);
+    } finally {
+      this.fixing = false;
+    }
+  }
+
+  /**
+   * 修复单个员工
+   */
+  async fixSingleEmployee(emp: any) {
+    if (this.fixing) return;
+
+    try {
+      this.fixing = true;
+      this.addLog(`开始修复 ${emp.name || emp.realname}...`, true);
+
+      const query = new this.Parse.Query('Profile');
+      const profile = await query.get(emp.id);
+
+      profile.set('isActivated', true);
+      if (!profile.get('activatedAt')) {
+        profile.set('activatedAt', new Date());
+      }
+
+      await profile.save();
+      this.addLog(`✅ ${emp.name || emp.realname} (${emp.userid}) 修复成功`, true);
+
+      // 从列表中移除
+      this.inactiveEmployees = this.inactiveEmployees.filter(e => e.id !== emp.id);
+      this.inactiveCount--;
+      this.activatedCount++;
+
+    } catch (error: any) {
+      console.error('修复失败:', error);
+      this.addLog(`❌ 修复失败: ${error.message}`, false);
+    } finally {
+      this.fixing = false;
+    }
+  }
+
+  /**
+   * 将Profile转换为简单对象
+   */
+  private profileToObject(profile: any): any {
+    return {
+      id: profile.id,
+      name: profile.get('name'),
+      realname: profile.get('realname'),
+      userid: profile.get('userid'),
+      roleName: profile.get('roleName'),
+      departmentName: profile.get('department')?.get?.('name') || profile.get('departmentName'),
+      isActivated: profile.get('isActivated'),
+      activatedAt: profile.get('activatedAt'),
+      createdAt: profile.get('createdAt'),
+      updatedAt: profile.get('updatedAt')
+    };
+  }
+
+  /**
+   * 添加日志
+   */
+  private addLog(message: string, success: boolean) {
+    this.fixLogs.unshift({
+      time: new Date(),
+      message,
+      success
+    });
+    console.log(success ? '✅' : '❌', message);
+  }
+}

+ 61 - 19
src/modules/project/components/project-files-modal/project-files-modal.component.ts

@@ -83,25 +83,67 @@ export class ProjectFilesModalComponent implements OnInit {
       const results = await query.find();
 
       this.files = results.map((file: FmodeObject) => {
-        let attach = file?.get("attach")
-        return {
-        id: file.id || '',
-        name: attach.get('name') || attach.get('originalName') || '',
-        originalName: attach.get('originalName') || '',
-        url: attach.get('url') || '',
-        key: file.get('key') || '',
-        type: attach.get('mime') || '',
-        mime: attach.get('mime') || '',
-        size: attach.get('size') || 0,
-        uploadedBy: file.get('uploadedBy')?.toJSON?.() || '',
-        uploadedAt: file.get('uploadedAt') || file.createdAt,
-        source: attach.get('source') || 'unknown',
-        md5: attach.get('md5'),
-        metadata: attach.get('metadata'),
-        description: attach.get('description') || ''
-      }
-      
-    });
+        let attach = file?.get("attach");
+        
+        // 兼容新旧两种ProjectFile结构
+        // 新结构:使用attach字段包含文件信息
+        // 旧结构:直接在ProjectFile上有name、url等字段
+        if (attach && typeof attach === 'object' && attach.get) {
+          // 新结构:有attach字段(Parse Object)
+          return {
+            id: file.id || '',
+            name: attach.get('name') || attach.get('originalName') || '',
+            originalName: attach.get('originalName') || '',
+            url: attach.get('url') || '',
+            key: file.get('key') || '',
+            type: attach.get('mime') || '',
+            mime: attach.get('mime') || '',
+            size: attach.get('size') || 0,
+            uploadedBy: file.get('uploadedBy')?.toJSON?.() || '',
+            uploadedAt: file.get('uploadedAt') || file.createdAt,
+            source: attach.get('source') || 'unknown',
+            md5: attach.get('md5'),
+            metadata: attach.get('metadata'),
+            description: attach.get('description') || ''
+          };
+        } else if (attach && typeof attach === 'object') {
+          // 新结构:attach是普通对象(非Parse Object)
+          return {
+            id: file.id || '',
+            name: attach.name || attach.originalName || '',
+            originalName: attach.originalName || '',
+            url: attach.url || '',
+            key: file.get('key') || '',
+            type: attach.mime || '',
+            mime: attach.mime || '',
+            size: attach.size || 0,
+            uploadedBy: file.get('uploadedBy')?.toJSON?.() || '',
+            uploadedAt: file.get('uploadedAt') || file.createdAt,
+            source: attach.source || 'unknown',
+            md5: attach.md5,
+            metadata: attach.metadata,
+            description: attach.description || ''
+          };
+        } else {
+          // 旧结构:直接在ProjectFile上有字段
+          return {
+            id: file.id || '',
+            name: file.get('name') || file.get('originalName') || '',
+            originalName: file.get('originalName') || '',
+            url: file.get('url') || '',
+            key: file.get('key') || '',
+            type: file.get('type') || file.get('mime') || '',
+            mime: file.get('mime') || file.get('type') || '',
+            size: file.get('size') || 0,
+            uploadedBy: file.get('uploadedBy')?.toJSON?.() || '',
+            uploadedAt: file.get('uploadedAt') || file.createdAt,
+            source: file.get('source') || 'unknown',
+            md5: file.get('md5'),
+            metadata: file.get('metadata'),
+            description: file.get('description') || ''
+          };
+        }
+      });
 
       this.calculateStats();
       console.log(`✅ 加载了 ${this.files.length} 个项目文件`);

+ 108 - 91
src/modules/project/pages/project-detail/stages/stage-requirements.component.html

@@ -267,10 +267,10 @@
                       <!-- 左侧按钮组 -->
                       <div class="input-actions-left">
                         <button class="action-btn" title="上传附件" [disabled]="aiDesignAnalyzing" (click)="openAttachmentDialog()">
-                          <ion-icon name="image"></ion-icon>
+                          <span class="icon-text">📷</span>
                         </button>
                         <button class="action-btn" title="语音输入" [disabled]="aiDesignAnalyzing" (click)="startVoiceInput()">
-                          <ion-icon name="mic"></ion-icon>
+                          <span class="icon-text">🎤</span>
                         </button>
                       </div>
                       
@@ -315,120 +315,137 @@
             <div class="analysis-result-section">
               <div class="result-header">
                 <div class="header-icon">✨</div>
-                <h3>AI分析结果</h3>
+                <h3>AI设计分析结果</h3>
                 <button class="btn-reset" (click)="resetAIAnalysis()">重新分析</button>
               </div>
 
-              <!-- 场景识别 -->
-              @if (aiDesignAnalysisResult.sceneRecognition) {
-                <div class="result-card">
+              <!-- 简洁摘要卡片 -->
+              @if (getAISummary()) {
+                <div class="result-card summary-card">
                   <div class="card-title">
-                    <span class="title-icon">{{ getSpaceToneIcon(aiDesignAnalysisResult.sceneRecognition.overallTone) }}</span>
-                    <h4>场景识别</h4>
+                    <span class="title-icon">📋</span>
+                    <h4>设计概要</h4>
                   </div>
                   <div class="card-content">
-                    <div class="info-row">
-                      <span class="label">空间类型:</span>
-                      <span class="value">{{ getSceneTypeName(aiDesignAnalysisResult.sceneRecognition.spaceType) }}</span>
-                    </div>
-                    <div class="info-row">
-                      <span class="label">整体基调:</span>
-                      <span class="value">{{ aiDesignAnalysisResult.sceneRecognition.overallTone }}</span>
-                    </div>
-                    <div class="info-row">
-                      <span class="label">识别置信度:</span>
-                      <span class="value">{{ aiDesignAnalysisResult.sceneRecognition.confidence }}%</span>
-                    </div>
+                    <p class="summary-text">{{ getAISummary() }}</p>
                   </div>
                 </div>
               }
 
-              <!-- 空间区域分析 -->
-              @if (aiDesignAnalysisResult.spaceRegionAnalysis) {
-                <div class="result-card">
+              <!-- 完整分析内容 -->
+              @if (aiDesignAnalysisResult.formattedContent || aiDesignAnalysisResult.rawContent) {
+                <div class="result-card full-analysis-card">
                   <div class="card-title">
-                    <span class="title-icon">🏛️</span>
-                    <h4>空间区域分析</h4>
+                    <span class="title-icon">📊</span>
+                    <h4>详细分析</h4>
                   </div>
-                  <div class="card-content">
-                    <div class="region-breakdown">
-                      <div class="breakdown-item">
-                        <span class="label">顶面:</span>
-                        <span class="percentage">{{ aiDesignAnalysisResult.spaceRegionAnalysis.percentageBreakdown?.ceiling || 0 }}%</span>
-                      </div>
-                      <div class="breakdown-item">
-                        <span class="label">墙面:</span>
-                        <span class="percentage">{{ aiDesignAnalysisResult.spaceRegionAnalysis.percentageBreakdown?.wall || 0 }}%</span>
-                      </div>
-                      <div class="breakdown-item">
-                        <span class="label">地面:</span>
-                        <span class="percentage">{{ aiDesignAnalysisResult.spaceRegionAnalysis.percentageBreakdown?.floor || 0 }}%</span>
-                      </div>
-                      <div class="breakdown-item">
-                        <span class="label">门窗:</span>
-                        <span class="percentage">{{ aiDesignAnalysisResult.spaceRegionAnalysis.percentageBreakdown?.doorWindow || 0 }}%</span>
-                      </div>
-                      <div class="breakdown-item">
-                        <span class="label">家具:</span>
-                        <span class="percentage">{{ aiDesignAnalysisResult.spaceRegionAnalysis.percentageBreakdown?.furniture || 0 }}%</span>
-                      </div>
-                    </div>
+                  <div class="card-content analysis-content">
+                    <pre class="analysis-text">{{ aiDesignAnalysisResult.formattedContent || aiDesignAnalysisResult.rawContent }}</pre>
                   </div>
                 </div>
               }
 
-              <!-- 灯光系统详解 -->
-              @if (aiDesignAnalysisResult.lightingSystemDetailed) {
-                <div class="result-card">
+              <!-- 按维度查看(可选) -->
+              @if (aiDesignAnalysisResult.structuredData) {
+                <div class="result-card dimensions-card">
                   <div class="card-title">
-                    <span class="title-icon">💡</span>
-                    <h4>灯光系统详解</h4>
+                    <span class="title-icon">🔍</span>
+                    <h4>分维度查看</h4>
                   </div>
                   <div class="card-content">
-                    <div class="lighting-row">
-                      <span class="label">自然光:</span>
-                      <span class="value">{{ aiDesignAnalysisResult.lightingSystemDetailed.naturalLight?.illuminance || '分析中' }}</span>
-                    </div>
-                    <div class="lighting-row">
-                      <span class="label">主照明:</span>
-                      <span class="value">{{ aiDesignAnalysisResult.lightingSystemDetailed.mainLighting?.fixtureType || '分析中' }}</span>
-                    </div>
-                    <div class="lighting-row">
-                      <span class="label">色温:</span>
-                      <span class="value">{{ aiDesignAnalysisResult.lightingSystemDetailed.mainLighting?.colorTemperature || '分析中' }}</span>
-                    </div>
-                    <div class="lighting-row">
-                      <span class="label">光质特征:</span>
-                      <span class="value">{{ aiDesignAnalysisResult.lightingSystemDetailed.lightQuality || '分析中' }}</span>
-                    </div>
-                  </div>
-                </div>
-              }
+                    <div class="dimensions-grid">
+                      <!-- 空间定位 -->
+                      @if (aiDesignAnalysisResult.structuredData.spacePositioning) {
+                        <div class="dimension-item">
+                          <div class="dimension-header" (click)="toggleDimension('spacePositioning')">
+                            <span class="dimension-icon">🏠</span>
+                            <span class="dimension-title">空间定位与场景属性</span>
+                            <span class="toggle-icon">{{ expandedDimensions.has('spacePositioning') ? '▼' : '▶' }}</span>
+                          </div>
+                          @if (expandedDimensions.has('spacePositioning')) {
+                            <div class="dimension-content">
+                              <p>{{ aiDesignAnalysisResult.structuredData.spacePositioning }}</p>
+                            </div>
+                          }
+                        </div>
+                      }
 
-              <!-- 色彩占比与权重 -->
-              @if (aiDesignAnalysisResult.colorProportionWeight && aiDesignAnalysisResult.colorProportionWeight.length > 0) {
-                <div class="result-card">
-                  <div class="card-title">
-                    <span class="title-icon">🎨</span>
-                    <h4>色彩占比与视觉权重</h4>
-                  </div>
-                  <div class="card-content">
-                    @for (color of aiDesignAnalysisResult.colorProportionWeight; track color.colorCategory) {
-                      <div class="color-weight-item">
-                        <div class="color-category">{{ color.colorCategory }}</div>
-                        <div class="color-details">
-                          <span class="rgb-range">RGB: {{ color.rgbRange }}</span>
-                          <span class="percentage">占比: {{ color.spacePercentage }}%</span>
-                          <span class="weight">{{ color.visualWeight }}</span>
+                      <!-- 色调分析 -->
+                      @if (aiDesignAnalysisResult.structuredData.colorAnalysis) {
+                        <div class="dimension-item">
+                          <div class="dimension-header" (click)="toggleDimension('colorAnalysis')">
+                            <span class="dimension-icon">🎨</span>
+                            <span class="dimension-title">色调精准分析</span>
+                            <span class="toggle-icon">{{ expandedDimensions.has('colorAnalysis') ? '▼' : '▶' }}</span>
+                          </div>
+                          @if (expandedDimensions.has('colorAnalysis')) {
+                            <div class="dimension-content">
+                              <p>{{ aiDesignAnalysisResult.structuredData.colorAnalysis }}</p>
+                            </div>
+                          }
                         </div>
-                      </div>
-                    }
+                      }
+
+                      <!-- 材质解析 -->
+                      @if (aiDesignAnalysisResult.structuredData.materials) {
+                        <div class="dimension-item">
+                          <div class="dimension-header" (click)="toggleDimension('materials')">
+                            <span class="dimension-icon">🪨</span>
+                            <span class="dimension-title">材质应用解析</span>
+                            <span class="toggle-icon">{{ expandedDimensions.has('materials') ? '▼' : '▶' }}</span>
+                          </div>
+                          @if (expandedDimensions.has('materials')) {
+                            <div class="dimension-content">
+                              <p>{{ aiDesignAnalysisResult.structuredData.materials }}</p>
+                            </div>
+                          }
+                        </div>
+                      }
+
+                      <!-- 风格氛围 -->
+                      @if (aiDesignAnalysisResult.structuredData.style) {
+                        <div class="dimension-item">
+                          <div class="dimension-header" (click)="toggleDimension('style')">
+                            <span class="dimension-icon">✨</span>
+                            <span class="dimension-title">风格与氛围营造</span>
+                            <span class="toggle-icon">{{ expandedDimensions.has('style') ? '▼' : '▶' }}</span>
+                          </div>
+                          @if (expandedDimensions.has('style')) {
+                            <div class="dimension-content">
+                              <p>{{ aiDesignAnalysisResult.structuredData.style }}</p>
+                            </div>
+                          }
+                        </div>
+                      }
+
+                      <!-- 优化建议 -->
+                      @if (aiDesignAnalysisResult.structuredData.suggestions) {
+                        <div class="dimension-item">
+                          <div class="dimension-header" (click)="toggleDimension('suggestions')">
+                            <span class="dimension-icon">💡</span>
+                            <span class="dimension-title">专业优化建议</span>
+                            <span class="toggle-icon">{{ expandedDimensions.has('suggestions') ? '▼' : '▶' }}</span>
+                          </div>
+                          @if (expandedDimensions.has('suggestions')) {
+                            <div class="dimension-content">
+                              <p>{{ aiDesignAnalysisResult.structuredData.suggestions }}</p>
+                            </div>
+                          }
+                        </div>
+                      }
+                    </div>
                   </div>
                 </div>
               }
 
-              <!-- 生成报告按钮 -->
+              <!-- 生成客服标注按钮 -->
               <div class="action-section">
+                <button
+                  class="btn btn-outline btn-generate"
+                  (click)="generateServiceNotes()">
+                  <ion-icon name="document-text"></ion-icon>
+                  <span>生成客服标注</span>
+                </button>
                 <button
                   class="btn btn-primary btn-generate"
                   (click)="generateClientReport()"
@@ -528,7 +545,7 @@
                           <ion-icon name="sparkles"></ion-icon>
                         </button>
                         <button class="btn-icon-small btn-edit" title="编辑特殊要求" (click)="toggleSpaceExpansion(space.id); $event.stopPropagation()">
-                          <ion-icon name="create"></ion-icon>
+                          <span class="icon-text">✏️</span>
                         </button>
                       }
                     </div>

+ 129 - 5
src/modules/project/pages/project-detail/stages/stage-requirements.component.scss

@@ -1191,6 +1191,12 @@
               justify-content: center;
               flex-shrink: 0;
 
+              .icon-text {
+                font-size: 16px;
+                line-height: 1;
+                display: block;
+              }
+
               ion-icon {
                 font-size: 16px;
               }
@@ -4133,17 +4139,25 @@
                 border: none;
                 border-radius: 6px;
                 background: rgba(102, 126, 234, 0.1);
-                color: #667eea;
                 cursor: pointer;
                 display: flex;
                 align-items: center;
                 justify-content: center;
                 transition: all 0.2s ease;
+                position: relative;
 
-                ion-icon {
+                .icon-text {
                   font-size: 18px;
-                  color: #667eea;
-                  --ionicon-stroke-width: 48px;
+                  line-height: 1;
+                  display: block;
+                }
+
+                ion-icon {
+                  font-size: 20px;
+                  color: #667eea !important;
+                  display: block;
+                  width: 20px;
+                  height: 20px;
                 }
 
                 &:hover:not(:disabled) {
@@ -4151,7 +4165,7 @@
                   transform: scale(1.05);
                   
                   ion-icon {
-                    color: #5568d3;
+                    color: #5568d3 !important;
                   }
                 }
 
@@ -4162,6 +4176,15 @@
                 &:disabled {
                   opacity: 0.4;
                   cursor: not-allowed;
+                  
+                  .icon-text {
+                    opacity: 0.6;
+                    filter: grayscale(1);
+                  }
+                  
+                  ion-icon {
+                    opacity: 0.5;
+                  }
                 }
               }
             }
@@ -4489,6 +4512,107 @@
           }
         }
       }
+
+      // 简洁摘要卡片特殊样式
+      &.summary-card {
+        background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+        border-color: #7dd3fc;
+
+        .summary-text {
+          font-size: 15px;
+          line-height: 1.8;
+          color: #0c4a6e;
+          margin: 0;
+          font-weight: 500;
+        }
+      }
+
+      // 完整分析卡片
+      &.full-analysis-card {
+        .analysis-content {
+          max-height: 600px;
+          overflow-y: auto;
+
+          .analysis-text {
+            font-size: 14px;
+            line-height: 2;
+            color: #1e293b;
+            white-space: pre-wrap;
+            word-wrap: break-word;
+            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
+            margin: 0;
+            padding: 16px;
+            background: #f8fafc;
+            border-radius: 8px;
+          }
+        }
+      }
+
+      // 维度查看卡片
+      &.dimensions-card {
+        .dimensions-grid {
+          display: flex;
+          flex-direction: column;
+          gap: 12px;
+
+          .dimension-item {
+            border: 1px solid #e2e8f0;
+            border-radius: 8px;
+            overflow: hidden;
+            transition: all 0.2s;
+
+            &:hover {
+              border-color: #94a3b8;
+            }
+
+            .dimension-header {
+              display: flex;
+              align-items: center;
+              gap: 12px;
+              padding: 14px 16px;
+              background: #f8fafc;
+              cursor: pointer;
+              transition: background 0.2s;
+
+              &:hover {
+                background: #f1f5f9;
+              }
+
+              .dimension-icon {
+                font-size: 20px;
+              }
+
+              .dimension-title {
+                flex: 1;
+                font-size: 15px;
+                font-weight: 600;
+                color: #334155;
+              }
+
+              .toggle-icon {
+                font-size: 12px;
+                color: #64748b;
+                transition: transform 0.2s;
+              }
+            }
+
+            .dimension-content {
+              padding: 16px;
+              background: white;
+              border-top: 1px solid #e2e8f0;
+
+              p {
+                margin: 0;
+                font-size: 14px;
+                line-height: 1.8;
+                color: #475569;
+                white-space: pre-wrap;
+                word-wrap: break-word;
+              }
+            }
+          }
+        }
+      }
   }
 
   // 报告区域

+ 262 - 23
src/modules/project/pages/project-detail/stages/stage-requirements.component.ts

@@ -15,6 +15,9 @@ import { add, chevronDown, colorPalette, send, sparkles, trash } from 'ionicons/
 import { generatePhaseDeadlines } from '../../../../../app/utils/phase-deadline.utils';
 import { addDays, normalizeDateInput } from '../../../../../app/utils/date-utils';
 
+// 初始化Parse对象
+const Parse = FmodeParse.with('nova');
+
 addIcons({
   add,sparkles,colorPalette,trash,chevronDown,send
 })
@@ -222,6 +225,7 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
   }> = [];
   aiChatInput: string = '';
   deepThinkingEnabled: boolean = false;
+  expandedDimensions: Set<string> = new Set(); // 管理维度折叠状态
   @ViewChild('chatMessagesWrapper') chatMessagesWrapper!: ElementRef;
   @ViewChild('chatInput') chatInputElement!: ElementRef;
   
@@ -3065,10 +3069,117 @@ ${context}
     this.aiDesignReportConfirmed = false;
     this.aiDesignAnalyzing = false;
     this.aiDesignGeneratingReport = false;
+    this.expandedDimensions.clear(); // 清空折叠状态
     this.cdr.markForCheck();
     console.log('🔄 重置AI分析');
   }
 
+  /**
+   * 获取AI简洁摘要
+   */
+  getAISummary(): string {
+    if (!this.aiDesignAnalysisResult) {
+      return '';
+    }
+    
+    // 使用AI服务生成简洁摘要
+    return this.designAnalysisAIService.generateBriefSummary(this.aiDesignAnalysisResult);
+  }
+
+  /**
+   * 切换维度折叠状态
+   */
+  toggleDimension(dimensionKey: string): void {
+    if (this.expandedDimensions.has(dimensionKey)) {
+      this.expandedDimensions.delete(dimensionKey);
+    } else {
+      this.expandedDimensions.add(dimensionKey);
+    }
+    this.cdr.markForCheck();
+  }
+
+  /**
+   * 生成客服标注
+   */
+  async generateServiceNotes(): Promise<void> {
+    if (!this.aiDesignAnalysisResult) {
+      window?.fmode?.alert('请先完成AI分析');
+      return;
+    }
+
+    try {
+      // 使用AI服务生成客服标注格式
+      const serviceNotes = this.designAnalysisAIService.generateCustomerServiceNotes(
+        this.aiDesignAnalysisResult,
+        this.aiDesignTextDescription
+      );
+
+      console.log('📋 生成的客服标注:', serviceNotes);
+
+      // 保存到项目data
+      const projectData = this.project.get('data') || {};
+      
+      if (!projectData.designReports) {
+        projectData.designReports = {};
+      }
+
+      if (!projectData.designReports[this.aiDesignCurrentSpace.id]) {
+        projectData.designReports[this.aiDesignCurrentSpace.id] = {};
+      }
+
+      projectData.designReports[this.aiDesignCurrentSpace.id].serviceNotes = serviceNotes;
+      projectData.designReports[this.aiDesignCurrentSpace.id].serviceNotesGeneratedAt = new Date();
+
+      this.project.set('data', projectData);
+      await this.project.save();
+
+      // 自动复制到剪贴板
+      const copied = await this.copyToClipboard(serviceNotes);
+      
+      // 显示客服标注
+      const message = `客服标注已生成${copied ? '并复制到剪贴板' : ''}!\n\n${serviceNotes}`;
+      window?.fmode?.alert(message);
+      
+      console.log('✅ 客服标注已生成并保存');
+
+      this.cdr.markForCheck();
+    } catch (error: any) {
+      console.error('❌ 生成客服标注失败:', error);
+      window?.fmode?.alert('生成失败: ' + (error.message || '未知错误'));
+    }
+  }
+
+  /**
+   * 复制到剪贴板
+   */
+  private async copyToClipboard(text: string): Promise<boolean> {
+    try {
+      await navigator.clipboard.writeText(text);
+      console.log('✅ 已复制到剪贴板');
+      return true;
+    } catch (error) {
+      console.warn('⚠️ 复制失败,使用备用方案');
+      try {
+        // 备用方案
+        const textarea = document.createElement('textarea');
+        textarea.value = text;
+        textarea.style.position = 'fixed';
+        textarea.style.opacity = '0';
+        document.body.appendChild(textarea);
+        textarea.select();
+        const success = document.execCommand('copy');
+        document.body.removeChild(textarea);
+        if (success) {
+          console.log('✅ 备用方案复制成功');
+        }
+        return success;
+      } catch (fallbackError) {
+        console.error('❌ 复制失败:', fallbackError);
+        return false;
+      }
+    }
+  }
+
   /**
    * 触发AI对话框文件选择
    */
@@ -3205,6 +3316,9 @@ ${context}
         }
 
         // 上传文件
+        console.log(`📤 准备上传文件: ${file.name}, 大小: ${(file.size / 1024 / 1024).toFixed(2)}MB`);
+        console.log(`📂 上传路径: ai-design-analysis/${this.projectId}`);
+        
         const uploadedFile = await storage.upload(file, {
           prefixKey: `ai-design-analysis/${this.projectId}`,
           onProgress: (progress: { total: { percent: number } }) => {
@@ -3212,23 +3326,105 @@ ${context}
           }
         });
 
-        // 保存文件信息
-        this.aiDesignUploadedImages.push(uploadedFile.url);
-        this.aiDesignUploadedFiles.push({
-          url: uploadedFile.url,
-          name: file.name,
-          type: file.type,
-          size: file.size,
-          extension: fileExt
-        });
+        console.log(`✅ 文件上传成功: ${uploadedFile.url}`);
+        
+        // 🔥 验证上传后的URL是否有效
+        if (!uploadedFile.url || (!uploadedFile.url.startsWith('http://') && !uploadedFile.url.startsWith('https://'))) {
+          console.error('❌ 上传返回的URL无效:', uploadedFile.url);
+          window?.fmode?.alert(`文件上传失败: ${file.name}\n返回的URL无效,请重试`);
+          continue;
+        }
+        
+        // 🔥 创建ProjectFile记录,保存图片到数据库(匹配现有ProjectFile结构)
+        try {
+          const ProjectFile = Parse.Object.extend('ProjectFile');
+          const projectFile = new ProjectFile();
+          
+          projectFile.set('project', {
+            __type: 'Pointer',
+            className: 'Project',
+            objectId: this.projectId
+          });
+          
+          // 🔥 使用attach字段包含文件信息(匹配现有结构)
+          projectFile.set('attach', {
+            name: file.name,
+            originalName: file.name,
+            url: uploadedFile.url,
+            mime: file.type,
+            size: file.size,
+            source: 'ai_design_upload',
+            description: 'AI设计分析参考图'
+          });
+          
+          projectFile.set('key', `ai-design-analysis/${this.projectId}/${file.name}`);
+          projectFile.set('uploadedAt', new Date());
+          projectFile.set('category', 'ai_design_reference'); // 标记为AI设计参考图
+          
+          // 如果有关联空间,保存关联
+          if (this.aiDesignCurrentSpace?.id) {
+            projectFile.set('product', {
+              __type: 'Pointer',
+              className: 'Product',
+              objectId: this.aiDesignCurrentSpace.id
+            });
+            
+            projectFile.set('data', {
+              spaceId: this.aiDesignCurrentSpace.id,
+              spaceName: this.aiDesignCurrentSpace.name,
+              uploadedFor: 'ai_design_analysis',
+              uploadedAt: new Date().toISOString()
+            });
+          }
+          
+          await projectFile.save();
+          console.log(`💾 ProjectFile记录已创建: ${projectFile.id}`);
+          console.log(`📂 文件结构: attach字段包含文件信息`);
+          
+          // 保存文件信息(包含ProjectFile ID)
+          this.aiDesignUploadedImages.push(uploadedFile.url);
+          this.aiDesignUploadedFiles.push({
+            url: uploadedFile.url,
+            name: file.name,
+            type: file.type,
+            size: file.size,
+            extension: fileExt,
+            projectFileId: projectFile.id // 🔥 保存ProjectFile ID
+          });
+          
+        } catch (saveError) {
+          console.error('❌ 创建ProjectFile记录失败:', saveError);
+          // 即使保存失败,也保留URL,允许继续分析
+          this.aiDesignUploadedImages.push(uploadedFile.url);
+          this.aiDesignUploadedFiles.push({
+            url: uploadedFile.url,
+            name: file.name,
+            type: file.type,
+            size: file.size,
+            extension: fileExt
+          });
+        }
       }
 
       this.cdr.markForCheck();
       console.log(`✅ 已上传${this.aiDesignUploadedImages.length}个文件`);
 
-    } catch (error) {
-      console.error('上传失败:', error);
-      window?.fmode?.alert('文件上传失败,请重试');
+    } catch (error: any) {
+      console.error('❌ 上传失败,详细错误:', error);
+      console.error('❌ 错误类型:', error?.constructor?.name);
+      console.error('❌ 错误消息:', error?.message);
+      console.error('❌ 错误代码:', error?.code || error?.status);
+      console.error('❌ 完整错误对象:', JSON.stringify(error, null, 2));
+      
+      // 根据错误类型提供更明确的提示
+      let errorMessage = '文件上传失败';
+      if (error?.status === 631 || error?.code === 631) {
+        errorMessage = '存储服务错误(631)。可能原因:\n1. 存储配额已满\n2. 项目ID无效\n3. 存储权限不足\n\n请联系管理员检查存储配置';
+      } else if (error?.message) {
+        errorMessage = `上传失败: ${error.message}`;
+      }
+      
+      window?.fmode?.alert(errorMessage);
     } finally {
       this.aiDesignUploading = false;
       this.cdr.markForCheck();
@@ -3244,13 +3440,39 @@ ${context}
       return;
     }
 
-    // 如果已有对话记录,提示用户使用对话输入
-    if (this.aiChatMessages.length > 0) {
-      window?.fmode?.alert('请在对话框中输入您的需求');
+    // 🔥 关键检查:验证图片URL是否有效(必须是完整的HTTP/HTTPS URL)
+    const validImages = this.aiDesignUploadedImages.filter(url => {
+      const isValid = url && (url.startsWith('http://') || url.startsWith('https://'));
+      if (!isValid) {
+        console.warn('⚠️ 无效的图片URL:', url);
+      }
+      return isValid;
+    });
+
+    if (validImages.length === 0) {
+      window?.fmode?.alert('图片上传失败,请重新上传。\n提示:确保图片格式为JPG/PNG,大小不超过50MB');
+      // 清空无效的图片列表
+      this.aiDesignUploadedImages = [];
+      this.aiDesignUploadedFiles = [];
+      this.cdr.markForCheck();
       return;
     }
 
+    if (validImages.length < this.aiDesignUploadedImages.length) {
+      console.warn(`⚠️ 发现${this.aiDesignUploadedImages.length - validImages.length}个无效图片URL,已自动过滤`);
+      this.aiDesignUploadedImages = validImages;
+    }
+
+    console.log('✅ 验证通过,有效图片数量:', validImages.length);
+    console.log('📸 有效图片URL列表:', validImages);
+
     try {
+      // 🔥 支持多轮对话:如果已有对话记录,将新上传的图片作为补充分析
+      const isFollowUp = this.aiChatMessages.length > 0;
+      
+      if (isFollowUp) {
+        console.log('📌 检测到已有对话记录,将作为补充分析');
+      }
       // 添加用户消息(简短描述,不显示完整提示词)
       const userMessage = {
         id: `user-${Date.now()}`,
@@ -3278,16 +3500,26 @@ ${context}
       // 滚动到底部
       this.scrollToBottom();
 
-      // 直接调用AI分析服务(使用空的对话历史,首次分析)
+      // 🔥 构建对话历史(排除当前正在添加的消息和流式输出中的消息)
+      const conversationHistory = this.aiChatMessages
+        .filter(m => m.id !== userMessage.id && m.id !== aiStreamMessage.id && !m.isStreaming)
+        .map(m => ({
+          role: m.role,
+          content: m.content || ''
+        }));
+
+      // 直接调用AI分析服务
       console.log('🤖 开始AI图片分析...');
       console.log('📸 图片数量:', this.aiDesignUploadedImages.length);
       console.log('🏠 空间类型:', this.aiDesignCurrentSpace?.name);
+      console.log('💬 对话历史数量:', conversationHistory.length, '条');
 
       const analysisResult = await this.designAnalysisAIService.analyzeReferenceImages({
         images: this.aiDesignUploadedImages,
         textDescription: this.aiDesignTextDescription,
-        spaceType: this.aiDesignCurrentSpace?.name || '',
-        conversationHistory: [], // 首次分析,不传递历史
+        // 🔥 不传递spaceType,让AI基于图片内容自动识别空间类型
+        spaceType: undefined,
+        conversationHistory: conversationHistory, // 🔥 传递对话历史,支持多轮对话
         deepThinking: this.deepThinkingEnabled,
         onProgressChange: (progress) => {
           console.log('📊 AI分析进度:', progress);
@@ -3550,23 +3782,30 @@ ${context}
       // 滚动到底部
       this.scrollToBottom();
 
-      // 构建对话历史
+      // 🔥 构建对话历史(排除当前消息和流式输出中的消息)
       const conversationHistory = this.aiChatMessages
-        .filter(m => !m.isLoading && !m.isStreaming && m.role !== 'assistant' || (m.role === 'assistant' && m.content))
+        .filter(m => 
+          m.id !== userMessage.id && 
+          m.id !== aiStreamMessage.id && 
+          !m.isStreaming && 
+          m.content && 
+          m.content.trim().length > 0
+        )
         .map(m => ({
           role: m.role,
-          content: m.content
+          content: m.content || ''
         }));
 
       // 调用AI分析
       console.log('🤖 开始AI对话分析...');
-      console.log('💬 对话历史:', conversationHistory);
+      console.log('💬 对话历史数量:', conversationHistory.length, '条');
       console.log('💡 深度思考模式:', this.deepThinkingEnabled);
 
       const analysisResult = await this.designAnalysisAIService.analyzeReferenceImages({
         images: this.aiDesignUploadedImages,
         textDescription: message,
-        spaceType: this.aiDesignCurrentSpace?.name || '',
+        // 🔥 不传递spaceType,让AI基于图片内容和对话上下文进行判断
+        spaceType: undefined,
         conversationHistory: conversationHistory,
         deepThinking: this.deepThinkingEnabled,
         onProgressChange: (progress) => {

+ 580 - 143
src/modules/project/services/design-analysis-ai.service.ts

@@ -1,6 +1,6 @@
 import { Injectable } from '@angular/core';
 import { FmodeParse } from 'fmode-ng/core';
-import { FmodeChatCompletion } from 'fmode-ng/core/agent/chat/completion';
+import { FmodeChatCompletion, completionJSON } from 'fmode-ng/core/agent/chat/completion';
 
 const Parse = FmodeParse.with('nova');
 
@@ -43,17 +43,12 @@ export class DesignAnalysisAIService {
 
         options.onProgressChange?.('正在识别场景和分析设计维度...');
 
-        // 使用FmodeChatCompletion进行流式输出
-        const messageList = [
-          {
-            role: 'user',
-            content: prompt,
-            images: options.images // 添加图片
-          }
-        ];
+        // 🔥 直接使用图片URL,不转base64(参考ai-k12-daofa的实现)
+        console.log('📸 准备传入图片URL到AI模型...');
+        console.log('📸 图片URL列表:', options.images);
 
         // 日志输出,帮助调试
-        console.log('🤖 调用豆包1.6模型...');
+        console.log('🤖 调用豆包1.6模型进行vision分析...');
         console.log('📸 图片数量:', options.images.length);
         console.log('📝 提示词长度:', prompt.length, '字符');
         console.log('🏠 空间类型:', options.spaceType);
@@ -64,74 +59,154 @@ export class DesignAnalysisAIService {
           console.warn('⚠️ 提示词过长,可能导致API调用失败');
         }
 
-        const completion = new FmodeChatCompletion(messageList, {
-          model: this.AI_MODEL,
-          max_tokens: 8000, // 支持长文本输出
-        });
-
-        let fullContent = '';
-        const subscription = completion.sendCompletion({
-          isDirect: true,
-        }).subscribe({
-          next: async (message: any) => {
-            const content = message?.content || '';
-            fullContent = content;
-
-            // 🔥 流式输出:每次接收到新内容就立即回调
-            if (content && content.length > 0) {
-              options.onContentStream?.(content);
-            }
-
-            if (options.loading) {
-              options.loading.message = '正在分析设计细节...' + content.length;
+        // 🔥 使用completionJSON进行vision分析(严格参考ai-k12-daofa的成功实现)
+        console.log('🚀 开始调用completionJSON进行vision分析...');
+        
+        // 定义JSON schema(与提示词中的JSON格式完全一致)
+        const outputSchema = `{
+  "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转为易读文本
+                const 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
             }
-            options.onProgressChange?.('正在分析设计细节...');
+          );
+          
+          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 (message?.complete && content) {
-              console.log('✅ AI分析完成,原始内容长度:', content.length);
-              console.log('📝 AI返回内容预览:', content.substring(0, 500));
-              
-              // 检查内容长度
-              if (content.length < 1000) {
-                console.warn('⚠️ AI返回内容较短,可能不完整');
-              }
+          // 检查必要字段
+          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);
+
+          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 analysisData = this.parseAnalysisContent(content);
+              const completion = new FmodeChatCompletion(messageList, {
+                model: this.AI_MODEL,
+                max_tokens: 8000
+              });
               
-              console.log('📊 解析后的分析数据:', analysisData);
+              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();
+                }
+              });
               
-              if (!analysisData || !analysisData.hasContent) {
-                reject(new Error('AI分析结果解析失败或内容不足'));
-                return;
-              }
-
-              resolve(analysisData);
-              subscription?.unsubscribe();
+              return; // 使用备选方案,不继续执行下面的reject
+            } catch (fallbackErr: any) {
+              console.error('❌ 备选方案失败:', fallbackErr);
             }
-          },
-          error: (err) => {
-            console.error('❌ AI分析失败,详细错误:', err);
-            console.error('❌ 错误类型:', err?.constructor?.name);
-            console.error('❌ 错误消息:', err?.message);
-            console.error('❌ 错误详情:', JSON.stringify(err, null, 2));
-            
-            // 根据错误类型提供更具体的错误信息
-            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));
-            subscription?.unsubscribe();
           }
-        });
+          
+          // 根据错误类型提供更具体的错误信息
+          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));
@@ -140,102 +215,464 @@ export class DesignAnalysisAIService {
   }
 
   /**
-   * 构建AI分析提示词(专业设计分析版本)
+   * 构建纯文本分析提示词(用于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;
+  }
+
+  /**
+   * 构建AI分析提示词(JSON格式输出,兼容completionJSON)
+   * 参考ai-k12-daofa的简洁提示词风格
    */
   private buildAnalysisPrompt(spaceType?: string, textDescription?: string, conversationHistory?: Array<{ role: string; content: string }>, deepThinking?: boolean): string {
-    let prompt = `🚨 重要:你必须仔细观察我上传的图片,根据图片中的实际内容进行分析。每张图片的内容都不同,你的分析结果也必须不同。不要生成模板化内容。
+    // 🔥 关键:提示词要简洁明了,直接要求JSON格式(参考ai-k12-daofa)
+    let prompt = `请分析图片中的室内设计,并按以下JSON格式输出:
+
+{
+  "spaceType": "空间类型(如:客餐厅一体化、主卧、玄关等)",
+  "spacePositioning": "空间定位与场景属性的详细分析",
+  "layout": "空间布局与动线的详细分析",
+  "hardDecoration": "硬装系统细节的详细分析(顶面、墙面、地面、门窗)",
+  "colorAnalysis": "色调精准分析(主色调、辅助色、色调关系)",
+  "materials": "材质应用解析(自然材质、现代材质、材质对比)",
+  "form": "形体与比例分析(空间形体、家具形体、造型细节)",
+  "style": "风格与氛围营造(风格识别、氛围手法)",
+  "suggestions": "专业优化建议(居住适配、细节优化、落地可行性)",
+  "summary": "简洁摘要(格式: 空间类型 | 风格 | 色调 | 氛围)"
+}
 
-你是资深室内设计师,请基于图片实际内容进行专业分析。
+要求:
+1. 基于图片实际视觉内容进行分析
+2. 每个字段提供详细描述(200-400字)
+3. 使用专业的室内设计术语
+4. 不提及品牌名称,仅描述材质、色调、形态`;
 
-【项目信息】
-${spaceType ? `空间类型:${spaceType}` : ''}
-${textDescription ? `用户需求:${textDescription}` : ''}
+    // 添加空间类型提示
+    if (spaceType) {
+      prompt += `\n5. 空间类型参考: ${spaceType}`;
+    }
 
-【核心要求】请仔细观察图片,从以下维度分析:`;
+    // 添加客户需求提示
+    if (textDescription) {
+      prompt += `\n6. 客户需求参考: ${textDescription}`;
+    }
+    
+    return prompt;
+  }
 
-    // 添加对话历史(限制2轮)
-    if (conversationHistory && conversationHistory.length > 0) {
-      const recentHistory = conversationHistory.slice(-4);
-      prompt += `\n\n【对话历史】\n`;
-      recentHistory.forEach((msg) => {
-        const role = msg.role === 'user' ? '用户' : 'AI';
-        const content = msg.content.length > 200 ? msg.content.substring(0, 200) + '...' : msg.content;
-        prompt += `${role}: ${content}\n`;
-      });
+  /**
+   * 解析JSON格式的AI分析结果(新方法,处理completionJSON返回的JSON对象)
+   */
+  private parseJSONAnalysis(jsonResult: any): any {
+    console.log('📝 解析JSON分析结果...');
+
+    // 将JSON字段转换为易读的格式化文本
+    const formattedContent = this.formatJSONToText(jsonResult);
+
+    return {
+      rawContent: JSON.stringify(jsonResult, null, 2), // 原始JSON
+      formattedContent: formattedContent,               // 格式化文本
+      structuredData: {
+        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 formatJSONToText(jsonResult: any): string {
+    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`);
+    }
+    
+    return sections.join('\n');
+  }
 
-    // 深度思考模式
-    if (deepThinking) {
-      prompt += `\n💡 深度模式:更详细分析设计心理、材质光影、色彩情绪。\n`;
+  /**
+   * 解析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()
+      };
     }
 
-    prompt += `
+    // 格式化处理:优化段落、间距、结构
+    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');
 
-【输出格式】
-- 纯文字段落,不使用Markdown符号
-- 每个维度之间空行分隔
-- 使用完整句子,不要简单列举
-- 总计800-2000字,根据图片内容详细程度调整
-- 不提及品牌名称
+    // 4. 确保维度之间有明确的空行分隔
+    formatted = formatted.replace(/(一、|二、|三、|四、|五、|六、|七、|八、)/g, '\n\n$1');
 
-【分析维度】请观察图片,从8个维度详细分析(必须基于图片实际内容):
+    // 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());
+    }
 
-请现在开始分析,用完整的句子和段落描述图片中的实际内容。`;
-    
-    return prompt;
+    // 2. 提取风格关键词
+    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.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.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, 2).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, 3).join('、'));
+    }
+
+    return summary.length > 0 ? summary.join(' | ') : '整体设计基于图片实际内容分析';
   }
 
   /**
-   * 解析AI分析内容(简化版:直接使用AI的纯文字输出)
+   * 生成客服标注格式:提取客户要求的关键点
    */
-  private parseAnalysisContent(content: string): any {
-    console.log('📝 AI返回的原始内容长度:', content.length);
-    console.log('📝 AI返回的内容预览:', content.substring(0, 500));
+  generateCustomerServiceNotes(analysisData: any, customerRequirements?: string): string {
+    if (!analysisData) {
+      return '暂无标注内容';
+    }
 
-    // 直接返回完整的AI分析内容(纯文字格式)
-    return {
-      rawContent: content,  // 完整的AI分析内容(纯文字格式)
-      hasContent: content && content.length > 50, // 检查是否有内容(至少50字)
-      timestamp: new Date().toISOString()
-    };
+    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') : '请根据分析内容补充具体要求';
   }
 
   /**
@@ -249,8 +686,8 @@ ${textDescription ? `用户需求:${textDescription}` : ''}
   }): Promise<string> {
     return new Promise(async (resolve, reject) => {
       try {
-        // 此方法暂时返回分析数据的原始内容
-        const content = options.analysisData?.rawContent || '暂无报告内容';
+        // 使用格式化后的内容
+        const content = options.analysisData?.formattedContent || options.analysisData?.rawContent || '暂无报告内容';
         resolve(content);
       } catch (error: any) {
         reject(new Error('生成报告失败: ' + error.message));