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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue_zhongyou",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
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,8 +162,8 @@ 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)
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
- // 关闭之前的SSE连接
239
- if (eventSourceRef.value) {
240
- eventSourceRef.value.close()
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
- // 创建SSE连接
299
+ // 使用fetch API发送SSE请求
273
300
  const baseURL = import.meta.env.VITE_API_BASE_URL || ''
274
- const url = `${baseURL}/api/ai/chat/stream`
301
+ const url = `http://localhost:8080/ai/chat`
275
302
 
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))
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
- const fullUrl = `${url}?${params.toString()}`
316
+ if (!response.ok) {
317
+ throw new Error(`HTTP error! status: ${response.status}`)
318
+ }
283
319
 
284
- // 创建EventSource连接
285
- eventSourceRef.value = new EventSource(fullUrl)
320
+ // 使用ReadableStream处理流式响应
321
+ const reader = response.body.getReader()
322
+ const decoder = new TextDecoder()
323
+ let buffer = ''
286
324
 
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)
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
- eventSourceRef.value.onerror = (error) => {
299
- console.error('SSE连接错误:', error)
300
- handleSSEError()
301
- }
302
-
303
- // 监听连接打开事件
304
- eventSourceRef.value.onopen = () => {
305
- console.log('SSE连接已建立')
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
- console.error('创建SSE连接失败:', error)
310
- showToast('连接失败,请重试')
311
- handleSSEError()
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 === 'done') {
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
- // 关闭SSE连接
352
- if (eventSourceRef.value) {
353
- eventSourceRef.value.close()
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
- // 关闭SSE连接
379
- if (eventSourceRef.value) {
380
- eventSourceRef.value.close()
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 (eventSourceRef.value) {
389
- eventSourceRef.value.close()
390
- eventSourceRef.value = null
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
- return content.replace(/\n/g, '<br>')
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 {