Commit 896134e7 authored by tyyin lan's avatar tyyin lan

Merge branch 'master'

parents 9dec341b 0039cb7c
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { throttle } from 'lodash-es'
import { CheckOne, Down } from '@icon-park/vue-next' import { CheckOne, Down } from '@icon-park/vue-next'
import CustomLoading from './custom-loading.vue' import CustomLoading from './custom-loading.vue'
import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config' import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue' import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { asBlob } from 'html-docx-js-typescript'
import { downloadFile } from '@/utils/download-file'
import { copyToClip } from '@/utils/copy'
interface Props { interface Props {
role: 'user' | 'assistant' role: 'user' | 'assistant'
...@@ -24,6 +28,8 @@ const { t } = useI18n() ...@@ -24,6 +28,8 @@ const { t } = useI18n()
const userStore = useUserStore() const userStore = useUserStore()
const personalAppConfigStore = usePersonalAppConfigStore() const personalAppConfigStore = usePersonalAppConfigStore()
const markdownRenderRef = useTemplateRef<InstanceType<typeof MarkdownRender>>('markdownRenderRef')
const isShowReasoningContent = ref(true) const isShowReasoningContent = ref(true)
const useAvatar = computed(() => { const useAvatar = computed(() => {
...@@ -65,6 +71,32 @@ function handleAudioControl() { ...@@ -65,6 +71,32 @@ function handleAudioControl() {
function handleShowReasoningContentSwitch() { function handleShowReasoningContentSwitch() {
isShowReasoningContent.value = !isShowReasoningContent.value isShowReasoningContent.value = !isShowReasoningContent.value
} }
const handleContentDownload = throttle(
() => {
if (markdownRenderRef.value) {
const content = markdownRenderRef.value.getRenderTextContent()
asBlob(content).then((data) => {
downloadFile(data as Blob).then(() => {
window.$message.success(t('common_module.copy_success_message'))
})
})
}
},
1000,
{ leading: true, trailing: false },
)
const handleContentCopy = throttle(
() => {
copyToClip(props.messageItem.textContent).then(() => {
window.$message.success(t('common_module.copy_success_message'))
})
},
1000,
{ leading: true, trailing: false },
)
</script> </script>
<template> <template>
...@@ -109,6 +141,7 @@ function handleShowReasoningContentSwitch() { ...@@ -109,6 +141,7 @@ function handleShowReasoningContentSwitch() {
/> />
<template v-else> <template v-else>
<MarkdownRender <MarkdownRender
ref="markdownRenderRef"
:raw-text-content=" :raw-text-content="
messageItem.reasoningContent messageItem.reasoningContent
? messageItem.reasoningContent ? messageItem.reasoningContent
...@@ -158,6 +191,7 @@ function handleShowReasoningContentSwitch() { ...@@ -158,6 +191,7 @@ function handleShowReasoningContentSwitch() {
<div v-else> <div v-else>
<p class="break-all"> <p class="break-all">
<MarkdownRender <MarkdownRender
ref="markdownRenderRef"
:raw-text-content=" :raw-text-content="
messageItem.isEmptyContent messageItem.isEmptyContent
? t('common_module.dialogue_module.empty_message_content') ? t('common_module.dialogue_module.empty_message_content')
...@@ -173,32 +207,53 @@ function handleShowReasoningContentSwitch() { ...@@ -173,32 +207,53 @@ function handleShowReasoningContentSwitch() {
</div> </div>
</div> </div>
<div <div class="flex w-full items-center justify-between">
v-show="isShowAudioControl" <div>
class="text-font-color flex items-center gap-0.5" <div
:class="isPlayableAudio ? 'hover:text-theme-color cursor-pointer hover:opacity-80' : 'cursor-not-allowed'" v-show="isShowAudioControl"
@click="handleAudioControl" class="text-font-color flex items-center gap-0.5"
> :class="isPlayableAudio ? 'hover:text-theme-color cursor-pointer hover:opacity-80' : 'cursor-not-allowed'"
<i v-if="!messageItem.isVoicePlaying" class="iconfont icon-play text-[24px]" /> @click="handleAudioControl"
<div v-else class="mx-1.5 my-3 h-[12px] w-[12px] bg-[url(@/assets/images/playing.gif)] bg-[length:100%_100%]" /> >
<i v-if="!messageItem.isVoicePlaying" class="iconfont icon-play text-[24px]" />
<div
v-else
class="mx-1.5 my-3 h-[12px] w-[12px] bg-[url(@/assets/images/playing.gif)] bg-[length:100%_100%]"
/>
<span <span
v-show="isPlayableAudio" v-show="isPlayableAudio"
class="text-[12px]" class="text-[12px]"
:class="messageItem.isVoicePlaying ? 'text-theme-color' : ''" :class="messageItem.isVoicePlaying ? 'text-theme-color' : ''"
> >
{{ messageItem.isVoicePlaying ? t('common_module.stop_playing') : t('common_module.start_playing') }} {{ messageItem.isVoicePlaying ? t('common_module.stop_playing') : t('common_module.start_playing') }}
</span> </span>
<n-popover style="max-width: 310px"> <n-popover style="max-width: 310px">
<template #trigger> <template #trigger>
<span v-show="!isPlayableAudio" class="text-[12px]"> {{ t('common_module.unplayable') }} </span> <span v-show="!isPlayableAudio" class="text-[12px]"> {{ t('common_module.unplayable') }} </span>
</template> </template>
{{ t('common_module.unplayable_tip') }} {{ t('common_module.unplayable_tip') }}
</n-popover> </n-popover>
</div> </div>
<div v-if="isShowVoiceLoading" class="py-3.5 pl-6">
<CustomLoading />
</div>
</div>
<div v-if="isShowVoiceLoading" class="py-3.5 pl-6"> <div
<CustomLoading /> v-show="role === 'assistant' && messageItem.textContent && !messageItem.isAnswerResponseLoading"
class="py-[10px] pr-[14px] text-end"
>
<i
class="iconfont icon-download hover:text-theme-color mr-[14px] transform cursor-pointer text-[14px]"
@click="handleContentDownload"
/>
<i
class="iconfont icon-copy1 hover:text-theme-color transform cursor-pointer text-[14px]"
@click="handleContentCopy"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
......
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { throttle } from 'lodash-es'
import { CheckOne, Down } from '@icon-park/vue-next' import { CheckOne, Down } from '@icon-park/vue-next'
import CustomLoading from './custom-loading.vue' import CustomLoading from './custom-loading.vue'
import MusicWavesLoading from './music-waves-loading.vue' import MusicWavesLoading from './music-waves-loading.vue'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue' import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
import EditorDrawer from '@/components/editor-drawer/editor-drawer.vue'
import { PersonalAppConfigState } from '@/store/types/personal-app-config' import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { useLayoutConfig } from '@/composables/useLayoutConfig' import { useLayoutConfig } from '@/composables/useLayoutConfig'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { copyToClip } from '@/utils/copy'
interface Props { interface Props {
role: 'user' | 'assistant' role: 'user' | 'assistant'
...@@ -28,7 +31,11 @@ const userStore = useUserStore() ...@@ -28,7 +31,11 @@ const userStore = useUserStore()
const { isMobile } = useLayoutConfig() const { isMobile } = useLayoutConfig()
const markdownRenderRef = useTemplateRef<InstanceType<typeof MarkdownRender>>('markdownRenderRef')
const isShowReasoningContent = ref(true) const isShowReasoningContent = ref(true)
const isShowEditorDrawer = ref(false)
const editorDrawerContent = ref('')
const useAvatar = computed(() => { const useAvatar = computed(() => {
return userStore.userInfo.avatarUrl || 'https://gsst-poe-sit.gz.bcebos.com/data/20240910/1725952917468.png' return userStore.userInfo.avatarUrl || 'https://gsst-poe-sit.gz.bcebos.com/data/20240910/1725952917468.png'
...@@ -84,6 +91,21 @@ function handleAudioControl() { ...@@ -84,6 +91,21 @@ function handleAudioControl() {
function handleShowReasoningContentSwitch() { function handleShowReasoningContentSwitch() {
isShowReasoningContent.value = !isShowReasoningContent.value isShowReasoningContent.value = !isShowReasoningContent.value
} }
function handleContentEdit() {
isShowEditorDrawer.value = true
editorDrawerContent.value = props.messageItem.textContent || ''
}
const handleContentCopy = throttle(
() => {
copyToClip(props.messageItem.textContent).then(() => {
window.$message.success(t('common_module.copy_success_message'))
})
},
1000,
{ leading: true, trailing: false },
)
</script> </script>
<template> <template>
...@@ -105,8 +127,8 @@ function handleShowReasoningContentSwitch() { ...@@ -105,8 +127,8 @@ function handleShowReasoningContentSwitch() {
/> />
<div <div
class="flex w-full flex-col overflow-x-auto" class="flex flex-col overflow-x-auto"
:class="isMobile && role === 'user' ? 'items-end' : 'items-start'" :class="[isMobile && role === 'user' ? 'items-end' : 'items-start', isMobile ? 'w-full' : '']"
> >
<template v-if="role === 'assistant' && isDeepSeekR1"> <template v-if="role === 'assistant' && isDeepSeekR1">
<div class="my-[7px] select-none text-[14px]"> <div class="my-[7px] select-none text-[14px]">
...@@ -138,6 +160,7 @@ function handleShowReasoningContentSwitch() { ...@@ -138,6 +160,7 @@ function handleShowReasoningContentSwitch() {
/> />
<template v-else> <template v-else>
<MarkdownRender <MarkdownRender
ref="markdownRenderRef"
:raw-text-content=" :raw-text-content="
messageItem.reasoningContent messageItem.reasoningContent
? messageItem.reasoningContent ? messageItem.reasoningContent
...@@ -189,6 +212,7 @@ function handleShowReasoningContentSwitch() { ...@@ -189,6 +212,7 @@ function handleShowReasoningContentSwitch() {
<div v-else> <div v-else>
<p class="break-all"> <p class="break-all">
<MarkdownRender <MarkdownRender
ref="markdownRenderRef"
:raw-text-content=" :raw-text-content="
messageItem.isEmptyContent messageItem.isEmptyContent
? t('common_module.dialogue_module.empty_message_content') ? t('common_module.dialogue_module.empty_message_content')
...@@ -220,34 +244,57 @@ function handleShowReasoningContentSwitch() { ...@@ -220,34 +244,57 @@ function handleShowReasoningContentSwitch() {
</n-popover> </n-popover>
</div> </div>
</div> </div>
<!-- pc端展示 -->
<div v-show="!isMobile" class="flex w-full items-center justify-between">
<div>
<div
v-show="isShowAudioControl"
class="text-font-color flex items-center gap-0.5"
:class="isPlayableAudio ? 'hover:text-theme-color cursor-pointer hover:opacity-80' : 'cursor-not-allowed'"
@click="handleAudioControl"
>
<i v-if="!messageItem.isVoicePlaying" class="iconfont icon-play text-[24px]" />
<div
v-else
class="mx-1.5 my-3 h-[12px] w-[12px] bg-[url(@/assets/images/playing.gif)] bg-[length:100%_100%]"
/>
<div <span
v-show="isShowAudioControl && !isMobile" v-show="isPlayableAudio"
class="text-font-color flex items-center gap-0.5" class="text-[12px]"
:class="isPlayableAudio ? 'hover:text-theme-color cursor-pointer hover:opacity-80' : 'cursor-not-allowed'" :class="messageItem.isVoicePlaying ? 'text-theme-color' : ''"
@click="handleAudioControl" >
> {{ messageItem.isVoicePlaying ? t('common_module.stop_playing') : t('common_module.start_playing') }}
<i v-if="!messageItem.isVoicePlaying" class="iconfont icon-play text-[24px]" /> </span>
<div v-else class="mx-1.5 my-3 h-[12px] w-[12px] bg-[url(@/assets/images/playing.gif)] bg-[length:100%_100%]" /> <n-popover style="max-width: 310px">
<template #trigger>
<span v-show="!isPlayableAudio" class="text-[12px]"> {{ t('common_module.unplayable') }} </span>
</template>
{{ t('common_module.unplayable_tip') }}
</n-popover>
</div>
<span <div v-if="isShowWebVoiceLoading" class="py-3.5 pl-5">
v-show="isPlayableAudio" <CustomLoading />
class="text-[12px]" </div>
:class="messageItem.isVoicePlaying ? 'text-theme-color' : ''" </div>
>
{{ messageItem.isVoicePlaying ? t('common_module.stop_playing') : t('common_module.start_playing') }}
</span>
<n-popover style="max-width: 310px">
<template #trigger>
<span v-show="!isPlayableAudio" class="text-[12px]"> {{ t('common_module.unplayable') }} </span>
</template>
{{ t('common_module.unplayable_tip') }}
</n-popover>
</div>
<div v-if="isShowWebVoiceLoading" class="py-3.5 pl-5"> <div
<CustomLoading /> v-show="role === 'assistant' && messageItem.textContent && !messageItem.isAnswerResponseLoading"
class="py-[10px] pr-[14px] text-end"
>
<i
class="iconfont icon-edit-document hover:text-theme-color mr-[14px] transform cursor-pointer text-[12px]"
@click="handleContentEdit"
/>
<i
class="iconfont icon-copy1 hover:text-theme-color transform cursor-pointer text-[14px]"
@click="handleContentCopy"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
<EditorDrawer v-model:is-show-editor-drawer="isShowEditorDrawer" :content="editorDrawerContent" />
</template> </template>
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