From ee486a5d9098f4a1a94191bad50a1498479372dd Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 28 四月 2026 17:47:35 +0800
Subject: [PATCH] refactor(AIChatSidebar): 重构AI聊天侧边栏组件

---
 src/components/AIChatSidebar/index.vue         |  153 +++++++++++++++++++++++++++++++++++++++++++++-----
 src/components/PurchaseAIChatSidebar/index.vue |   24 ++++++++
 2 files changed, 160 insertions(+), 17 deletions(-)

diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index 1f1a580..bab4dea 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -2,9 +2,9 @@
   <div class="ai-chat-sidebar-wrapper">
     <!-- 鎮诞鍥炬爣 -->
     <div class="ai-chat-trigger" @click="toggleSidebar" v-show="!visible">
-      <el-tooltip content="AI 鍔╂墜" placement="left">
+      <el-tooltip :content="currentAssistant.tooltip" placement="left">
         <div class="trigger-icon">
-          <el-icon :size="30" color="#fff"><Cpu /></el-icon>
+          <el-icon :size="30" color="#fff"><component :is="currentAssistant.icon" /></el-icon>
         </div>
       </el-tooltip>
     </div>
@@ -25,8 +25,19 @@
       <template #header>
         <div class="drawer-header">
           <div class="header-left">
-            <el-icon :size="20" class="header-icon"><Cpu /></el-icon>
-            <span class="title">AI 鏅鸿兘鍔╂墜</span>
+            <el-icon :size="20" class="header-icon"><component :is="currentAssistant.icon" /></el-icon>
+            <span class="title">{{ currentAssistant.title }}</span>
+          </div>
+          <div v-if="showAssistantSwitch" class="assistant-switcher">
+            <el-radio-group v-model="selectedAssistantKey" size="small">
+              <el-radio-button
+                  v-for="assistant in assistants"
+                  :key="assistant.key"
+                  :label="assistant.key"
+              >
+                {{ assistant.label }}
+              </el-radio-button>
+            </el-radio-group>
           </div>
           <div class="header-actions">
             <el-tooltip content="浼氳瘽鍘嗗彶" placement="bottom">
@@ -82,7 +93,7 @@
                   <el-icon><Delete /></el-icon>
                 </el-button>
               </div>
-              <el-empty v-if="sessions.length === 0" description="鏆傛棤鍘嗗彶浼氳瘽" />
+              <el-empty v-if="sessions.length === 0" :description="currentAssistant.emptySessionText" />
             </div>
           </el-skeleton>
         </div>
@@ -145,6 +156,7 @@
                 <el-icon><VideoPause /></el-icon>鍋滄鐢熸垚
               </el-button>
               <el-upload
+                  v-if="currentAssistant.allowFileUpload"
                   class="file-upload-trigger"
                   action="#"
                   :auto-upload="false"
@@ -167,7 +179,7 @@
                   v-model="inputMessage"
                   type="textarea"
                   :rows="selectedFile ? 2 : 3"
-                  placeholder="璇疯緭鍏ユ偍鐨勯棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)"
+                  :placeholder="currentAssistant.placeholder"
                   resize="none"
                   @keydown.enter.exact.prevent="sendMessage"
               />
@@ -191,8 +203,53 @@
 import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
 import request from '@/utils/request'
 import * as echarts from 'echarts'
-import { Cpu, User, Plus, Loading, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close } from '@element-plus/icons-vue'
+import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, ShoppingCart } from '@element-plus/icons-vue'
 import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  assistants: {
+    type: Array,
+    default: () => []
+  },
+  defaultAssistant: {
+    type: String,
+    default: ''
+  }
+})
+
+const builtInAssistants = [
+  {
+    key: 'general',
+    label: '寰呭姙鍔╃悊',
+    title: '寰呭姙鏅鸿兘鍔╃悊',
+    tooltip: '寰呭姙鍔╂墜',
+    icon: Cpu,
+    apiBase: '/xiaozhi',
+    storageKey: 'ai_chat_uuid',
+    placeholder: '璇疯緭鍏ユ偍鐨勯棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+    welcomeMessage: '浣犲ソ',
+    allowFileUpload: true,
+    emptySessionText: '鏆傛棤鍘嗗彶浼氳瘽'
+  },
+  {
+    key: 'purchase',
+    label: '閲囪喘鍔╃悊',
+    title: '閲囪喘鏅鸿兘鍔╃悊',
+    tooltip: '閲囪喘鏅鸿兘鍔╃悊',
+    icon: ShoppingCart,
+    apiBase: '/purchase-ai',
+    storageKey: 'purchase_ai_chat_uuid',
+    placeholder: '璇疯緭鍏ラ噰璐棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+    welcomeMessage: '浣犲ソ',
+    allowFileUpload: false,
+    emptySessionText: '鏆傛棤閲囪喘浼氳瘽'
+  }
+]
+
+const assistants = computed(() => props.assistants?.length ? props.assistants : builtInAssistants)
+const selectedAssistantKey = ref(props.defaultAssistant || assistants.value[0]?.key || 'general')
+const currentAssistant = computed(() => assistants.value.find(item => item.key === selectedAssistantKey.value) || assistants.value[0] || builtInAssistants[0])
+const showAssistantSwitch = computed(() => assistants.value.length > 1)
 
 const visible = ref(false)
 const windowWidth = ref(window.innerWidth)
@@ -227,7 +284,7 @@
 const loadSessions = async () => {
   loadingSessions.value = true
   try {
-    const res = await request.get('/xiaozhi/history/sessions')
+    const res = await request.get(`${currentAssistant.value.apiBase}/history/sessions`)
     if (res.code === 200) {
       sessions.value = res.data || []
     }
@@ -241,11 +298,11 @@
 const selectSession = async (session) => {
   showHistory.value = false
   uuid.value = session.memoryId
-  localStorage.setItem('ai_chat_uuid', uuid.value)
+  localStorage.setItem(currentAssistant.value.storageKey, uuid.value)
 
   // 鍔犺浇浼氳瘽娑堟伅
   try {
-    const res = await request.get(`/xiaozhi/history/messages/${uuid.value}`)
+    const res = await request.get(`${currentAssistant.value.apiBase}/history/messages/${uuid.value}`)
     if (res.code === 200) {
       disposeCharts()
       messages.value = []
@@ -299,7 +356,7 @@
 
 const handleDeleteSession = async (memoryId) => {
   try {
-    const res = await request.delete(`/xiaozhi/history/${memoryId}`)
+    const res = await request.delete(`${currentAssistant.value.apiBase}/history/${memoryId}`)
     if (res.code === 200) {
       loadSessions()
       if (uuid.value === memoryId) {
@@ -335,6 +392,26 @@
   window.removeEventListener('resize', handleWindowResize)
 })
 
+watch(selectedAssistantKey, (nextKey, prevKey) => {
+  if (!prevKey || nextKey === prevKey) return
+
+  if (currentAbortController.value) {
+    currentAbortController.value.abort()
+    currentAbortController.value = null
+  }
+
+  isSending.value = false
+  disposeCharts()
+  messages.value = []
+  outputState.value = {}
+  sessions.value = []
+  showHistory.value = false
+  selectedFile.value = null
+  inputMessage.value = ''
+  initUUID()
+  hello()
+})
+
 const handleWindowResize = () => {
   windowWidth.value = window.innerWidth
 }
@@ -351,23 +428,26 @@
 }
 
 const initUUID = () => {
-  let storedUUID = localStorage.getItem('ai_chat_uuid')
+  let storedUUID = localStorage.getItem(currentAssistant.value.storageKey)
   if (!storedUUID) {
     storedUUID = Math.random().toString(36).substring(2, 10) + Date.now().toString(36).substring(4)
-    localStorage.setItem('ai_chat_uuid', storedUUID)
+    localStorage.setItem(currentAssistant.value.storageKey, storedUUID)
   }
   uuid.value = storedUUID
 }
 
 const hello = () => {
-  sendRequest('浣犲ソ')
+  sendRequest(currentAssistant.value.welcomeMessage || '浣犲ソ')
 }
 
 const newChat = () => {
   disposeCharts()
   messages.value = []
   outputState.value = {}
-  localStorage.removeItem('ai_chat_uuid')
+  sessions.value = []
+  showHistory.value = false
+  selectedFile.value = null
+  localStorage.removeItem(currentAssistant.value.storageKey)
   initUUID()
   hello()
 }
@@ -527,7 +607,7 @@
     formData.append('message', message.trim())
   }
 
-  request.post('/xiaozhi/analyze-file', formData, {
+  request.post(`${currentAssistant.value.apiBase}/analyze-file`, formData, {
     headers: {
       'Content-Type': 'multipart/form-data'
     },
@@ -642,7 +722,7 @@
 
   scrollToBottom()
 
-  request.post('/xiaozhi/chat',
+  request.post(`${currentAssistant.value.apiBase}/chat`,
       { memoryId: uuid.value, message },
       {
         signal: currentAbortController.value.signal,
@@ -1297,6 +1377,45 @@
       }
     }
   }
+
+  .assistant-switcher {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex: 1;
+    padding: 0 12px;
+    position: relative;
+    z-index: 1;
+
+    :deep(.el-radio-group) {
+      display: flex;
+      gap: 6px;
+      flex-wrap: wrap;
+      justify-content: center;
+    }
+
+    :deep(.el-radio-button__inner) {
+      border-radius: 999px;
+      border: 1px solid rgba(255, 255, 255, 0.18);
+      background: rgba(255, 255, 255, 0.12);
+      color: rgba(255, 255, 255, 0.86);
+      box-shadow: none;
+      padding: 7px 14px;
+      font-weight: 500;
+    }
+
+    :deep(.el-radio-button:first-child .el-radio-button__inner),
+    :deep(.el-radio-button:last-child .el-radio-button__inner) {
+      border-radius: 999px;
+    }
+
+    :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
+      background: #fff;
+      color: $primary-blue;
+      border-color: #fff;
+      box-shadow: 0 6px 14px rgba(0, 40, 120, 0.16);
+    }
+  }
 }
 
 @keyframes headerGlow {
diff --git a/src/components/PurchaseAIChatSidebar/index.vue b/src/components/PurchaseAIChatSidebar/index.vue
new file mode 100644
index 0000000..f97d521
--- /dev/null
+++ b/src/components/PurchaseAIChatSidebar/index.vue
@@ -0,0 +1,24 @@
+<template>
+  <AIChatSidebar :assistants="assistants" default-assistant="purchase" />
+</template>
+
+<script setup>
+import { ShoppingCart } from '@element-plus/icons-vue'
+import AIChatSidebar from '@/components/AIChatSidebar/index.vue'
+
+const assistants = [
+  {
+    key: 'purchase',
+    label: '閲囪喘鍔╃悊',
+    title: '閲囪喘鏅鸿兘鍔╃悊',
+    tooltip: '閲囪喘鏅鸿兘鍔╃悊',
+    icon: ShoppingCart,
+    apiBase: '/purchase-ai',
+    storageKey: 'purchase_ai_chat_uuid',
+    placeholder: '璇疯緭鍏ラ噰璐棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+    welcomeMessage: '浣犲ソ',
+    allowFileUpload: false,
+    emptySessionText: '鏆傛棤閲囪喘浼氳瘽'
+  }
+]
+</script>

--
Gitblit v1.9.3