agilebuilder-ui 1.1.50-sit4 → 1.1.51

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.
Files changed (26) hide show
  1. package/lib/{401-53507b6f.js → 401-aead36a9.js} +1 -1
  2. package/lib/{404-f3c9f985.js → 404-9ad11c2e.js} +1 -1
  3. package/lib/{iframe-page-737b57ae.js → iframe-page-bf851287.js} +1 -1
  4. package/lib/{index-90b19f05.js → index-75ff2a06.js} +14273 -14178
  5. package/lib/super-ui.css +1 -1
  6. package/lib/super-ui.js +1 -1
  7. package/lib/super-ui.umd.cjs +110 -110
  8. package/lib/{tab-content-iframe-index-a6efa4fc.js → tab-content-iframe-index-c7bb8381.js} +1 -1
  9. package/lib/{tab-content-index-fecc0e4d.js → tab-content-index-95a59e50.js} +1 -1
  10. package/lib/{tache-subprocess-history-3204f7f2.js → tache-subprocess-history-106dc6ec.js} +1 -1
  11. package/package.json +1 -1
  12. package/packages/chat-embed/src/chat-embed-message.ts +5 -2
  13. package/packages/chat-embed/src/chat-sender.vue +41 -30
  14. package/packages/chat-embed/src/header.vue +2 -2
  15. package/packages/chat-embed/src/index.vue +503 -158
  16. package/packages/department-tree-inline/src/search-result.vue +1 -2
  17. package/packages/department-user-tree-inline/src/search-result.vue +1 -2
  18. package/packages/fs-preview/src/fs-preview.vue +1 -1
  19. package/packages/fs-upload-new/src/fs-button-upload.vue +1 -0
  20. package/packages/fs-upload-new/src/fs-drag-upload.vue +1 -0
  21. package/packages/fs-upload-new/src/fs-preview-new.vue +39 -13
  22. package/packages/fs-upload-new/src/fs-upload-new.vue +3 -3
  23. package/packages/super-grid/src/normal-column-content.vue +0 -1
  24. package/packages/super-grid/src/row-operation.vue +9 -13
  25. package/packages/utils/value-set.js +1 -5
  26. 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
- <Typewriter
44
- v-if="item.role === 'ai' && !item.isRecommend"
45
- :content="item.content"
46
- :typing="{ interval: 10 }"
47
- :is-markdown="true"
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
- <el-tooltip class="box-item" effect="dark" :content="$t('chatEmbed.regenerate')">
107
- <el-button type="info" :icon="Refresh" size="small" circle @click="regenerateQuestion(item)" />
108
- </el-tooltip>
109
- <el-tooltip class="box-item" effect="dark" :content="$t('chatEmbed.copy')">
110
- <el-button
111
- type="info"
112
- color="#626aef"
113
- :icon="DocumentCopy"
114
- size="small"
115
- circle
116
- @click="cpoyContent(item)"
117
- />
118
- </el-tooltip>
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="sendQuestion"
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
- const enableAiChat = ref(false)
155
- if (window.$vueApp.config.globalProperties.enableAiChat) {
156
- enableAiChat.value = true
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
- let isActualClick = ref(true)
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
- const talkInputRef = ref()
201
- // 显示聊天窗口的状态
202
- const showChatVisable = ref(false)
203
- // 聊天窗口的引用
204
- const chatContainerRef = ref()
205
- // 聊天窗口主区域的引用
206
- const talkMainRef = ref()
207
- // 聊天输入框状态
208
- const loading = ref(false)
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
- const aiAvatar = ref('https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png')
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
- const messageList: any = ref([])
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
- const { cancel, data } = useXStream()
253
-
254
- // 发送聊天内容
255
- function sendQuestion(submitData: any) {
256
- const question = submitData.question
257
- if (!question) {
258
- return
259
- }
260
- data.value = []
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
- messageList.value.push(message)
269
- // console.log('submit', question)
270
- loading.value = true
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
- answerContent.value = getMessageTemplate('ai', '', false)
276
- // 设置消息的状态为正在思考 并存下历史的 question信息
277
- answerContent.value.thinkingStatus = 'thinking'
278
- answerContent.value.question = question
279
- answerContent.value.menuName = submitData.menuName
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
- window.$vueApp.config.globalProperties.$http
285
- .post(window.$vueApp.config.globalProperties.baseAPI + '/component/ai/multiple-fuck', {
286
- question: question,
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
- .then((res) => {
291
- // 创建一个新的消息对象
292
- answerContent.value.thinkingStatus = 'end'
293
- answerContent.value.content = res
294
- answerContent.value = {}
295
- loading.value = false
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('Fetch error:', err)
299
- messageList.push(getMessageTemplate('ai', t('chatEmbed.requestFailed'), false))
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
- messageList.value[messageList.value.length - 1].thinkingStatus = 'end'
308
- cancel()
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
- function clickRecommendation(item: BubbleProps) {
344
- console.log('clickRecommendation', item)
345
- sendQuestion(item.content)
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
- sendQuestion({
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
- if (session_id.value) {
375
- try {
376
- window.$vueApp.config.globalProperties.$http
377
- .post(window.$vueApp.config.globalProperties.baseAPI + '/component/ai/multiple-fuck/clear/' + session_id.value)
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
- messageList.value.push(defaultMessage)
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>