yangg 3 giorni fa
parent
commit
f3614e8b95
6 ha cambiato i file con 1004 aggiunte e 65 eliminazioni
  1. 1 1
      .env.development
  2. 2 0
      package.json
  3. 4 1
      src/main.js
  4. 6 0
      src/router/menu.js
  5. 485 0
      src/views/demo.vue
  6. 506 63
      src/views/report.vue

+ 1 - 1
.env.development

@@ -1,4 +1,4 @@
 NODE_ENV=development
 #http://192.168.100.187:8062 
-VITE_BASE_AI_API=https://aiapi.raycos.com.cn
+VITE_BASE_AI_API=http://192.168.100.187:8082
 VITE_BASE_AI_F_API=http://58.246.234.210:8082

+ 2 - 0
package.json

@@ -19,6 +19,8 @@
     "axios": "^1.7.9",
     "dayjs": "^1.11.13",
     "docx": "^9.1.0",
+    "echarts": "^5.6.0",
+    "eventsource-polyfill": "^0.9.6",
     "file-saver": "^2.0.5",
     "html2pdf.js": "^0.10.2",
     "less": "^4.2.1",

+ 4 - 1
src/main.js

@@ -8,6 +8,8 @@ import antdUse from './antdUse'
 import 'dayjs/locale/zh-cn';
 import './app.less'
 import './assets/styles/theme.css';
+// 导入 ECharts 插件
+/* import EChartsPlugin from './plugins/echarts' */
 
 // 创建应用上下文
 const app = createApp(App)
@@ -19,7 +21,8 @@ app.config.globalProperties.$dayjs = dayjs
 antdUse(app)
 app.use(router)
 app.use(store)
-
+// 使用 ECharts 插件
+/* app.use(EChartsPlugin) */
 // 挂载dom
 app.mount('#app')
 

+ 6 - 0
src/router/menu.js

@@ -39,5 +39,11 @@ export const ROUTE = [
     path: '/:catchAll(.*)',
     redirect: '/404',
     hidden: true
+  },
+  {
+    path:'/demo',
+    name:'demo',
+     hidden: true,
+    component: () => import('@views/demo.vue')
   }
 ]

+ 485 - 0
src/views/demo.vue

@@ -0,0 +1,485 @@
+<template>
+  <div class="sse-container">
+    <h2>SSE 实时数据连接</h2>
+    
+    <div class="status-indicator">
+      <span class="status-dot" :class="{ connected: isConnected }"></span>
+      <span>{{ connectionStatusText }}</span>
+    </div>
+    
+    <div class="controls">
+      <button @click="connect" :disabled="isConnected">连接</button>
+      <button @click="disconnect" :disabled="!isConnected">断开</button>
+      <button @click="clearMessages">清空消息</button>
+    </div>
+    
+    <div class="message-list">
+      <div v-if="messages.length === 0" class="empty-message">
+        暂无消息,请先连接服务器
+      </div>
+      
+      <div v-for="(msg, index) in messages" :key="index" class="message-item" :class="msg.type">
+        <div class="message-header">
+          <span class="message-type">{{ msg.type.toUpperCase() }}</span>
+          <span class="message-time">{{ formatTime(msg.timestamp) }}</span>
+        </div>
+        <div class="message-content">{{ msg.content }}</div>
+      </div>
+    </div>
+  </div>
+  
+  <!-- WebSocket 连接部分 -->
+  <div class="ws-container">
+    <h2>WebSocket 实时数据连接</h2>
+    
+    <div class="status-indicator">
+      <span class="status-dot" :class="{ connected: isWsConnected }"></span>
+      <span>{{ wsConnectionStatusText }}</span>
+    </div>
+    
+    <div class="controls">
+      <button @click="connectWs" :disabled="isWsConnected">连接</button>
+      <button @click="disconnectWs" :disabled="!isWsConnected">断开</button>
+      <button @click="clearWsMessages">清空消息</button>
+    </div>
+    
+    <div class="message-list">
+      <div v-if="wsMessages.length === 0" class="empty-message">
+        暂无消息,请先连接WebSocket服务器
+      </div>
+      
+      <div v-for="(msg, index) in wsMessages" :key="index" class="message-item" :class="msg.type">
+        <div class="message-header">
+          <span class="message-type">{{ msg.type.toUpperCase() }}</span>
+          <span class="message-time">{{ formatTime(msg.timestamp) }}</span>
+        </div>
+        <div class="message-content">{{ msg.content }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'SSEConnection',
+  data() {
+    return {
+      isConnected: false,
+      eventSource: null,
+      messages: [],
+      retryCount: 0,
+      maxRetries: 5,
+      retryDelay: 3000,
+      // WebSocket相关数据
+      isWsConnected: false,
+      wsConnection: null,
+      wsMessages: [],
+      wsRetryCount: 0,
+      wsMaxRetries: 5,
+      wsRetryDelay: 3000,
+    };
+  },
+  computed: {
+    connectionStatusText() {
+      return this.isConnected ? '已连接' : '未连接';
+    },
+    wsConnectionStatusText() {
+      return this.isWsConnected ? '已连接' : '未连接';
+    }
+  },
+  methods: {
+    async connect() {
+      if (this.isConnected) return;
+      
+      try {
+        // 获取JWT token
+        const token = localStorage.getItem('token');
+        
+        // 请求参数
+        const requestData = {
+         
+            message: "压缩空气系统中变频器的应用如何实现节能?",
+            user_id: 13817302290,
+            session_id: ""/* 35fe2491-dca8-4a1e-b927-43bba0c2b669 */
+  
+        };
+
+        // 调用接口获取SSE URL
+        const response = await fetch('http://192.168.100.187:8062/api/agents/chat/multimodal/ ', {
+          method: 'POST',
+          mode: 'cors',
+          credentials: 'include',
+          headers: {
+            'Authorization': `JWT ${token}`,
+            'Content-Type': 'application/json',
+          },
+          body: JSON.stringify(requestData)
+        });
+        
+        if (!response.ok) {
+          throw new Error('获取SSE URL失败');
+        }
+        
+        // 尝试从响应头获取SSE URL
+        console.log('Response Headers:', response.headers);
+        console.log('All Headers:', Array.from(response.headers.entries()));
+        let sseUrl = response.headers.get('X-Sse-Url');
+        
+        // 如果响应头中没有,尝试从响应体中获取
+        if (!sseUrl) {
+          const responseData = await response.json();
+          console.log('Response Data:', responseData);
+          sseUrl = responseData.data.sse_url || responseData.sse_url;
+          if (!sseUrl) {
+            throw new Error('未找到SSE URL');
+          }
+        }
+        
+        // 将token添加到SSE URL中
+        const urlWithToken = `http://192.168.100.187:8062${sseUrl}${sseUrl.includes('?') ? '&' : '?'}token=JWT ${token}`;
+        
+        this.eventSource = new EventSource(urlWithToken);
+        
+        // 通用消息处理器
+        this.eventSource.onmessage = (event) => {
+          console.log('收到SSE消息:', event.data);
+          try {
+            const data = JSON.parse(event.data);
+            let messageContent = '';
+            let messageType = 'message';
+
+            switch(data.type) {
+              case 'connected':
+                messageContent = `已连接 - 会话ID: ${data.session_id}`;
+                messageType = 'system';
+                break;
+              case 'heartbeat':
+                // 不显示心跳消息
+                return;
+              case 'thinking_step':
+                messageContent = data.data.content;
+                messageType = 'thinking';
+                break;
+              case 'completed':
+                messageContent = '会话已完成';
+                messageType = 'system';
+                break;
+              default:
+                messageContent = JSON.stringify(data);
+            }
+
+            if (messageContent) {
+              this.addMessage(messageContent, messageType);
+            }
+          } catch (error) {
+            console.error('解析SSE消息失败:', error);
+            this.addMessage(`消息解析错误: ${error.message}`, 'error');
+          }
+        };
+        
+        // 自定义事件处理器
+        this.eventSource.addEventListener('status', (event) => {
+          const data = JSON.parse(event.data);
+          this.addMessage(`状态更新: ${data.status}`, 'status');
+        });
+        
+        this.eventSource.addEventListener('error', (event) => {
+          this.addMessage(`错误: ${event.message || '连接错误'}`, 'error');
+        });
+        
+        // 错误处理
+        this.eventSource.onerror = (error) => {
+          if (this.eventSource.readyState === EventSource.CLOSED) {
+            this.addMessage('连接已关闭', 'system');
+          } else {
+            this.addMessage(`连接错误: ${error.message}`, 'error');
+            this.attemptReconnect();
+          }
+        };
+        
+        this.isConnected = true;
+        this.retryCount = 0;
+        this.addMessage('成功连接到SSE服务器', 'system');
+      } catch (error) {
+        this.addMessage(`连接失败: ${error.message}`, 'error');
+        this.attemptReconnect();
+      }
+    },
+    
+    disconnect() {
+      if (this.eventSource) {
+        this.eventSource.close();
+        this.eventSource = null;
+        this.isConnected = false;
+        this.addMessage('已手动断开连接', 'system');
+      }
+    },
+    
+    attemptReconnect() {
+      if (this.retryCount < this.maxRetries) {
+        this.retryCount++;
+        this.addMessage(`尝试重新连接 (${this.retryCount}/${this.maxRetries})...`, 'system');
+        
+        setTimeout(() => {
+          this.connect();
+        }, this.retryDelay);
+      } else {
+        this.addMessage('达到最大重试次数,停止尝试连接', 'error');
+        this.isConnected = false;
+      }
+    },
+    
+    addMessage(content, type = 'message') {
+      this.messages.unshift({
+        content,
+        type,
+        timestamp: new Date()
+      });
+      
+      // 限制消息数量
+      if (this.messages.length > 100) {
+        this.messages.pop();
+      }
+    },
+    
+    clearMessages() {
+      this.messages = [];
+    },
+    
+    formatTime(date) {
+      return date.toLocaleTimeString();
+    },
+    
+    // WebSocket相关方法
+    async connectWs() {
+      if (this.isWsConnected) return;
+      
+      try {
+        const token = localStorage.getItem('token');
+        const wsUrl = `ws://192.168.100.187:8062/ws/thinking/13/35fe2491-dca8-4a1e-b927-43bba0c2b669/?token=JWT ${token}`;
+        
+        this.wsConnection = new WebSocket(wsUrl);
+        
+        this.wsConnection.onopen = () => {
+          this.isWsConnected = true;
+          this.wsRetryCount = 0;
+          this.addWsMessage('成功连接到WebSocket服务器', 'system');
+        };
+        
+        this.wsConnection.onmessage = (event) => {
+          console.log('收到WebSocket消息:', event.data);
+          try {
+            const data = JSON.parse(event.data);
+            let messageContent = '';
+            let messageType = 'message';
+
+            switch(data.type) {
+              case 'connected':
+                messageContent = `已连接 - 会话ID: ${data.session_id}`;
+                messageType = 'system';
+                break;
+              case 'thinking_step':
+                messageContent = data.data.content;
+                messageType = 'thinking';
+                break;
+              case 'completed':
+                messageContent = '会话已完成';
+                messageType = 'system';
+                break;
+              default:
+                messageContent = JSON.stringify(data);
+            }
+
+            if (messageContent) {
+              this.addWsMessage(messageContent, messageType);
+            }
+          } catch (error) {
+            console.error('解析WebSocket消息失败:', error);
+            this.addWsMessage(`消息解析错误: ${error.message}`, 'error');
+          }
+        };
+        
+        this.wsConnection.onerror = (error) => {
+          this.addWsMessage(`连接错误: ${error.message || '未知错误'}`, 'error');
+          this.attemptWsReconnect();
+        };
+        
+        this.wsConnection.onclose = () => {
+          this.isWsConnected = false;
+          this.addWsMessage('连接已关闭', 'system');
+        };
+        
+      } catch (error) {
+        this.addWsMessage(`连接失败: ${error.message}`, 'error');
+        this.attemptWsReconnect();
+      }
+    },
+    
+    disconnectWs() {
+      if (this.wsConnection) {
+        this.wsConnection.close();
+        this.wsConnection = null;
+        this.isWsConnected = false;
+        this.addWsMessage('已手动断开连接', 'system');
+      }
+    },
+    
+    attemptWsReconnect() {
+      if (this.wsRetryCount < this.wsMaxRetries) {
+        this.wsRetryCount++;
+        this.addWsMessage(`尝试重新连接 (${this.wsRetryCount}/${this.wsMaxRetries})...`, 'system');
+        
+        setTimeout(() => {
+          this.connectWs();
+        }, this.wsRetryDelay);
+      } else {
+        this.addWsMessage('达到最大重试次数,停止尝试连接', 'error');
+        this.isWsConnected = false;
+      }
+    },
+    
+    addWsMessage(content, type = 'message') {
+      this.wsMessages.unshift({
+        content,
+        type,
+        timestamp: new Date()
+      });
+      
+      // 限制消息数量
+      if (this.wsMessages.length > 100) {
+        this.wsMessages.pop();
+      }
+    },
+    
+    clearWsMessages() {
+      this.wsMessages = [];
+    },
+  },
+  
+  beforeUnmount() {
+    // 组件卸载前关闭所有连接
+    if (this.eventSource) {
+      this.eventSource.close();
+    }
+    if (this.wsConnection) {
+      this.wsConnection.close();
+    }
+  }
+};
+</script>
+
+<style scoped>
+.sse-container {
+  max-width: 800px;
+  margin: 0 auto;
+  padding: 20px;
+  font-family: Arial, sans-serif;
+}
+
+.status-indicator {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin: 15px 0;
+  font-size: 1.1em;
+}
+
+.status-dot {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  background-color: #ccc;
+}
+
+.status-dot.connected {
+  background-color: #4CAF50;
+  box-shadow: 0 0 8px #4CAF50;
+}
+
+.controls {
+  margin: 20px 0;
+  display: flex;
+  gap: 10px;
+}
+
+.controls button {
+  padding: 8px 16px;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  background-color: #f0f0f0;
+  transition: background-color 0.3s;
+}
+
+.controls button:hover {
+  background-color: #e0e0e0;
+}
+
+.controls button:disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+}
+
+.message-list {
+  border: 1px solid #eee;
+  border-radius: 4px;
+  max-height: 500px;
+  overflow-y: auto;
+  padding: 10px;
+}
+
+.empty-message {
+  text-align: center;
+  padding: 20px;
+  color: #888;
+}
+
+.message-item {
+  margin-bottom: 15px;
+  padding: 10px;
+  border-radius: 4px;
+  background-color: #f9f9f9;
+  border-left: 4px solid #ddd;
+}
+
+.message-item.system {
+  border-left-color: #2196F3;
+}
+
+.message-item.status {
+  border-left-color: #4CAF50;
+}
+
+.message-item.error {
+  border-left-color: #f44336;
+}
+
+.message-item.thinking {
+  border-left-color: #9c27b0;
+  background-color: #f3e5f5;
+}
+
+.message-header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 5px;
+  font-size: 0.9em;
+  color: #666;
+}
+
+.message-type {
+  font-weight: bold;
+}
+
+.message-content {
+  word-break: break-word;
+}
+
+.ws-container {
+  max-width: 800px;
+  margin: 20px auto;
+  padding: 20px;
+  font-family: Arial, sans-serif;
+  border-top: 1px solid #eee;
+}
+</style>

+ 506 - 63
src/views/report.vue

@@ -30,7 +30,7 @@
           </transition>
         </div>
         <div class="user-info" v-if="!isMobile" @click="toggleDropdown">
-          <div class="user-name">商户名称:{{ userInfo.merchant?.merchant_name || '未登录用户' }}</div>
+          <div class="user-name">商户名称:{{ userInfo.merchant_name || '未登录用户' }}</div>
           <div class="user-name">当前用户:{{ userInfo.username || '未登录用户' }}</div>
           <!-- <div class="user-role">{{ userInfo.role || '未设置角色' }}</div> -->
         </div>
@@ -186,6 +186,21 @@
       </div>
       <!-- 修改消息列表部分 -->
       <div v-else class="message-list" ref="messageList">
+        <!-- 添加流式思考步骤显示 -->
+        <div v-if="currentThinkingSteps && currentThinkingSteps.length > 0" class="thinking-steps-stream">
+          <div v-for="(step, index) in currentThinkingSteps" 
+               :key="index" 
+               class="thinking-step-stream"
+               :class="[step.type, {'fade-in': true}]">
+            {{ step.content }}
+            <div v-if="index === currentThinkingSteps.length - 1" class="typing-indicator">
+              <span></span>
+              <span></span>
+              <span></span>
+            </div>
+          </div>
+        </div>
+        
         <div
           v-for="message in messages"
           :key="message.id"
@@ -196,6 +211,15 @@
         >
           <div class="message-bubble">
             <div class="message-content">
+              <!-- 添加thinking steps显示 -->
+              <div v-if="message.thinkingSteps && message.thinkingSteps.length > 0" class="thinking-steps">
+                <div v-for="(step, index) in message.thinkingSteps" 
+                     :key="index" 
+                     class="thinking-step"
+                     :class="step.type">
+                  {{ step.content }}
+                </div>
+              </div>
               <template v-if="message.role === 'user' && message.files">
                 <div
                   v-for="file in message.files"
@@ -320,6 +344,27 @@
               </div>
             </div>
           </div>
+          <div class="search-toggle-item">
+            <div class="toggle-content">
+              <DatabaseOutlined />
+              <span class="toggle-text" >推理模式</span><!-- v-show="!isCompact" -->
+              <div class="toggle-switch">
+                <input
+                  type="checkbox"
+                  id="inferenceModelId"
+                  v-model="inferenceModel"
+                  @change="handleInferenceModel"
+                  class="toggle-input"
+                />
+                <label
+                  for="inferenceModelId"
+                  class="toggle-label"
+                >
+                  <span class="toggle-slider"></span>
+                </label>
+              </div>
+            </div>
+          </div>
           </div>
 
           
@@ -859,6 +904,9 @@ const knowledgeResults = ref([]);
 const knowledgeTotal = ref(0);
 const isKnowledgeLoading = ref(false);
 
+// 推理模式
+const inferenceModel = ref(true);
+
 // 检测设备类型
 const checkMobile = () => {
   isMobile.value = window.innerWidth <= 768;
@@ -946,7 +994,7 @@ const fetchDocumentSummary = async () => {
   isLoadingSummary.value = true;
   try {
     const response = await axios.get(
-      `${import.meta.env.VITE_BASE_AI_API}/api/document/summary`,
+      `${import.meta.env.VITE_BASE_AI_API}/api/document/summary?limit=2`,
       {
         headers: {
           'Authorization': `JWT ${localStorage.getItem('token')}`
@@ -955,8 +1003,8 @@ const fetchDocumentSummary = async () => {
     );
     console.log("API Response:", response.data);
 
-    if (response.data.code === 200) {
-      documentSummary.value = response.data.data.items;
+    if (response.data.status === 200) {
+      documentSummary.value = response.data.data;
       console.log("Document Summary:", documentSummary.value);
       startRotation();
     }
@@ -975,40 +1023,40 @@ const startRotation = () => {
 
   const updateItems = () => {
     if (!documentSummary.value?.length) {
-      console.log("No document summary data available");
+      console.log("没有可用的文档摘要数据");
       return;
     }
 
-    // 处理第一项数据(Hot Topics
+    // 处理第一篇文档的问题(如:热门话题
     try {
-      const hotTopicsData = documentSummary.value[0];
-      if (hotTopicsData?.questions) {
-        const parsedQaPairs =
-          /* JSON.parse( */ hotTopicsData.questions; /* .replace(/'/g, '"')); */
-        const formattedQaPairs = parsedQaPairs.map((question) => ({
+      const firstDocData = documentSummary.value[0];
+      if (firstDocData?.questions) {
+        // 直接使用questions数组中的问题
+        const formattedQaPairs = firstDocData.questions.map((question) => ({
           question,
         }));
-        // 随机选择3个问题
+        // 随机抽取3个问题显示
         const shuffled = [...formattedQaPairs].sort(() => 0.5 - Math.random());
         currentQaPairs.value = shuffled.slice(0, 3);
       }
     } catch (error) {
-      console.error("解析 Hot Topics 失败:", error);
+      console.error("处理第一篇文档问题失败:", error);
     }
 
-    // 处理第二项数据(Design Guide
+    // 处理第二篇文档的问题(如:设计指南
     try {
-      const designGuideData = documentSummary.value[1];
-      if (designGuideData?.questions) {
-        const parsedGuides =
-          /* JSON.parse( */ designGuideData.questions; /* .replace(/'/g, '"')); */
-        const formattedGuides = parsedGuides.map((question) => ({ question }));
-        // 随机选择3个指南
+      const secondDocData = documentSummary.value[1];
+      if (secondDocData?.questions) {
+        // 直接使用questions数组中的问题
+        const formattedGuides = secondDocData.questions.map((question) => ({
+          question,
+        }));
+        // 随机抽取3个问题显示
         const shuffled = [...formattedGuides].sort(() => 0.5 - Math.random());
         currentDesignGuides.value = shuffled.slice(0, 3);
       }
     } catch (error) {
-      console.error("解析 Design Guide 失败:", error);
+      console.error("处理第二篇文档问题失败:", error);
     }
   };
 
@@ -1250,13 +1298,15 @@ const sendMessage = async () => {
     isTyping.value
   )
     return;
-    isTyping.value = true;
+  isTyping.value = true;
+  
   // 保存当前输入内容
   const currentMessage = inputContent.value.trim();
 
   // 立即清空输入框
   inputContent.value = '';
-  
+
+    
   // 根据开启的搜索类型执行相应的搜索
   if (enableWebSearch.value) {
     currentSearchQuery.value = currentMessage;
@@ -1292,13 +1342,13 @@ const sendMessage = async () => {
       documentUrls.push(file.url);
     }
   });
-
+  
   // 添加用户消息
   const userMessage = {
     id: Date.now(),
     role: "user",
-    content: messageContent,
-    displayContent: messageContent,
+    content: currentMessage,
+    displayContent: currentMessage,
     files: attachedFiles.value.map((file) => ({
       name: file.name,
       size: file.sizeFormatted,
@@ -1307,29 +1357,39 @@ const sendMessage = async () => {
     })),
   };
   messages.value.push(userMessage);
-/*   scrollToBottom(); */
 
   // 添加临时AI消息
   const tempAiMessage = {
     id: Date.now() + 1,
     role: "assistant",
-    content: "Thinking...",
-    displayContent: "Thinking...",
+    content: "思考中...",
+    displayContent: "思考中...",
     isLoading: true,
   };
   messages.value.push(tempAiMessage);
- /*  scrollToBottom(); */
 
   try {
     // 创建新的 AbortController
     abortController.value = new AbortController();
-    
-    // 根据开关状态选择不同的 API 端点
+    if(!inferenceModel.value){
+       // 构建请求参数
+    /* const requestData = {
+      message: currentMessage,
+      chat_config_id: "2",
+      user_id: "13365429324",
+      session_id: session_id.value || "",
+      source: "pc",
+      image_urls: attachedFiles.value.filter(f => isImageFile(f)).map(f => f.url),
+      video_urls: attachedFiles.value.filter(f => isVideoFile(f)).map(f => f.url),
+      documents: attachedFiles.value.filter(f => !isImageFile(f) && !isVideoFile(f)).map(f => f.url),
+      merchant_id: JSON.parse(localStorage.getItem('userInfo')).merchant.merchant_id||1,
+      model_type: selectedModelInfo.value.type,
+      model_name: selectedModelInfo.value.name
+    }; */
     const apiEndpoint = enableWebSearch.value
       ? `${import.meta.env.VITE_BASE_AI_API}/api/chat/web-search-llm/`
       : `${import.meta.env.VITE_BASE_AI_API}/api/chat/online/multimodal`;
-
-    const response = await axios.post(apiEndpoint, {
+      const response = await axios.post(apiEndpoint, {
       message: currentMessage,
       chat_config_id: "2",
       user_id: "13365429324",
@@ -1383,36 +1443,238 @@ const sendMessage = async () => {
 
     // 显示打字效果
     await typeMessage(aiMessage);
-    
-  } catch (error) {
-    if (error.name === 'CanceledError') {
-      console.log('请求已被取消');
-      messages.value = messages.value.filter(
-        (msg) => msg.id !== tempAiMessage.id
-      );
-      messages.value.push({
-        id: Date.now() + 2,
-        role: "assistant",
-        content: "生成已取消",
-        displayContent: "生成已取消",
-      });
-    } else {
-      console.error("发送消息失败:", error);
-      messages.value = messages.value.filter(
-        (msg) => msg.id !== tempAiMessage.id
-      );
-      messages.value.push({
-        id: Date.now() + 2,
-        role: "assistant",
-        content: "Sorry, there was an error processing your request.",
-        displayContent: "Sorry, there was an error processing your request.",
+
+    }else{
+  // 构建请求参数
+      const requestData = {
+        message: currentMessage,
+        user_id: "13365429324",
+        session_id: session_id.value || "",
+      
+      };
+
+      // 发送初始请求获取SSE URL
+      const response = await fetch(`${import.meta.env.VITE_BASE_AI_API}/api/agents/chat/multimodal/`, {
+        method: 'POST',
+        headers: {
+          'Authorization': `JWT ${token}`,
+          'Content-Type': 'application/json'
+        },
+        body: JSON.stringify(requestData)
       });
+
+      if (!response.ok) {
+        throw new Error('请求失败');
+      }
+
+      // 获取SSE URL
+      const responseData = await response.json();
+      console.log(responseData)
+      const sseUrl = responseData.data.sse_url;
+      
+      if (!sseUrl) {
+        throw new Error('未获取到SSE URL');
+      }
+
+            // 建立SSE连接
+        const sseUrlWithToken = `${import.meta.env.VITE_BASE_AI_API}${sseUrl}${sseUrl.includes('?') ? '&' : '?'}token=JWT ${encodeURIComponent(token)}`;
+        console.log('SSE连接URL:', sseUrlWithToken);
+        
+        const eventSource = new EventSource(sseUrlWithToken, {
+          withCredentials: true // 启用跨域认证
+        });
+
+        // 处理SSE消息
+      eventSource.onmessage = (event) => {
+        try {
+          console.log('收到SSE消息:', event.data);
+          const data = JSON.parse(event.data);
+          
+          switch(data.type) {
+            case 'connected':
+              // 更新session_id
+              if (data.session_id) {
+                session_id.value = data.session_id;
+                console.log('已连接,会话ID:', data.session_id);
+              }
+              break;
+              
+            case 'thinking_step':
+              // 更新临时消息的内容
+              const lastMessage = messages.value[messages.value.length - 1];
+              if (lastMessage && lastMessage.id === tempAiMessage.id) {
+                // 初始化thinking steps数组(如果不存在)
+                if (!lastMessage.thinkingSteps) {
+                  lastMessage.thinkingSteps = [];
+                }
+                
+                let stepContent = '';
+                let stepType = data.data.step_type;
+                
+                // 根据step_type处理不同类型的思考步骤
+                switch(data.data.step_type) {
+                  case 'start':
+                    stepContent = `${data.data.content}`;
+                    break;
+                  case 'request_analysis':
+                    stepContent = `${data.data.content}`;
+                    break;
+                  case 'knowledge_search':
+                    stepContent = `${data.data.content}`;
+                    break;
+                  case 'search_error':
+                    stepContent = `${data.data.content}`;
+                    break;
+                  case 'intent_analysis':
+                    stepContent = `${data.data.content}`;
+                    break;
+                  case 'intent_result':
+                    stepContent = `${data.data.content}`;
+                    break;
+                  case 'model_call':
+                    stepContent = `${data.data.content}`;
+                    break;
+                  case 'response_generation':
+                    stepContent = `${data.data.content}`;
+                    break;
+                  case 'text_analysis':
+                    stepContent = ` ${data.data.content}`;
+                    break;
+                  case 'planning':
+                    stepContent = ` ${data.data.content}`;
+                    break;
+                  case 'execution':
+                    stepContent = `${data.data.content}`;
+                    break;
+                  case 'result':
+                    stepContent = ` ${data.data.content}`;
+                    break;
+                  case 'conclusion':
+                    if (data.data.metadata && data.data.metadata.final_content) {
+                      // 添加最后一个thinking step
+                      lastMessage.thinkingSteps.push({
+                        type: 'conclusion',
+                        content: `✨ 完成: ${data.data.content}`
+                      });
+                      
+                      // 创建最终的AI消息
+                      const aiMessage = reactive({
+                        id: Date.now() + 2,
+                        role: "assistant",
+                        content: data.data.metadata.final_content,
+                        displayContent: "",
+                        audioData: null,
+                        thinkingSteps: lastMessage.thinkingSteps // 保留thinking steps
+                      });
+                      
+                      // 替换临时消息
+                      const msgIndex = messages.value.findIndex(msg => msg.id === tempAiMessage.id);
+                      if (msgIndex !== -1) {
+                        messages.value.splice(msgIndex, 1, aiMessage);
+                      }
+                      
+                      // 显示打字效果
+                      typeMessage(aiMessage);
+                      return;
+                    } else if (data.data.content) {
+                      // 根据内容是否包含某些关键词来决定显示的emoji
+                      let emoji = '💭'; // 默认emoji
+                      if (data.data.content.includes('分析')) {
+                        emoji = '🔍';
+                      } else if (data.data.content.includes('规划')) {
+                        emoji = '📋';
+                      } else if (data.data.content.includes('执行')) {
+                        emoji = '⚙️';
+                      } else if (data.data.content.includes('完成')) {
+                        emoji = '✨';
+                      }
+                      stepContent = `${emoji} ${data.data.content}`;
+                    }
+                    break;
+                }
+                
+                // 添加新的thinking step
+                if (stepContent) {
+                  lastMessage.thinkingSteps.push({
+                    type: stepType,
+                    content: stepContent
+                  });
+                  
+                  // 更新显示内容
+                  lastMessage.content = "AI正在思考中...";
+                  lastMessage.displayContent = "AI正在思考中...";
+                }
+              }
+              break;
+              
+          /*   case 'idle_timeout':
+              console.log('连接空闲超时,关闭连接');
+              eventSource.close();
+              // 移除临时消息
+              messages.value = messages.value.filter(msg => msg.id !== tempAiMessage.id);
+              // 添加提示消息
+              messages.value.push({
+                id: Date.now() + 2,
+                role: "assistant",
+                content: "连接超时,请重试",
+                displayContent: "连接超时,请重试",
+              });
+              break; */
+              
+            case 'session_ended':
+              console.log('会话结束');
+              eventSource.close();
+              break;
+              
+            case 'heartbeat':
+              // 忽略心跳消息
+              console.log('收到心跳消息');
+              break;
+              
+            default:
+              console.log('未处理的消息类型:', data.type);
+              break;
+          }
+        } catch (error) {
+          console.error('处理SSE消息失败:', error);
+        }
+      };
+
+      // 处理SSE错误
+      eventSource.onerror = (error) => {
+        console.error('SSE连接错误:', error);
+        eventSource.close();
+        
+        // 移除临时消息
+        messages.value = messages.value.filter(msg => msg.id !== tempAiMessage.id);
+        
+        // 添加错误消息
+        messages.value.push({
+          id: Date.now() + 2,
+          role: "assistant",
+          content: "连接出错,请重试",
+          displayContent: "连接出错,请重试",
+        });
+      };
     }
+    
+    
+
+  } catch (error) {
+    console.error("发送消息失败:", error);
+    // 移除临时消息
+    messages.value = messages.value.filter(msg => msg.id !== tempAiMessage.id);
+    
+    // 添加错误消息
+    messages.value.push({
+      id: Date.now() + 2,
+      role: "assistant",
+      content: "发送消息失败,请重试",
+      displayContent: "发送消息失败,请重试",
+    });
   } finally {
     isTyping.value = false;
     abortController.value = null;
-    await nextTick();
-    /* scrollToBottom(); */
   }
 };
 
@@ -1453,14 +1715,43 @@ const typeMessage = async (message) => {
   }
 
   return new Promise((resolve) => {
-    const delay = 30; // 打字延迟时间
+    const minDelay = 20; // 最小打字延迟
+    const maxDelay = 50; // 最大打字延迟
     let i = 0;
     message.displayContent = ""; // 确保开始时是空的
 
     const typeChar = () => {
       if (i < message.content.length && isTyping.value) {
-        message.displayContent = message.content.substring(0, i + 1);
-        i++;
+        // 处理特殊字符和标点符号
+        let chunk = "";
+        const char = message.content[i];
+        
+        // 如果是中文字符,整个字符一起显示
+        if (/[\u4e00-\u9fa5]/.test(char)) {
+          chunk = char;
+        } 
+        // 如果是标点符号,与前面的字符一起显示
+        else if (/[.,!?;,。!?;]/.test(char)) {
+          chunk = char;
+        }
+        // 如果是空格,与下一个单词一起显示
+        else if (char === " " && i + 1 < message.content.length) {
+          let j = i + 1;
+          while (j < message.content.length && !/[\s\n]/.test(message.content[j])) {
+            j++;
+          }
+          chunk = message.content.substring(i, j);
+        }
+        // 其他情况,单个字符显示
+        else {
+          chunk = char;
+        }
+
+        message.displayContent = message.content.substring(0, i) + chunk;
+        i += chunk.length;
+
+        // 根据字符类型动态调整延迟
+        const delay = chunk.length > 1 ? maxDelay : minDelay;
         typingTimeout.value = setTimeout(typeChar, delay);
       } else {
         if (!isTyping.value) {
@@ -1673,6 +1964,21 @@ onUnmounted(() => {
 // 添加 renderMarkdown 方法
 const renderMarkdown = (content) => {
   if (!content) return "";
+  
+  /* // 处理代码块
+  content = content.replace(/```([\s\S]*?)```/g, (match, code) => {
+    return `<pre class="code-block"><code>${code}</code></pre>`;
+  });
+  
+  // 处理行内代码
+  content = content.replace(/`([^`]+)`/g, '<code>$1</code>');
+  
+  // 处理标题
+  content = content.replace(/^(#{1,6})\s+(.*)$/gm, (match, hashes, title) => {
+    const level = hashes.length;
+    return `<h${level}>${title}</h${level}>`;
+  }); */
+  
   return md.render(content);
 };
 
@@ -1825,6 +2131,10 @@ const updateCollapsedState = () => {
       (activeTab.value === 'preview' && attachedFiles.value.length > 0)
     )); */
   });
+  // 推理模式
+  const handleInferenceModel = () => {
+    inferenceModel.value = !inferenceModel.value;
+  };
 
   // 修改知识库搜索开关处理方法
   const handleKnowledgeToggle = (event) => {
@@ -1936,7 +2246,7 @@ const updateCollapsedState = () => {
   const fetchModels = async () => {
     isLoadingModels.value = true;
     try {
-      const response = await fetch(`${API_BASE_URL}/api/models`);
+      const response = await fetch(`${API_BASE_URL}/api/models?merchant_id=${JSON.parse(localStorage.getItem('userInfo')).merchant.merchant_id}`);
       const data = await response.json();
       console.log(data);
       if (data.code === 2000) {
@@ -2271,6 +2581,36 @@ const updateCollapsedState = () => {
   const handleDocClick = (doc) => {
     currentDoc.value = doc;
   };
+
+ /*  
+
+ */
+
+  // 添加流式思考步骤
+  const currentThinkingSteps = ref([]);
+
+  // 添加流式思考步骤
+  const addThinkingStep = (step) => {
+    currentThinkingSteps.value.push({
+      type: step.type || 'default',
+      content: step.content,
+      timestamp: Date.now()
+    });
+  };
+
+  // 清除当前思考步骤
+  const clearThinkingSteps = () => {
+    currentThinkingSteps.value = [];
+  };
+
+  // 在消息发送完成后将思考步骤添加到消息中
+  const finalizeThinkingSteps = (messageId) => {
+    const message = messages.value.find(m => m.id === messageId);
+    if (message) {
+      message.thinkingSteps = [...currentThinkingSteps.value];
+      clearThinkingSteps();
+    }
+  };
  </script>
 
 <style scoped>
@@ -5454,4 +5794,107 @@ button,
   }
 }
 
+.thinking-steps {
+    margin-bottom: 10px;
+    font-size: 12px;
+    color: #666;
+  }
+
+  .thinking-step {
+    padding: 4px 8px;
+    margin-bottom: 4px;
+    border-radius: 4px;
+    background: #f5f5f5;
+    transition: all 0.3s ease;
+  }
+
+  .thinking-step.start {
+    background: #e8f5e9;
+  }
+
+  .thinking-step.text_analysis {
+    background: #e3f2fd;
+  }
+
+  .thinking-step.planning {
+    background: #fff3e0;
+  }
+
+  .thinking-step.execution {
+    background: #f3e5f5;
+  }
+
+  .thinking-step.result {
+    background: #e8eaf6;
+  }
+
+  .thinking-step.conclusion {
+    background: #e0f7fa;
+    font-weight: 500;
+  }
+
+</style>
+
+<style  scoped>
+.thinking-steps-stream {
+  position: sticky;
+  top: 0;
+  z-index: 100;
+  background: rgba(255, 255, 255, 0.95);
+  padding: 10px;
+  margin: 10px 0;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+
+  .thinking-step-stream {
+    padding: 8px 12px;
+    margin: 4px 0;
+    border-radius: 6px;
+    background: #f5f5f5;
+    font-size: 14px;
+    line-height: 1.5;
+    opacity: 0;
+    transform: translateY(-10px);
+    
+    &.fade-in {
+      animation: fadeInDown 0.3s ease forwards;
+    }
+
+    .typing-indicator {
+      display: inline-flex;
+      align-items: center;
+      margin-left: 8px;
+      
+      span {
+        width: 4px;
+        height: 4px;
+        margin: 0 2px;
+        background: #666;
+        border-radius: 50%;
+        animation: typing 1s infinite ease-in-out;
+        
+        &:nth-child(1) { animation-delay: 0.2s; }
+        &:nth-child(2) { animation-delay: 0.3s; }
+        &:nth-child(3) { animation-delay: 0.4s; }
+      }
+    }
+  }
+}
+
+@keyframes fadeInDown {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+@keyframes typing {
+  0%, 60%, 100% { transform: translateY(0); }
+  30% { transform: translateY(-4px); }
+}
+
 </style>