vue_zhongyou 1.0.24 → 1.0.27

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue_zhongyou",
3
- "version": "1.0.24",
3
+ "version": "1.0.27",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "keywords": [],
@@ -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,10 +162,19 @@ const chatContainerRef = ref(null)
138
162
  const sessionId = ref('')
139
163
  // 输入组件引用
140
164
  const inputComRef = ref(null)
141
- // SSE连接引用
142
- const eventSourceRef = ref(null)
165
+ // Axios取消请求控制器
166
+ const abortControllerRef = ref(null)
143
167
  // 当前正在接收流式响应的消息索引
144
168
  const streamingMessageIndex = ref(-1)
169
+ // SSE重连相关
170
+ const reconnectAttempts = ref(0)
171
+ const maxReconnectAttempts = ref(3)
172
+ const reconnectDelay = ref(1000) // 初始重连延迟1秒
173
+ const reconnectTimerRef = ref(null)
174
+ // 当前请求体,用于重连时重新发送
175
+ const currentRequestBody = ref(null)
176
+ // 当前用户输入,用于重连时重新发送
177
+ const currentUserInput = ref('')
145
178
 
146
179
 
147
180
  // 判断消息类型
@@ -159,6 +192,7 @@ const isCardListMessage = (message) => {
159
192
 
160
193
  // 初始化欢迎消息
161
194
  onMounted(() => {
195
+
162
196
  addMessage('assistant', '您好!有什么可以帮助您的吗?', false)
163
197
  scrollToBottom()
164
198
  })
@@ -235,11 +269,21 @@ const sendMessage = async () => {
235
269
  const text = inputComRef.value.inputText.trim()
236
270
  if (!text || isLoading.value) return
237
271
 
238
- // 关闭之前的SSE连接
239
- if (eventSourceRef.value) {
240
- eventSourceRef.value.close()
241
- eventSourceRef.value = null
272
+ // 取消之前的请求和重连定时器
273
+ if (abortControllerRef.value) {
274
+ abortControllerRef.value.abort()
242
275
  }
276
+ if (reconnectTimerRef.value) {
277
+ clearTimeout(reconnectTimerRef.value)
278
+ reconnectTimerRef.value = null
279
+ }
280
+
281
+ // 重置重连计数器和延迟
282
+ reconnectAttempts.value = 0
283
+ reconnectDelay.value = 1000
284
+
285
+ // 保存当前请求信息,用于重连
286
+ currentUserInput.value = text
243
287
 
244
288
  // 添加用户消息
245
289
  addMessage('user', text)
@@ -267,60 +311,159 @@ const sendMessage = async () => {
267
311
  }
268
312
  }
269
313
  }
314
+
315
+ // 保存请求体,用于重连
316
+ currentRequestBody.value = requestBody
317
+
318
+ // 执行SSE请求
319
+ await executeSSERequest(requestBody, text)
320
+ }
321
+
322
+ // 执行SSE请求
323
+ const executeSSERequest = async (requestBody, userText) => {
324
+ // 创建新的AbortController
325
+ abortControllerRef.value = new AbortController()
270
326
 
271
327
  try {
272
- // 创建SSE连接
328
+ // 使用fetch API发送SSE请求
273
329
  const baseURL = import.meta.env.VITE_API_BASE_URL || ''
274
- const url = `${baseURL}/api/ai/chat/stream`
330
+ const url = `http://localhost:8080/ai/chat`
275
331
 
276
- // 构建查询参数
277
- const params = new URLSearchParams()
278
- params.append('sessionId', requestBody.sessionId)
279
- params.append('input', requestBody.input)
280
- params.append('payload', JSON.stringify(requestBody.payload))
332
+ const response = await fetch(url, {
333
+ method: 'POST',
334
+ headers: {
335
+ 'Content-Type': 'application/json',
336
+ 'Accept': 'text/event-stream',
337
+ 'Cache-Control': 'no-cache'
338
+ },
339
+ body: JSON.stringify({
340
+ message: userText
341
+ }),
342
+ signal: abortControllerRef.value.signal
343
+ })
281
344
 
282
- const fullUrl = `${url}?${params.toString()}`
345
+ if (!response.ok) {
346
+ throw new Error(`HTTP error! status: ${response.status}`)
347
+ }
283
348
 
284
- // 创建EventSource连接
285
- eventSourceRef.value = new EventSource(fullUrl)
349
+ // 使用ReadableStream处理流式响应
350
+ const reader = response.body.getReader()
351
+ const decoder = new TextDecoder()
352
+ let buffer = ''
286
353
 
287
- // 监听消息事件
288
- eventSourceRef.value.onmessage = (event) => {
289
- try {
290
- const data = JSON.parse(event.data)
291
- handleSSEMessage(data)
292
- } catch (error) {
293
- console.error('解析SSE消息失败:', error)
294
- }
295
- }
354
+ // 重置重连计数器(连接成功)
355
+ reconnectAttempts.value = 0
296
356
 
297
- // 监听错误事件
298
- eventSourceRef.value.onerror = (error) => {
299
- console.error('SSE连接错误:', error)
300
- handleSSEError()
357
+ while (true) {
358
+ const { done, value } = await reader.read()
359
+ console.log(done, value);
360
+
361
+ if (done) {
362
+ console.log('SSE流读取完成')
363
+ // 流正常结束,不需要重连
364
+ break
365
+ }
366
+
367
+ // 解码数据块
368
+ const chunk = decoder.decode(value, { stream: true })
369
+ console.log(chunk);
370
+
371
+ buffer += chunk
372
+
373
+ // 处理buffer中的完整消息
374
+ const lines = buffer.split('\n')
375
+ buffer = lines.pop() || '' // 保留最后一个不完整的行
376
+
377
+ for (const line of lines) {
378
+ if (line.startsWith('data: ')) {
379
+ const dataStr = line.slice(6).trim()
380
+ if (dataStr) {
381
+ try {
382
+ const data = JSON.parse(dataStr)
383
+ handleSSEMessage(data)
384
+ } catch (error) {
385
+ console.error('解析SSE消息失败:', error, dataStr)
386
+ }
387
+ }
388
+ }
389
+ }
301
390
  }
302
391
 
303
- // 监听连接打开事件
304
- eventSourceRef.value.onopen = () => {
305
- console.log('SSE连接已建立')
392
+ // 处理buffer中剩余的数据
393
+ if (buffer.trim()) {
394
+ const lines = buffer.split('\n')
395
+ for (const line of lines) {
396
+ if (line.startsWith('data: ')) {
397
+ const dataStr = line.slice(6).trim()
398
+ if (dataStr) {
399
+ try {
400
+ const data = JSON.parse(dataStr)
401
+ handleSSEMessage(data)
402
+ } catch (error) {
403
+ console.error('解析SSE消息失败:', error, dataStr)
404
+ }
405
+ }
406
+ }
407
+ }
306
408
  }
307
409
 
410
+ // 请求完成
411
+ console.log('SSE请求完成')
308
412
  } catch (error) {
309
- console.error('创建SSE连接失败:', error)
310
- showToast('连接失败,请重试')
311
- handleSSEError()
413
+ if (error.name === 'AbortError') {
414
+ console.log('请求已取消')
415
+ } else {
416
+ console.error('SSE请求失败:', error)
417
+ // 处理SSE错误并重试
418
+ handleSSEError('连接失败,请重试')
419
+ // 尝试重连
420
+ attemptReconnect()
421
+ }
422
+ }
423
+ }
424
+
425
+ // 尝试重新连接
426
+ const attemptReconnect = () => {
427
+ // 检查是否达到最大重试次数
428
+ if (reconnectAttempts.value >= maxReconnectAttempts.value) {
429
+ console.error('已达到最大重连次数,停止重连')
430
+ // 更新消息状态
431
+ const messageIndex = streamingMessageIndex.value
432
+ if (messageIndex !== -1 && messages.value[messageIndex]) {
433
+ messages.value[messageIndex].loading = false
434
+ messages.value[messageIndex].content = '连接已断开,建议重试'
435
+ }
436
+ isLoading.value = false
437
+ return
312
438
  }
439
+
440
+ // 增加重连计数器
441
+ reconnectAttempts.value++
442
+
443
+ // 指数退避策略,每次重连延迟翻倍
444
+ const delay = reconnectDelay.value * Math.pow(2, reconnectAttempts.value - 1)
445
+
446
+ console.log(`SSE连接失败,${delay}ms后尝试第${reconnectAttempts.value}次重连...`)
447
+
448
+ // 设置重连定时器
449
+ reconnectTimerRef.value = setTimeout(async () => {
450
+ console.log(`尝试第${reconnectAttempts.value}次重连...`)
451
+
452
+ // 重新执行SSE请求
453
+ await executeSSERequest(currentRequestBody.value, currentUserInput.value)
454
+ }, delay)
313
455
  }
314
456
 
315
457
  // 处理SSE消息
316
458
  const handleSSEMessage = (data) => {
459
+
317
460
  const messageIndex = streamingMessageIndex.value
461
+
318
462
  if (messageIndex === -1 || !messages.value[messageIndex]) return
319
463
 
320
464
  const currentMessage = messages.value[messageIndex]
321
-
322
465
  // 根据消息类型处理
323
- if (data.type === 'text') {
466
+ if (data.type === 'text' || data.type === 'message') {
324
467
  // 文本流式更新
325
468
  if (typeof currentMessage.content !== 'string') {
326
469
  currentMessage.content = ''
@@ -340,7 +483,7 @@ const handleSSEMessage = (data) => {
340
483
  for (let key in data) {
341
484
  currentMessage[key] = data[key]
342
485
  }
343
- } else if (data.type === 'done') {
486
+ } else if (data.type === 'end') {
344
487
  // 流式传输完成
345
488
  currentMessage.loading = false
346
489
  isLoading.value = false
@@ -348,16 +491,17 @@ const handleSSEMessage = (data) => {
348
491
  // 为AI助手消息添加推荐信息
349
492
  currentMessage.suggestions = generateSuggestions(currentMessage.content)
350
493
 
351
- // 关闭SSE连接
352
- if (eventSourceRef.value) {
353
- eventSourceRef.value.close()
354
- eventSourceRef.value = null
494
+ // 清理请求控制器
495
+ if (abortControllerRef.value) {
496
+ abortControllerRef.value = null
355
497
  }
356
498
  streamingMessageIndex.value = -1
357
499
  } else if (data.type === 'error') {
358
500
  // 错误消息
359
501
  handleSSEError(data.message || '服务器错误')
360
502
  }
503
+ console.log(messages.value);
504
+
361
505
  }
362
506
 
363
507
  // 处理SSE错误
@@ -375,26 +519,39 @@ const handleSSEError = (errorMessage = '连接中断,请重试') => {
375
519
  isLoading.value = false
376
520
  showToast(errorMessage)
377
521
 
378
- // 关闭SSE连接
379
- if (eventSourceRef.value) {
380
- eventSourceRef.value.close()
381
- eventSourceRef.value = null
522
+ // 清理请求控制器
523
+ if (abortControllerRef.value) {
524
+ abortControllerRef.value = null
382
525
  }
383
526
  streamingMessageIndex.value = -1
384
527
  }
385
528
 
386
529
  // 组件卸载时清理SSE连接
387
530
  onUnmounted(() => {
388
- if (eventSourceRef.value) {
389
- eventSourceRef.value.close()
390
- eventSourceRef.value = null
531
+ if (abortControllerRef.value) {
532
+ abortControllerRef.value.abort()
533
+ abortControllerRef.value = null
534
+ }
535
+ // 清理重连定时器
536
+ if (reconnectTimerRef.value) {
537
+ clearTimeout(reconnectTimerRef.value)
538
+ reconnectTimerRef.value = null
391
539
  }
392
540
  })
393
541
 
394
- // 格式化消息内容(支持换行)
542
+ // 格式化消息内容(支持Markdown)
395
543
  const formatMessage = (content) => {
396
544
  if (!content) return ''
397
- return content.replace(/\n/g, '<br>')
545
+
546
+ try {
547
+ // 使用marked将Markdown转换为HTML
548
+ const html = marked(content)
549
+ return html
550
+ } catch (error) {
551
+ console.error('Markdown转换失败:', error)
552
+ // 如果转换失败,回退到简单的换行处理
553
+ return content.replace(/\n/g, '<br>')
554
+ }
398
555
  }
399
556
 
400
557
  // 格式化时间
@@ -625,6 +782,7 @@ const handleLoadMore = (message) => {
625
782
  // max-width: 75%;
626
783
  flex: 1;
627
784
  gap: 6px;
785
+ overflow: hidden;
628
786
  }
629
787
 
630
788
  .message-bubble {
@@ -632,11 +790,216 @@ const handleLoadMore = (message) => {
632
790
  word-wrap: break-word;
633
791
  word-break: break-word;
634
792
  line-height: 1.5;
793
+ max-width: 100%;
794
+ box-sizing: border-box;
795
+ overflow: hidden;
635
796
  }
636
797
 
637
798
  .message-text {
638
799
  font-size: 15px;
639
800
  white-space: pre-wrap;
801
+ overflow: hidden;
802
+
803
+ // Markdown样式隔离 - 使用深度选择器控制所有Markdown元素
804
+ :deep(*) {
805
+ margin: 0;
806
+ padding: 0;
807
+ box-sizing: border-box;
808
+ }
809
+
810
+ // 标题样式
811
+ :deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
812
+ margin: 0.5em 0 0.3em 0;
813
+ font-weight: 600;
814
+ line-height: 1.4;
815
+ color: inherit;
816
+ }
817
+
818
+ :deep(h1) {
819
+ font-size: 1.5em;
820
+ border-bottom: 1px solid #eaecef;
821
+ padding-bottom: 0.3em;
822
+ }
823
+
824
+ :deep(h2) {
825
+ font-size: 1.3em;
826
+ border-bottom: 1px solid #eaecef;
827
+ padding-bottom: 0.3em;
828
+ }
829
+
830
+ :deep(h3) {
831
+ font-size: 1.15em;
832
+ }
833
+
834
+ :deep(h4) {
835
+ font-size: 1.05em;
836
+ }
837
+
838
+ // 段落样式
839
+ :deep(p) {
840
+ margin: 0.3em 0;
841
+ line-height: 1.6;
842
+ }
843
+
844
+ // 列表样式
845
+ :deep(ul), :deep(ol) {
846
+ margin: 0.5em 0;
847
+ padding-left: 1.5em;
848
+ list-style-position: outside;
849
+ }
850
+
851
+ :deep(ul) {
852
+ list-style-type: disc;
853
+ }
854
+
855
+ :deep(ol) {
856
+ list-style-type: decimal;
857
+ }
858
+
859
+ :deep(li) {
860
+ margin: 0.25em 0;
861
+ line-height: 1.6;
862
+ padding-left: 0.3em;
863
+ }
864
+
865
+ :deep(ul ul), :deep(ol ul), :deep(ul ol), :deep(ol ol) {
866
+ margin: 0.25em 0;
867
+ }
868
+
869
+ // 嵌套列表样式
870
+ :deep(ul ul) {
871
+ list-style-type: circle;
872
+ }
873
+
874
+ :deep(ul ul ul) {
875
+ list-style-type: square;
876
+ }
877
+
878
+ // 代码样式
879
+ :deep(code) {
880
+ font-family: 'Courier New', Courier, monospace;
881
+ background-color: rgba(0, 0, 0, 0.06);
882
+ padding: 0.2em 0.4em;
883
+ border-radius: 3px;
884
+ font-size: 0.9em;
885
+ color: #e83e8c;
886
+ word-break: break-all;
887
+ word-wrap: break-word;
888
+ }
889
+
890
+ :deep(pre) {
891
+ background-color: #f6f8fa;
892
+ border: 1px solid #e1e4e8;
893
+ border-radius: 6px;
894
+ padding: 12px;
895
+ margin: 0.5em 0;
896
+ overflow-x: auto;
897
+ overflow-y: hidden;
898
+ font-size: 0.85em;
899
+ line-height: 1.5;
900
+ max-width: 100%;
901
+ box-sizing: border-box;
902
+ -webkit-overflow-scrolling: touch;
903
+ word-break: break-all;
904
+ }
905
+
906
+ :deep(pre code) {
907
+ background-color: transparent;
908
+ padding: 0;
909
+ border-radius: 0;
910
+ font-size: inherit;
911
+ color: inherit;
912
+ word-break: break-all;
913
+ word-wrap: break-word;
914
+ white-space: pre-wrap;
915
+ overflow-wrap: anywhere;
916
+ }
917
+
918
+ // 引用样式
919
+ :deep(blockquote) {
920
+ margin: 0.5em 0;
921
+ padding: 0.5em 1em;
922
+ border-left: 4px solid #dfe2e5;
923
+ background-color: #f6f8fa;
924
+ color: #6a737d;
925
+ }
926
+
927
+ // 表格样式
928
+ :deep(table) {
929
+ border-collapse: collapse;
930
+ width: 100%;
931
+ margin: 0.5em 0;
932
+ font-size: 0.95em;
933
+ display: block;
934
+ overflow-x: auto;
935
+ -webkit-overflow-scrolling: touch;
936
+ }
937
+
938
+ :deep(table th), :deep(table td) {
939
+ border: 1px solid #dfe2e5;
940
+ padding: 6px 12px;
941
+ text-align: left;
942
+ white-space: nowrap;
943
+ min-width: 80px;
944
+ }
945
+
946
+ :deep(table th) {
947
+ background-color: #f6f8fa;
948
+ font-weight: 600;
949
+ }
950
+
951
+ :deep(table tr:nth-child(even)) {
952
+ background-color: #fafbfc;
953
+ }
954
+
955
+ // 链接样式
956
+ :deep(a) {
957
+ color: #1989fa;
958
+ text-decoration: none;
959
+ border-bottom: 1px solid transparent;
960
+ transition: border-bottom-color 0.2s;
961
+ word-break: break-all;
962
+ }
963
+
964
+ :deep(a:hover) {
965
+ border-bottom-color: #1989fa;
966
+ }
967
+
968
+ // 图片样式
969
+ :deep(img) {
970
+ max-width: 100%;
971
+ height: auto;
972
+ border-radius: 4px;
973
+ margin: 0.5em 0;
974
+ }
975
+
976
+ // 分隔线样式
977
+ :deep(hr) {
978
+ border: none;
979
+ border-top: 2px solid #e1e4e8;
980
+ margin: 1em 0;
981
+ }
982
+
983
+ // 强调样式
984
+ :deep(strong), :deep(b) {
985
+ font-weight: 600;
986
+ }
987
+
988
+ :deep(em), :deep(i) {
989
+ font-style: italic;
990
+ }
991
+
992
+ // 删除线样式
993
+ :deep(del), :deep(s) {
994
+ text-decoration: line-through;
995
+ color: #6a737d;
996
+ }
997
+
998
+ // 任务列表样式
999
+ :deep(input[type="checkbox"]) {
1000
+ margin-right: 0.5em;
1001
+ vertical-align: middle;
1002
+ }
640
1003
  }
641
1004
 
642
1005
  .message-output {
@@ -648,6 +1011,57 @@ const handleLoadMore = (message) => {
648
1011
  background-color: #f8f9fa;
649
1012
  border-radius: 8px;
650
1013
  // border-left: 3px solid #1989fa;
1014
+
1015
+ // Markdown样式隔离
1016
+ :deep(*) {
1017
+ margin: 0;
1018
+ padding: 0;
1019
+ box-sizing: border-box;
1020
+ }
1021
+
1022
+ :deep(p) {
1023
+ margin: 0.3em 0;
1024
+ line-height: 1.6;
1025
+ }
1026
+
1027
+ :deep(ul), :deep(ol) {
1028
+ margin: 0.5em 0;
1029
+ padding-left: 1.5em;
1030
+ list-style-position: outside;
1031
+ }
1032
+
1033
+ :deep(ul) {
1034
+ list-style-type: disc;
1035
+ }
1036
+
1037
+ :deep(ol) {
1038
+ list-style-type: decimal;
1039
+ }
1040
+
1041
+ :deep(li) {
1042
+ margin: 0.25em 0;
1043
+ line-height: 1.6;
1044
+ padding-left: 0.3em;
1045
+ }
1046
+
1047
+ :deep(code) {
1048
+ font-family: 'Courier New', Courier, monospace;
1049
+ background-color: rgba(0, 0, 0, 0.06);
1050
+ padding: 0.2em 0.4em;
1051
+ border-radius: 3px;
1052
+ font-size: 0.9em;
1053
+ color: #e83e8c;
1054
+ }
1055
+
1056
+ :deep(a) {
1057
+ color: #1989fa;
1058
+ text-decoration: none;
1059
+ border-bottom: 1px solid transparent;
1060
+ }
1061
+
1062
+ :deep(a:hover) {
1063
+ border-bottom-color: #1989fa;
1064
+ }
651
1065
  }
652
1066
 
653
1067
  .message-time {