vue_zhongyou 1.0.24 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package//345/212/237/350/203/275/344/273/243/347/240/201/AI/345/257/271/350/257/235/aiChatPage.vue
CHANGED
|
@@ -125,6 +125,30 @@ import ChatForm from '@/components/ChatForm.vue'
|
|
|
125
125
|
import NewChatCardList from '@/components/newChatCardList.vue'
|
|
126
126
|
import DynamicMobileForm from '@/components/dynamicMobileForm.vue'
|
|
127
127
|
import inputCom from '@/components/inputCom.vue'
|
|
128
|
+
import request from '@/api/request'
|
|
129
|
+
// 引入核心库
|
|
130
|
+
import { marked } from 'marked';
|
|
131
|
+
import hljs from 'highlight.js';
|
|
132
|
+
// 引入高亮样式(任选一种)
|
|
133
|
+
import 'highlight.js/styles/github.css';
|
|
134
|
+
|
|
135
|
+
// 配置marked
|
|
136
|
+
marked.setOptions({
|
|
137
|
+
renderer: new marked.Renderer(),
|
|
138
|
+
highlight: function(code, lang) {
|
|
139
|
+
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
|
140
|
+
return hljs.highlight(code, { language }).value;
|
|
141
|
+
},
|
|
142
|
+
langPrefix: 'hljs language-',
|
|
143
|
+
pedantic: false,
|
|
144
|
+
gfm: true,
|
|
145
|
+
breaks: true,
|
|
146
|
+
sanitize: false,
|
|
147
|
+
smartLists: true,
|
|
148
|
+
smartypants: false,
|
|
149
|
+
xhtml: false
|
|
150
|
+
});
|
|
151
|
+
|
|
128
152
|
|
|
129
153
|
// 消息列表
|
|
130
154
|
const messages = ref([])
|
|
@@ -138,8 +162,8 @@ const chatContainerRef = ref(null)
|
|
|
138
162
|
const sessionId = ref('')
|
|
139
163
|
// 输入组件引用
|
|
140
164
|
const inputComRef = ref(null)
|
|
141
|
-
//
|
|
142
|
-
const
|
|
165
|
+
// Axios取消请求控制器
|
|
166
|
+
const abortControllerRef = ref(null)
|
|
143
167
|
// 当前正在接收流式响应的消息索引
|
|
144
168
|
const streamingMessageIndex = ref(-1)
|
|
145
169
|
|
|
@@ -159,6 +183,7 @@ const isCardListMessage = (message) => {
|
|
|
159
183
|
|
|
160
184
|
// 初始化欢迎消息
|
|
161
185
|
onMounted(() => {
|
|
186
|
+
|
|
162
187
|
addMessage('assistant', '您好!有什么可以帮助您的吗?', false)
|
|
163
188
|
scrollToBottom()
|
|
164
189
|
})
|
|
@@ -235,10 +260,9 @@ const sendMessage = async () => {
|
|
|
235
260
|
const text = inputComRef.value.inputText.trim()
|
|
236
261
|
if (!text || isLoading.value) return
|
|
237
262
|
|
|
238
|
-
//
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
eventSourceRef.value = null
|
|
263
|
+
// 取消之前的请求
|
|
264
|
+
if (abortControllerRef.value) {
|
|
265
|
+
abortControllerRef.value.abort()
|
|
242
266
|
}
|
|
243
267
|
|
|
244
268
|
// 添加用户消息
|
|
@@ -268,59 +292,109 @@ const sendMessage = async () => {
|
|
|
268
292
|
}
|
|
269
293
|
}
|
|
270
294
|
|
|
295
|
+
// 创建新的AbortController
|
|
296
|
+
abortControllerRef.value = new AbortController()
|
|
297
|
+
|
|
271
298
|
try {
|
|
272
|
-
//
|
|
299
|
+
// 使用fetch API发送SSE请求
|
|
273
300
|
const baseURL = import.meta.env.VITE_API_BASE_URL || ''
|
|
274
|
-
const url =
|
|
301
|
+
const url = `http://localhost:8080/ai/chat`
|
|
275
302
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
303
|
+
const response = await fetch(url, {
|
|
304
|
+
method: 'POST',
|
|
305
|
+
headers: {
|
|
306
|
+
'Content-Type': 'application/json',
|
|
307
|
+
'Accept': 'text/event-stream',
|
|
308
|
+
'Cache-Control': 'no-cache'
|
|
309
|
+
},
|
|
310
|
+
body: JSON.stringify({
|
|
311
|
+
message: text
|
|
312
|
+
}),
|
|
313
|
+
signal: abortControllerRef.value.signal
|
|
314
|
+
})
|
|
281
315
|
|
|
282
|
-
|
|
316
|
+
if (!response.ok) {
|
|
317
|
+
throw new Error(`HTTP error! status: ${response.status}`)
|
|
318
|
+
}
|
|
283
319
|
|
|
284
|
-
//
|
|
285
|
-
|
|
320
|
+
// 使用ReadableStream处理流式响应
|
|
321
|
+
const reader = response.body.getReader()
|
|
322
|
+
const decoder = new TextDecoder()
|
|
323
|
+
let buffer = ''
|
|
286
324
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
325
|
+
while (true) {
|
|
326
|
+
const { done, value } = await reader.read()
|
|
327
|
+
|
|
328
|
+
if (done) {
|
|
329
|
+
console.log('SSE流读取完成')
|
|
330
|
+
break
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 解码数据块
|
|
334
|
+
const chunk = decoder.decode(value, { stream: true })
|
|
335
|
+
console.log(chunk);
|
|
336
|
+
|
|
337
|
+
buffer += chunk
|
|
338
|
+
|
|
339
|
+
// 处理buffer中的完整消息
|
|
340
|
+
const lines = buffer.split('\n')
|
|
341
|
+
buffer = lines.pop() || '' // 保留最后一个不完整的行
|
|
342
|
+
|
|
343
|
+
for (const line of lines) {
|
|
344
|
+
if (line.startsWith('data: ')) {
|
|
345
|
+
const dataStr = line.slice(6).trim()
|
|
346
|
+
if (dataStr) {
|
|
347
|
+
try {
|
|
348
|
+
const data = JSON.parse(dataStr)
|
|
349
|
+
handleSSEMessage(data)
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error('解析SSE消息失败:', error, dataStr)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
294
355
|
}
|
|
295
356
|
}
|
|
296
357
|
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
358
|
+
// 处理buffer中剩余的数据
|
|
359
|
+
if (buffer.trim()) {
|
|
360
|
+
const lines = buffer.split('\n')
|
|
361
|
+
for (const line of lines) {
|
|
362
|
+
if (line.startsWith('data: ')) {
|
|
363
|
+
const dataStr = line.slice(6).trim()
|
|
364
|
+
if (dataStr) {
|
|
365
|
+
try {
|
|
366
|
+
const data = JSON.parse(dataStr)
|
|
367
|
+
handleSSEMessage(data)
|
|
368
|
+
} catch (error) {
|
|
369
|
+
console.error('解析SSE消息失败:', error, dataStr)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
306
374
|
}
|
|
307
375
|
|
|
376
|
+
// 请求完成
|
|
377
|
+
console.log('SSE请求完成')
|
|
308
378
|
} catch (error) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
379
|
+
if (error.name === 'AbortError') {
|
|
380
|
+
console.log('请求已取消')
|
|
381
|
+
} else {
|
|
382
|
+
console.error('SSE请求失败:', error)
|
|
383
|
+
handleSSEError('连接失败,请重试')
|
|
384
|
+
}
|
|
312
385
|
}
|
|
313
386
|
}
|
|
314
387
|
|
|
315
388
|
// 处理SSE消息
|
|
316
389
|
const handleSSEMessage = (data) => {
|
|
390
|
+
|
|
317
391
|
const messageIndex = streamingMessageIndex.value
|
|
392
|
+
|
|
318
393
|
if (messageIndex === -1 || !messages.value[messageIndex]) return
|
|
319
394
|
|
|
320
395
|
const currentMessage = messages.value[messageIndex]
|
|
321
|
-
|
|
322
396
|
// 根据消息类型处理
|
|
323
|
-
if (data.type === 'text') {
|
|
397
|
+
if (data.type === 'text' || data.type === 'message') {
|
|
324
398
|
// 文本流式更新
|
|
325
399
|
if (typeof currentMessage.content !== 'string') {
|
|
326
400
|
currentMessage.content = ''
|
|
@@ -340,7 +414,7 @@ const handleSSEMessage = (data) => {
|
|
|
340
414
|
for (let key in data) {
|
|
341
415
|
currentMessage[key] = data[key]
|
|
342
416
|
}
|
|
343
|
-
} else if (data.type === '
|
|
417
|
+
} else if (data.type === 'end') {
|
|
344
418
|
// 流式传输完成
|
|
345
419
|
currentMessage.loading = false
|
|
346
420
|
isLoading.value = false
|
|
@@ -348,16 +422,17 @@ const handleSSEMessage = (data) => {
|
|
|
348
422
|
// 为AI助手消息添加推荐信息
|
|
349
423
|
currentMessage.suggestions = generateSuggestions(currentMessage.content)
|
|
350
424
|
|
|
351
|
-
//
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
eventSourceRef.value = null
|
|
425
|
+
// 清理请求控制器
|
|
426
|
+
if (abortControllerRef.value) {
|
|
427
|
+
abortControllerRef.value = null
|
|
355
428
|
}
|
|
356
429
|
streamingMessageIndex.value = -1
|
|
357
430
|
} else if (data.type === 'error') {
|
|
358
431
|
// 错误消息
|
|
359
432
|
handleSSEError(data.message || '服务器错误')
|
|
360
433
|
}
|
|
434
|
+
console.log(messages.value);
|
|
435
|
+
|
|
361
436
|
}
|
|
362
437
|
|
|
363
438
|
// 处理SSE错误
|
|
@@ -375,26 +450,34 @@ const handleSSEError = (errorMessage = '连接中断,请重试') => {
|
|
|
375
450
|
isLoading.value = false
|
|
376
451
|
showToast(errorMessage)
|
|
377
452
|
|
|
378
|
-
//
|
|
379
|
-
if (
|
|
380
|
-
|
|
381
|
-
eventSourceRef.value = null
|
|
453
|
+
// 清理请求控制器
|
|
454
|
+
if (abortControllerRef.value) {
|
|
455
|
+
abortControllerRef.value = null
|
|
382
456
|
}
|
|
383
457
|
streamingMessageIndex.value = -1
|
|
384
458
|
}
|
|
385
459
|
|
|
386
460
|
// 组件卸载时清理SSE连接
|
|
387
461
|
onUnmounted(() => {
|
|
388
|
-
if (
|
|
389
|
-
|
|
390
|
-
|
|
462
|
+
if (abortControllerRef.value) {
|
|
463
|
+
abortControllerRef.value.abort()
|
|
464
|
+
abortControllerRef.value = null
|
|
391
465
|
}
|
|
392
466
|
})
|
|
393
467
|
|
|
394
|
-
//
|
|
468
|
+
// 格式化消息内容(支持Markdown)
|
|
395
469
|
const formatMessage = (content) => {
|
|
396
470
|
if (!content) return ''
|
|
397
|
-
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
// 使用marked将Markdown转换为HTML
|
|
474
|
+
const html = marked(content)
|
|
475
|
+
return html
|
|
476
|
+
} catch (error) {
|
|
477
|
+
console.error('Markdown转换失败:', error)
|
|
478
|
+
// 如果转换失败,回退到简单的换行处理
|
|
479
|
+
return content.replace(/\n/g, '<br>')
|
|
480
|
+
}
|
|
398
481
|
}
|
|
399
482
|
|
|
400
483
|
// 格式化时间
|
|
@@ -637,6 +720,190 @@ const handleLoadMore = (message) => {
|
|
|
637
720
|
.message-text {
|
|
638
721
|
font-size: 15px;
|
|
639
722
|
white-space: pre-wrap;
|
|
723
|
+
|
|
724
|
+
// Markdown样式隔离 - 使用深度选择器控制所有Markdown元素
|
|
725
|
+
:deep(*) {
|
|
726
|
+
margin: 0;
|
|
727
|
+
padding: 0;
|
|
728
|
+
box-sizing: border-box;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// 标题样式
|
|
732
|
+
:deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
|
|
733
|
+
margin: 0.5em 0 0.3em 0;
|
|
734
|
+
font-weight: 600;
|
|
735
|
+
line-height: 1.4;
|
|
736
|
+
color: inherit;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
:deep(h1) {
|
|
740
|
+
font-size: 1.5em;
|
|
741
|
+
border-bottom: 1px solid #eaecef;
|
|
742
|
+
padding-bottom: 0.3em;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
:deep(h2) {
|
|
746
|
+
font-size: 1.3em;
|
|
747
|
+
border-bottom: 1px solid #eaecef;
|
|
748
|
+
padding-bottom: 0.3em;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
:deep(h3) {
|
|
752
|
+
font-size: 1.15em;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
:deep(h4) {
|
|
756
|
+
font-size: 1.05em;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// 段落样式
|
|
760
|
+
:deep(p) {
|
|
761
|
+
margin: 0.3em 0;
|
|
762
|
+
line-height: 1.6;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// 列表样式
|
|
766
|
+
:deep(ul), :deep(ol) {
|
|
767
|
+
margin: 0.5em 0;
|
|
768
|
+
padding-left: 1.5em;
|
|
769
|
+
list-style-position: outside;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
:deep(ul) {
|
|
773
|
+
list-style-type: disc;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
:deep(ol) {
|
|
777
|
+
list-style-type: decimal;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
:deep(li) {
|
|
781
|
+
margin: 0.25em 0;
|
|
782
|
+
line-height: 1.6;
|
|
783
|
+
padding-left: 0.3em;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
:deep(ul ul), :deep(ol ul), :deep(ul ol), :deep(ol ol) {
|
|
787
|
+
margin: 0.25em 0;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// 嵌套列表样式
|
|
791
|
+
:deep(ul ul) {
|
|
792
|
+
list-style-type: circle;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
:deep(ul ul ul) {
|
|
796
|
+
list-style-type: square;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// 代码样式
|
|
800
|
+
:deep(code) {
|
|
801
|
+
font-family: 'Courier New', Courier, monospace;
|
|
802
|
+
background-color: rgba(0, 0, 0, 0.06);
|
|
803
|
+
padding: 0.2em 0.4em;
|
|
804
|
+
border-radius: 3px;
|
|
805
|
+
font-size: 0.9em;
|
|
806
|
+
color: #e83e8c;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
:deep(pre) {
|
|
810
|
+
background-color: #f6f8fa;
|
|
811
|
+
border: 1px solid #e1e4e8;
|
|
812
|
+
border-radius: 6px;
|
|
813
|
+
padding: 12px;
|
|
814
|
+
margin: 0.5em 0;
|
|
815
|
+
overflow-x: auto;
|
|
816
|
+
font-size: 0.9em;
|
|
817
|
+
line-height: 1.5;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
:deep(pre code) {
|
|
821
|
+
background-color: transparent;
|
|
822
|
+
padding: 0;
|
|
823
|
+
border-radius: 0;
|
|
824
|
+
font-size: inherit;
|
|
825
|
+
color: inherit;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// 引用样式
|
|
829
|
+
:deep(blockquote) {
|
|
830
|
+
margin: 0.5em 0;
|
|
831
|
+
padding: 0.5em 1em;
|
|
832
|
+
border-left: 4px solid #dfe2e5;
|
|
833
|
+
background-color: #f6f8fa;
|
|
834
|
+
color: #6a737d;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// 表格样式
|
|
838
|
+
:deep(table) {
|
|
839
|
+
border-collapse: collapse;
|
|
840
|
+
width: 100%;
|
|
841
|
+
margin: 0.5em 0;
|
|
842
|
+
font-size: 0.95em;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
:deep(table th), :deep(table td) {
|
|
846
|
+
border: 1px solid #dfe2e5;
|
|
847
|
+
padding: 6px 12px;
|
|
848
|
+
text-align: left;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
:deep(table th) {
|
|
852
|
+
background-color: #f6f8fa;
|
|
853
|
+
font-weight: 600;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
:deep(table tr:nth-child(even)) {
|
|
857
|
+
background-color: #fafbfc;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// 链接样式
|
|
861
|
+
:deep(a) {
|
|
862
|
+
color: #1989fa;
|
|
863
|
+
text-decoration: none;
|
|
864
|
+
border-bottom: 1px solid transparent;
|
|
865
|
+
transition: border-bottom-color 0.2s;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
:deep(a:hover) {
|
|
869
|
+
border-bottom-color: #1989fa;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// 图片样式
|
|
873
|
+
:deep(img) {
|
|
874
|
+
max-width: 100%;
|
|
875
|
+
height: auto;
|
|
876
|
+
border-radius: 4px;
|
|
877
|
+
margin: 0.5em 0;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// 分隔线样式
|
|
881
|
+
:deep(hr) {
|
|
882
|
+
border: none;
|
|
883
|
+
border-top: 2px solid #e1e4e8;
|
|
884
|
+
margin: 1em 0;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// 强调样式
|
|
888
|
+
:deep(strong), :deep(b) {
|
|
889
|
+
font-weight: 600;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
:deep(em), :deep(i) {
|
|
893
|
+
font-style: italic;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// 删除线样式
|
|
897
|
+
:deep(del), :deep(s) {
|
|
898
|
+
text-decoration: line-through;
|
|
899
|
+
color: #6a737d;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// 任务列表样式
|
|
903
|
+
:deep(input[type="checkbox"]) {
|
|
904
|
+
margin-right: 0.5em;
|
|
905
|
+
vertical-align: middle;
|
|
906
|
+
}
|
|
640
907
|
}
|
|
641
908
|
|
|
642
909
|
.message-output {
|
|
@@ -648,6 +915,57 @@ const handleLoadMore = (message) => {
|
|
|
648
915
|
background-color: #f8f9fa;
|
|
649
916
|
border-radius: 8px;
|
|
650
917
|
// border-left: 3px solid #1989fa;
|
|
918
|
+
|
|
919
|
+
// Markdown样式隔离
|
|
920
|
+
:deep(*) {
|
|
921
|
+
margin: 0;
|
|
922
|
+
padding: 0;
|
|
923
|
+
box-sizing: border-box;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
:deep(p) {
|
|
927
|
+
margin: 0.3em 0;
|
|
928
|
+
line-height: 1.6;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
:deep(ul), :deep(ol) {
|
|
932
|
+
margin: 0.5em 0;
|
|
933
|
+
padding-left: 1.5em;
|
|
934
|
+
list-style-position: outside;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
:deep(ul) {
|
|
938
|
+
list-style-type: disc;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
:deep(ol) {
|
|
942
|
+
list-style-type: decimal;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
:deep(li) {
|
|
946
|
+
margin: 0.25em 0;
|
|
947
|
+
line-height: 1.6;
|
|
948
|
+
padding-left: 0.3em;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
:deep(code) {
|
|
952
|
+
font-family: 'Courier New', Courier, monospace;
|
|
953
|
+
background-color: rgba(0, 0, 0, 0.06);
|
|
954
|
+
padding: 0.2em 0.4em;
|
|
955
|
+
border-radius: 3px;
|
|
956
|
+
font-size: 0.9em;
|
|
957
|
+
color: #e83e8c;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
:deep(a) {
|
|
961
|
+
color: #1989fa;
|
|
962
|
+
text-decoration: none;
|
|
963
|
+
border-bottom: 1px solid transparent;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
:deep(a:hover) {
|
|
967
|
+
border-bottom-color: #1989fa;
|
|
968
|
+
}
|
|
651
969
|
}
|
|
652
970
|
|
|
653
971
|
.message-time {
|