Commit d2479c35 authored by nick zheng's avatar nick zheng

Merge branch 'beta' into 'master'

feat: 应用预览调试及发布支持推理内容展示

See merge request !164
parents 1a700198 852687d7
import { ref } from 'vue'
import { throttle } from 'lodash-es'
export function useBackBottom(cb: () => void) {
const visible = ref(false)
const lastScrollTop = ref(0)
function clickBackBottom() {
visible.value = false
cb()
}
function handleScrollMessageContainer(e: Event) {
const target = e.target as HTMLElement
const currentScrollTop = target.scrollTop
// 向上滚动,显示返回底部图标
if (currentScrollTop < lastScrollTop.value) {
visible.value = true
}
// 是否滚动触底
if (currentScrollTop + target.clientHeight >= target.scrollHeight - 5) {
visible.value = false
}
lastScrollTop.value = currentScrollTop
}
const throttleScrollMessageContainer = throttle(handleScrollMessageContainer, 100, { leading: true })
return { visible, clickBackBottom, throttleScrollMessageContainer }
}
......@@ -5,6 +5,12 @@ import { useUserStore } from '@/store/modules/user'
import { useSystemLanguageStore } from '@/store/modules/system-language'
import { languageKeyTransform } from '@/utils/language-key-transform'
interface ResponseData {
message: string
reasoningContent: string
function: { name: string }
}
const { t } = i18n.global
const ENV = import.meta.env.VITE_APP_ENV
......@@ -15,7 +21,7 @@ export function fetchCustomEventSource(config: {
path: string
payload: any
controller: AbortController
onMessage: (data: string) => void
onResponse: (_data: ResponseData) => void
onRequestError: (err: any) => void
onError?: (err: any) => void
onFinally?: () => void
......@@ -36,7 +42,6 @@ export function fetchCustomEventSource(config: {
openWhenHidden: true,
onmessage: (e) => {
if (e.data === '[DONE]' && !responseError) {
config.onMessage(e.data)
config.onFinally && config.onFinally()
return
......@@ -66,13 +71,7 @@ export function fetchCustomEventSource(config: {
return
}
// 插件调用
if (data.function && data.function.name) {
config.onMessage(data.function)
return
}
config.onMessage(data.message)
config.onResponse(data)
} catch (err) {
config.onRequestError(err)
......
......@@ -140,6 +140,8 @@ common_module:
plugin_in_progress: '{pluginName} Plugin in Progress'
plugin_executed_successfully: '{pluginName} Plugin Executed Successfully'
upload_image: 'Upload Image'
deep_thinking: '{modelName} deep thinking'
have_thought_deeply: 'Have thought deeply'
dialogue_module:
continue_question_message: 'You can keep asking questions'
......
......@@ -139,6 +139,8 @@ common_module:
plugin_in_progress: '{pluginName}插件执行中...'
plugin_executed_successfully: '{pluginName}插件执行成功'
upload_image: '上传图片'
deep_thinking: '{modelName}深度思考中...'
have_thought_deeply: '已深度思考'
dialogue_module:
continue_question_message: '你可以继续提问'
......
......@@ -139,6 +139,8 @@ common_module:
plugin_in_progress: '{pluginName}插件執行中...'
plugin_executed_successfully: '{pluginName}插件執行成功'
upload_image: '上傳圖片'
deep_thinking: '{modelName}深度思考中...'
have_thought_deeply: '已深度思考'
dialogue_module:
continue_question_message: '你可以繼續提問'
......
......@@ -95,6 +95,7 @@ function messageItemFactory() {
isAnswerResponseLoading: false,
pluginName: '',
imageUrl: '',
reasoningContent: '',
} as MessageItemInterface
}
......@@ -147,6 +148,7 @@ function handleQuestionSubmit() {
const answerMessageId = nanoid()
let messageContent = ''
let reasoningContent = ''
let isFirstClip = true
emit(
......@@ -173,22 +175,25 @@ function handleQuestionSubmit() {
channel: ChannelType.multi_preview,
},
controller: modelItem.controller,
onMessage: (data: any) => {
if (data === '[DONE]') {
emit('updateMessageItem', answerMessageId, { isAnswerResponseLoading: false }, modelIndex)
emit('messageListScrollToBottom')
modelItem.isAnswerResponseWait = false
return
onResponse: (data) => {
// 推理内容
if (data.reasoningContent) {
reasoningContent += data.reasoningContent
emit('updateMessageItem', answerMessageId, { reasoningContent }, modelIndex)
messageListScrollToBottomThrottle()
}
if (data && data.name) {
emit('updateMessageItem', answerMessageId, { pluginName: data.name }, modelIndex)
// 插件
if (data.function && data.function.name) {
emit('updateMessageItem', answerMessageId, { pluginName: data.function.name }, modelIndex)
emit('messageListScrollToBottom')
return
}
if (data) {
messageContent += data
// 回复内容
if (data.message) {
messageContent += data.message
if (isFirstClip) {
emit(
......@@ -215,6 +220,7 @@ function handleQuestionSubmit() {
modelItem.controller = null
modelItem.isAnswerResponseWait = false
userStore.fetchUpdateEquityInfo()
emit('updateMessageItem', answerMessageId, { isAnswerResponseLoading: false }, modelIndex)
emit('messageListScrollToBottom')
},
})
......
<script setup lang="ts">
import { computed, readonly } from 'vue'
import { computed, readonly, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { CheckOne } from '@icon-park/vue-next'
import { CheckOne, Down } from '@icon-park/vue-next'
import type { MessageItemInterface } from '../types'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
import MessageBubbleLoading from './message-bubble-loading.vue'
......@@ -14,6 +14,8 @@ const props = defineProps<Props>()
const { t } = useI18n()
const isShowReasoningContent = ref(true)
const agentAvatarUrl = readonly({ url: 'https://gsst-poe-sit.gz.bcebos.com/icon/agent-avatar.png' })
const isAssistant = computed(() => {
......@@ -23,6 +25,10 @@ const isAssistant = computed(() => {
const assistantAvatarUrl = computed(() => {
return props.messageItem.avatar || agentAvatarUrl.url
})
function handleShowReasoningContentSwitch() {
isShowReasoningContent.value = !isShowReasoningContent.value
}
</script>
<template>
......@@ -34,12 +40,53 @@ const assistantAvatarUrl = computed(() => {
alt="Avatar"
/>
<div class="ml-[11px] overflow-auto">
<div class="mb-[7px] line-clamp-1 break-all text-[12px] text-[#999]">
<div class="ml-[11px] flex flex-col items-start overflow-hidden">
<template v-if="isAssistant && messageItem.nickName === 'DeepSeek'">
<div class="mb-[7px] select-none text-[14px]">
<div class="inline-flex cursor-pointer" @click="handleShowReasoningContentSwitch">
<span v-if="messageItem.isTextContentLoading" class="mr-[6px]">
{{ t('common_module.deep_thinking', { modelName: messageItem.nickName }) }}
</span>
<span v-else class="mr-[6px]">{{ t('common_module.have_thought_deeply') }}</span>
<Down
theme="outline"
size="21"
fill="#333"
:stroke-width="3"
class="transition-[rotate] duration-100 ease-linear"
:class="{ '-rotate-180': isShowReasoningContent }"
/>
</div>
</div>
<n-collapse-transition :show="isShowReasoningContent">
<div class="my-[14px] border-l-[1px] border-solid border-l-[#ccc] p-[13px]">
<div>
<img
v-if="!messageItem.reasoningContent && !messageItem.content"
src="@/assets/images/home/bubble-loading.gif"
alt="bubble-loading"
/>
<template v-else>
<MarkdownRender
:raw-text-content="
messageItem.reasoningContent
? messageItem.reasoningContent
: t('common_module.dialogue_module.empty_message_content')
"
color="#999"
/>
</template>
</div>
</div>
</n-collapse-transition>
</template>
<div v-else class="mb-[7px] line-clamp-1 break-all text-[12px] text-[#999]">
{{ props.messageItem.nickName }}
</div>
<div
class="box-content min-h-[21px] min-w-[10px] rounded-[10px] border border-[#9EA3FF] px-[15px] py-[12px] text-justify"
class="min-h-[21px] min-w-[10px] max-w-full flex-wrap rounded-[10px] border border-[#9EA3FF] px-[15px] py-[12px] text-justify"
:class="{
'bg-[#777EF9]': isAssistant,
'text-[#fff]': isAssistant,
......
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { nextTick, useTemplateRef, watch } from 'vue'
import { ScrollbarInst } from 'naive-ui'
import MessageItem from './message-item.vue'
import type { MessageItemInterface } from '../types'
import { useBackBottom } from '@/composables/useBackBottom'
interface Props {
messageList: Map<string, MessageItemInterface>
}
defineProps<Props>()
const props = defineProps<Props>()
defineExpose({
scrollToBottom,
......@@ -16,21 +17,45 @@ defineExpose({
const scrollbarRef = useTemplateRef<ScrollbarInst | null>('scrollbarRef')
const { visible, clickBackBottom, throttleScrollMessageContainer } = useBackBottom(scrollToBottom)
function scrollToBottom() {
nextTick(() => {
if (scrollbarRef.value) {
scrollbarRef.value.scrollTo({ top: 999999999, behavior: 'smooth' })
!visible.value && scrollbarRef.value.scrollTo({ top: 999999999, behavior: 'smooth' })
}
})
}
watch(
() => props.messageList.size,
() => {
clickBackBottom()
},
)
</script>
<template>
<div class="flex-1 overflow-hidden overflow-y-auto py-[20px]">
<n-scrollbar ref="scrollbarRef" class="min-h-full" content-class="min-h-full flex">
<div class="relative flex-1 overflow-hidden overflow-y-auto py-[20px]">
<n-scrollbar
ref="scrollbarRef"
class="min-h-full"
content-class="min-h-full flex"
@scroll="throttleScrollMessageContainer"
>
<div class="flex flex-col-reverse overflow-hidden">
<div class="pr-[10px]">
<MessageItem v-for="[key, messageItem] in messageList" :key="key" :message-item="messageItem" />
</div>
</div>
<div
v-show="visible"
class="flex-center hover:text-theme-color absolute bottom-5 right-5 h-6 w-6 cursor-pointer rounded-full bg-white shadow-[0_0_0_1px_#ededed]"
@click.stop="clickBackBottom"
>
<i class="iconfont icon-decrease text-sm" />
</div>
</n-scrollbar>
</div>
</template>
......@@ -35,6 +35,7 @@ export interface MessageItemInterface {
isAnswerResponseLoading: boolean
pluginName?: string
imageUrl?: string
reasoningContent: string
}
export interface LargeModelItem {
......
......@@ -5,6 +5,12 @@ import { useUserStore } from '@/store/modules/user'
import { useSystemLanguageStore } from '@/store/modules/system-language'
import { languageKeyTransform } from '@/utils/language-key-transform'
interface ResponseData {
message: string
reasoningContent: string
function: { name: string }
}
const { t } = i18n.global
const ENV = import.meta.env.VITE_APP_ENV
......@@ -15,7 +21,7 @@ export function fetchEventStreamSource(config: {
path: string
payload: any
controller: AbortController
onMessage: (data: string) => void
onResponse: (data: ResponseData) => void
onRequestError: (err: any) => void
onError?: (err: any) => void
onFinally?: () => void
......@@ -36,7 +42,6 @@ export function fetchEventStreamSource(config: {
openWhenHidden: true,
onmessage: (e) => {
if (e.data === '[DONE]' && !responseError) {
config.onMessage(e.data)
config.onFinally && config.onFinally()
return
......@@ -65,13 +70,7 @@ export function fetchEventStreamSource(config: {
return
}
// 插件调用
if (data.function && data.function.name) {
config.onMessage(data.function)
return
}
config.onMessage(data.message)
config.onResponse(data)
} catch (err) {
config.onRequestError(err)
......
......@@ -318,7 +318,7 @@ function handleAudioPause(isClearMessageList = false) {
<Preamble />
</div>
<div v-show="messageList.size > 0" class="w-full">
<div v-show="messageList.size > 0" class="relative w-full">
<MessageList
ref="messageListRef"
:message-list="messageList"
......
......@@ -148,6 +148,7 @@ function messageItemFactory(): ConversationMessageItem {
isVoiceEnabled: false,
pluginName: '',
imageUrl: '',
reasoningContent: '',
}
}
......@@ -236,6 +237,7 @@ function handleMessageSend() {
emit('updatePageScroll')
let replyTextContent = ''
let reasoningContent = ''
controller = new AbortController()
......@@ -248,31 +250,45 @@ function handleMessageSend() {
messages,
},
controller,
onMessage: (data: any) => {
if (data === '[DONE]') {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
isEmptyContent: !replyTextContent,
isTextContentLoading: false,
isAnswerResponseLoading: false,
})
isAnswerResponseLoading.value = false
isCreateContinueQuestions.value && emit('createContinueQuestions', replyTextContent)
onResponse: (data) => {
// 推理内容
if (data.reasoningContent) {
reasoningContent += data.reasoningContent
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { reasoningContent: reasoningContent })
emit('updatePageScroll')
blockMessageResponse()
assistantFullAnswerContent.value = (assistantFullAnswerContent.value + data.reasoningContent).replace(
/\^\[[\d\\[\]-]+?\]\^/g,
'',
)
if (!sentenceExtractCheckEnabled.value && isVoiceEnabled) {
sentenceExtract(latestAssistantMessageKey)
sentenceExtractCheckEnabled.value = true
messageAudioLoading.value = true
}
return
}
if (data && data.name) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { pluginName: data.name })
// 插件
if (data.function && data.function.name) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { pluginName: data.function.name })
emit('updatePageScroll')
return
}
if (data) {
replyTextContent += data
// 回复消息
if (data.message) {
replyTextContent += data.message
assistantFullAnswerContent.value = (assistantFullAnswerContent.value + data).replace(
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
textContent: replyTextContent,
isTextContentLoading: false,
})
emit('updatePageScroll')
assistantFullAnswerContent.value = (assistantFullAnswerContent.value + data.message).replace(
/\^\[[\d\\[\]-]+?\]\^/g,
'',
)
......@@ -282,12 +298,6 @@ function handleMessageSend() {
sentenceExtractCheckEnabled.value = true
messageAudioLoading.value = true
}
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
textContent: replyTextContent,
isTextContentLoading: false,
})
emit('updatePageScroll')
}
},
onRequestError: () => {
......@@ -297,6 +307,15 @@ function handleMessageSend() {
errorMessageResponse()
},
onFinally: () => {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
isEmptyContent: !replyTextContent,
isTextContentLoading: false,
isAnswerResponseLoading: false,
})
isAnswerResponseLoading.value = false
isCreateContinueQuestions.value && emit('createContinueQuestions', replyTextContent)
emit('updatePageScroll')
blockMessageResponse()
controller = null
userStore.fetchUpdateEquityInfo()
},
......
<script setup lang="ts">
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { CheckOne } from '@icon-park/vue-next'
import { CheckOne, Down } from '@icon-park/vue-next'
import CustomLoading from './custom-loading.vue'
import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
......@@ -24,6 +24,8 @@ const { t } = useI18n()
const userStore = useUserStore()
const personalAppConfigStore = usePersonalAppConfigStore()
const isShowReasoningContent = ref(true)
const useAvatar = computed(() => {
return userStore.userInfo.avatarUrl || 'https://gsst-poe-sit.gz.bcebos.com/data/20240910/1725952917468.png'
})
......@@ -44,6 +46,10 @@ const isShowVoiceLoading = computed(() => {
return props.role === 'assistant' && props.messageItem.isVoiceLoading && props.messageItem.isVoiceEnabled
})
const isDeepSeekR1 = computed(() => {
return personalAppConfigStore.commModelConfig.largeModel === 'DeepSeek'
})
function handleAudioControl() {
if (!isPlayableAudio.value) {
return
......@@ -55,6 +61,10 @@ function handleAudioControl() {
emit('audioPlay')
}
}
function handleShowReasoningContentSwitch() {
isShowReasoningContent.value = !isShowReasoningContent.value
}
</script>
<template>
......@@ -69,6 +79,49 @@ function handleAudioControl() {
/>
<div class="flex flex-col items-start overflow-x-auto">
<template v-if="role === 'assistant' && isDeepSeekR1">
<div class="my-[7px] select-none text-[14px]">
<div class="inline-flex cursor-pointer" @click="handleShowReasoningContentSwitch">
<span v-if="messageItem.isTextContentLoading" class="mr-[6px]">
{{ t('common_module.deep_thinking', { modelName: personalAppConfigStore.commModelConfig.largeModel }) }}
</span>
<span v-else class="mr-[6px]">
{{ t('common_module.have_thought_deeply') }}
</span>
<Down
theme="outline"
size="21"
fill="#333"
:stroke-width="3"
class="transition-[rotate] duration-100 ease-linear"
:class="{ '-rotate-180': isShowReasoningContent }"
/>
</div>
</div>
<n-collapse-transition :show="isShowReasoningContent">
<div class="my-[14px] border-l-[1px] border-solid border-l-[#ccc] p-[13px]">
<div>
<img
v-if="!messageItem.reasoningContent && !messageItem.textContent"
src="@/assets/images/home/bubble-loading.gif"
alt="bubble-loading"
/>
<template v-else>
<MarkdownRender
:raw-text-content="
messageItem.reasoningContent
? messageItem.reasoningContent
: t('common_module.dialogue_module.empty_message_content')
"
color="#999"
/>
</template>
</div>
</div>
</n-collapse-transition>
</template>
<div
class="min-w-[80px] max-w-full flex-wrap rounded-xl border border-[#e8e9eb] px-4 py-[11px]"
:class="role === 'user' ? 'bg-[#4b87ff] text-white' : 'bg-white text-[#333]'"
......
<script setup lang="ts">
import { computed } from 'vue'
import { computed, nextTick, watch } from 'vue'
import MessageItem from './message-item.vue'
import ContinueQuestion from './continue-question.vue'
import { useScroll } from '@/composables/useScroll'
import { useBackBottom } from '@/composables/useBackBottom'
interface Props {
messageList: Map<string, ConversationMessageItem>
......@@ -21,6 +22,8 @@ defineEmits<{
const { scrollRef, scrollToBottom } = useScroll()
const { visible, clickBackBottom, throttleScrollMessageContainer } = useBackBottom(scrollToBottom)
const isShowContinueQuestion = computed(() => {
return (
props.continuousQuestionStatus === 'default' &&
......@@ -30,13 +33,26 @@ const isShowContinueQuestion = computed(() => {
)
})
watch(
() => props.messageList.size,
() => {
clickBackBottom()
},
)
defineExpose({
scrollToBottom,
scrollToBottom: handleScrollToBottom,
})
function handleScrollToBottom() {
nextTick(() => {
!visible.value && scrollToBottom()
})
}
</script>
<template>
<main ref="scrollRef" class="h-full overflow-y-auto overflow-x-hidden px-5">
<main ref="scrollRef" class="h-full overflow-y-auto overflow-x-hidden px-5" @scroll="throttleScrollMessageContainer">
<div>
<MessageItem
v-for="[key, messageItem] in messageList"
......@@ -51,5 +67,13 @@ defineExpose({
<div v-show="isShowContinueQuestion">
<ContinueQuestion :continuous-question-list="continuousQuestionList" />
</div>
<div
v-show="visible"
class="flex-center hover:text-theme-color absolute bottom-5 right-5 h-6 w-6 cursor-pointer rounded-full bg-white shadow-[0_0_0_1px_#ededed]"
@click.stop="clickBackBottom"
>
<i class="iconfont icon-decrease text-sm" />
</div>
</main>
</template>
......@@ -301,18 +301,12 @@ function handleAIGenerateAgentSystem() {
input: personalAppConfigStore.baseInfo.agentTitle || personalAppConfigStore.baseInfo.agentSystem,
},
controller: generateAgentSystemController,
onMessage: (data: any) => {
if (data === '[DONE]') {
blockAnswerResponse()
resolve(data)
return
}
if (data) {
onResponse: (data) => {
if (data && data.message) {
useThrottleFn(
() => {
nextTick(() => {
baseInfo.value.agentSystem += data
baseInfo.value.agentSystem += data.message
agentSystemInputRef.value!.textareaElRef!.scrollTop =
agentSystemInputRef.value?.textareaElRef?.scrollHeight || 0
})
......@@ -331,6 +325,8 @@ function handleAIGenerateAgentSystem() {
blockAnswerResponse()
},
onFinally: () => {
blockAnswerResponse()
resolve('')
generateAgentSystemController = null
},
})
......
......@@ -65,14 +65,9 @@ function handleGenerateAgentSystem() {
input: personalAppConfigStore.baseInfo.agentSystem,
},
controller,
onMessage: (data: any) => {
if (data === '[DONE]') {
blockMessageResponse()
return
}
if (data) {
agentSystem.value += data
onResponse: (data) => {
if (data && data.message) {
agentSystem.value += data.message
useThrottleFn(
() => {
......@@ -94,6 +89,7 @@ function handleGenerateAgentSystem() {
},
onFinally: () => {
controller = null
blockMessageResponse()
},
})
}
......
......@@ -142,6 +142,7 @@ function messageItemFactory(): ConversationMessageItem {
voiceFragmentUrlList: [],
pluginName: '',
imageUrl: '',
reasoningContent: '',
}
}
......@@ -195,6 +196,7 @@ function handleMessageSend() {
emit('updatePageScroll')
let replyTextContent = ''
let reasoningContent = ''
isAnswerResponseLoading.value = true
isAnswerResponseWait.value = true
currentReplyContentSentenceExtractIndex.value = 0
......@@ -217,31 +219,46 @@ function handleMessageSend() {
imageUrl: uploadImageList.value?.[0]?.url || '',
},
controller,
onMessage: (data: any) => {
if (data === '[DONE]') {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
isEmptyContent: !replyTextContent,
isTextContentLoading: false,
isAnswerResponseLoading: false,
})
onResponse: (data) => {
// 推理内容
if (data.reasoningContent) {
reasoningContent += data.reasoningContent
isAnswerResponseLoading.value = false
isCreateContinueQuestions.value && emit('createContinueQuestions', replyTextContent)
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { reasoningContent })
emit('updatePageScroll')
blockMessageResponse()
assistantFullAnswerContent.value = (assistantFullAnswerContent.value + data.reasoningContent).replace(
/\^\[[\d\\[\]-]+?\]\^/g,
'',
)
if (!sentenceExtractCheckEnabled.value && props.isEnableVoice) {
sentenceExtract(latestAssistantMessageKey)
sentenceExtractCheckEnabled.value = true
messageAudioLoading.value = true
}
return
}
if (data && data.name) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { pluginName: data.name })
// 插件
if (data.function && data.function.name) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { pluginName: data.function.name })
emit('updatePageScroll')
return
}
if (data) {
replyTextContent += data
// 回复内容
if (data.message) {
replyTextContent += data.message
assistantFullAnswerContent.value = (assistantFullAnswerContent.value + data).replace(
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
textContent: replyTextContent,
isTextContentLoading: false,
})
emit('updatePageScroll')
assistantFullAnswerContent.value = (assistantFullAnswerContent.value + data.message).replace(
/\^\[[\d\\[\]-]+?\]\^/g,
'',
)
......@@ -251,12 +268,6 @@ function handleMessageSend() {
sentenceExtractCheckEnabled.value = true
messageAudioLoading.value = true
}
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
textContent: replyTextContent,
isTextContentLoading: false,
})
emit('updatePageScroll')
}
},
onRequestError: () => {
......@@ -266,6 +277,16 @@ function handleMessageSend() {
errorMessageResponse()
},
onFinally: () => {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
isEmptyContent: !replyTextContent,
isTextContentLoading: false,
isAnswerResponseLoading: false,
})
isAnswerResponseLoading.value = false
isCreateContinueQuestions.value && emit('createContinueQuestions', replyTextContent)
emit('updatePageScroll')
blockMessageResponse()
controller = null
userStore.isLogin && userStore.fetchUpdateEquityInfo()
},
......
<script setup lang="ts">
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { CheckOne } from '@icon-park/vue-next'
import { CheckOne, Down } from '@icon-park/vue-next'
import CustomLoading from './custom-loading.vue'
import MusicWavesLoading from './music-waves-loading.vue'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
......@@ -28,6 +28,8 @@ const userStore = useUserStore()
const { isMobile } = useLayoutConfig()
const isShowReasoningContent = ref(true)
const useAvatar = computed(() => {
return userStore.userInfo.avatarUrl || 'https://gsst-poe-sit.gz.bcebos.com/data/20240910/1725952917468.png'
})
......@@ -63,6 +65,10 @@ const isShowMobileLoading = computed(() => {
)
})
const isDeepSeekR1 = computed(() => {
return props.agentApplicationConfig.commModelConfig.largeModel === 'DeepSeek'
})
function handleAudioControl() {
if (!isPlayableAudio.value) {
return
......@@ -74,6 +80,10 @@ function handleAudioControl() {
emit('audioPlay')
}
}
function handleShowReasoningContentSwitch() {
isShowReasoningContent.value = !isShowReasoningContent.value
}
</script>
<template>
......@@ -98,6 +108,48 @@ function handleAudioControl() {
class="flex w-full flex-col overflow-x-auto"
:class="isMobile && role === 'user' ? 'items-end' : 'items-start'"
>
<template v-if="role === 'assistant' && isDeepSeekR1">
<div class="my-[7px] select-none text-[14px]">
<div class="inline-flex cursor-pointer" @click="handleShowReasoningContentSwitch">
<span v-if="messageItem.isTextContentLoading" class="mr-[6px]">
{{ t('common_module.deep_thinking', { modelName: agentApplicationConfig.commModelConfig.largeModel }) }}
</span>
<span v-else class="mr-[6px]">
{{ t('common_module.have_thought_deeply') }}
</span>
<Down
theme="outline"
size="21"
fill="#333"
:stroke-width="3"
class="transition-[rotate] duration-100 ease-linear"
:class="{ '-rotate-180': isShowReasoningContent }"
/>
</div>
</div>
<n-collapse-transition :show="isShowReasoningContent">
<div class="my-[14px] border-l-[1px] border-solid border-l-[#ccc] p-[13px]">
<div>
<img
v-if="!messageItem.reasoningContent && !messageItem.textContent"
src="@/assets/images/home/bubble-loading.gif"
alt="bubble-loading"
/>
<template v-else>
<MarkdownRender
:raw-text-content="
messageItem.reasoningContent
? messageItem.reasoningContent
: t('common_module.dialogue_module.empty_message_content')
"
color="#999"
/>
</template>
</div>
</div>
</n-collapse-transition>
</template>
<div
class="min-w-[80px] flex-wrap rounded-xl border border-[#e8e9eb] px-4 py-[11px]"
:class="[
......
......@@ -3,7 +3,8 @@ import MessageItem from './message-item.vue'
import ContinueQuestion from './continue-question.vue'
import { useScroll } from '@/composables/useScroll'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { computed } from 'vue'
import { computed, nextTick, watch } from 'vue'
import { useBackBottom } from '@/composables/useBackBottom'
interface Props {
messageList: Map<string, ConversationMessageItem>
......@@ -23,6 +24,8 @@ defineEmits<{
const { scrollRef, scrollToBottom } = useScroll()
const { visible, clickBackBottom, throttleScrollMessageContainer } = useBackBottom(scrollToBottom)
const isShowContinueQuestion = computed(() => {
return (
props.continuousQuestionStatus === 'default' &&
......@@ -32,13 +35,26 @@ const isShowContinueQuestion = computed(() => {
)
})
watch(
() => props.messageList.size,
() => {
clickBackBottom()
},
)
defineExpose({
scrollToBottom,
scrollToBottom: handleScrollToBottom,
})
function handleScrollToBottom() {
nextTick(() => {
!visible.value && scrollToBottom()
})
}
</script>
<template>
<main ref="scrollRef" class="h-full overflow-y-auto overflow-x-hidden px-5">
<main ref="scrollRef" class="h-full overflow-y-auto overflow-x-hidden px-5" @scroll="throttleScrollMessageContainer">
<div>
<MessageItem
v-for="[key, messageItem] in messageList"
......@@ -54,5 +70,13 @@ defineExpose({
<div v-show="isShowContinueQuestion">
<ContinueQuestion :continuous-question-list="continuousQuestionList" />
</div>
<div
v-show="visible"
class="flex-center hover:text-theme-color absolute bottom-5 right-5 h-6 w-6 cursor-pointer rounded-full bg-white shadow-[0_0_0_1px_#ededed]"
@click.stop="clickBackBottom"
>
<i class="iconfont icon-decrease text-sm" />
</div>
</main>
</template>
......@@ -322,7 +322,7 @@ function handleAudioPause(isClearMessageList = false) {
</div>
<div v-if="messageList.size > 0" class="flex w-full flex-1 flex-col overflow-hidden pt-5">
<div class="flex-1 overflow-auto">
<div class="relative flex-1 overflow-auto">
<MessageList
ref="messageListRef"
:agent-application-config="agentApplicationConfig"
......
......@@ -345,7 +345,7 @@ function handleAudioPause(isClearMessageList = false) {
</div>
<div v-if="messageList.size > 0" class="flex w-full flex-1 flex-col overflow-hidden">
<div class="mt-20 flex-1 overflow-auto">
<div class="relative mt-20 flex-1 overflow-auto">
<MessageList
ref="messageListRef"
:agent-application-config="agentApplicationConfig"
......
......@@ -11,4 +11,5 @@ declare interface ConversationMessageItem {
isVoiceEnabled?: boolean
pluginName?: string
imageUrl?: string
reasoningContent: string
}
......@@ -139,6 +139,8 @@ declare namespace I18n {
plugin_in_progress: string
plugin_executed_successfully: string
upload_image: string
deep_thinking: string
have_thought_deeply: string
dialogue_module: {
continue_question_message: string
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment