vue2-client 1.22.19 → 1.22.21
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/.env.his +19 -19
- package/.env.message +19 -19
- package/.history/.eslintrc_20260521171150.js +74 -0
- package/.history/.eslintrc_20260521171213.js +74 -0
- package/.history/src/base-client/components/common/HIS/HAddNativeForm/HAddNativeForm_20260601154443.vue +726 -0
- package/.history/src/base-client/components/common/HIS/HAddNativeForm/HAddNativeForm_20260601154700.vue +478 -0
- package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260512175435.vue +706 -0
- package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260512175450.vue +694 -0
- package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260611152602.vue +755 -0
- package/.history/src/base-client/components/common/HIS/HForm/HForm_20260513145941.vue +524 -0
- package/.history/src/base-client/components/common/HIS/HForm/HForm_20260513153133.vue +731 -0
- package/.history/src/base-client/components/common/HIS/HForm/HForm_20260513160316.vue +525 -0
- package/.history/src/base-client/components/common/HIS/HForm/HForm_20260601144150.vue +1046 -0
- package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260310142713.vue +512 -0
- package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260310145118.vue +511 -0
- package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260311094834.vue +696 -0
- package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260320143028.vue +693 -0
- package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260409101450.vue +677 -0
- package/.history/src/base-client/components/common/HIS/HTab/HTab_20260625173703.vue +767 -0
- package/.history/src/base-client/components/common/HIS/HTab/HTab_20260626112425.vue +803 -0
- package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260513140637.vue +1455 -0
- package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260513140935.vue +1441 -0
- package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260513150818.vue +1441 -0
- package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260513153119.vue +1442 -0
- package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260513153126.vue +1486 -0
- package/.history/src/base-client/components/common/XForm/XFormItem_20260513140854.vue +1607 -0
- package/.history/src/base-client/components/common/XMarkdownViewer/XMarkdownViewer_20260519140403.vue +643 -0
- package/.history/src/base-client/components/common/XMarkdownViewer/XMarkdownViewer_20260519140829.vue +628 -0
- package/.history/src/base-client/components/common/XMarkdownViewer/demo_20260519142824.vue +104 -0
- package/.history/src/base-client/components/common/XMarkdownViewer/demo_20260519143155.vue +102 -0
- package/.history/src/base-client/components/common/XReportGrid/XReport_20260309171231.vue +1241 -0
- package/.history/src/base-client/components/common/XReportGrid/XReport_20260309171441.vue +1223 -0
- package/.history/src/base-client/components/his/HAi/HAi_20260612174826.vue +472 -0
- package/.history/src/base-client/components/his/HAi/HAi_20260612175839.vue +538 -0
- package/.history/src/base-client/components/his/HAi/HAi_20260615103331.vue +650 -0
- package/.history/src/base-client/components/his/HAi/HAi_20260618140214.vue +1396 -0
- package/.history/src/base-client/components/his/HAi/HAi_20260618140556.vue +1396 -0
- package/.history/src/base-client/components/his/XHDescriptions/XHDescriptions_20260424134504.vue +1469 -0
- package/.history/src/base-client/components/his/XSidebar/XSidebar_20260610171133.vue +788 -0
- package/.history/src/base-client/components/his/XSidebar/XSidebar_20260610171151.vue +780 -0
- package/.history/src/base-client/components/his/XTransfer/XTransfer_20260511170841.vue +585 -0
- package/.history/src/base-client/components/his/XTransfer/XTransfer_20260511171138.vue +787 -0
- package/.history/src/base-client/components/his/XTransfer/XTransfer_20260512141830.vue +739 -0
- package/.history/src/components/STable/index_20260409155138.js +806 -0
- package/.history/src/components/STable/index_20260409155218.js +814 -0
- package/.history/src/expression/core/Expression_20260305164427.js +1371 -0
- package/.history/src/expression/core/Expression_20260305170258.js +1358 -0
- package/.history/src/expression/core/Program_20260305111830.js +944 -0
- package/.history/src/expression/core/Program_20260305112041.js +931 -0
- package/.history/src/logic/LogicRunner_20260304154306.js +170 -0
- package/.history/src/logic/LogicRunner_20260304155553.js +112 -0
- package/.history/src/logic/LogicRunner_20260305105834.js +112 -0
- package/.history/src/logic/LogicRunner_20260305112718.js +129 -0
- package/.history/src/logic/LogicRunner_20260305182436.js +133 -0
- package/.history/src/logic/LogicRunner_20260306151301.js +213 -0
- package/.history/src/logic/LogicRunner_20260306152419.js +213 -0
- package/.history/src/logic/plugins/common/DateTools_20260305154159.js +61 -0
- package/.history/src/logic/plugins/common/DateTools_20260305154217.js +44 -0
- package/.history/src/logic/plugins/common/DateTools_20260305161014.js +44 -0
- package/.history/src/logic/plugins/common/HttpTools_20260305164352.js +80 -0
- package/.history/src/logic/plugins/common/HttpTools_20260305170258.js +75 -0
- package/.history/src/logic/plugins/common/HttpTools_20260305171634.js +75 -0
- package/.history/src/logic/plugins/common/HttpTools_20260306152419.js +72 -0
- package/.history/src/services/api/restTools_20260427142149.js +245 -0
- package/.history/src/services/api/restTools_20260427142853.js +230 -0
- package/.history/src/services/api/restTools_20260519135558.js +230 -0
- package/.history/src/services/api/restTools_20260519140825.js +230 -0
- package/.history/src/services/api/restTools_20260519151223.js +230 -0
- package/.history/src/utils/indexedDB_20260306150918.js +593 -0
- package/.history/src/utils/indexedDB_20260306151301.js +586 -0
- package/.idea/af-vue2-client.iml +4 -7
- package/.idea/codeStyles/Project.xml +62 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/misc.xml +6 -0
- package/CHANGELOG.md +837 -837
- package/package.json +117 -117
- package/preview-input-box.html +180 -0
- package/src/base-client/components/common/HIS/HForm/HForm.vue +1186 -1186
- package/src/base-client/components/common/HIS/HTab/HTab.vue +790 -757
- package/src/base-client/components/common/ImagePreviewModal/index.js +3 -3
- package/src/base-client/components/common/XMarkdownViewer/demo.vue +102 -102
- package/src/base-client/components/his/HAi/HAi.vue +159 -30
- package/src/base-client/components/his/XTransfer/index.md +327 -327
- package/src/components/MarkdownEditor/demo.vue +300 -300
- package/src/components/MarkdownEditor/index.vue +590 -590
- package/src/router/async/router.map.js +141 -141
- package/src/services/api/restTools.js +1 -1
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import ImagePreviewModal from './ImagePreviewModal'
|
|
2
|
-
|
|
3
|
-
export default ImagePreviewModal
|
|
1
|
+
import ImagePreviewModal from './ImagePreviewModal'
|
|
2
|
+
|
|
3
|
+
export default ImagePreviewModal
|
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div style="padding: 16px">
|
|
3
|
-
<h2>XMarkdownViewer Demo</h2>
|
|
4
|
-
|
|
5
|
-
<div style="margin: 12px 0; padding: 12px; background: #f5f5f5; border-radius: 4px">
|
|
6
|
-
<h4 style="margin: 0 0 8px 0">流式 AI 调用示例</h4>
|
|
7
|
-
<div style="margin-bottom: 8px">
|
|
8
|
-
<input
|
|
9
|
-
v-model="userMessage"
|
|
10
|
-
type="text"
|
|
11
|
-
placeholder="输入消息"
|
|
12
|
-
style="width: 300px; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px"
|
|
13
|
-
@keyup.enter="sendMessage"
|
|
14
|
-
>
|
|
15
|
-
<button
|
|
16
|
-
type="button"
|
|
17
|
-
style="margin-left: 8px; padding: 6px 16px; background: #1890ff; color: #fff; border: none; border-radius: 4px; cursor: pointer"
|
|
18
|
-
@click="sendMessage"
|
|
19
|
-
>
|
|
20
|
-
发送
|
|
21
|
-
</button>
|
|
22
|
-
<button
|
|
23
|
-
v-if="isStreaming"
|
|
24
|
-
type="button"
|
|
25
|
-
style="margin-left: 8px; padding: 6px 16px; background: #ff4d4f; color: #fff; border: none; border-radius: 4px; cursor: pointer"
|
|
26
|
-
@click="stopMessage"
|
|
27
|
-
>
|
|
28
|
-
停止
|
|
29
|
-
</button>
|
|
30
|
-
</div>
|
|
31
|
-
<div v-if="isStreaming" style="color: #1890ff; font-size: 12px">AI 正在思考中...</div>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<div style="margin: 8px 0; color: #666; font-size: 12px">
|
|
35
|
-
<span v-if="fileName">当前文件:{{ fileName }}({{ md.length }} 字符)</span>
|
|
36
|
-
<span v-else-if="md">当前内容:流式响应({{ md.length }} 字符)</span>
|
|
37
|
-
<span v-else>请输入消息并点击发送</span>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<x-markdown-viewer ref="viewer" :markdown="md" />
|
|
41
|
-
</div>
|
|
42
|
-
</template>
|
|
43
|
-
|
|
44
|
-
<script>
|
|
45
|
-
import XMarkdownViewer from './XMarkdownViewer.vue'
|
|
46
|
-
|
|
47
|
-
export default {
|
|
48
|
-
name: 'XMarkdownViewerDemo',
|
|
49
|
-
components: {
|
|
50
|
-
XMarkdownViewer
|
|
51
|
-
},
|
|
52
|
-
data () {
|
|
53
|
-
return {
|
|
54
|
-
md: '',
|
|
55
|
-
fileName: '',
|
|
56
|
-
userMessage: '',
|
|
57
|
-
isStreaming: false,
|
|
58
|
-
currentStream: null
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
methods: {
|
|
62
|
-
async sendMessage () {
|
|
63
|
-
const message = this.userMessage.trim()
|
|
64
|
-
if (!message) return
|
|
65
|
-
|
|
66
|
-
const viewer = this.$refs.viewer
|
|
67
|
-
if (!viewer || typeof viewer.callAiStream !== 'function') {
|
|
68
|
-
console.warn('[Demo] viewer 不存在或没有 callAiStream 方法')
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 开始时显示提示
|
|
73
|
-
viewer.receiveMarkdownString('### 思考中,请稍后...\n')
|
|
74
|
-
this.isStreaming = true
|
|
75
|
-
|
|
76
|
-
// 调用流式接口
|
|
77
|
-
const stream = viewer.callAiStream(message, {
|
|
78
|
-
onChunk: (text) => {
|
|
79
|
-
viewer.receiveMarkdownString('### 思考中,请稍后...\n\n' + viewer.getStreamContent())
|
|
80
|
-
},
|
|
81
|
-
onEnd: (fullContent) => {
|
|
82
|
-
viewer.receiveMarkdownString(fullContent)
|
|
83
|
-
this.isStreaming = false
|
|
84
|
-
},
|
|
85
|
-
onError: (error) => {
|
|
86
|
-
viewer.receiveMarkdownString('### 抱歉,发生了错误\n\n' + error)
|
|
87
|
-
this.isStreaming = false
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
this.currentStream = stream
|
|
92
|
-
},
|
|
93
|
-
stopMessage () {
|
|
94
|
-
if (this.currentStream && typeof this.currentStream.stop === 'function') {
|
|
95
|
-
this.currentStream.stop()
|
|
96
|
-
this.isStreaming = false
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
</script>
|
|
102
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<div style="padding: 16px">
|
|
3
|
+
<h2>XMarkdownViewer Demo</h2>
|
|
4
|
+
|
|
5
|
+
<div style="margin: 12px 0; padding: 12px; background: #f5f5f5; border-radius: 4px">
|
|
6
|
+
<h4 style="margin: 0 0 8px 0">流式 AI 调用示例</h4>
|
|
7
|
+
<div style="margin-bottom: 8px">
|
|
8
|
+
<input
|
|
9
|
+
v-model="userMessage"
|
|
10
|
+
type="text"
|
|
11
|
+
placeholder="输入消息"
|
|
12
|
+
style="width: 300px; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px"
|
|
13
|
+
@keyup.enter="sendMessage"
|
|
14
|
+
>
|
|
15
|
+
<button
|
|
16
|
+
type="button"
|
|
17
|
+
style="margin-left: 8px; padding: 6px 16px; background: #1890ff; color: #fff; border: none; border-radius: 4px; cursor: pointer"
|
|
18
|
+
@click="sendMessage"
|
|
19
|
+
>
|
|
20
|
+
发送
|
|
21
|
+
</button>
|
|
22
|
+
<button
|
|
23
|
+
v-if="isStreaming"
|
|
24
|
+
type="button"
|
|
25
|
+
style="margin-left: 8px; padding: 6px 16px; background: #ff4d4f; color: #fff; border: none; border-radius: 4px; cursor: pointer"
|
|
26
|
+
@click="stopMessage"
|
|
27
|
+
>
|
|
28
|
+
停止
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
<div v-if="isStreaming" style="color: #1890ff; font-size: 12px">AI 正在思考中...</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div style="margin: 8px 0; color: #666; font-size: 12px">
|
|
35
|
+
<span v-if="fileName">当前文件:{{ fileName }}({{ md.length }} 字符)</span>
|
|
36
|
+
<span v-else-if="md">当前内容:流式响应({{ md.length }} 字符)</span>
|
|
37
|
+
<span v-else>请输入消息并点击发送</span>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<x-markdown-viewer ref="viewer" :markdown="md" />
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script>
|
|
45
|
+
import XMarkdownViewer from './XMarkdownViewer.vue'
|
|
46
|
+
|
|
47
|
+
export default {
|
|
48
|
+
name: 'XMarkdownViewerDemo',
|
|
49
|
+
components: {
|
|
50
|
+
XMarkdownViewer
|
|
51
|
+
},
|
|
52
|
+
data () {
|
|
53
|
+
return {
|
|
54
|
+
md: '',
|
|
55
|
+
fileName: '',
|
|
56
|
+
userMessage: '',
|
|
57
|
+
isStreaming: false,
|
|
58
|
+
currentStream: null
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
methods: {
|
|
62
|
+
async sendMessage () {
|
|
63
|
+
const message = this.userMessage.trim()
|
|
64
|
+
if (!message) return
|
|
65
|
+
|
|
66
|
+
const viewer = this.$refs.viewer
|
|
67
|
+
if (!viewer || typeof viewer.callAiStream !== 'function') {
|
|
68
|
+
console.warn('[Demo] viewer 不存在或没有 callAiStream 方法')
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 开始时显示提示
|
|
73
|
+
viewer.receiveMarkdownString('### 思考中,请稍后...\n')
|
|
74
|
+
this.isStreaming = true
|
|
75
|
+
|
|
76
|
+
// 调用流式接口
|
|
77
|
+
const stream = viewer.callAiStream(message, {
|
|
78
|
+
onChunk: (text) => {
|
|
79
|
+
viewer.receiveMarkdownString('### 思考中,请稍后...\n\n' + viewer.getStreamContent())
|
|
80
|
+
},
|
|
81
|
+
onEnd: (fullContent) => {
|
|
82
|
+
viewer.receiveMarkdownString(fullContent)
|
|
83
|
+
this.isStreaming = false
|
|
84
|
+
},
|
|
85
|
+
onError: (error) => {
|
|
86
|
+
viewer.receiveMarkdownString('### 抱歉,发生了错误\n\n' + error)
|
|
87
|
+
this.isStreaming = false
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
this.currentStream = stream
|
|
92
|
+
},
|
|
93
|
+
stopMessage () {
|
|
94
|
+
if (this.currentStream && typeof this.currentStream.stop === 'function') {
|
|
95
|
+
this.currentStream.stop()
|
|
96
|
+
this.isStreaming = false
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
</script>
|
|
102
|
+
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
v-for="(navItem, navIndex) in navMessages"
|
|
9
9
|
:key="`nav-${navIndex}`"
|
|
10
10
|
class="nav-dot"
|
|
11
|
-
:class="{ active: activeNavIndex ===
|
|
11
|
+
:class="{ active: activeNavIndex === navItem.index }"
|
|
12
12
|
:title="navItem.content"
|
|
13
13
|
@click="scrollToMessage(navItem.index)"
|
|
14
14
|
/>
|
|
@@ -184,6 +184,7 @@ const BAICHUAN_API_KEY = 'sk-c6368da98129d38b74d7df85a2be8414'
|
|
|
184
184
|
|
|
185
185
|
export default {
|
|
186
186
|
name: 'AIConsultation',
|
|
187
|
+
emits: ['favorite', 'delete'],
|
|
187
188
|
data() {
|
|
188
189
|
return {
|
|
189
190
|
currentDialogId: null,
|
|
@@ -203,7 +204,8 @@ export default {
|
|
|
203
204
|
previewImageUrl: '',
|
|
204
205
|
activeNavIndex: -1,
|
|
205
206
|
uploading: false,
|
|
206
|
-
uploadProgress: 0
|
|
207
|
+
uploadProgress: 0,
|
|
208
|
+
editingMessage: null
|
|
207
209
|
}
|
|
208
210
|
},
|
|
209
211
|
computed: {
|
|
@@ -248,34 +250,131 @@ export default {
|
|
|
248
250
|
copyMessage(item) {
|
|
249
251
|
// 复制消息内容
|
|
250
252
|
const text = item.content || ''
|
|
251
|
-
|
|
253
|
+
if (!text) {
|
|
254
|
+
this.$message.warning('无内容可复制')
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
// 兼容方式复制文本
|
|
258
|
+
const textarea = document.createElement('textarea')
|
|
259
|
+
textarea.value = text
|
|
260
|
+
textarea.style.position = 'fixed'
|
|
261
|
+
textarea.style.opacity = '0'
|
|
262
|
+
document.body.appendChild(textarea)
|
|
263
|
+
textarea.select()
|
|
264
|
+
try {
|
|
265
|
+
document.execCommand('copy')
|
|
252
266
|
this.$message.success('已复制')
|
|
253
|
-
}
|
|
267
|
+
} catch {
|
|
254
268
|
this.$message.error('复制失败')
|
|
255
|
-
}
|
|
269
|
+
} finally {
|
|
270
|
+
document.body.removeChild(textarea)
|
|
271
|
+
}
|
|
256
272
|
},
|
|
257
273
|
|
|
258
274
|
editMessage(item) {
|
|
259
|
-
|
|
260
|
-
|
|
275
|
+
const group = this.getNeighborGroup(item)
|
|
276
|
+
const userItem = group.find(msg => msg.type === 'user')
|
|
277
|
+
if (!userItem) {
|
|
278
|
+
this.$message.warning('未找到对应的用户消息')
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
this.editingMessage = userItem
|
|
282
|
+
this.doctorInputText = userItem.content
|
|
283
|
+
this.autoResizeTextarea()
|
|
261
284
|
},
|
|
262
285
|
|
|
263
286
|
favoriteMessage(item) {
|
|
264
|
-
|
|
265
|
-
this.$
|
|
287
|
+
const group = this.getNeighborGroup(item)
|
|
288
|
+
this.$emit('favorite', group)
|
|
266
289
|
},
|
|
267
290
|
|
|
268
291
|
deleteMessage(item) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (
|
|
272
|
-
|
|
292
|
+
const group = this.getNeighborGroup(item)
|
|
293
|
+
this.$emit('delete', group)
|
|
294
|
+
if (!group.length) return
|
|
295
|
+
const removeIds = new Set(group.map(msg => msg.messageId).filter(Boolean))
|
|
296
|
+
const removeObjects = new Set(group)
|
|
297
|
+
this.dialogMessages = this.dialogMessages.filter(msg => {
|
|
298
|
+
if (removeObjects.has(msg)) {
|
|
299
|
+
if (msg.attachments) {
|
|
300
|
+
msg.attachments.forEach(file => {
|
|
301
|
+
if (file.url && file.url.startsWith('blob:')) {
|
|
302
|
+
URL.revokeObjectURL(file.url)
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
return false
|
|
307
|
+
}
|
|
308
|
+
if (msg.messageId && removeIds.has(msg.messageId)) {
|
|
309
|
+
return false
|
|
310
|
+
}
|
|
311
|
+
return true
|
|
312
|
+
})
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
getNeighborGroup(item) {
|
|
316
|
+
const idx = this.dialogMessages.findIndex(msg => msg === item || (msg.messageId && msg.messageId === item.messageId))
|
|
317
|
+
if (idx === -1) return [item]
|
|
318
|
+
|
|
319
|
+
const current = this.dialogMessages[idx]
|
|
320
|
+
if (current.type === 'ai') {
|
|
321
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
322
|
+
if (this.dialogMessages[i].type === 'user') {
|
|
323
|
+
return [this.dialogMessages[i], current]
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return [current]
|
|
327
|
+
} else {
|
|
328
|
+
for (let i = idx + 1; i < this.dialogMessages.length; i++) {
|
|
329
|
+
if (this.dialogMessages[i].type === 'ai') {
|
|
330
|
+
return [current, this.dialogMessages[i]]
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return [current]
|
|
273
334
|
}
|
|
274
335
|
},
|
|
275
336
|
|
|
276
337
|
regenerateAiResponse(item) {
|
|
277
|
-
|
|
278
|
-
|
|
338
|
+
if (this.isAiStreaming) {
|
|
339
|
+
this.$message.warning('AI 正在生成中,请稍候')
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const group = this.getNeighborGroup(item)
|
|
344
|
+
const aiItem = group.find(msg => msg.type === 'ai') || item
|
|
345
|
+
const userItem = group.find(msg => msg.type === 'user')
|
|
346
|
+
|
|
347
|
+
const aiIndex = this.dialogMessages.findIndex(
|
|
348
|
+
msg => msg === aiItem || (msg.messageId && msg.messageId === aiItem.messageId)
|
|
349
|
+
)
|
|
350
|
+
if (aiIndex === -1) {
|
|
351
|
+
this.$message.warning('未找到需要刷新的 AI 消息')
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (this.currentStreamConnection) {
|
|
356
|
+
this.currentStreamConnection()
|
|
357
|
+
this.currentStreamConnection = null
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const messageContent = userItem ? userItem.content : ''
|
|
361
|
+
const chatId = this.currentDialogId || moment().format('YYYYMMDDHHmmss')
|
|
362
|
+
this.currentDialogId = chatId
|
|
363
|
+
|
|
364
|
+
this.dialogMessages[aiIndex].content = ''
|
|
365
|
+
this.scrollToBottom()
|
|
366
|
+
|
|
367
|
+
const requestBody = {
|
|
368
|
+
userMessage: messageContent,
|
|
369
|
+
chatId,
|
|
370
|
+
model: '',
|
|
371
|
+
prompt: '',
|
|
372
|
+
useContext: true,
|
|
373
|
+
user: this.currUser.operInfo,
|
|
374
|
+
files: []
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
this.startStream(aiIndex, undefined, requestBody)
|
|
279
378
|
},
|
|
280
379
|
|
|
281
380
|
autoResizeTextarea() {
|
|
@@ -331,6 +430,22 @@ export default {
|
|
|
331
430
|
return
|
|
332
431
|
}
|
|
333
432
|
|
|
433
|
+
// 如果是编辑后发送,先删除旧的那组消息
|
|
434
|
+
if (this.editingMessage) {
|
|
435
|
+
const oldGroup = this.getNeighborGroup(this.editingMessage)
|
|
436
|
+
oldGroup.forEach(msg => {
|
|
437
|
+
if (msg.attachments) {
|
|
438
|
+
msg.attachments.forEach(file => {
|
|
439
|
+
if (file.url && file.url.startsWith('blob:')) {
|
|
440
|
+
URL.revokeObjectURL(file.url)
|
|
441
|
+
}
|
|
442
|
+
})
|
|
443
|
+
}
|
|
444
|
+
})
|
|
445
|
+
this.dialogMessages = this.dialogMessages.filter(msg => !oldGroup.includes(msg))
|
|
446
|
+
this.editingMessage = null
|
|
447
|
+
}
|
|
448
|
+
|
|
334
449
|
// 确保有dialogId
|
|
335
450
|
if (!this.currentDialogId) {
|
|
336
451
|
this.initDialog()
|
|
@@ -344,6 +459,7 @@ export default {
|
|
|
344
459
|
.filter(f => f.file_id)
|
|
345
460
|
.map(f => ({ file_id: f.file_id }))
|
|
346
461
|
|
|
462
|
+
const userMsgIndex = this.dialogMessages.length
|
|
347
463
|
const doctorMessage = {
|
|
348
464
|
type: 'user',
|
|
349
465
|
content: messageContent,
|
|
@@ -359,8 +475,6 @@ export default {
|
|
|
359
475
|
this.autoResizeTextarea()
|
|
360
476
|
|
|
361
477
|
const chatId = this.currentDialogId
|
|
362
|
-
let aiContent = ''
|
|
363
|
-
const aiMsgIndex = this.dialogMessages.length
|
|
364
478
|
this.dialogMessages.push({
|
|
365
479
|
type: 'ai',
|
|
366
480
|
content: '',
|
|
@@ -369,8 +483,6 @@ export default {
|
|
|
369
483
|
})
|
|
370
484
|
this.scrollToBottom()
|
|
371
485
|
|
|
372
|
-
this.isAiStreaming = true
|
|
373
|
-
|
|
374
486
|
const requestBody = {
|
|
375
487
|
userMessage: messageContent,
|
|
376
488
|
chatId,
|
|
@@ -381,12 +493,31 @@ export default {
|
|
|
381
493
|
files: fileIds
|
|
382
494
|
}
|
|
383
495
|
|
|
496
|
+
const aiMsgIndex = this.dialogMessages.length - 1
|
|
497
|
+
this.startStream(aiMsgIndex, userMsgIndex, requestBody)
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
startStream(aiMsgIndex, userMsgIndex, requestBody) {
|
|
501
|
+
this.isAiStreaming = true
|
|
502
|
+
|
|
384
503
|
this.currentStreamConnection = startEventStreamPOST(
|
|
385
504
|
'/his-web/api/af-his/ai/chat/stream',
|
|
386
505
|
requestBody,
|
|
387
506
|
{'Content-Type': 'application/json'},
|
|
388
507
|
(data, type) => {
|
|
389
|
-
if (type === 'additionalInfo' || type === 'sourceInfo')
|
|
508
|
+
if (type === 'additionalInfo' || type === 'sourceInfo' || type === 'message_id') {
|
|
509
|
+
if (type === 'message_id') {
|
|
510
|
+
const userMessageId = data.userMessageId || ''
|
|
511
|
+
const aiMessageId = data.aiMessageId || ''
|
|
512
|
+
if (userMessageId && userMsgIndex !== undefined && this.dialogMessages[userMsgIndex]) {
|
|
513
|
+
this.dialogMessages[userMsgIndex].messageId = userMessageId
|
|
514
|
+
}
|
|
515
|
+
if (aiMessageId && this.dialogMessages[aiMsgIndex]) {
|
|
516
|
+
this.dialogMessages[aiMsgIndex].messageId = aiMessageId
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return
|
|
520
|
+
}
|
|
390
521
|
|
|
391
522
|
let textChunk = ''
|
|
392
523
|
if (typeof data === 'object' && data !== null) {
|
|
@@ -394,12 +525,10 @@ export default {
|
|
|
394
525
|
if (Array.isArray(choices) && choices.length > 0) {
|
|
395
526
|
const delta = choices[0].delta
|
|
396
527
|
|
|
397
|
-
// 优先取 delta.content(标准 OpenAI 格式)
|
|
398
528
|
if (delta && typeof delta.content === 'string' && delta.content) {
|
|
399
529
|
textChunk = delta.content
|
|
400
530
|
}
|
|
401
531
|
|
|
402
|
-
// 兼容百川模型的 thinking 字段
|
|
403
532
|
if (!textChunk && delta && delta.thinking) {
|
|
404
533
|
const thinking = delta.thinking
|
|
405
534
|
if (thinking.summary && typeof thinking.summary === 'string') {
|
|
@@ -413,7 +542,6 @@ export default {
|
|
|
413
542
|
}
|
|
414
543
|
}
|
|
415
544
|
|
|
416
|
-
// 检测模型拒答
|
|
417
545
|
const finishReason = choices[0].finish_reason
|
|
418
546
|
if (finishReason === 'refuse_answer') {
|
|
419
547
|
const refuseTip = '\n\n> ⚠️ 很抱歉,当前问题我无法回答,请尝试调整提问方式或联系管理员。'
|
|
@@ -429,8 +557,7 @@ export default {
|
|
|
429
557
|
}
|
|
430
558
|
|
|
431
559
|
if (textChunk) {
|
|
432
|
-
|
|
433
|
-
this.dialogMessages[aiMsgIndex].content = aiContent
|
|
560
|
+
this.dialogMessages[aiMsgIndex].content += textChunk
|
|
434
561
|
this.scrollToBottom()
|
|
435
562
|
}
|
|
436
563
|
},
|
|
@@ -441,8 +568,7 @@ export default {
|
|
|
441
568
|
},
|
|
442
569
|
() => {
|
|
443
570
|
this.isAiStreaming = false
|
|
444
|
-
|
|
445
|
-
if (!aiContent && this.dialogMessages[aiMsgIndex]) {
|
|
571
|
+
if (!this.dialogMessages[aiMsgIndex].content) {
|
|
446
572
|
this.dialogMessages[aiMsgIndex].content = '很抱歉,当前问题我无法回答,请尝试调整提问方式或联系管理员。'
|
|
447
573
|
}
|
|
448
574
|
this.scrollToBottom()
|
|
@@ -483,6 +609,7 @@ export default {
|
|
|
483
609
|
},
|
|
484
610
|
|
|
485
611
|
scrollToMessage(displayIndex) {
|
|
612
|
+
this.activeNavIndex = displayIndex
|
|
486
613
|
this.$nextTick(() => {
|
|
487
614
|
const messages = document.querySelectorAll('.dialog-area > div')
|
|
488
615
|
const target = messages[displayIndex]
|
|
@@ -587,6 +714,7 @@ export default {
|
|
|
587
714
|
if (!msg) return null
|
|
588
715
|
|
|
589
716
|
const message = {
|
|
717
|
+
messageId: msg.id || '',
|
|
590
718
|
type: msg.type === 'assistant' ? 'ai' : (msg.type || 'user'),
|
|
591
719
|
content: msg.content || '',
|
|
592
720
|
time: msg.time || '',
|
|
@@ -858,17 +986,18 @@ export default {
|
|
|
858
986
|
height: 8px;
|
|
859
987
|
border-radius: 6px;
|
|
860
988
|
opacity: 1;
|
|
861
|
-
background: #
|
|
989
|
+
background: #94979E;
|
|
862
990
|
cursor: pointer;
|
|
863
|
-
transition: transform 0.2s;
|
|
991
|
+
transition: transform 0.2s, background 0.2s;
|
|
864
992
|
}
|
|
865
993
|
|
|
866
994
|
.nav-dot:hover {
|
|
867
995
|
transform: scale(1.2);
|
|
996
|
+
background: #0057FE;
|
|
868
997
|
}
|
|
869
998
|
|
|
870
999
|
.nav-dot.active {
|
|
871
|
-
background: #
|
|
1000
|
+
background: #0057FE;
|
|
872
1001
|
transform: scale(1.3);
|
|
873
1002
|
}
|
|
874
1003
|
|