Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ui/src/api/type/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,18 @@ export class ChatManagement {
return chatRecord ? chatRecord.is_stop : false
}

/**
* 获取指定会话中仍在流式输出(尚未写完)的在途消息
* 用于切回会话时, 把后台还在跑的流重新接回列表继续实时显示
* @param chatId 会话id (chat.chat_id)
* @returns 在途的 chat 对象列表
*/
static getActiveByChatId(chatId: string): chatType[] {
return Object.values(this.chatMessageContainer)
.filter((record) => record.chat.chat_id === chatId && !record.write_ed)
.map((record) => record.chat)
}

/**
* 清除无用数据 也就是被close掉的和stop的数据
*/
Expand Down
8 changes: 1 addition & 7 deletions ui/src/components/ai-chat/component/answer-content/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
</div>
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { computed } from 'vue'
import KnowledgeSourceComponent from '@/components/ai-chat/component/knowledge-source-component/index.vue'
import MdRenderer from '@/components/markdown/MdRenderer.vue'
import OperationButton from '@/components/ai-chat/component/operation-button/index.vue'
Expand Down Expand Up @@ -210,11 +210,5 @@ const stopChat = (chat: chatType) => {
const startChat = (chat: chatType) => {
props.chatManagement.write(chat.id)
}

onMounted(() => {
bus.on('chat:stop', () => {
stopChat(props.chatRecord)
})
})
</script>
<style lang="scss" scoped></style>
12 changes: 3 additions & 9 deletions ui/src/components/ai-chat/component/chat-input-operate/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -446,14 +446,8 @@ const chatId_context = computed({
emit('update:chatId', v)
},
})
const localLoading = computed({
get: () => {
return props.loading
},
set: (v) => {
emit('update:loading', v)
},
})
// 语音转写的请求 spinner, 独立于 loading prop(loading 现在是父级单向传入的"当前会话生成态")
const speechLoading = ref(false)

const showURLSetting = ref(false)
const urlForm = reactive({
Expand Down Expand Up @@ -807,7 +801,7 @@ const uploadRecording = async (audioBlob: Blob) => {
if (props.applicationDetails.stt_autosend) {
bus.emit('on:transcribing', true)
}
speechToTextAPI(props.applicationDetails.id as string, formData, localLoading)
speechToTextAPI(props.applicationDetails.id as string, formData, speechLoading)
.then((response) => {
inputValue.value = typeof response.data === 'string' ? response.data : ''
// 自动发送
Expand Down
32 changes: 28 additions & 4 deletions ui/src/components/ai-chat/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
<!-- 回答 -->
<AnswerContent
:application="applicationDetails"
:loading="loading"
:loading="currentChatGenerating"
v-model:chat-record="chatList[index]"
:type="type"
:send-message="sendMessage"
Expand Down Expand Up @@ -202,7 +202,7 @@
:validate="validate"
:chat-management="ChatManagement"
v-model:chat-id="chartOpenId"
v-model:loading="loading"
:loading="currentChatGenerating"
v-model:show-user-input="showUserInput"
v-else-if="type !== 'log' && type !== 'share'"
>
Expand Down Expand Up @@ -310,6 +310,7 @@ const props = withDefaults(
)
const emit = defineEmits([
'refresh',
'openChat',
'scroll',
'openExecutionDetail',
'openParagraph',
Expand Down Expand Up @@ -346,6 +347,12 @@ const loading = ref(false)
const inputValue = ref<string>('')
const chartOpenId = ref<string>('')
const chatList = ref<any[]>([])
// 当前正在查看的会话是否有在途消息(还在吐字)。
// 用它驱动"停止回答"按钮、输入禁用、发送拦截, 替代组件级全局 loading,
// 这样后台其它会话的流式不会把当前会话的输入栏按住。
const currentChatGenerating = computed(() =>
chatList.value.some((c) => c && c.write_ed === false && c.is_stop !== true),
)
const form_data = ref<any>({})
const api_form_data = ref<any>({})
const userFormRef = ref<InstanceType<typeof UserForm>>()
Expand Down Expand Up @@ -531,7 +538,7 @@ function sendMessage(val: string, other_params_data?: any, chat?: chatType): Pro

showUserInput.value = false

if (!loading.value && props.applicationDetails?.name) {
if (!currentChatGenerating.value && props.applicationDetails?.name) {
handleDebounceClick(val, other_params_data, chat)
return true
}
Expand All @@ -551,7 +558,7 @@ function sendMessage(val: string, other_params_data?: any, chat?: chatType): Pro
}
} else {
showUserInput.value = false
if (!loading.value && props.applicationDetails?.name) {
if (!currentChatGenerating.value && props.applicationDetails?.name) {
handleDebounceClick(val, other_params_data, chat)
return Promise.resolve(true)
}
Expand Down Expand Up @@ -656,6 +663,16 @@ const errorWrite = (chat: any, message?: string) => {
ChatManagement.close(chat.id)
}

// 停止"当前正在查看的会话"里在途的消息。
// 只动 chatList(当前会话), 不会波及后台其它正在跑的会话。
const stopGenerating = () => {
chatList.value.forEach((c) => {
if (c && c.write_ed === false && c.is_stop !== true) {
ChatManagement.stop(c.id)
}
})
}

// 保存上传文件列表

function chatMessage(chat?: any, problem?: string, re_chat?: boolean, other_params_data?: any) {
Expand Down Expand Up @@ -731,6 +748,11 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean, other_para
} else if (response.status === 461) {
return Promise.reject(t('aiChat.tip.errorLimitMessage'))
} else {
// 新建会话: 此刻后端已执行 set_chat 建好 Chat 行(在产出流之前),
// 通知父级把新会话加入历史列表, 这样长回答流式期间切走也能切回来继续看
if (props.chatId === 'new') {
emit('openChat', chartOpenId.value)
}
nextTick(() => {
// 将滚动条滚动到最下面
scrollDiv.value.setScrollTop(getMaxHeight())
Expand Down Expand Up @@ -907,11 +929,13 @@ onMounted(() => {
checkAll.value = multipleSelectionChat.value.length === chatList.value.length
emit('update:selection', true)
})
bus.on('chat:stop', stopGenerating)
})

onBeforeUnmount(() => {
window.sendMessage = null
window.chatUserProfile = null
bus.off('chat:stop', stopGenerating)
})

function setScrollBottom() {
Expand Down
23 changes: 23 additions & 0 deletions ui/src/views/chat/embed/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
:chatId="currentChatId"
type="ai-chat"
@refresh="refresh"
@openChat="refresh"
@scroll="handleScroll"
class="AiChat-embed"
v-model:selection="showSelection"
Expand Down Expand Up @@ -107,6 +108,7 @@ import { hexToRgba } from '@/utils/theme'
import { t } from '@/locales'
import ChatHistoryDrawer from './component/ChatHistoryDrawer.vue'
import chatAPI from '@/api/chat/chat'
import { ChatManagement } from '@/api/type/application'

provide('scrollData', loadInfiniteScroll)
provide('chatLogPagination', () => chatLogPagination)
Expand Down Expand Up @@ -223,6 +225,26 @@ function loadInfiniteScroll() {
getChatLog(true)
}

/**
* 切回会话时, 把内存中属于该会话、仍在后台流式输出的在途消息接回列表,
* 这样切走时没被打断的流, 切回来能继续实时显示。
* - 与 DB 记录 record_id 相同的, 用 live 对象覆盖(否则会显示落库前的空答案)
* - DB 里还没有的(尚未落库), 追加到末尾
*/
function attachActiveStreams() {
const activeChats = ChatManagement.getActiveByChatId(currentChatId.value)
if (!activeChats.length) {
return
}
const activeMap = new Map(activeChats.map((chat) => [chat.record_id, chat]))
const existIds = new Set(currentRecordList.value.map((v: any) => v.record_id))
const merged = currentRecordList.value.map((v: any) =>
activeMap.has(v.record_id) ? activeMap.get(v.record_id) : v,
)
const appendList = activeChats.filter((chat) => !existIds.has(chat.record_id))
currentRecordList.value = [...merged, ...appendList]
}

function getChatRecord() {
return chatAPI
.pageChatRecord(
Expand All @@ -242,6 +264,7 @@ function getChatRecord() {
a.create_time.localeCompare(b.create_time),
)
if (paginationConfig.current_page === 1) {
attachActiveStreams()
nextTick(() => {
// 将滚动条滚动到最下面
AiChatRef.value.setScrollBottom()
Expand Down
23 changes: 23 additions & 0 deletions ui/src/views/chat/mobile/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
:chatId="currentChatId"
type="ai-chat"
@refresh="refresh"
@openChat="refresh"
@scroll="handleScroll"
v-model:selection="showSelection"
>
Expand Down Expand Up @@ -106,6 +107,7 @@ import useStore from '@/stores'
import { t } from '@/locales'
import ChatHistoryDrawer from './component/ChatHistoryDrawer.vue'
import chatAPI from '@/api/chat/chat'
import { ChatManagement } from '@/api/type/application'

provide('scrollData', loadInfiniteScroll)
provide('chatLogPagination', () => chatLogPagination)
Expand Down Expand Up @@ -227,6 +229,26 @@ function loadInfiniteScroll() {
getChatLog(true)
}

/**
* 切回会话时, 把内存中属于该会话、仍在后台流式输出的在途消息接回列表,
* 这样切走时没被打断的流, 切回来能继续实时显示。
* - 与 DB 记录 record_id 相同的, 用 live 对象覆盖(否则会显示落库前的空答案)
* - DB 里还没有的(尚未落库), 追加到末尾
*/
function attachActiveStreams() {
const activeChats = ChatManagement.getActiveByChatId(currentChatId.value)
if (!activeChats.length) {
return
}
const activeMap = new Map(activeChats.map((chat) => [chat.record_id, chat]))
const existIds = new Set(currentRecordList.value.map((v: any) => v.record_id))
const merged = currentRecordList.value.map((v: any) =>
activeMap.has(v.record_id) ? activeMap.get(v.record_id) : v,
)
const appendList = activeChats.filter((chat) => !existIds.has(chat.record_id))
currentRecordList.value = [...merged, ...appendList]
}

function getChatRecord() {
return chatAPI
.pageChatRecord(
Expand All @@ -246,6 +268,7 @@ function getChatRecord() {
a.create_time.localeCompare(b.create_time),
)
if (paginationConfig.current_page === 1) {
attachActiveStreams()
nextTick(() => {
// 将滚动条滚动到最下面
AiChatRef.value.setScrollBottom()
Expand Down
23 changes: 23 additions & 0 deletions ui/src/views/chat/pc/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
:chatId="currentChatId"
executionIsRightPanel
@refresh="refresh"
@openChat="refresh"
@scroll="handleScroll"
@open-execution-detail="openExecutionDetail"
@openParagraph="openKnowledgeSource"
Expand Down Expand Up @@ -258,6 +259,7 @@ import ExecutionDetailContent from '@/components/ai-chat/component/knowledge-sou
import ParagraphSourceContent from '@/components/ai-chat/component/knowledge-source-component/ParagraphSourceContent.vue'
import ParagraphDocumentContent from '@/components/ai-chat/component/knowledge-source-component/ParagraphDocumentContent.vue'
import HistoryPanel from '@/views/chat/component/HistoryPanel.vue'
import { ChatManagement } from '@/api/type/application'
import { cloneDeep } from 'lodash'
import { getFileUrl } from '@/utils/common'
import PdfExport from '@/components/pdf-export/index.vue'
Expand Down Expand Up @@ -443,6 +445,26 @@ function loadInfiniteScroll() {
getChatLog(true)
}

/**
* 切回会话时, 把内存中属于该会话、仍在后台流式输出的在途消息接回列表,
* 这样切走时没被打断的流, 切回来能继续实时显示。
* - 与 DB 记录 record_id 相同的, 用 live 对象覆盖(否则会显示落库前的空答案)
* - DB 里还没有的(尚未落库), 追加到末尾
*/
function attachActiveStreams() {
const activeChats = ChatManagement.getActiveByChatId(currentChatId.value)
if (!activeChats.length) {
return
}
const activeMap = new Map(activeChats.map((chat) => [chat.record_id, chat]))
const existIds = new Set(currentRecordList.value.map((v: any) => v.record_id))
const merged = currentRecordList.value.map((v: any) =>
activeMap.has(v.record_id) ? activeMap.get(v.record_id) : v,
)
const appendList = activeChats.filter((chat) => !existIds.has(chat.record_id))
currentRecordList.value = [...merged, ...appendList]
}

function getChatRecord() {
return chatAPI
.pageChatRecord(
Expand All @@ -462,6 +484,7 @@ function getChatRecord() {
a.create_time.localeCompare(b.create_time),
)
if (paginationConfig.value.current_page === 1) {
attachActiveStreams()
nextTick(() => {
// 将滚动条滚动到最下面
AiChatRef.value.setScrollBottom()
Expand Down
3 changes: 3 additions & 0 deletions ui/src/views/tool/component/ToolListContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,9 @@ function openEditDialog(data?: any) {
checkAll.value = multipleSelection.value.length === tool.toolList.length
return
}
if (!permissionPrecise.value.edit(data?.id)) {
return
}
// 有template_id的不允许编辑,是模板转换来的
if (data?.template_id) {
return
Expand Down
Loading