Преглед на файлове

新增功能:在智能体发送消息若出错,则可点击重新发送

余思翰 преди 1 ден
родител
ревизия
3854a66ffd
променени са 1 файла, в които са добавени 128 реда и са изтрити 33 реда
  1. 128
    33
      llm-ui/src/views/llm/agent/AgentDetail.vue

+ 128
- 33
llm-ui/src/views/llm/agent/AgentDetail.vue Целия файл

@@ -2,7 +2,7 @@
2 2
  * @Author: wrh
3 3
  * @Date: 2025-01-01 00:00:00
4 4
  * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-07-30 16:06:36
5
+ * @LastEditTime: 2025-08-01 14:33:27
6 6
 -->
7 7
 <template>
8 8
   <div class="agent-detail-container" v-loading="loading">
@@ -38,15 +38,8 @@
38 38
                 <div class="topic-time">{{ topic.createTime }}</div>
39 39
               </div>
40 40
               <div class="topic-actions">
41
-                <el-button 
42
-                  type="danger" 
43
-                  size="small" 
44
-                  :icon="Delete" 
45
-                  circle 
46
-                  @click.stop="handleDeleteTopic(topic)"
47
-                  class="delete-btn"
48
-                  title="删除对话"
49
-                />
41
+                <el-button type="danger" size="small" :icon="Delete" circle @click.stop="handleDeleteTopic(topic)"
42
+                  class="delete-btn" title="删除对话" />
50 43
               </div>
51 44
             </div>
52 45
           </div>
@@ -127,7 +120,13 @@
127 120
               <div class="message-content">
128 121
                 <div v-if="message.isHtml" class="message-text" v-html="message.content"></div>
129 122
                 <div v-else class="message-text">{{ message.content }}</div>
130
-                <div class="message-time">{{ message.timestamp }}</div>
123
+                <div class="message-actions">
124
+                  <span class="message-time">{{ message.timestamp }}</span>
125
+                  <el-button v-if="message.canRetry" type="text" size="small" @click="retryMessage(message)" class="retry-btn">
126
+                    <el-icon><Refresh /></el-icon>
127
+                    重新发送
128
+                  </el-button>
129
+                </div>
131 130
               </div>
132 131
             </div>
133 132
 
@@ -149,10 +148,10 @@
149 148
           <!-- 输入区域 -->
150 149
           <div class="chat-input-area">
151 150
             <el-input v-model="inputMessage" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }"
152
-              placeholder="请输入您的问题..." @keyup.ctrl.enter="sendMessage" />
151
+              placeholder="请输入您的问题..." @keydown.enter="handleEnterKeydown" />
153 152
             <div class="input-actions">
154
-              <span class="input-tip">Ctrl + Enter 发送</span>
155
-              <el-button type="primary" @click="sendMessage" :disabled="!inputMessage.trim() || isTyping">
153
+              <span class="input-tip">Enter 发送,Shift + Enter 换行</span>
154
+              <el-button type="primary" @click="sendMessage()" :disabled="!inputMessage.trim() || isTyping">
156 155
                 发送
157 156
               </el-button>
158 157
             </div>
@@ -172,7 +171,7 @@
172 171
 <script setup>
173 172
 import { ref, reactive, watch, nextTick, computed, getCurrentInstance } from 'vue';
174 173
 import { ElMessage, ElMessageBox } from 'element-plus';
175
-import { Delete } from '@element-plus/icons-vue';
174
+import { Delete, Refresh } from '@element-plus/icons-vue';
176 175
 import { getAgent, opening, uploadFile } from '@/api/llm/agent';
177 176
 import { answer } from '@/api/llm/mcp';
178 177
 import { listTopic, getTopic, delTopic, addTopic, updateTopic } from "@/api/llm/topic";
@@ -292,7 +291,9 @@ const loadChatMessages = async (topicId) => {
292 291
             role: 'user',
293 292
             content: chat.input,
294 293
             timestamp: chat.inputTime || new Date(chat.createTime).toLocaleTimeString(),
295
-            isHtml: false
294
+            isHtml: false,
295
+            originalContent: chat.input, // 为历史消息添加原始内容
296
+            canRetry: false // 历史消息默认不显示重试按钮
296 297
           })
297 298
         }
298 299
         // 添加助手回复消息
@@ -359,7 +360,7 @@ const handleDeleteTopic = async (topic) => {
359 360
 
360 361
     // 调用删除API
361 362
     await delTopic(topic.topicId)
362
-    
363
+
363 364
     // 如果删除的是当前选中的话题,则重置对话状态
364 365
     if (currentTopicId.value === topic.topicId) {
365 366
       currentTopicId.value = null
@@ -369,7 +370,7 @@ const handleDeleteTopic = async (topic) => {
369 370
 
370 371
     // 重新加载话题列表
371 372
     await loadTopic()
372
-    
373
+
373 374
     ElMessage.success('话题删除成功')
374 375
   } catch (error) {
375 376
     if (error !== 'cancel') {
@@ -379,9 +380,23 @@ const handleDeleteTopic = async (topic) => {
379 380
   }
380 381
 }
381 382
 
382
-// 发送消息
383
-const sendMessage = async () => {
384
-  const message = inputMessage.value.trim()
383
+// 处理 Enter 键事件
384
+const handleEnterKeydown = (event) => {
385
+  // 如果按住 Shift 键,允许换行
386
+  if (event.shiftKey) {
387
+    return
388
+  }
389
+  // 阻止默认的换行行为
390
+  event.preventDefault()
391
+  // 发送消息
392
+  sendMessage()
393
+}
394
+
395
+// 发送消息(支持重试)
396
+const sendMessage = async (retryContent = null) => {
397
+  // 确保 retryContent 是字符串类型,如果是事件对象则忽略
398
+  const validRetryContent = (typeof retryContent === 'string') ? retryContent : null
399
+  const message = validRetryContent || inputMessage.value.trim()
385 400
   if (!message || isTyping.value) return
386 401
 
387 402
   // 添加用户消息
@@ -389,10 +404,17 @@ const sendMessage = async () => {
389 404
     role: 'user',
390 405
     content: message,
391 406
     timestamp: new Date().toLocaleTimeString(),
392
-    isHtml: false // 用户消息不使用HTML渲染
407
+    isHtml: false, // 用户消息不使用HTML渲染
408
+    originalContent: message, // 保存原始内容用于重试
409
+    canRetry: false // 初始时不显示重试按钮
393 410
   }
394 411
   chatMessages.value.push(userMessage)
395
-  inputMessage.value = ''
412
+  
413
+  // 只有在非重试模式下才清空输入框
414
+  if (!retryContent) {
415
+    inputMessage.value = ''
416
+  }
417
+  
396 418
   isTyping.value = true
397 419
 
398 420
   nextTick(() => {
@@ -405,15 +427,26 @@ const sendMessage = async () => {
405 427
       topicId: currentTopicId.value,
406 428
       question: message
407 429
     })
408
-
430
+    let content = JSON.parse(response.resultContent).content;
431
+    console.log(content);
432
+    
433
+    // 检查是否是默认的失败回复
434
+    const defaultFailureMessage = '抱歉,我暂时无法回答这个问题。';
435
+    const finalContent = content || defaultFailureMessage;
436
+    
409 437
     // 添加助手回复
410 438
     const assistantMessage = {
411 439
       role: 'assistant',
412
-      content: response.resultContent || '抱歉,我暂时无法回答这个问题。',
440
+      content: finalContent,
413 441
       timestamp: new Date().toLocaleTimeString(),
414
-      isHtml: false // 普通聊天消息不使用HTML渲染
442
+      isHtml: true // 普通聊天消息使用HTML渲染
415 443
     }
416 444
     chatMessages.value.push(assistantMessage)
445
+    
446
+    // 如果是默认失败回复,为用户消息添加重试按钮
447
+    if (finalContent === defaultFailureMessage) {
448
+      userMessage.canRetry = true
449
+    }
417 450
   } catch (error) {
418 451
     console.error('发送消息失败:', error)
419 452
     ElMessage.error('发送消息失败')
@@ -426,6 +459,9 @@ const sendMessage = async () => {
426 459
       isHtml: false
427 460
     }
428 461
     chatMessages.value.push(errorMessage)
462
+    
463
+    // 为用户消息添加重试按钮
464
+    userMessage.canRetry = true
429 465
   } finally {
430 466
     isTyping.value = false
431 467
     nextTick(() => {
@@ -434,6 +470,27 @@ const sendMessage = async () => {
434 470
   }
435 471
 }
436 472
 
473
+// 重新发送消息
474
+const retryMessage = async (message) => {
475
+  // 找到当前消息在数组中的索引
476
+  const messageIndex = chatMessages.value.findIndex(msg => msg === message)
477
+  if (messageIndex === -1) return
478
+  
479
+  // 获取原始消息内容
480
+  const originalContent = message.originalContent || message.content
481
+  if (!originalContent || typeof originalContent !== 'string') {
482
+    ElMessage.error('无法获取原始消息内容')
483
+    return
484
+  }
485
+  
486
+  // 移除从当前用户消息开始的所有消息(包括后续的助手回复)
487
+  const messagesToKeep = chatMessages.value.slice(0, messageIndex)
488
+  chatMessages.value = messagesToKeep
489
+  
490
+  // 重新发送原始消息
491
+  await sendMessage(originalContent)
492
+}
493
+
437 494
 // 滚动到底部
438 495
 const scrollToBottom = () => {
439 496
   if (messagesContainer.value) {
@@ -750,7 +807,7 @@ const formatContentLinks = (content) => {
750 807
           cursor: pointer;
751 808
           min-width: 0; // 允许flex项目缩小到内容以下
752 809
           max-width: calc(100% - 40px); // 为删除按钮预留40px空间
753
-          
810
+
754 811
           .topic-name {
755 812
             font-size: 13px;
756 813
             font-weight: 500;
@@ -786,19 +843,19 @@ const formatContentLinks = (content) => {
786 843
           justify-content: center;
787 844
           opacity: 0;
788 845
           transition: opacity 0.3s ease;
789
-          
846
+
790 847
           .delete-btn {
791 848
             width: 24px;
792 849
             height: 24px;
793 850
             padding: 0;
794 851
             border: none;
795 852
             background: #ff4757;
796
-            
853
+
797 854
             &:hover {
798 855
               background: #ff3742;
799 856
               transform: scale(1.1);
800 857
             }
801
-            
858
+
802 859
             .el-icon {
803 860
               font-size: 12px;
804 861
             }
@@ -872,6 +929,22 @@ const formatContentLinks = (content) => {
872 929
               background: #1890ff;
873 930
               color: white;
874 931
             }
932
+            
933
+            .message-actions {
934
+              justify-content: flex-end;
935
+              
936
+              .retry-btn {
937
+                order: -1; // 将重试按钮放在时间前面
938
+                margin-left: 0;
939
+                margin-right: 8px;
940
+                color: #1890ff;
941
+                background-color: rgba(255, 255, 255, 0.9);
942
+                
943
+                &:hover {
944
+                  background-color: white;
945
+                }
946
+              }
947
+            }
875 948
           }
876 949
         }
877 950
 
@@ -952,10 +1025,32 @@ const formatContentLinks = (content) => {
952 1025
             }
953 1026
           }
954 1027
 
955
-          .message-time {
956
-            font-size: 11px;
957
-            color: #999;
1028
+          .message-actions {
1029
+            display: flex;
1030
+            align-items: center;
1031
+            justify-content: space-between;
958 1032
             margin-top: 4px;
1033
+            
1034
+            .message-time {
1035
+              font-size: 11px;
1036
+              color: #999;
1037
+            }
1038
+            
1039
+            .retry-btn {
1040
+              font-size: 11px;
1041
+              color: #1890ff;
1042
+              padding: 2px 6px;
1043
+              margin-left: 8px;
1044
+              
1045
+              &:hover {
1046
+                background-color: #f0f8ff;
1047
+              }
1048
+              
1049
+              .el-icon {
1050
+                font-size: 12px;
1051
+                margin-right: 2px;
1052
+              }
1053
+            }
959 1054
           }
960 1055
         }
961 1056
 

Loading…
Отказ
Запис