Browse Source

feat: 添加AI设计助手功能,包含颜色推荐和趋势分析

0235625 2 days ago
parent
commit
cd25163d44

+ 176 - 29
cloth-design/src/app/modules/cloth/mobile/page-design/page-design.component.ts

@@ -1,7 +1,23 @@
-import { Component } from '@angular/core';
+// page-design.component.ts
+import { Component, ChangeDetectorRef } from '@angular/core';
 import { FormsModule } from '@angular/forms';
 import { CommonModule } from '@angular/common';
 import { ColorUsage } from '../../../../../lib/ncloud';
+import { TestMessage, TestCompletion } from '../../../../../lib/completion';
+import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
+
+// 定义类型
+interface TrendRecommendation {
+  title: string;
+  colors: string[];
+  description: string;
+}
+
+interface PresetQuestion {
+  text: string;
+  description: string;
+  icon: string;
+}
 
 @Component({
   selector: 'app-page-design',
@@ -11,6 +27,7 @@ import { ColorUsage } from '../../../../../lib/ncloud';
   styleUrls: ['./page-design.scss']
 })
 export class PageDesignComponent {
+  // 原有设计界面的变量(保持不变)
   activePart: string = 'hood';
   selectedColor: string = '#3498db';
   colors: string[] = [
@@ -19,7 +36,6 @@ export class PageDesignComponent {
     '#ecf0f1', '#e84393', '#ffeaa7','#ffb6c1',
     '#98ff98','#b399d4','#ff7f50','#89cff0',
    '#ff9aa2','#40e0d0','#f0e68c','#c8a2c8'
-
   ];
   
   partColors: { [key: string]: string } = {
@@ -29,13 +45,63 @@ export class PageDesignComponent {
     'sleeve-right': '#3498db'
   };
   
-  aiMessages: any[] = [
+  colorUsage: ColorUsage = new ColorUsage();
+
+  // 新增的AI助手相关变量
+  aiMessages: { text: string | SafeHtml; isUser: boolean; isLoading?: boolean; isTrend?: boolean }[] = [
     { text: '您好!我是您的羽绒服设计助手。我可以根据您的喜好推荐颜色搭配,或者帮您分析当前设计的流行度。有什么我可以帮您的吗?', isUser: false }
   ];
   aiInput: string = '';
+  showAIModal: boolean = false;
+  isAILoading: boolean = false;
+  
+  presetQuestions: PresetQuestion[] = [
+    { 
+      text: '推荐一些流行颜色搭配', 
+      description: '获取当前最受欢迎的配色方案',
+      icon: 'fas fa-palette'
+    },
+    { 
+      text: '分析我的设计流行度', 
+      description: '评估当前设计的市场吸引力',
+      icon: 'fas fa-chart-line'
+    },
+    { 
+      text: '生成3D预览效果', 
+      description: '创建设计的3D可视化预览',
+      icon: 'fas fa-cube'
+    },
+    { 
+      text: '提供设计灵感', 
+      description: '获取创意设计建议',
+      icon: 'fas fa-lightbulb'
+    }
+  ];
+  
+  trendRecommendations: TrendRecommendation[] = [
+    {
+      title: '海洋蓝渐变',
+      colors: ['#89CFF0', '#40E0D0', '#008080'],
+      description: '从浅蓝到深蓝的渐变效果,清新自然'
+    },
+    {
+      title: '日落橙黄',
+      colors: ['#FF7F50', '#FFD700', '#FFA500'],
+      description: '温暖活力的橙黄色系,充满活力'
+    },
+    {
+      title: '森林绿系',
+      colors: ['#98FF98', '#2E8B57', '#006400'],
+      description: '自然森林色调,环保且时尚'
+    }
+  ];
 
-  colorUsage: ColorUsage = new ColorUsage();
+  constructor(
+    private cdr: ChangeDetectorRef,
+    private sanitizer: DomSanitizer
+  ) {}
 
+  // 原有设计界面的方法(保持不变)
   selectPart(part: string) {
     this.activePart = part;
   }
@@ -64,32 +130,9 @@ export class PageDesignComponent {
   }
 
   saveDesign() {
-    // 在实际应用中,这里会调用API保存设计
     alert('设计已保存!');
   }
 
-  sendAiMessage() {
-    const text = this.aiInput.trim();
-    if (!text) return;
-    
-    this.aiMessages.push({ text, isUser: true });
-    this.aiInput = '';
-    
-    // 模拟AI回复
-    setTimeout(() => {
-      let response = '';
-      if (text.includes('推荐') || text.includes('搭配')) {
-        response = '根据您的设计,我推荐尝试海洋蓝与珊瑚橙的搭配,这种组合在近期很受欢迎,能体现活力与时尚感。';
-      } else if (text.includes('流行') || text.includes('趋势')) {
-        response = '当前最流行的羽绒服颜色搭配是深海蓝黑渐变和日落橙黄渐变。这两种搭配在本月使用率上升了35%。';
-      } else {
-        response = '我理解您的需求了。作为AI设计助手,我可以为您提供以下帮助:\n1. 推荐颜色搭配方案\n2. 分析当前设计流行度\n3. 提供设计灵感\n4. 生成3D预览效果\n请告诉我您想了解哪方面的内容?';
-      }
-      
-      this.aiMessages.push({ text: response, isUser: false });
-    }, 1000);
-  }
-
   private darkenColor(color: string, percent: number): string {
     const num = parseInt(color.replace('#', ''), 16);
     const amt = Math.round(2.55 * percent);
@@ -104,8 +147,112 @@ export class PageDesignComponent {
       (B < 255 ? B < 1 ? 0 : B : 255)
     ).toString(16).slice(1);
   }
-  // 新增颜色使用统计
+
   getColorUsage(): { [key: string]: number } {
     return this.colorUsage.getAll();
-}
+  }
+
+  // 新增的AI助手方法
+  toggleAIModal() {
+    this.showAIModal = !this.showAIModal;
+    if (this.showAIModal) {
+      this.showTrendRecommendations();
+    }
+  }
+  
+  showTrendRecommendations() {
+    if (this.aiMessages.length > 1) return;
+    
+    let trendMessage = '根据当前流行趋势,我为您推荐以下设计方向:\n\n';
+    
+    this.trendRecommendations.forEach((trend: TrendRecommendation, index: number) => {
+      trendMessage += `${index + 1}. ${trend.title}\n`;
+      trendMessage += `   ${trend.description}\n`;
+      trendMessage += `   推荐颜色: `;
+      
+      trend.colors.forEach((color: string) => {
+        trendMessage += `<span class="color-dot" style="background-color:${color}"></span> `;
+      });
+      
+      trendMessage += '\n\n';
+    });
+    
+    trendMessage += '您可以直接选择应用这些推荐,或者告诉我您的具体需求!';
+    
+    this.aiMessages.push({ 
+      text: this.sanitizer.bypassSecurityTrustHtml(trendMessage), 
+      isUser: false,
+      isTrend: true
+    });
+  }
+  
+  applyTrendRecommendation(index: number) {
+    if (index < 0 || index >= this.trendRecommendations.length) return;
+    
+    const trend = this.trendRecommendations[index];
+    
+    // 应用推荐颜色
+    this.partColors['hood'] = trend.colors[0];
+    this.partColors['body'] = trend.colors[1];
+    this.partColors['sleeve-left'] = trend.colors[0];
+    this.partColors['sleeve-right'] = trend.colors[0];
+    
+    // 更新选中颜色
+    this.selectedColor = trend.colors[0];
+    
+    this.aiMessages.push({ 
+      text: `已应用推荐:${trend.title}`, 
+      isUser: true 
+    });
+    
+    setTimeout(() => {
+      this.aiMessages.push({ 
+        text: `太棒了!您已成功应用了${trend.title}设计。这个方案在当前非常流行,预计会受到年轻群体的喜爱。您想进一步调整还是保存设计?`, 
+        isUser: false 
+      });
+    }, 500);
+  }
+
+  async sendAiMessage() {
+    const text = this.aiInput.trim();
+    if (!text) return;
+    
+    this.aiMessages.push({ text, isUser: true });
+    this.aiInput = '';
+    
+    const aiReplyIndex = this.aiMessages.length;
+    this.aiMessages.push({ text: '思考中...', isUser: false, isLoading: true });
+    
+    this.isAILoading = true;
+    this.cdr.detectChanges();
+    
+    try {
+      const messages: TestMessage[] = this.aiMessages
+        .filter(msg => !msg.isLoading && typeof msg.text === 'string')
+        .map(msg => ({
+          role: msg.isUser ? 'user' : 'assistant',
+          content: msg.text as string
+        }));
+      
+      const completion = new TestCompletion(messages);
+      
+      await completion.sendMessage(null, (content) => {
+        this.aiMessages[aiReplyIndex].text = content;
+        this.cdr.detectChanges();
+      });
+      
+    } catch (error) {
+      console.error('AI请求失败:', error);
+      this.aiMessages[aiReplyIndex].text = '抱歉,AI助手暂时无法回答,请稍后再试。';
+    } finally {
+      this.aiMessages[aiReplyIndex].isLoading = false;
+      this.isAILoading = false;
+      this.cdr.detectChanges();
+    }
+  }
+  
+  usePresetQuestion(question: string) {
+    this.aiInput = question;
+    this.sendAiMessage();
+  }
 }

+ 60 - 20
cloth-design/src/app/modules/cloth/mobile/page-design/page-design.html

@@ -1,3 +1,5 @@
+<!-- page-design.html -->
+<!-- 原有设计界面保持不变 -->
 <div class="panel">
   <h2><i class="fas fa-palette"></i> 个性定制</h2>
   <p class="section-desc">选择羽绒服的不同部位并自定义颜色,打造属于你的独特设计</p>
@@ -34,28 +36,66 @@
     </button>
   </div>
   
-  <div class="ai-recommendation">
-    <div class="ai-header">
-      <div class="ai-icon">
-        <i class="fas fa-robot"></i>
+  <!-- 新增的AI助手部分 -->
+  <button class="ai-float-btn" (click)="toggleAIModal()">
+    <i class="fas fa-robot"></i>
+    <span>AI助手</span>
+  </button>
+  
+  <div class="ai-modal" *ngIf="showAIModal">
+    <div class="ai-modal-content">
+      <div class="ai-modal-header">
+        <div class="ai-icon">
+          <i class="fas fa-robot"></i>
+        </div>
+        <div class="ai-title">AI设计助手</div>
+        <button class="close-btn" (click)="toggleAIModal()">&times;</button>
       </div>
-      <div class="ai-title">AI设计助手</div>
-    </div>
-    
-    <div class="ai-messages">
-      <div *ngFor="let message of aiMessages" class="message" 
-           [class.user-message]="message.isUser" 
-           [class.ai-message]="!message.isUser">
-        {{ message.text }}
+      
+      <div class="ai-messages">
+        <div *ngFor="let message of aiMessages; let i = index" class="message" 
+             [class.user-message]="message.isUser" 
+             [class.ai-message]="!message.isUser"
+             [class.trend-message]="message.isTrend">
+          <div *ngIf="message.isLoading" class="loading-dots">
+            <span></span>
+            <span></span>
+            <span></span>
+          </div>
+          
+          <div [innerHTML]="message.text"></div>
+          
+          <div *ngIf="message.isTrend" class="trend-actions">
+            <button *ngFor="let trend of trendRecommendations; let j = index" 
+                    class="trend-btn"
+                    (click)="applyTrendRecommendation(j)">
+              应用{{ trend.title }}
+            </button>
+          </div>
+        </div>
+      </div>
+      
+      <div class="preset-questions">
+        <div class="preset-title">快捷提问:</div>
+        <div class="preset-buttons">
+          <button *ngFor="let question of presetQuestions" 
+                  class="preset-btn"
+                  (click)="usePresetQuestion(question.text)">
+            <i [class]="question.icon"></i>
+            {{ question.text }}
+            <span class="preset-desc">{{ question.description }}</span>
+          </button>
+        </div>
+      </div>
+      
+      <div class="ai-input-group">
+        <input type="text" class="ai-input" placeholder="输入您的问题..." 
+               [(ngModel)]="aiInput" [disabled]="isAILoading"
+               (keyup.enter)="sendAiMessage()">
+        <button class="ai-send-btn" (click)="sendAiMessage()" [disabled]="isAILoading">
+          <i class="fas fa-paper-plane"></i>
+        </button>
       </div>
-    </div>
-    
-    <div class="ai-input-group">
-      <input type="text" class="ai-input" placeholder="输入您的问题..." 
-             [(ngModel)]="aiInput" (keyup.enter)="sendAiMessage()">
-      <button class="ai-send-btn" (click)="sendAiMessage()">
-        <i class="fas fa-paper-plane"></i>
-      </button>
     </div>
   </div>
 </div>

+ 294 - 56
cloth-design/src/app/modules/cloth/mobile/page-design/page-design.scss

@@ -1,3 +1,5 @@
+/* page-design.scss */
+/* 原有设计界面样式保持不变 */
 .panel {
   background: white;
   border-radius: 25px;
@@ -6,6 +8,7 @@
   box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
   animation: fadeIn 0.6s ease;
   transform-origin: top;
+  position: relative; /* 为浮动按钮提供定位上下文 */
 }
 
 h2 {
@@ -207,46 +210,153 @@ h2 i {
   transform: translateY(-3px);
 }
 
-.ai-recommendation {
-  background: white;
-  border-radius: 20px;
-  padding: 25px;
-  margin-top: 30px;
-  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
+@keyframes fadeIn {
+  from { opacity: 0; transform: translateY(20px); }
+  to { opacity: 1; transform: translateY(0); }
 }
 
-.ai-header {
+@keyframes pulse {
+  0% { transform: scale(1); }
+  50% { transform: scale(1.05); }
+  100% { transform: scale(1); }
+}
+
+.pulse {
+  animation: pulse 2s infinite;
+}
+
+@media (max-width: 480px) {
+  .jacket-display {
+    width: 280px;
+    height: 400px;
+  }
+  
+  .panel {
+    padding: 25px 20px;
+  }
+}
+
+/* ===== 新增的AI助手样式 ===== */
+
+/* 右下角AI助手按钮 */
+.ai-float-btn {
+  position: fixed;
+  bottom: 30px;
+  right: 30px;
+  z-index: 1000;
   display: flex;
   align-items: center;
-  gap: 12px;
-  margin-bottom: 20px;
-  color: #2c3e50;
+  gap: 10px;
+  padding: 15px 20px;
+  border-radius: 50px;
+  background: linear-gradient(135deg, #9b59b6, #3498db);
+  color: white;
+  border: none;
+  box-shadow: 0 8px 25px rgba(52, 152, 219, 0.4);
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  
+  i {
+    font-size: 1.2rem;
+  }
+  
+  span {
+    font-weight: 600;
+  }
+  
+  &:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 12px 30px rgba(52, 152, 219, 0.5);
+  }
 }
 
-.ai-icon {
-  width: 40px;
-  height: 40px;
-  background: linear-gradient(135deg, #9b59b6, #3498db);
-  border-radius: 50%;
+/* AI模态框 */
+.ai-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
   display: flex;
   align-items: center;
   justify-content: center;
-  color: white;
-  font-size: 1.2rem;
+  z-index: 2000;
+  backdrop-filter: blur(5px);
+}
+
+.ai-modal-content {
+  width: 90%;
+  max-width: 500px;
+  max-height: 90vh;
+  background: white;
+  border-radius: 25px;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  box-shadow: 0 15px 50px rgba(0, 0, 0, 0.2);
+  animation: modalFadeIn 0.4s ease;
 }
 
-.ai-title {
-  font-size: 1.3rem;
-  font-weight: 700;
+@keyframes modalFadeIn {
+  from { opacity: 0; transform: translateY(50px); }
+  to { opacity: 1; transform: translateY(0); }
+}
+
+.ai-modal-header {
+  display: flex;
+  align-items: center;
+  padding: 20px;
+  background: linear-gradient(135deg, #9b59b6, #3498db);
+  color: white;
+  position: relative;
+  
+  .ai-icon {
+    width: 40px;
+    height: 40px;
+    background: white;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #9b59b6;
+    font-size: 1.2rem;
+  }
+  
+  .ai-title {
+    font-size: 1.3rem;
+    font-weight: 700;
+    margin-left: 15px;
+    flex: 1;
+  }
+  
+  .close-btn {
+    background: none;
+    border: none;
+    color: white;
+    font-size: 1.8rem;
+    cursor: pointer;
+    width: 40px;
+    height: 40px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 50%;
+    transition: all 0.3s;
+    
+    &:hover {
+      background: rgba(255, 255, 255, 0.2);
+    }
+  }
 }
 
 .ai-messages {
-  height: 250px;
+  flex: 1;
+  min-height: 300px;
+  max-height: 50vh;
   overflow-y: auto;
-  padding: 15px;
+  padding: 20px;
   background: #f9f9f9;
-  border-radius: 15px;
-  margin-bottom: 20px;
 }
 
 .message {
@@ -255,6 +365,7 @@ h2 i {
   border-radius: 18px;
   max-width: 85%;
   position: relative;
+  transition: all 0.3s;
 }
 
 .user-message {
@@ -271,9 +382,134 @@ h2 i {
   border-bottom-left-radius: 5px;
 }
 
-.ai-input-group {
+/* 趋势推荐消息样式 */
+.trend-message {
+  background: #e3f2fd !important;
+  border-left: 4px solid #2196f3;
+  padding: 15px;
+}
+
+.color-dot {
+  display: inline-block;
+  width: 16px;
+  height: 16px;
+  border-radius: 50%;
+  border: 1px solid rgba(0,0,0,0.1);
+  vertical-align: middle;
+  margin: 0 2px;
+}
+
+.trend-actions {
   display: flex;
   gap: 10px;
+  margin-top: 15px;
+  flex-wrap: wrap;
+}
+
+.trend-btn {
+  padding: 8px 15px;
+  background: rgba(33, 150, 243, 0.1);
+  border: 1px solid #2196f3;
+  border-radius: 20px;
+  color: #2196f3;
+  font-size: 0.9rem;
+  cursor: pointer;
+  transition: all 0.3s;
+  
+  &:hover {
+    background: rgba(33, 150, 243, 0.2);
+    transform: translateY(-2px);
+  }
+}
+
+.loading-dots {
+  display: inline-flex;
+  gap: 5px;
+  position: absolute;
+  right: 15px;
+  top: 50%;
+  transform: translateY(-50%);
+  
+  span {
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    background: rgba(0, 0, 0, 0.3);
+    display: inline-block;
+    animation: dotPulse 1.5s infinite ease-in-out;
+    
+    &:nth-child(2) {
+      animation-delay: 0.2s;
+    }
+    
+    &:nth-child(3) {
+      animation-delay: 0.4s;
+    }
+  }
+}
+
+@keyframes dotPulse {
+  0%, 100% { transform: scale(0.8); opacity: 0.5; }
+  50% { transform: scale(1.2); opacity: 1; }
+}
+
+/* 预设问题模板样式 */
+.preset-questions {
+  padding: 15px;
+  background: #f8f9fa;
+  border-top: 1px solid #eee;
+}
+
+.preset-title {
+  color: #6c757d;
+  margin-bottom: 10px;
+  font-size: 0.95rem;
+  font-weight: 600;
+}
+
+.preset-buttons {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 10px;
+}
+
+.preset-btn {
+  padding: 12px 15px;
+  background: white;
+  border: 1px solid #e0e0e0;
+  border-radius: 12px;
+  color: #495057;
+  font-size: 0.95rem;
+  cursor: pointer;
+  transition: all 0.3s;
+  text-align: left;
+  display: flex;
+  flex-direction: column;
+  
+  i {
+    font-size: 1.2rem;
+    margin-bottom: 8px;
+    color: #3498db;
+  }
+  
+  &:hover {
+    border-color: #3498db;
+    box-shadow: 0 5px 15px rgba(52, 152, 219, 0.1);
+    transform: translateY(-3px);
+  }
+}
+
+.preset-desc {
+  font-size: 0.8rem;
+  color: #6c757d;
+  margin-top: 5px;
+}
+
+.ai-input-group {
+  display: flex;
+  padding: 15px;
+  background: white;
+  border-top: 1px solid #eee;
 }
 
 .ai-input {
@@ -284,10 +520,10 @@ h2 i {
   font-size: 1rem;
   outline: none;
   transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
-}
-
-.ai-input:focus {
-  border-color: #3498db;
+  
+  &:focus {
+    border-color: #3498db;
+  }
 }
 
 .ai-send-btn {
@@ -301,40 +537,42 @@ h2 i {
   align-items: center;
   justify-content: center;
   cursor: pointer;
+  margin-left: 10px;
   transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  
+  &:hover {
+    transform: translateY(-3px);
+    box-shadow: 0 8px 15px rgba(52, 152, 219, 0.3);
+  }
+  
+  &:disabled {
+    background: #bdc3c7;
+    cursor: not-allowed;
+    transform: none;
+    box-shadow: none;
+  }
 }
 
-.ai-send-btn:hover {
-  transform: translateY(-3px);
-  box-shadow: 0 8px 15px rgba(52, 152, 219, 0.3);
-}
-
-@keyframes fadeIn {
-  from { opacity: 0; transform: translateY(20px); }
-  to { opacity: 1; transform: translateY(0); }
-}
-
-@keyframes pulse {
-  0% { transform: scale(1); }
-  50% { transform: scale(1.05); }
-  100% { transform: scale(1); }
-}
-
-.pulse {
-  animation: pulse 2s infinite;
-}
-
-@media (max-width: 480px) {
-  .jacket-display {
-    width: 280px;
-    height: 400px;
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .ai-float-btn {
+    bottom: 20px;
+    right: 20px;
+    padding: 12px 15px;
+    font-size: 0.9rem;
   }
   
-  .panel {
-    padding: 25px 20px;
+  .ai-modal-content {
+    width: 95%;
+    max-height: 85vh;
   }
   
   .ai-messages {
-    height: 200px;
+    min-height: 250px;
+    max-height: 60vh;
+  }
+  
+  .preset-buttons {
+    grid-template-columns: 1fr;
   }
 }