Pre Merge pull request !824 from 灬霍霍/master

pull/824/MERGE
灬霍霍 2026-05-05 10:10:48 +00:00 committed by Gitee
commit 659f4e3153
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
5 changed files with 95 additions and 22 deletions

View File

@ -17,6 +17,7 @@ export interface ChatMessageVO {
attachmentUrls?: string[] // 附件 URL 数组
tokens: number // 消耗 Token 数量
segmentIds?: number[] // 段落编号
followUps?:string[]//问题推荐
segments?: {
id: number // 段落编号
content: string // 段落内容

View File

@ -0,0 +1,44 @@
<template>
<div v-if="followUps && followUps.length > 0" class="w-[100%]">
{{item}}
<div
class="follow"
@click="followClick(follow)"
v-for="(follow,index) in followUps"
:key="index"
>
{{follow}}
</div>
</div>
</template>
<script setup lang="ts">
import {ChatMessageVO} from "@/api/ai/chat/message";
import {cloneDeep} from "lodash-es";
const emits = defineEmits([ 'onRefresh']) // emits
defineOptions({ name: 'MessageFollowUps' })
defineProps<{
followUps?: string[]
}>()
/** 刷新 */
const followClick = async (follow:string) => {
emits('onRefresh', {content:follow})
}
</script>
<style scoped lang="scss">
.follow{
border: 1px solid #dcdfe6;
padding: 10px;
margin-bottom: 5px;
width: 100%;
color:#606266;
border-radius: 5px;
}
.follow:hover{
background-color: #dcdcdc;
cursor: pointer;
}
</style>

View File

@ -13,7 +13,9 @@
@click="handleClick(doc)"
>
<div class="text-14px text-[#333] mb-4px">
{{ doc.title }}
{{ doc.title }}<el-link v-if="document?.url" :href="document?.url" target="_blank" type="primary">
下载
</el-link>
<span class="text-12px text-[#999] ml-4px">{{ doc.segments.length }} </span>
</div>
</div>
@ -33,7 +35,9 @@
<div ref="documentRef"></div>
</template>
<template #default>
<div class="text-16px font-bold mb-12px">{{ document?.title }}</div>
<div class="text-16px font-bold mb-12px">{{ document?.title }}
</div>
<div class="max-h-[60vh] overflow-y-auto">
<div
v-for="(segment, index) in document?.segments"
@ -60,6 +64,7 @@ const props = defineProps<{
id: number
documentId: number
documentName: string
documentUrl: string
content: string
}[]
}>()
@ -70,6 +75,7 @@ const document = ref<{
segments: {
id: number
content: string
url: string
}[]
} | null>(null) //
const dialogVisible = ref(false) //
@ -85,12 +91,14 @@ const documentList = computed(() => {
docMap.set(segment.documentId, {
id: segment.documentId,
title: segment.documentName,
url:segment.documentUrl,
segments: []
})
}
docMap.get(segment.documentId).segments.push({
id: segment.id,
content: segment.content
content: segment.content,
url:segment.documentUrl
})
})
return Array.from(docMap.values())

View File

@ -23,6 +23,7 @@
:content="item.content"
/>
<MessageFiles :attachment-urls="item.attachmentUrls" />
<MessageKnowledge v-if="item.segments" :segments="item.segments" />
<MessageWebSearch v-if="item.webSearchPages" :web-search-pages="item.webSearchPages" />
</div>
@ -43,6 +44,10 @@
<img class="h-17px" src="@/assets/ai/delete.svg" />
</el-button>
</div>
<div class="flex flex-row mt-8px" v-if="index==list.length-1">
<MessageFollowUps @on-refresh="onRefresh" :followUps="item.followUps"/>
</div>
</div>
</div>
<!-- 靠右 messageuser 类型 -->
@ -114,6 +119,7 @@ import { PropType } from 'vue'
import { formatDate } from '@/utils/formatTime'
import MarkdownView from '@/components/MarkdownView/index.vue'
import MessageKnowledge from './MessageKnowledge.vue'
import MessageFollowUps from './MessageFollowUps.vue'
import MessageReasoning from './MessageReasoning.vue'
import MessageFiles from './MessageFiles.vue'
import MessageWebSearch from './MessageWebSearch.vue'

View File

@ -21,7 +21,7 @@
<div class="flex w-300px flex-row justify-end" v-if="activeConversation">
<el-button type="primary" bg plain size="small" @click="openChatConversationUpdateForm">
<span v-html="activeConversation?.modelName"></span>
<Icon icon="ep:setting" class="ml-10px" />
<Icon icon="ep:setting" class="ml-10px"/>
</el-button>
<el-button size="small" class="p-10px" @click="handlerMessageClear">
<Icon
@ -30,10 +30,10 @@
/>
</el-button>
<el-button size="small" class="p-10px">
<Icon icon="ep:download" color="var(--el-text-color-placeholder)" />
<Icon icon="ep:download" color="var(--el-text-color-placeholder)"/>
</el-button>
<el-button size="small" class="p-10px" @click="handleGoTopMessage">
<Icon icon="ep:top" color="var(--el-text-color-placeholder)" />
<Icon icon="ep:top" color="var(--el-text-color-placeholder)"/>
</el-button>
</div>
</el-header>
@ -43,7 +43,7 @@
<div>
<div class="absolute top-0 bottom-0 left-0 right-0 overflow-y-hidden p-0 m-0">
<!-- 情况一消息加载中 -->
<MessageLoading v-if="activeMessageListLoading" />
<MessageLoading v-if="activeMessageListLoading"/>
<!-- 情况二无聊天对话时 -->
<MessageNewConversation
v-if="!activeConversation"
@ -87,10 +87,10 @@
</textarea>
<div class="flex justify-between pb-0 pt-5px">
<div class="flex items-center">
<MessageFileUpload v-model="uploadFiles" :limit="5" :max-size="10" class="mr-10px" />
<el-switch v-model="enableContext" />
<MessageFileUpload v-model="uploadFiles" :limit="5" :max-size="10" class="mr-10px"/>
<el-switch v-model="enableContext"/>
<span class="ml-5px mr-15px text-14px text-#8f8f8f">上下文</span>
<el-switch v-model="enableWebSearch" />
<el-switch v-model="enableWebSearch"/>
<span class="ml-5px text-14px text-#8f8f8f">联网搜索</span>
</div>
<el-button
@ -124,8 +124,8 @@
</template>
<script setup lang="ts">
import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
import {ChatMessageApi, ChatMessageVO} from '@/api/ai/chat/message'
import {ChatConversationApi, ChatConversationVO} from '@/api/ai/chat/conversation'
import ConversationList from './components/conversation/ConversationList.vue'
import ConversationUpdateForm from './components/conversation/ConversationUpdateForm.vue'
import MessageList from './components/message/MessageList.vue'
@ -135,7 +135,7 @@ import MessageNewConversation from './components/message/MessageNewConversation.
import MessageFileUpload from './components/message/MessageFileUpload.vue'
/** AI 聊天对话 列表 */
defineOptions({ name: 'AiChat' })
defineOptions({name: 'AiChat'})
const route = useRoute() //
const message = useMessage() //
@ -165,6 +165,7 @@ const enableWebSearch = ref<boolean>(false) // 是否开启联网搜索
const uploadFiles = ref<string[]>([]) // URL
// Stream
const receiveMessageFullText = ref('')
const receiveFollowUps = ref<string[]>([])//
const receiveMessageDisplayedText = ref('')
// =========== ===========
@ -335,7 +336,8 @@ const handlerMessageClear = async () => {
await ChatMessageApi.deleteByConversationId(activeConversationId.value)
// message
activeMessageList.value = []
} catch {}
} catch {
}
}
/** 回到 message 列表的顶部 */
@ -439,7 +441,7 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
conversationInProgress.value = true
//
receiveMessageFullText.value = ''
receiveFollowUps.value = []
try {
// 1.1 stream
activeMessageList.value.push({
@ -473,7 +475,14 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
enableContext.value,
enableWebSearch.value,
async (res) => {
const { code, data, msg } = JSON.parse(res.data)
const {code, data, msg} = JSON.parse(res.data)
if (data.receive.followUps.length>0) {
receiveFollowUps.value = data.receive.followUps
}
if (code !== 0) {
message.alert(`对话异常! ${msg}`)
//
@ -487,6 +496,7 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
if (data.receive.content === '' && !data.receive.reasoningContent) {
return
}
//
// message
if (isFirstChunk) {
@ -497,16 +507,17 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
//
activeMessageList.value.push(data.send)
data.send.attachmentUrls = userMessage.attachmentUrls
activeMessageList.value.push(data.receive)
}
// reasoningContent
if (data.receive.reasoningContent) {
const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
lastMessage.reasoningContent =
lastMessage.reasoningContent + data.receive.reasoningContent
}
//
if (data.receive.content !== '') {
receiveMessageFullText.value = receiveMessageFullText.value + data.receive.content
@ -526,7 +537,8 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
},
userMessage.attachmentUrls
)
} catch {}
} catch {
}
}
/** 停止 stream 流式调用 */
@ -587,13 +599,14 @@ const textRoll = async () => {
if (!conversationInProgress.value) {
textSpeed.value = 10
}
// message
const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
lastMessage.followUps = receiveFollowUps.value
if (index < receiveMessageFullText.value.length) {
receiveMessageDisplayedText.value += receiveMessageFullText.value[index]
index++
// message
const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
lastMessage.content = receiveMessageDisplayedText.value
//
await scrollToBottom()
@ -611,7 +624,8 @@ const textRoll = async () => {
}
}
let timer = setTimeout(task, textSpeed.value)
} catch {}
} catch {
}
}
/** 初始化 **/