|
@@ -124,7 +124,7 @@
|
124
|
124
|
</div>
|
125
|
125
|
|
126
|
126
|
<div v-else class="file-list">
|
127
|
|
- <div v-for="(file, index) in fileList" :key="index" class="file-item">
|
|
127
|
+ <div v-for="(file, index) in pagedFileList" :key="index" class="file-item">
|
128
|
128
|
<div class="file-info">
|
129
|
129
|
<el-icon class="file-icon">
|
130
|
130
|
<Document />
|
|
@@ -143,6 +143,10 @@
|
143
|
143
|
</div>
|
144
|
144
|
</div>
|
145
|
145
|
</div>
|
|
146
|
+
|
|
147
|
+ <!-- 文件列表分页 -->
|
|
148
|
+ <pagination v-show="fileTotal > 0" :total="fileTotal" v-model:page="filePageNum" v-model:limit="filePageSize" :autoScroll="false"
|
|
149
|
+ @pagination="handleFilePagination" />
|
146
|
150
|
</div>
|
147
|
151
|
|
148
|
152
|
<!-- 聊天模式 -->
|
|
@@ -167,7 +171,7 @@
|
167
|
171
|
</div>
|
168
|
172
|
|
169
|
173
|
<div v-else class="message-list">
|
170
|
|
- <div v-for="(message, index) in chatMessages" :key="index" class="message-item"
|
|
174
|
+ <div v-for="(message, index) in chatMessages" :key="index" class="message-item"
|
171
|
175
|
:class="[message.type, { 'typing': message.type === 'ai' && isSending && index === chatMessages.length - 1 }]">
|
172
|
176
|
<div class="message-avatar">
|
173
|
177
|
<div v-if="message.type === 'user'" class="user-avatar">
|
|
@@ -182,13 +186,47 @@
|
182
|
186
|
<div class="message-content">
|
183
|
187
|
<div class="message-bubble" :class="message.type">
|
184
|
188
|
<div class="message-text" v-html="formatMessage(message.content)"></div>
|
185
|
|
- <div class="message-time">{{ formatMessageTime(message.time) }}</div>
|
|
189
|
+
|
|
190
|
+ <!-- AI回答的引用文件信息 -->
|
|
191
|
+ <div v-if="message.type === 'ai' && message.references && message.references.length > 0" class="message-references">
|
|
192
|
+ <div class="references-header">
|
|
193
|
+ <el-icon class="reference-icon"><Document /></el-icon>
|
|
194
|
+ <span>参考文件 ({{ message.references.length }})</span>
|
|
195
|
+ <el-button
|
|
196
|
+ v-if="message.references.length > 3"
|
|
197
|
+ type="text"
|
|
198
|
+ size="small"
|
|
199
|
+ class="toggle-references"
|
|
200
|
+ @click="toggleReferences(index)"
|
|
201
|
+ >
|
|
202
|
+ {{ message.showAllReferences ? '收起' : '展开全部' }}
|
|
203
|
+ </el-button>
|
|
204
|
+ </div>
|
|
205
|
+ <div class="references-list">
|
|
206
|
+ <div
|
|
207
|
+ v-for="(ref, refIndex) in getDisplayedReferences(message)"
|
|
208
|
+ :key="refIndex"
|
|
209
|
+ class="reference-item"
|
|
210
|
+ >
|
|
211
|
+ <div class="reference-info">
|
|
212
|
+ <span class="reference-filename" :title="ref.fileName">{{ ref.fileName }}</span>
|
|
213
|
+ <span class="reference-similarity" :class="getSimilarityClass(ref.similarity)">
|
|
214
|
+ 相似度: {{ formatSimilarity(ref.similarity) }}%
|
|
215
|
+ </span>
|
|
216
|
+ </div>
|
|
217
|
+ </div>
|
|
218
|
+ </div>
|
|
219
|
+ </div>
|
|
220
|
+
|
|
221
|
+ <div class="message-time">{{ message.time }}</div>
|
186
|
222
|
</div>
|
187
|
223
|
</div>
|
188
|
224
|
</div>
|
189
|
225
|
|
190
|
226
|
<!-- AI回答loading状态 -->
|
191
|
|
- <div v-if="isSending && chatMessages.length > 0 && chatMessages[chatMessages.length - 1].type === 'ai' && chatMessages[chatMessages.length - 1].content === ''" class="message-item ai">
|
|
227
|
+ <div
|
|
228
|
+ v-if="isSending && chatMessages.length > 0 && chatMessages[chatMessages.length - 1].type === 'ai' && chatMessages[chatMessages.length - 1].content === ''"
|
|
229
|
+ class="message-item ai">
|
192
|
230
|
<div class="message-avatar">
|
193
|
231
|
<div class="ai-avatar">
|
194
|
232
|
<el-icon>
|
|
@@ -212,10 +250,9 @@
|
212
|
250
|
<!-- 输入区域 -->
|
213
|
251
|
<div class="chat-input-area">
|
214
|
252
|
<div class="input-container">
|
215
|
|
- <el-input v-model="chatInput" type="textarea" :rows="3"
|
216
|
|
- :placeholder="isSending ? 'AI正在思考中...' : '请输入您的问题...'"
|
217
|
|
- @keydown.enter="handleEnter" @keydown.ctrl.enter="handleCtrlEnter" :disabled="isSending"
|
218
|
|
- resize="none" />
|
|
253
|
+ <el-input v-model="chatInput" type="textarea" :rows="3"
|
|
254
|
+ :placeholder="isSending ? 'AI正在思考中...' : '请输入您的问题...'" @keydown.enter="handleEnter"
|
|
255
|
+ @keydown.ctrl.enter="handleCtrlEnter" :disabled="isSending" resize="none" />
|
219
|
256
|
<div class="input-actions">
|
220
|
257
|
<el-button v-if="!isSending" type="primary" :icon="Promotion" @click="sendMessage"
|
221
|
258
|
:disabled="!chatInput.trim() || chatInput.length > 2000">
|
|
@@ -297,7 +334,7 @@
|
297
|
334
|
import { ref, reactive, getCurrentInstance, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
|
298
|
335
|
import { Search, Refresh, Plus, Edit, Delete, Upload, UploadFilled, Folder, MoreFilled, FolderOpened, Document, ChatRound, User, ChatDotRound, Promotion } from '@element-plus/icons-vue'
|
299
|
336
|
import { listKnowledge, addKnowledge, updateKnowledge, delKnowledge, insertKnowledgeFile, listKnowledgeDocument, deleteKnowledgeFile } from "@/api/llm/knowLedge";
|
300
|
|
-import { getAnswer, getAnswerStream } from '@/api/llm/rag';
|
|
337
|
+import { getAnswer, getAnswerStream,getContextFile } from '@/api/llm/rag';
|
301
|
338
|
import { getToken } from "@/utils/auth";
|
302
|
339
|
|
303
|
340
|
const { proxy } = getCurrentInstance();
|
|
@@ -317,6 +354,16 @@ const isModify = ref(false);
|
317
|
354
|
const selectedKnowledge = ref(null);
|
318
|
355
|
const fileList = ref([]);
|
319
|
356
|
|
|
357
|
+// 文件分页相关状态
|
|
358
|
+const fileTotal = ref(0);
|
|
359
|
+const filePageNum = ref(1);
|
|
360
|
+const filePageSize = ref(10);
|
|
361
|
+const pagedFileList = computed(() => {
|
|
362
|
+ const start = (filePageNum.value - 1) * filePageSize.value;
|
|
363
|
+ const end = start + filePageSize.value;
|
|
364
|
+ return fileList.value.slice(start, end);
|
|
365
|
+});
|
|
366
|
+
|
320
|
367
|
// 聊天相关状态
|
321
|
368
|
const isChatMode = ref(false);
|
322
|
369
|
const chatMessages = ref([]);
|
|
@@ -375,6 +422,11 @@ function getList() {
|
375
|
422
|
// 确保返回的数据是数组格式
|
376
|
423
|
if (Array.isArray(response.data)) {
|
377
|
424
|
knowledgeList.value = response.data;
|
|
425
|
+ knowledgeList.value.map(item => {
|
|
426
|
+ listKnowledgeDocument(item.collectionName).then(response => {
|
|
427
|
+ item.fileCount = response.data.length;
|
|
428
|
+ })
|
|
429
|
+ })
|
378
|
430
|
} else {
|
379
|
431
|
knowledgeList.value = [];
|
380
|
432
|
}
|
|
@@ -391,16 +443,21 @@ function selectKnowledge(knowledge) {
|
391
|
443
|
if (isSending.value) {
|
392
|
444
|
stopGenerating();
|
393
|
445
|
}
|
394
|
|
-
|
|
446
|
+
|
395
|
447
|
selectedKnowledge.value = knowledge;
|
396
|
448
|
// 重置聊天模式
|
397
|
449
|
isChatMode.value = false;
|
398
|
450
|
chatMessages.value = [];
|
399
|
451
|
chatInput.value = '';
|
400
|
452
|
|
|
453
|
+ // 重置文件分页状态
|
|
454
|
+ filePageNum.value = 1;
|
|
455
|
+ filePageSize.value = 10;
|
|
456
|
+
|
401
|
457
|
fileLoading.value = true;
|
402
|
458
|
listKnowledgeDocument(knowledge.collectionName).then(response => {
|
403
|
459
|
fileList.value = response.data;
|
|
460
|
+ fileTotal.value = response.data.length;
|
404
|
461
|
fileLoading.value = false;
|
405
|
462
|
}).catch(error => {
|
406
|
463
|
fileLoading.value = false;
|
|
@@ -418,7 +475,7 @@ function handleChat(knowledge) {
|
418
|
475
|
if (isSending.value) {
|
419
|
476
|
stopGenerating();
|
420
|
477
|
}
|
421
|
|
-
|
|
478
|
+
|
422
|
479
|
isChatMode.value = true;
|
423
|
480
|
chatMessages.value = [];
|
424
|
481
|
chatInput.value = '';
|
|
@@ -434,14 +491,14 @@ function switchToFileMode() {
|
434
|
491
|
if (isSending.value) {
|
435
|
492
|
stopGenerating();
|
436
|
493
|
}
|
437
|
|
-
|
|
494
|
+
|
438
|
495
|
isChatMode.value = false;
|
439
|
496
|
}
|
440
|
497
|
|
441
|
498
|
/** 发送消息 */
|
442
|
499
|
function sendMessage() {
|
443
|
500
|
if (!chatInput.value.trim()) return;
|
444
|
|
-
|
|
501
|
+
|
445
|
502
|
// 如果正在发送,先停止当前请求
|
446
|
503
|
if (isSending.value) {
|
447
|
504
|
stopGenerating();
|
|
@@ -450,7 +507,7 @@ function sendMessage() {
|
450
|
507
|
const userMessage = {
|
451
|
508
|
type: 'user',
|
452
|
509
|
content: chatInput.value.trim(),
|
453
|
|
- time: new Date()
|
|
510
|
+ time: proxy.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
|
454
|
511
|
};
|
455
|
512
|
|
456
|
513
|
chatMessages.value.push(userMessage);
|
|
@@ -467,33 +524,53 @@ function sendMessage() {
|
467
|
524
|
const aiMessage = reactive({
|
468
|
525
|
type: 'ai',
|
469
|
526
|
content: '',
|
470
|
|
- time: new Date()
|
|
527
|
+ time: proxy.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'),
|
|
528
|
+ references: [], // 引用的文件信息
|
|
529
|
+ showAllReferences: false // 是否显示所有引用文件
|
471
|
530
|
});
|
472
|
531
|
chatMessages.value.push(aiMessage);
|
473
|
532
|
|
474
|
533
|
// 使用流式API获取回答
|
475
|
534
|
const eventSource = getAnswerStream(
|
476
|
|
- currentInput,
|
|
535
|
+ currentInput,
|
477
|
536
|
selectedKnowledge.value.collectionName,
|
478
|
|
- // onMessage: 接收到每个字符时的回调
|
|
537
|
+ // onMessage: 接收到每个字符时的回调
|
479
|
538
|
(content) => {
|
480
|
539
|
// 处理接收到的内容
|
481
|
540
|
console.log('=== 前端接收到内容 ===', content)
|
482
|
|
-
|
|
541
|
+
|
483
|
542
|
// 清理内容中的</think>标签
|
484
|
543
|
let cleanContent = content.replace(/<\/?think>/g, '');
|
485
|
|
-
|
|
544
|
+
|
486
|
545
|
// 如果内容为空或只包含空白字符,跳过
|
487
|
546
|
if (!cleanContent.trim()) {
|
488
|
547
|
return;
|
489
|
548
|
}
|
490
|
|
-
|
|
549
|
+
|
491
|
550
|
// 直接替换内容,避免重复叠加
|
492
|
551
|
aiMessage.content = cleanContent;
|
493
|
|
-
|
|
552
|
+ aiMessage.time = proxy.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}');
|
494
|
553
|
console.log('=== 清理后的内容 ===', cleanContent)
|
495
|
554
|
console.log('=== 当前AI消息完整内容 ===', aiMessage.content)
|
|
555
|
+
|
|
556
|
+ // 每次接收到消息都重置超时定时器
|
|
557
|
+ if (window.responseTimeout) {
|
|
558
|
+ clearTimeout(window.responseTimeout);
|
|
559
|
+ }
|
496
|
560
|
|
|
561
|
+ // 重新设置超时定时器(30秒无新消息才超时)
|
|
562
|
+ window.responseTimeout = setTimeout(() => {
|
|
563
|
+ if (isSending.value) {
|
|
564
|
+ console.log('=== 响应超时强制结束 ===')
|
|
565
|
+ isSending.value = false;
|
|
566
|
+ if (window.currentController) {
|
|
567
|
+ window.currentController.abort();
|
|
568
|
+ window.currentController = null;
|
|
569
|
+ }
|
|
570
|
+ window.responseTimeout = null;
|
|
571
|
+ }
|
|
572
|
+ }, 30000); // 30秒无响应超时
|
|
573
|
+
|
497
|
574
|
// 滚动到底部
|
498
|
575
|
nextTick(() => {
|
499
|
576
|
scrollToBottom();
|
|
@@ -502,6 +579,13 @@ function sendMessage() {
|
502
|
579
|
// onError: 发生错误时的回调
|
503
|
580
|
(error) => {
|
504
|
581
|
console.error('=== 流式回答错误 ===', error);
|
|
582
|
+
|
|
583
|
+ // 清除超时定时器
|
|
584
|
+ if (window.responseTimeout) {
|
|
585
|
+ clearTimeout(window.responseTimeout);
|
|
586
|
+ window.responseTimeout = null;
|
|
587
|
+ }
|
|
588
|
+
|
505
|
589
|
if (aiMessage.content === '') {
|
506
|
590
|
aiMessage.content = '抱歉,我暂时无法回答您的问题,请稍后再试。';
|
507
|
591
|
} else {
|
|
@@ -509,6 +593,12 @@ function sendMessage() {
|
509
|
593
|
}
|
510
|
594
|
isSending.value = false;
|
511
|
595
|
console.log('=== 错误时isSending设置为false ===')
|
|
596
|
+
|
|
597
|
+ // 清理控制器
|
|
598
|
+ if (window.currentController) {
|
|
599
|
+ window.currentController = null;
|
|
600
|
+ }
|
|
601
|
+
|
512
|
602
|
// 滚动到底部
|
513
|
603
|
nextTick(() => {
|
514
|
604
|
scrollToBottom();
|
|
@@ -516,10 +606,22 @@ function sendMessage() {
|
516
|
606
|
},
|
517
|
607
|
// onComplete: 回答完成时的回调
|
518
|
608
|
() => {
|
519
|
|
- console.log('=== 回答完成 ===')
|
520
|
|
- isSending.value = false;
|
521
|
|
- console.log('=== isSending设置为false ===')
|
|
609
|
+ console.log('=== 回答完成,正确进入onComplete ===')
|
|
610
|
+
|
|
611
|
+ // 清除超时定时器
|
|
612
|
+ if (window.responseTimeout) {
|
|
613
|
+ clearTimeout(window.responseTimeout);
|
|
614
|
+ window.responseTimeout = null;
|
|
615
|
+ }
|
522
|
616
|
|
|
617
|
+ isSending.value = false;
|
|
618
|
+ console.log('=== onComplete中isSending设置为false ===')
|
|
619
|
+
|
|
620
|
+ // 清理控制器
|
|
621
|
+ if (window.currentController) {
|
|
622
|
+ window.currentController = null;
|
|
623
|
+ }
|
|
624
|
+
|
523
|
625
|
// 确保状态更新
|
524
|
626
|
nextTick(() => {
|
525
|
627
|
console.log('=== 最终isSending状态 ===', isSending.value)
|
|
@@ -527,28 +629,51 @@ function sendMessage() {
|
527
|
629
|
});
|
528
|
630
|
}
|
529
|
631
|
);
|
530
|
|
-
|
|
632
|
+ // 获取上下文引用文件
|
|
633
|
+ getContextFile(currentInput, selectedKnowledge.value.collectionName).then(response => {
|
|
634
|
+ console.log('=== 上下文文件 ===', response)
|
|
635
|
+ if (response && Array.isArray(response)) {
|
|
636
|
+ aiMessage.references = response.map(item => ({
|
|
637
|
+ fileName: item.file_name,
|
|
638
|
+ similarity: item.distance,
|
|
639
|
+ content: item.content
|
|
640
|
+ }));
|
|
641
|
+ }
|
|
642
|
+ }).catch(error => {
|
|
643
|
+ console.error('获取上下文文件失败:', error);
|
|
644
|
+ });
|
531
|
645
|
// 如果用户快速发送多条消息,取消之前的请求
|
532
|
646
|
if (window.currentController) {
|
533
|
647
|
window.currentController.abort();
|
534
|
648
|
}
|
535
|
|
- window.currentController = eventSource;
|
|
649
|
+ if (window.responseTimeout) {
|
|
650
|
+ clearTimeout(window.responseTimeout);
|
|
651
|
+ }
|
536
|
652
|
|
537
|
|
- // 添加超时机制,确保状态能够正确切换
|
538
|
|
- setTimeout(() => {
|
|
653
|
+ window.currentController = eventSource;
|
|
654
|
+
|
|
655
|
+ // 设置初始超时定时器(30秒无任何响应才超时)
|
|
656
|
+ window.responseTimeout = setTimeout(() => {
|
539
|
657
|
if (isSending.value) {
|
540
|
|
- console.log('=== 超时强制结束 ===')
|
|
658
|
+ console.log('=== 初始连接超时强制结束 ===')
|
541
|
659
|
isSending.value = false;
|
542
|
660
|
if (window.currentController) {
|
543
|
661
|
window.currentController.abort();
|
544
|
662
|
window.currentController = null;
|
545
|
663
|
}
|
|
664
|
+ window.responseTimeout = null;
|
546
|
665
|
}
|
547
|
|
- }, 10000); // 10秒超时
|
|
666
|
+ }, 30000); // 30秒超时
|
548
|
667
|
}
|
549
|
668
|
|
550
|
669
|
/** 停止生成回答 */
|
551
|
670
|
function stopGenerating() {
|
|
671
|
+ // 清除超时定时器
|
|
672
|
+ if (window.responseTimeout) {
|
|
673
|
+ clearTimeout(window.responseTimeout);
|
|
674
|
+ window.responseTimeout = null;
|
|
675
|
+ }
|
|
676
|
+
|
552
|
677
|
if (window.currentController) {
|
553
|
678
|
window.currentController.abort();
|
554
|
679
|
window.currentController = null;
|
|
@@ -583,14 +708,43 @@ function formatMessage(content) {
|
583
|
708
|
return content.replace(/\n/g, '<br>');
|
584
|
709
|
}
|
585
|
710
|
|
586
|
|
-/** 格式化消息时间 */
|
587
|
|
-function formatMessageTime(time) {
|
588
|
|
- if (!time) return '';
|
589
|
|
- const date = new Date(time);
|
590
|
|
- return date.toLocaleTimeString('zh-CN', {
|
591
|
|
- hour: '2-digit',
|
592
|
|
- minute: '2-digit'
|
593
|
|
- });
|
|
711
|
+/** 格式化相似度 */
|
|
712
|
+function formatSimilarity(similarity) {
|
|
713
|
+ // 如果相似度已经是百分比形式(>1),直接返回
|
|
714
|
+ if (similarity > 1) {
|
|
715
|
+ return similarity.toFixed(1);
|
|
716
|
+ }
|
|
717
|
+ // 如果是0-1之间的值,转换为百分比
|
|
718
|
+ return (similarity * 100).toFixed(1);
|
|
719
|
+}
|
|
720
|
+
|
|
721
|
+/** 获取相似度的CSS类 */
|
|
722
|
+function getSimilarityClass(similarity) {
|
|
723
|
+ const percentage = similarity > 1 ? similarity : similarity * 100;
|
|
724
|
+ if (percentage >= 80) return 'high-similarity';
|
|
725
|
+ if (percentage >= 60) return 'medium-similarity';
|
|
726
|
+ return 'low-similarity';
|
|
727
|
+}
|
|
728
|
+
|
|
729
|
+/** 切换引用文件的展开/收起状态 */
|
|
730
|
+function toggleReferences(messageIndex) {
|
|
731
|
+ const message = chatMessages.value[messageIndex];
|
|
732
|
+ message.showAllReferences = !message.showAllReferences;
|
|
733
|
+}
|
|
734
|
+
|
|
735
|
+/** 获取要显示的引用文件列表 */
|
|
736
|
+function getDisplayedReferences(message) {
|
|
737
|
+ if (!message.references || message.references.length === 0) {
|
|
738
|
+ return [];
|
|
739
|
+ }
|
|
740
|
+
|
|
741
|
+ // 如果引用文件数量 <= 3 或者设置为显示全部,则显示所有
|
|
742
|
+ if (message.references.length <= 3 || message.showAllReferences) {
|
|
743
|
+ return message.references;
|
|
744
|
+ }
|
|
745
|
+
|
|
746
|
+ // 否则只显示前3个
|
|
747
|
+ return message.references.slice(0, 3);
|
594
|
748
|
}
|
595
|
749
|
|
596
|
750
|
/** 处理卡片操作 */
|
|
@@ -619,6 +773,14 @@ function handleDeleteFile(file) {
|
619
|
773
|
fileLoading.value = true;
|
620
|
774
|
listKnowledgeDocument(selectedKnowledge.value.collectionName).then(response => {
|
621
|
775
|
fileList.value = response.data;
|
|
776
|
+ fileTotal.value = response.data.length;
|
|
777
|
+
|
|
778
|
+ // 如果当前页没有数据了,回到上一页
|
|
779
|
+ const maxPage = Math.ceil(fileTotal.value / filePageSize.value);
|
|
780
|
+ if (filePageNum.value > maxPage && maxPage > 0) {
|
|
781
|
+ filePageNum.value = maxPage;
|
|
782
|
+ }
|
|
783
|
+
|
622
|
784
|
fileLoading.value = false;
|
623
|
785
|
}).catch(error => {
|
624
|
786
|
fileLoading.value = false;
|
|
@@ -645,6 +807,12 @@ function getFileType(fileName) {
|
645
|
807
|
}
|
646
|
808
|
}
|
647
|
809
|
|
|
810
|
+/** 文件分页处理 */
|
|
811
|
+function handleFilePagination(pagination) {
|
|
812
|
+ filePageNum.value = pagination.page;
|
|
813
|
+ filePageSize.value = pagination.limit;
|
|
814
|
+}
|
|
815
|
+
|
648
|
816
|
// 取消按钮
|
649
|
817
|
function cancel() {
|
650
|
818
|
open.value = false;
|
|
@@ -786,6 +954,7 @@ function handleFileSuccess(response, file, fileList) {
|
786
|
954
|
fileLoading.value = true;
|
787
|
955
|
listKnowledgeDocument(selectedKnowledge.value.collectionName).then(response => {
|
788
|
956
|
fileList.value = response.data;
|
|
957
|
+ fileTotal.value = response.data.length;
|
789
|
958
|
fileLoading.value = false;
|
790
|
959
|
}).catch(error => {
|
791
|
960
|
fileLoading.value = false;
|
|
@@ -816,12 +985,16 @@ onMounted(() => {
|
816
|
985
|
getList();
|
817
|
986
|
});
|
818
|
987
|
|
819
|
|
-// 组件卸载时清理请求控制器
|
|
988
|
+// 组件卸载时清理请求控制器和超时定时器
|
820
|
989
|
onUnmounted(() => {
|
821
|
990
|
if (window.currentController) {
|
822
|
991
|
window.currentController.abort();
|
823
|
992
|
window.currentController = null;
|
824
|
993
|
}
|
|
994
|
+ if (window.responseTimeout) {
|
|
995
|
+ clearTimeout(window.responseTimeout);
|
|
996
|
+ window.responseTimeout = null;
|
|
997
|
+ }
|
825
|
998
|
});
|
826
|
999
|
</script>
|
827
|
1000
|
|
|
@@ -1025,7 +1198,7 @@ onUnmounted(() => {
|
1025
|
1198
|
padding: 12px 16px;
|
1026
|
1199
|
background: #f0f9ff;
|
1027
|
1200
|
border-radius: 6px;
|
1028
|
|
- margin: 20px 20px 0 20px;
|
|
1201
|
+ margin-bottom: 20px;
|
1029
|
1202
|
|
1030
|
1203
|
.folder-icon {
|
1031
|
1204
|
color: #409eff;
|
|
@@ -1156,6 +1329,94 @@ onUnmounted(() => {
|
1156
|
1329
|
white-space: pre-wrap;
|
1157
|
1330
|
}
|
1158
|
1331
|
|
|
1332
|
+ .message-references {
|
|
1333
|
+ margin: 12px 0;
|
|
1334
|
+ padding: 12px;
|
|
1335
|
+ background: rgba(0, 0, 0, 0.05);
|
|
1336
|
+ border-radius: 6px;
|
|
1337
|
+ border-left: 3px solid #409eff;
|
|
1338
|
+
|
|
1339
|
+ .references-header {
|
|
1340
|
+ display: flex;
|
|
1341
|
+ align-items: center;
|
|
1342
|
+ justify-content: space-between;
|
|
1343
|
+ gap: 6px;
|
|
1344
|
+ margin-bottom: 8px;
|
|
1345
|
+ font-size: 13px;
|
|
1346
|
+ font-weight: 600;
|
|
1347
|
+ color: #409eff;
|
|
1348
|
+
|
|
1349
|
+ .reference-icon {
|
|
1350
|
+ font-size: 14px;
|
|
1351
|
+ }
|
|
1352
|
+
|
|
1353
|
+ .toggle-references {
|
|
1354
|
+ font-size: 11px;
|
|
1355
|
+ color: #909399;
|
|
1356
|
+ padding: 0;
|
|
1357
|
+ height: auto;
|
|
1358
|
+
|
|
1359
|
+ &:hover {
|
|
1360
|
+ color: #409eff;
|
|
1361
|
+ }
|
|
1362
|
+ }
|
|
1363
|
+ }
|
|
1364
|
+
|
|
1365
|
+ .references-list {
|
|
1366
|
+ display: flex;
|
|
1367
|
+ flex-direction: column;
|
|
1368
|
+ gap: 6px;
|
|
1369
|
+ }
|
|
1370
|
+
|
|
1371
|
+ .reference-item {
|
|
1372
|
+ padding: 6px 8px;
|
|
1373
|
+ background: rgba(255, 255, 255, 0.8);
|
|
1374
|
+ border-radius: 4px;
|
|
1375
|
+ border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1376
|
+
|
|
1377
|
+ .reference-info {
|
|
1378
|
+ display: flex;
|
|
1379
|
+ justify-content: space-between;
|
|
1380
|
+ align-items: center;
|
|
1381
|
+ gap: 8px;
|
|
1382
|
+
|
|
1383
|
+ .reference-filename {
|
|
1384
|
+ font-size: 12px;
|
|
1385
|
+ color: #303133;
|
|
1386
|
+ font-weight: 500;
|
|
1387
|
+ flex: 1;
|
|
1388
|
+ overflow: hidden;
|
|
1389
|
+ text-overflow: ellipsis;
|
|
1390
|
+ white-space: nowrap;
|
|
1391
|
+ }
|
|
1392
|
+
|
|
1393
|
+ .reference-similarity {
|
|
1394
|
+ font-size: 11px;
|
|
1395
|
+ padding: 2px 6px;
|
|
1396
|
+ border-radius: 10px;
|
|
1397
|
+ white-space: nowrap;
|
|
1398
|
+ font-weight: 500;
|
|
1399
|
+ transition: all 0.3s ease;
|
|
1400
|
+
|
|
1401
|
+ &.high-similarity {
|
|
1402
|
+ color: #67c23a;
|
|
1403
|
+ background: #e1f3d8;
|
|
1404
|
+ }
|
|
1405
|
+
|
|
1406
|
+ &.medium-similarity {
|
|
1407
|
+ color: #e6a23c;
|
|
1408
|
+ background: #fdf5e6;
|
|
1409
|
+ }
|
|
1410
|
+
|
|
1411
|
+ &.low-similarity {
|
|
1412
|
+ color: #f56c6c;
|
|
1413
|
+ background: #fef0f0;
|
|
1414
|
+ }
|
|
1415
|
+ }
|
|
1416
|
+ }
|
|
1417
|
+ }
|
|
1418
|
+ }
|
|
1419
|
+
|
1159
|
1420
|
.message-time {
|
1160
|
1421
|
font-size: 12px;
|
1161
|
1422
|
opacity: 0.7;
|
|
@@ -1170,8 +1431,16 @@ onUnmounted(() => {
|
1170
|
1431
|
}
|
1171
|
1432
|
|
1172
|
1433
|
@keyframes blink {
|
1173
|
|
- 0%, 50% { opacity: 1; }
|
1174
|
|
- 51%, 100% { opacity: 0; }
|
|
1434
|
+
|
|
1435
|
+ 0%,
|
|
1436
|
+ 50% {
|
|
1437
|
+ opacity: 1;
|
|
1438
|
+ }
|
|
1439
|
+
|
|
1440
|
+ 51%,
|
|
1441
|
+ 100% {
|
|
1442
|
+ opacity: 0;
|
|
1443
|
+ }
|
1175
|
1444
|
}
|
1176
|
1445
|
|
1177
|
1446
|
.chat-input-area {
|
|
@@ -1202,15 +1471,15 @@ onUnmounted(() => {
|
1202
|
1471
|
display: flex;
|
1203
|
1472
|
justify-content: space-between;
|
1204
|
1473
|
align-items: center;
|
1205
|
|
-
|
|
1474
|
+
|
1206
|
1475
|
.char-count {
|
1207
|
1476
|
color: #409eff;
|
1208
|
1477
|
font-weight: 500;
|
1209
|
|
-
|
|
1478
|
+
|
1210
|
1479
|
&.warning {
|
1211
|
1480
|
color: #e6a23c;
|
1212
|
1481
|
}
|
1213
|
|
-
|
|
1482
|
+
|
1214
|
1483
|
.error-text {
|
1215
|
1484
|
color: #f56c6c;
|
1216
|
1485
|
font-size: 11px;
|
|
@@ -1283,6 +1552,16 @@ onUnmounted(() => {
|
1283
|
1552
|
}
|
1284
|
1553
|
}
|
1285
|
1554
|
|
|
1555
|
+// 文件列表分页样式
|
|
1556
|
+.file-content {
|
|
1557
|
+ :deep(.pagination-container) {
|
|
1558
|
+ margin-top: 20px;
|
|
1559
|
+ padding: 20px 0;
|
|
1560
|
+ text-align: center;
|
|
1561
|
+ background: transparent;
|
|
1562
|
+ }
|
|
1563
|
+}
|
|
1564
|
+
|
1286
|
1565
|
.empty-state {
|
1287
|
1566
|
display: flex;
|
1288
|
1567
|
flex-direction: column;
|
|
@@ -1315,7 +1594,7 @@ onUnmounted(() => {
|
1315
|
1594
|
}
|
1316
|
1595
|
|
1317
|
1596
|
:deep(.el-upload-dragger) {
|
1318
|
|
- width: 100%;
|
|
1597
|
+ width: 350px;
|
1319
|
1598
|
}
|
1320
|
1599
|
|
1321
|
1600
|
:deep(.el-upload__text) {
|
|
@@ -1330,6 +1609,47 @@ onUnmounted(() => {
|
1330
|
1609
|
margin-top: 7px;
|
1331
|
1610
|
}
|
1332
|
1611
|
|
|
1612
|
+// 修复上传文件列表中文件名过长的问题
|
|
1613
|
+:deep(.el-upload-list) {
|
|
1614
|
+ max-width: 100%;
|
|
1615
|
+
|
|
1616
|
+ .el-upload-list__item {
|
|
1617
|
+ max-width: 100%;
|
|
1618
|
+ overflow: hidden;
|
|
1619
|
+
|
|
1620
|
+ .el-upload-list__item-name {
|
|
1621
|
+ width: 350px; // 为删除按钮等留出空间
|
|
1622
|
+ white-space: nowrap;
|
|
1623
|
+ overflow: hidden;
|
|
1624
|
+ text-overflow: ellipsis;
|
|
1625
|
+ display: inline-block;
|
|
1626
|
+ vertical-align: middle;
|
|
1627
|
+ }
|
|
1628
|
+ }
|
|
1629
|
+}
|
|
1630
|
+
|
|
1631
|
+// 修复拖拽区域内文件名显示问题
|
|
1632
|
+:deep(.el-upload-dragger) {
|
|
1633
|
+ .el-upload-list {
|
|
1634
|
+ .el-upload-list__item {
|
|
1635
|
+ margin: 0 10px 10px 10px;
|
|
1636
|
+ padding: 8px 12px;
|
|
1637
|
+ background: #f5f7fa;
|
|
1638
|
+ border-radius: 4px;
|
|
1639
|
+
|
|
1640
|
+ .el-upload-list__item-name {
|
|
1641
|
+ color: #606266;
|
|
1642
|
+ font-size: 13px;
|
|
1643
|
+ max-width: calc(100% - 60px);
|
|
1644
|
+ word-wrap: break-word;
|
|
1645
|
+ word-break: break-all;
|
|
1646
|
+ white-space: normal;
|
|
1647
|
+ line-height: 1.4;
|
|
1648
|
+ }
|
|
1649
|
+ }
|
|
1650
|
+ }
|
|
1651
|
+}
|
|
1652
|
+
|
1333
|
1653
|
// 响应式设计
|
1334
|
1654
|
@media (max-width: 1200px) {
|
1335
|
1655
|
.main-content {
|
|
@@ -1375,6 +1695,35 @@ onUnmounted(() => {
|
1375
|
1695
|
.message-content {
|
1376
|
1696
|
max-width: 85%;
|
1377
|
1697
|
}
|
|
1698
|
+
|
|
1699
|
+ .message-references {
|
|
1700
|
+ .references-header {
|
|
1701
|
+ flex-direction: column;
|
|
1702
|
+ align-items: flex-start;
|
|
1703
|
+ gap: 4px;
|
|
1704
|
+
|
|
1705
|
+ .toggle-references {
|
|
1706
|
+ font-size: 10px;
|
|
1707
|
+ }
|
|
1708
|
+ }
|
|
1709
|
+
|
|
1710
|
+ .reference-item {
|
|
1711
|
+ .reference-info {
|
|
1712
|
+ flex-direction: column;
|
|
1713
|
+ align-items: flex-start;
|
|
1714
|
+ gap: 4px;
|
|
1715
|
+
|
|
1716
|
+ .reference-filename {
|
|
1717
|
+ font-size: 11px;
|
|
1718
|
+ }
|
|
1719
|
+
|
|
1720
|
+ .reference-similarity {
|
|
1721
|
+ font-size: 10px;
|
|
1722
|
+ align-self: flex-end;
|
|
1723
|
+ }
|
|
1724
|
+ }
|
|
1725
|
+ }
|
|
1726
|
+ }
|
1378
|
1727
|
}
|
1379
|
1728
|
|
1380
|
1729
|
// Loading动画样式
|