agilebuilder-ui 1.1.50-sit4 → 1.1.50
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/lib/{401-53507b6f.js → 401-aead36a9.js} +1 -1
- package/lib/{404-f3c9f985.js → 404-9ad11c2e.js} +1 -1
- package/lib/{iframe-page-737b57ae.js → iframe-page-bf851287.js} +1 -1
- package/lib/{index-90b19f05.js → index-75ff2a06.js} +14273 -14178
- package/lib/super-ui.css +1 -1
- package/lib/super-ui.js +1 -1
- package/lib/super-ui.umd.cjs +110 -110
- package/lib/{tab-content-iframe-index-a6efa4fc.js → tab-content-iframe-index-c7bb8381.js} +1 -1
- package/lib/{tab-content-index-fecc0e4d.js → tab-content-index-95a59e50.js} +1 -1
- package/lib/{tache-subprocess-history-3204f7f2.js → tache-subprocess-history-106dc6ec.js} +1 -1
- package/package.json +1 -1
- package/packages/chat-embed/src/chat-embed-message.ts +5 -2
- package/packages/chat-embed/src/chat-sender.vue +41 -30
- package/packages/chat-embed/src/header.vue +2 -2
- package/packages/chat-embed/src/index.vue +503 -158
- package/packages/department-tree-inline/src/search-result.vue +1 -2
- package/packages/department-user-tree-inline/src/search-result.vue +1 -2
- package/packages/fs-preview/src/fs-preview.vue +1 -1
- package/packages/fs-upload-new/src/fs-button-upload.vue +1 -0
- package/packages/fs-upload-new/src/fs-drag-upload.vue +1 -0
- package/packages/fs-upload-new/src/fs-preview-new.vue +39 -13
- package/packages/fs-upload-new/src/fs-upload-new.vue +3 -3
- package/packages/super-grid/src/normal-column-content.vue +0 -1
- package/packages/super-grid/src/row-operation.vue +9 -13
- package/packages/utils/value-set.js +1 -5
- package/src/views/login/update-password.vue +3 -0
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
<!-- <div ref="talkMainRef" :key="talkMainRefKey" class="chat-embed__main"> -->
|
|
18
18
|
<div ref="talkMainRef" class="chat-embed__main" :key="talkMainRefKey">
|
|
19
19
|
<div class="chat-embed__main_content">
|
|
20
|
-
<BubbleList :list="messageList" :max-height="bubbleMaxHeight" :is-fog="true">
|
|
20
|
+
<BubbleList ref="bubbleListRef" :list="messageList" :max-height="bubbleMaxHeight" :is-fog="true">
|
|
21
21
|
<template #content="{ item }">
|
|
22
22
|
<template v-if="!item.isRecommend && item.thinkingContent !== null">
|
|
23
23
|
<!-- ai消息正在回复中 -->
|
|
24
24
|
<Thinking
|
|
25
|
+
v-if="item.messageStatus !== 'failed'"
|
|
25
26
|
style="margin-bottom: 5px"
|
|
26
27
|
v-model="item.showThinkingContent"
|
|
27
28
|
:status="item.thinkingStatus"
|
|
@@ -40,42 +41,27 @@
|
|
|
40
41
|
:class="item.role === 'ai' ? 'content-container' : 'content-borderless-container'"
|
|
41
42
|
:style="{ maxWidth: bubbleMaxWidth }"
|
|
42
43
|
>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
<template v-if="item.role === 'ai' && !item.isRecommend">
|
|
45
|
+
<Typewriter
|
|
46
|
+
:content="item.content"
|
|
47
|
+
:typing="item.messageType === 'history' ? false : { interval: 10 }"
|
|
48
|
+
@finish="finishedAnswer($event, item)"
|
|
49
|
+
:is-markdown="true"
|
|
50
|
+
/>
|
|
51
|
+
</template>
|
|
49
52
|
<template v-else-if="item.role === 'user'">
|
|
50
53
|
{{ item.content }}
|
|
51
54
|
<template v-if="item.additionalData?.length > 0">
|
|
52
55
|
<el-divider border-style="dashed" />
|
|
53
|
-
<el-tag type="primary">
|
|
56
|
+
<el-tag type="primary" style="width: 100%">
|
|
54
57
|
<template #default>
|
|
55
|
-
<div
|
|
56
|
-
style="
|
|
57
|
-
display: flex;
|
|
58
|
-
align-items: center;
|
|
59
|
-
flex-shrink: 0;
|
|
60
|
-
min-width: max-content;
|
|
61
|
-
margin-left: auto;
|
|
62
|
-
gap: 10px;
|
|
63
|
-
"
|
|
64
|
-
>
|
|
58
|
+
<div class="additional-data-tag">
|
|
65
59
|
<el-icon><Paperclip /></el-icon>
|
|
66
60
|
{{ item.additionalDataLabel }}
|
|
67
61
|
</div>
|
|
68
62
|
</template>
|
|
69
63
|
</el-tag>
|
|
70
|
-
<div
|
|
71
|
-
style="
|
|
72
|
-
display: flex;
|
|
73
|
-
align-items: center;
|
|
74
|
-
justify-content: center;
|
|
75
|
-
margin-top: 10px;
|
|
76
|
-
gap: 10px;
|
|
77
|
-
"
|
|
78
|
-
>
|
|
64
|
+
<div class="additional-data-button">
|
|
79
65
|
<el-button plain round size="small" @click="viewJson(item.additionalData)">
|
|
80
66
|
<el-icon><View /></el-icon> <span>预览数据</span>
|
|
81
67
|
</el-button>
|
|
@@ -91,6 +77,9 @@
|
|
|
91
77
|
</template>
|
|
92
78
|
</template>
|
|
93
79
|
<template v-else> {{ item.content }} </template>
|
|
80
|
+
<div style="font-size: 10px; color: #999; text-align: right">
|
|
81
|
+
<span>{{ item.sendTime }}</span>
|
|
82
|
+
</div>
|
|
94
83
|
</div>
|
|
95
84
|
<template v-if="item.recommendations && item.recommendations.length > 0">
|
|
96
85
|
<!-- 推荐内容的消息 -->
|
|
@@ -100,34 +89,38 @@
|
|
|
100
89
|
</template>
|
|
101
90
|
<template #footer="{ item }">
|
|
102
91
|
<div
|
|
92
|
+
class="footer-wrapper"
|
|
103
93
|
v-if="item.role === 'ai' && !item.isRecommend && item.thinkingStatus !== 'thinking'"
|
|
104
|
-
class="footer-container"
|
|
105
94
|
>
|
|
106
|
-
<
|
|
107
|
-
<el-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<el-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
95
|
+
<div class="footer-container">
|
|
96
|
+
<el-tooltip class="box-item" effect="dark" :content="$t('chatEmbed.regenerate')">
|
|
97
|
+
<el-button type="info" :icon="Refresh" size="small" circle @click="regenerateQuestion(item)" />
|
|
98
|
+
</el-tooltip>
|
|
99
|
+
<el-tooltip class="box-item" effect="dark" :content="$t('chatEmbed.copy')">
|
|
100
|
+
<el-button
|
|
101
|
+
type="info"
|
|
102
|
+
color="#626aef"
|
|
103
|
+
:icon="DocumentCopy"
|
|
104
|
+
size="small"
|
|
105
|
+
circle
|
|
106
|
+
@click="cpoyContent(item)"
|
|
107
|
+
/>
|
|
108
|
+
</el-tooltip>
|
|
109
|
+
</div>
|
|
119
110
|
</div>
|
|
120
111
|
</template>
|
|
121
112
|
</BubbleList>
|
|
113
|
+
<div style="display: none">{{ content }}</div>
|
|
122
114
|
</div>
|
|
123
115
|
<!-- 对话输入框 -->
|
|
124
116
|
<div ref="talkInputRef" class="chat-embed__input_container">
|
|
125
117
|
<ChatSender
|
|
126
118
|
:loading="loading"
|
|
127
|
-
@submit-question="
|
|
119
|
+
@submit-question="submitQuestion"
|
|
128
120
|
@new-chat-session="newChatSession"
|
|
129
121
|
@handle-cancel="handleCancel"
|
|
130
122
|
@view-json="viewJson"
|
|
123
|
+
@change-ai-model="changeAiModel"
|
|
131
124
|
/>
|
|
132
125
|
</div>
|
|
133
126
|
</div>
|
|
@@ -137,58 +130,127 @@
|
|
|
137
130
|
</div>
|
|
138
131
|
</template>
|
|
139
132
|
<script setup lang="ts">
|
|
140
|
-
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
|
133
|
+
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue'
|
|
134
|
+
import { useI18n } from 'vue-i18n'
|
|
141
135
|
import { Refresh, DocumentCopy } from '@element-plus/icons-vue'
|
|
142
136
|
import { BubbleList, Thinking, useXStream, Typewriter } from 'vue-element-plus-x'
|
|
137
|
+
import { BubbleProps } from 'vue-element-plus-x/types/Bubble'
|
|
143
138
|
import { useDraggable } from '@vueuse/core'
|
|
144
139
|
import { ElMessage } from 'element-plus'
|
|
140
|
+
import { getToken } from '../../../src/utils/auth'
|
|
145
141
|
import { getMessageTemplate, defaultMessage } from './chat-embed-message'
|
|
146
142
|
import RecommendationMessage from './recommendation-message.vue'
|
|
147
|
-
import { BubbleProps } from 'vue-element-plus-x/types/Bubble'
|
|
148
|
-
import { useI18n } from 'vue-i18n'
|
|
149
143
|
import Header from './header.vue'
|
|
150
144
|
import ChatSender from './chat-sender.vue'
|
|
151
145
|
import { JsonViewDialog } from '../../json-view'
|
|
152
146
|
import { generateFileName, downloadJsonFile } from './util.ts'
|
|
153
147
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
148
|
+
// 类型定义
|
|
149
|
+
interface MessageItem {
|
|
150
|
+
role: 'ai' | 'user'
|
|
151
|
+
content: string
|
|
152
|
+
messageType?: 'history' | 'realtime'
|
|
153
|
+
messageStatus?: 'failed' | 'success'
|
|
154
|
+
thinkingStatus?: 'thinking' | 'end'
|
|
155
|
+
thinkingContent?: string
|
|
156
|
+
showThinkingContent?: boolean
|
|
157
|
+
isRecommend?: boolean
|
|
158
|
+
sendTime?: string
|
|
159
|
+
question?: string
|
|
160
|
+
additionalData?: any[]
|
|
161
|
+
additionalDataLabel?: string
|
|
162
|
+
menuName?: string
|
|
163
|
+
menuCode?: string
|
|
164
|
+
askAdditionalData?: any[]
|
|
165
|
+
recommendations?: any[]
|
|
157
166
|
}
|
|
158
167
|
|
|
168
|
+
interface SubmitData {
|
|
169
|
+
question: string
|
|
170
|
+
additionalData?: any[]
|
|
171
|
+
additionalDataLabel?: string
|
|
172
|
+
menuName?: string
|
|
173
|
+
menuCode?: string
|
|
174
|
+
aiModel?: string
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 常量定义
|
|
178
|
+
const AI_MODELS = {
|
|
179
|
+
DEEPSEEK: 'deepseek',
|
|
180
|
+
RAGFLOW: 'ragflow',
|
|
181
|
+
DATABASE: 'database'
|
|
182
|
+
} as const
|
|
183
|
+
|
|
184
|
+
const MESSAGE_STATUS = {
|
|
185
|
+
THINKING: 'thinking',
|
|
186
|
+
END: 'end',
|
|
187
|
+
FAILED: 'failed'
|
|
188
|
+
} as const
|
|
189
|
+
|
|
190
|
+
// 初始化
|
|
191
|
+
const enableAiChat = ref(window.$vueApp?.config?.globalProperties?.enableAiChat || false)
|
|
192
|
+
const { startStream, cancel, data, error, isLoading } = useXStream()
|
|
159
193
|
const { t } = useI18n()
|
|
160
194
|
const chatIcon = ref()
|
|
161
|
-
|
|
162
|
-
const isActualClickTimer = ref(null)
|
|
195
|
+
const isActualClick = ref(true)
|
|
163
196
|
|
|
197
|
+
// 聊天内容状态
|
|
198
|
+
const messageList = ref<MessageItem[]>([])
|
|
199
|
+
const answerContent = ref<MessageItem>({} as MessageItem)
|
|
200
|
+
const session_id = ref<string | null>(null)
|
|
201
|
+
const ragflowSession_id = ref<string | null>(null)
|
|
202
|
+
const loading = ref(false)
|
|
203
|
+
const currentAiModel = ref<string>(AI_MODELS.RAGFLOW)
|
|
204
|
+
const aiModelAnswerHistory: Record<string, MessageItem[]> = {}
|
|
205
|
+
|
|
206
|
+
// UI状态
|
|
207
|
+
const showChatVisable = ref(false)
|
|
208
|
+
const dialogFullScreen = ref(false)
|
|
209
|
+
const talkMainRefKey = ref(0)
|
|
210
|
+
const bubbleMaxWidth = ref('')
|
|
211
|
+
const bubbleMaxHeight = ref('500px')
|
|
212
|
+
const aiAvatar = ref('https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png')
|
|
213
|
+
|
|
214
|
+
// 弹窗状态
|
|
164
215
|
const jsonDialogVisible = ref(false)
|
|
165
216
|
const jsonObject = ref({})
|
|
166
217
|
|
|
218
|
+
// DOM引用
|
|
219
|
+
const bubbleListRef = ref()
|
|
220
|
+
const talkInputRef = ref()
|
|
221
|
+
const chatContainerRef = ref()
|
|
222
|
+
const talkMainRef = ref()
|
|
223
|
+
|
|
224
|
+
// 流式处理状态
|
|
225
|
+
const isStreamActive = ref(false)
|
|
226
|
+
let abortController: AbortController | null = null
|
|
227
|
+
|
|
228
|
+
// 拖拽功能设置
|
|
167
229
|
const initialX = document.documentElement.clientWidth - 100
|
|
168
230
|
const initialY = document.documentElement.clientHeight - 100
|
|
169
231
|
const { x, y, style } = useDraggable(chatIcon, {
|
|
170
|
-
// 聊天图标的初始位置
|
|
171
232
|
initialValue: { x: initialX, y: initialY },
|
|
172
233
|
preventDefault: true,
|
|
173
234
|
onStart() {
|
|
174
|
-
// 拖拽开始时重置状态
|
|
175
235
|
isActualClick.value = true
|
|
176
236
|
},
|
|
177
237
|
onMove(position) {
|
|
178
238
|
isActualClick.value = false
|
|
179
|
-
|
|
180
|
-
const maxX = window.innerWidth - chatIcon.value.offsetWidth
|
|
181
|
-
const maxY = window.innerHeight - chatIcon.value.offsetHeight
|
|
182
|
-
position.x = Math.max(0, Math.min(position.x, maxX))
|
|
183
|
-
position.y = Math.max(0, Math.min(position.y, maxY))
|
|
239
|
+
constrainPosition(position)
|
|
184
240
|
},
|
|
185
241
|
onEnd() {
|
|
186
242
|
adjustChatIconPosition()
|
|
187
|
-
// 延迟重置确保 click 事件先处理
|
|
188
243
|
setTimeout(() => (isActualClick.value = true), 50)
|
|
189
244
|
}
|
|
190
245
|
})
|
|
191
246
|
|
|
247
|
+
function constrainPosition(position: { x: number; y: number }) {
|
|
248
|
+
const maxX = window.innerWidth - (chatIcon.value?.offsetWidth || 0)
|
|
249
|
+
const maxY = window.innerHeight - (chatIcon.value?.offsetHeight || 0)
|
|
250
|
+
position.x = Math.max(0, Math.min(position.x, maxX))
|
|
251
|
+
position.y = Math.max(0, Math.min(position.y, maxY))
|
|
252
|
+
}
|
|
253
|
+
|
|
192
254
|
function adjustChatIconPosition() {
|
|
193
255
|
if (!chatIcon.value) return
|
|
194
256
|
const maxX = window.innerWidth - chatIcon.value.offsetWidth
|
|
@@ -197,34 +259,24 @@ function adjustChatIconPosition() {
|
|
|
197
259
|
if (y.value > maxY) y.value = maxY
|
|
198
260
|
}
|
|
199
261
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const dialogFullScreen = ref(false)
|
|
211
|
-
|
|
212
|
-
const talkMainRefKey = ref(0)
|
|
213
|
-
|
|
214
|
-
const bubbleMaxWidth = ref('')
|
|
215
|
-
const bubbleMaxHeight = ref('500px')
|
|
262
|
+
function resetChat(aiModel: string) {
|
|
263
|
+
currentAiModel.value = aiModel
|
|
264
|
+
messageList.value = []
|
|
265
|
+
if (!aiModelAnswerHistory[aiModel]) {
|
|
266
|
+
messageList.value.push(defaultMessage)
|
|
267
|
+
} else {
|
|
268
|
+
messageList.value = aiModelAnswerHistory[aiModel]
|
|
269
|
+
bubbleListRef.value?.scrollToBottom()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
216
272
|
|
|
217
|
-
|
|
273
|
+
function showError(message: string = t('chatEmbed.requestFailed')) {
|
|
274
|
+
messageList.value.push(getMessageTemplate('ai', message, false))
|
|
275
|
+
loading.value = false
|
|
276
|
+
}
|
|
218
277
|
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
// 默认ai提示消息
|
|
222
|
-
messageList.value.push(defaultMessage)
|
|
223
|
-
// messageList.value.push(getMessageTemplate('user', '11111111111', true))
|
|
224
|
-
// 正在生成的ai消息
|
|
225
|
-
const answerContent: any = ref({})
|
|
226
|
-
// 后端会话session_id
|
|
227
|
-
const session_id = ref(null)
|
|
278
|
+
// 初始化聊天
|
|
279
|
+
resetChat(currentAiModel.value)
|
|
228
280
|
|
|
229
281
|
onMounted(() => {
|
|
230
282
|
window.addEventListener('resize', adjustChatIconPosition)
|
|
@@ -232,13 +284,19 @@ onMounted(() => {
|
|
|
232
284
|
|
|
233
285
|
onUnmounted(() => {
|
|
234
286
|
window.removeEventListener('resize', adjustChatIconPosition)
|
|
235
|
-
|
|
236
|
-
if (isActualClickTimer.value) {
|
|
237
|
-
clearTimeout(isActualClickTimer.value)
|
|
238
|
-
}
|
|
287
|
+
cleanup()
|
|
239
288
|
})
|
|
240
289
|
|
|
241
|
-
|
|
290
|
+
function cleanup() {
|
|
291
|
+
if (abortController) {
|
|
292
|
+
abortController.abort()
|
|
293
|
+
abortController = null
|
|
294
|
+
}
|
|
295
|
+
cancel()
|
|
296
|
+
isStreamActive.value = false
|
|
297
|
+
loading.value = false
|
|
298
|
+
}
|
|
299
|
+
// 聊天相关方法
|
|
242
300
|
function showChatDialog() {
|
|
243
301
|
if (isActualClick.value) {
|
|
244
302
|
showChatVisable.value = !showChatVisable.value
|
|
@@ -249,63 +307,267 @@ function showChatDialog() {
|
|
|
249
307
|
}
|
|
250
308
|
}
|
|
251
309
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const message = getMessageTemplate('user', question, true)
|
|
262
|
-
if (submitData.additionalData && submitData.additionalData.length > 0) {
|
|
263
|
-
message.additionalData = submitData.additionalData
|
|
264
|
-
message.additionalDataLabel = submitData.additionalDataLabel
|
|
265
|
-
message.menuName = submitData.menuName
|
|
266
|
-
message.menuCode = submitData.menuCode
|
|
310
|
+
function createUserMessage(submitData: SubmitData): MessageItem {
|
|
311
|
+
const message = getMessageTemplate('user', submitData.question, true) as MessageItem
|
|
312
|
+
if (submitData.additionalData?.length) {
|
|
313
|
+
Object.assign(message, {
|
|
314
|
+
additionalData: submitData.additionalData,
|
|
315
|
+
additionalDataLabel: submitData.additionalDataLabel,
|
|
316
|
+
menuName: submitData.menuName,
|
|
317
|
+
menuCode: submitData.menuCode
|
|
318
|
+
})
|
|
267
319
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
320
|
+
return message
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function createAiMessage(submitData: SubmitData): MessageItem {
|
|
324
|
+
const message = getMessageTemplate('ai', '', false) as MessageItem
|
|
325
|
+
return Object.assign(message, {
|
|
326
|
+
thinkingStatus: MESSAGE_STATUS.THINKING,
|
|
327
|
+
question: submitData.question,
|
|
328
|
+
menuName: submitData.menuName,
|
|
329
|
+
menuCode: submitData.menuCode,
|
|
330
|
+
askAdditionalData: submitData.additionalData
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function initializeSession(): string {
|
|
271
335
|
if (!session_id.value) {
|
|
272
336
|
session_id.value = new Date().getTime().toString()
|
|
273
337
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
answerContent.value.menuCode = submitData.menuCode
|
|
281
|
-
answerContent.value.askAdditionalData = submitData.additionalData
|
|
282
|
-
messageList.value.push(answerContent.value)
|
|
338
|
+
return session_id.value
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function sendDeepSeekQuestion(submitData: SubmitData) {
|
|
342
|
+
if (!submitData.question) return
|
|
343
|
+
|
|
283
344
|
try {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
345
|
+
// 添加用户消息
|
|
346
|
+
const userMessage = createUserMessage(submitData)
|
|
347
|
+
messageList.value.push(userMessage)
|
|
348
|
+
isStreamActive.value = true
|
|
349
|
+
// 初始化状态
|
|
350
|
+
loading.value = true
|
|
351
|
+
initializeSession()
|
|
352
|
+
// 创建AI响应消息
|
|
353
|
+
answerContent.value = createAiMessage(submitData)
|
|
354
|
+
messageList.value.push(answerContent.value)
|
|
355
|
+
|
|
356
|
+
abortController = new AbortController()
|
|
357
|
+
|
|
358
|
+
const token = getToken()
|
|
359
|
+
const response = await fetch(window.$vueApp.config.globalProperties.baseAPI + '/component/ai/multiple-fuck', {
|
|
360
|
+
method: 'POST',
|
|
361
|
+
headers: { 'Content-Type': 'application/json', authorization: token },
|
|
362
|
+
body: JSON.stringify({
|
|
363
|
+
question: submitData.question,
|
|
287
364
|
messageId: session_id.value,
|
|
288
365
|
data: JSON.stringify(submitData.additionalData)
|
|
289
|
-
})
|
|
290
|
-
.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
})
|
|
366
|
+
}),
|
|
367
|
+
signal: abortController.signal // 绑定 abort 信号
|
|
368
|
+
})
|
|
369
|
+
if (!response.body) {
|
|
370
|
+
throw new Error('No response body')
|
|
371
|
+
}
|
|
372
|
+
await startStream({ readableStream: response.body })
|
|
297
373
|
} catch (err) {
|
|
298
|
-
console.error('
|
|
299
|
-
|
|
374
|
+
console.error('DeepSeek error:', err)
|
|
375
|
+
showError()
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function sendRagflowQuestion(question: string | undefined) {
|
|
380
|
+
if (!question) return
|
|
381
|
+
|
|
382
|
+
data.value = []
|
|
383
|
+
messageList.value.push(getMessageTemplate('user', question, true))
|
|
384
|
+
loading.value = true
|
|
385
|
+
|
|
386
|
+
if (!ragflowSession_id.value) {
|
|
387
|
+
createRagflowSession(question)
|
|
388
|
+
} else {
|
|
389
|
+
startSSE(question)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function createRagflowSession(question: string) {
|
|
394
|
+
try {
|
|
395
|
+
const response = await window.$vueApp.config.globalProperties.$http.post(
|
|
396
|
+
window.$vueApp.config.globalProperties.baseAPI + '/mc/rag-flow/converse-with-chat',
|
|
397
|
+
{ question }
|
|
398
|
+
)
|
|
399
|
+
ragflowSession_id.value = response.session_id
|
|
400
|
+
startSSE(question)
|
|
401
|
+
} catch (err) {
|
|
402
|
+
console.error('Failed to create session:', err)
|
|
403
|
+
showError()
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function startSSE(question: string) {
|
|
408
|
+
try {
|
|
409
|
+
answerContent.value = createAiMessage({ question } as SubmitData)
|
|
410
|
+
messageList.value.push(answerContent.value)
|
|
411
|
+
|
|
412
|
+
// 创建新的 AbortController 用于 SSE 请求
|
|
413
|
+
abortController = new AbortController()
|
|
414
|
+
|
|
415
|
+
const token = getToken()
|
|
416
|
+
const response = await fetch(
|
|
417
|
+
window.$vueApp.config.globalProperties.baseAPI + '/mc/rag-flow/converse-with-chat-stream',
|
|
418
|
+
{
|
|
419
|
+
method: 'POST',
|
|
420
|
+
headers: { 'Content-Type': 'text/event-stream', authorization: token },
|
|
421
|
+
body: JSON.stringify({
|
|
422
|
+
question,
|
|
423
|
+
stream: true,
|
|
424
|
+
session_id: ragflowSession_id.value
|
|
425
|
+
}),
|
|
426
|
+
signal: abortController.signal // 绑定 abort 信号
|
|
427
|
+
}
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
if (!response.body) {
|
|
431
|
+
throw new Error('No response body')
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
await startStream({ readableStream: response.body })
|
|
435
|
+
} catch (err: any) {
|
|
436
|
+
if (err?.name === 'AbortError') {
|
|
437
|
+
console.log('SSE request was cancelled')
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
console.error('SSE error:', err)
|
|
300
441
|
loading.value = false
|
|
301
442
|
}
|
|
302
443
|
}
|
|
444
|
+
// 优化的计算属性 - 减少不必要的重新计算
|
|
445
|
+
const content = computed(() => {
|
|
446
|
+
if (!isStreamActive.value || !data.value.length) {
|
|
447
|
+
return ''
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return processStreamData()
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
function processStreamData(): string {
|
|
454
|
+
let text = ''
|
|
455
|
+
// 获取data.value的最后一条
|
|
456
|
+
const item = data.value[data.value.length - 1]
|
|
457
|
+
try {
|
|
458
|
+
const responseMessage = JSON.parse(item.data)
|
|
459
|
+
if (responseMessage.isChunk) {
|
|
460
|
+
// 主要是百练ai后端处理的数据
|
|
461
|
+
if (responseMessage.data === true) {
|
|
462
|
+
answerContent.value.thinkingStatus = MESSAGE_STATUS.END
|
|
463
|
+
answerContent.value.showThinkingContent = true
|
|
464
|
+
answerContent.value = {} as MessageItem
|
|
465
|
+
loading.value = false
|
|
466
|
+
} else {
|
|
467
|
+
updateStreamContent(responseMessage)
|
|
468
|
+
}
|
|
469
|
+
} else {
|
|
470
|
+
if (!responseMessage.data?.id) {
|
|
471
|
+
finishStreamProcessing()
|
|
472
|
+
return text
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (responseMessage.data === true || responseMessage.data?.created_at) {
|
|
476
|
+
finishStreamWithAnswer(responseMessage.data.answer)
|
|
477
|
+
return text
|
|
478
|
+
}
|
|
479
|
+
updateStreamContent(responseMessage)
|
|
480
|
+
}
|
|
481
|
+
} catch (error) {
|
|
482
|
+
console.error('解析数据时出错:', error)
|
|
483
|
+
loading.value = false
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return text
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function finishStreamProcessing() {
|
|
490
|
+
loading.value = false
|
|
491
|
+
if (answerContent.value) {
|
|
492
|
+
answerContent.value.thinkingStatus = MESSAGE_STATUS.END
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function finishStreamWithAnswer(answer: string) {
|
|
497
|
+
loading.value = false
|
|
498
|
+
if (!answerContent.value) return
|
|
499
|
+
|
|
500
|
+
answerContent.value.thinkingStatus = MESSAGE_STATUS.END
|
|
501
|
+
answerContent.value.showThinkingContent = true
|
|
303
502
|
|
|
304
|
-
|
|
503
|
+
const { thinkingContent, mainContent } = parseAnswerContent(answer)
|
|
504
|
+
answerContent.value.thinkingContent = thinkingContent
|
|
505
|
+
answerContent.value.content = mainContent
|
|
506
|
+
answerContent.value = {} as MessageItem
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function parseAnswerContent(answer: string): { thinkingContent: string; mainContent: string } {
|
|
510
|
+
if (answer.startsWith('<think>')) {
|
|
511
|
+
const thinkEndIndex = answer.indexOf('</think>')
|
|
512
|
+
return {
|
|
513
|
+
thinkingContent: answer.substring(7, thinkEndIndex), // 7 is length of '<think>'
|
|
514
|
+
mainContent: answer.substring(thinkEndIndex + 8) // 8 is length of '</think>'
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return { thinkingContent: '', mainContent: answer }
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function updateStreamContent(responseMessage: any) {
|
|
521
|
+
const answer = responseMessage.data.answer
|
|
522
|
+
if (!answer || !answerContent.value) return
|
|
523
|
+
|
|
524
|
+
const { thinkingContent, mainContent } = parseAnswerContent(answer)
|
|
525
|
+
|
|
526
|
+
if (thinkingContent) {
|
|
527
|
+
answerContent.value.thinkingContent = thinkingContent
|
|
528
|
+
}
|
|
529
|
+
if (mainContent) {
|
|
530
|
+
if (responseMessage.isChunk) {
|
|
531
|
+
answerContent.value.content += mainContent
|
|
532
|
+
} else {
|
|
533
|
+
answerContent.value.content = mainContent
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// 取消操作的统一处理
|
|
305
539
|
function handleCancel() {
|
|
306
540
|
loading.value = false
|
|
307
|
-
|
|
308
|
-
|
|
541
|
+
|
|
542
|
+
// 取消网络请求
|
|
543
|
+
if (abortController) {
|
|
544
|
+
abortController.abort()
|
|
545
|
+
abortController = null
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 取消流式处理
|
|
549
|
+
if (isStreamActive.value) {
|
|
550
|
+
cancel() // 调用 useXStream 的 cancel 方法
|
|
551
|
+
isStreamActive.value = false
|
|
552
|
+
|
|
553
|
+
// 更新最后一条 AI 消息的状态
|
|
554
|
+
if (messageList.value.length > 0) {
|
|
555
|
+
const lastMessage = messageList.value[messageList.value.length - 1]
|
|
556
|
+
if (lastMessage && lastMessage.role === 'ai') {
|
|
557
|
+
lastMessage.thinkingStatus = MESSAGE_STATUS.END
|
|
558
|
+
// 如果消息内容为空,可以添加一个取消的提示
|
|
559
|
+
if (!lastMessage.content) {
|
|
560
|
+
lastMessage.content = '回答已取消'
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// 重置答案内容
|
|
567
|
+
if (answerContent.value && Object.keys(answerContent.value).length > 0) {
|
|
568
|
+
answerContent.value.thinkingStatus = MESSAGE_STATUS.END
|
|
569
|
+
answerContent.value = {} as MessageItem
|
|
570
|
+
}
|
|
309
571
|
}
|
|
310
572
|
|
|
311
573
|
// 处理聊天窗口的全屏和缩小
|
|
@@ -326,7 +588,6 @@ function setHeightAndWidth() {
|
|
|
326
588
|
// talkMainRef.value.style.height = `calc(${heightVh}vh - var(--header-height) - var(--chat-padding) * 2)`
|
|
327
589
|
const talkHeight = talkInputRef.value.offsetHeight
|
|
328
590
|
// alert(`talkHeight: ${talkHeight}`)
|
|
329
|
-
// bubbleMaxHeight.value = `calc(${heightVh}vh - var(--header-height) - var(--chat-padding) * 5 - 144px)`
|
|
330
591
|
nextTick(() => {
|
|
331
592
|
const talkHeight = talkInputRef.value?.offsetHeight || 80
|
|
332
593
|
// 定义具体的像素值
|
|
@@ -338,21 +599,54 @@ function setHeightAndWidth() {
|
|
|
338
599
|
})
|
|
339
600
|
}
|
|
340
601
|
|
|
341
|
-
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
602
|
+
function submitQuestion(submitData: SubmitData) {
|
|
603
|
+
// 先取消之前的请求
|
|
604
|
+
if (loading.value) {
|
|
605
|
+
handleCancel()
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
isStreamActive.value = false
|
|
609
|
+
switch (submitData.aiModel) {
|
|
610
|
+
case AI_MODELS.DEEPSEEK:
|
|
611
|
+
isStreamActive.value = true
|
|
612
|
+
sendDeepSeekQuestion(submitData)
|
|
613
|
+
break
|
|
614
|
+
case AI_MODELS.RAGFLOW:
|
|
615
|
+
isStreamActive.value = true
|
|
616
|
+
sendRagflowQuestion(submitData.question)
|
|
617
|
+
break
|
|
618
|
+
case AI_MODELS.DATABASE:
|
|
619
|
+
ElMessage({
|
|
620
|
+
message: '数据库类问答功能开发中,敬请期待',
|
|
621
|
+
type: 'warning'
|
|
622
|
+
})
|
|
623
|
+
break
|
|
624
|
+
default:
|
|
625
|
+
console.warn('Unknown AI model:', submitData.aiModel)
|
|
626
|
+
}
|
|
346
627
|
}
|
|
347
628
|
|
|
348
|
-
function regenerateQuestion(answerData) {
|
|
349
|
-
|
|
629
|
+
function regenerateQuestion(answerData: any) {
|
|
630
|
+
const submitData: SubmitData = {
|
|
350
631
|
question: answerData.question,
|
|
351
632
|
additionalData: answerData.additionalData,
|
|
352
633
|
additionalDataLabel: answerData.additionalDataLabel,
|
|
353
634
|
menuName: answerData.menuName,
|
|
354
|
-
menuCode: answerData.menuCode
|
|
355
|
-
|
|
635
|
+
menuCode: answerData.menuCode,
|
|
636
|
+
aiModel: currentAiModel.value
|
|
637
|
+
}
|
|
638
|
+
sendDeepSeekQuestion(submitData)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function clickRecommendation(item: BubbleProps) {
|
|
642
|
+
console.log('clickRecommendation', item)
|
|
643
|
+
if (item.content) {
|
|
644
|
+
const submitData: SubmitData = {
|
|
645
|
+
question: item.content,
|
|
646
|
+
aiModel: currentAiModel.value
|
|
647
|
+
}
|
|
648
|
+
sendDeepSeekQuestion(submitData)
|
|
649
|
+
}
|
|
356
650
|
}
|
|
357
651
|
|
|
358
652
|
function cpoyContent(item: BubbleProps) {
|
|
@@ -369,33 +663,46 @@ function cpoyContent(item: BubbleProps) {
|
|
|
369
663
|
}
|
|
370
664
|
|
|
371
665
|
function newChatSession() {
|
|
372
|
-
// 清空聊天记录
|
|
373
666
|
messageList.value = []
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
.then((res) => {
|
|
379
|
-
//
|
|
380
|
-
})
|
|
381
|
-
} catch (err) {
|
|
382
|
-
console.error('Fetch error:', err)
|
|
383
|
-
messageList.push(getMessageTemplate('ai', t('chatEmbed.requestFailed'), false))
|
|
384
|
-
loading.value = false
|
|
385
|
-
}
|
|
667
|
+
|
|
668
|
+
// 清理会话(仅对DeepSeek模型)
|
|
669
|
+
if (session_id.value && currentAiModel.value === AI_MODELS.DEEPSEEK) {
|
|
670
|
+
clearDeepSeekSession()
|
|
386
671
|
}
|
|
672
|
+
|
|
673
|
+
// 重置状态
|
|
387
674
|
session_id.value = null
|
|
388
|
-
// 重置加载状态
|
|
389
675
|
loading.value = false
|
|
390
|
-
|
|
676
|
+
aiModelAnswerHistory[currentAiModel.value] = []
|
|
677
|
+
resetChat(currentAiModel.value)
|
|
391
678
|
}
|
|
392
679
|
|
|
680
|
+
async function clearDeepSeekSession() {
|
|
681
|
+
try {
|
|
682
|
+
await (window as any).$vueApp.config.globalProperties.$http.post(
|
|
683
|
+
(window as any).$vueApp.config.globalProperties.baseAPI + '/component/ai/multiple-fuck/clear/' + session_id.value
|
|
684
|
+
)
|
|
685
|
+
} catch (err) {
|
|
686
|
+
console.error('Failed to clear session:', err)
|
|
687
|
+
showError()
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function changeAiModel(newModel: string) {
|
|
692
|
+
aiModelAnswerHistory[currentAiModel.value] = JSON.parse(JSON.stringify(messageList.value))
|
|
693
|
+
resetChat(newModel)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function finishedAnswer(event: any, item: any) {
|
|
697
|
+
item.messageType = 'history'
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// JSON 相关操作
|
|
393
701
|
function viewJson(viewData: any) {
|
|
394
702
|
jsonObject.value = viewData
|
|
395
703
|
jsonDialogVisible.value = true
|
|
396
704
|
}
|
|
397
705
|
|
|
398
|
-
// 下载 JSON 数据功能
|
|
399
706
|
function downloadJsonData(data: any, fileName: string) {
|
|
400
707
|
try {
|
|
401
708
|
downloadJsonFile(data, generateFileName(fileName || 'data'))
|
|
@@ -422,4 +729,42 @@ h4 {
|
|
|
422
729
|
:deep(.el-divider--horizontal) {
|
|
423
730
|
margin: 5px 0;
|
|
424
731
|
}
|
|
732
|
+
.additional-data-tag {
|
|
733
|
+
display: flex;
|
|
734
|
+
align-items: center;
|
|
735
|
+
flex-shrink: 0;
|
|
736
|
+
min-width: max-content;
|
|
737
|
+
margin-left: auto;
|
|
738
|
+
gap: 10px;
|
|
739
|
+
}
|
|
740
|
+
.additional-data-button {
|
|
741
|
+
display: flex;
|
|
742
|
+
align-items: center;
|
|
743
|
+
justify-content: center;
|
|
744
|
+
margin-top: 10px;
|
|
745
|
+
gap: 10px;
|
|
746
|
+
}
|
|
747
|
+
.footer-wrapper {
|
|
748
|
+
display: flex;
|
|
749
|
+
align-items: center;
|
|
750
|
+
justify-content: space-between;
|
|
751
|
+
gap: 10px;
|
|
752
|
+
width: 100%;
|
|
753
|
+
|
|
754
|
+
.footer-time {
|
|
755
|
+
font-size: 10px;
|
|
756
|
+
color: #999;
|
|
757
|
+
margin-top: 3px;
|
|
758
|
+
flex-shrink: 0;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.footer-container {
|
|
763
|
+
display: flex;
|
|
764
|
+
gap: 8px;
|
|
765
|
+
|
|
766
|
+
:deep(.el-button + .el-button) {
|
|
767
|
+
margin-left: 0;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
425
770
|
</style>
|