fix: Optimize small screen dialogue style

This commit is contained in:
wangdan-fit2cloud 2025-03-19 16:07:02 +08:00 committed by GitHub
parent 470105f895
commit 96562b9f16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 203 additions and 181 deletions

View File

@ -1,66 +1,71 @@
<template> <template>
<div class="flex align-center mt-16" v-if="!isWorkFlow(props.type)"> <div class="chat-knowledge-source">
<span class="mr-4 color-secondary">{{ $t('chat.KnowledgeSource.title') }}</span> <div class="flex align-center mt-16" v-if="!isWorkFlow(props.type)">
<el-divider direction="vertical" /> <span class="mr-4 color-secondary">{{ $t('chat.KnowledgeSource.title') }}</span>
<el-button type="primary" class="mr-8" link @click="openParagraph(data)"> <el-divider direction="vertical" />
<AppIcon iconName="app-reference-outlined" class="mr-4"></AppIcon> <el-button type="primary" class="mr-8" link @click="openParagraph(data)">
{{ $t('chat.KnowledgeSource.referenceParagraph') }} <AppIcon iconName="app-reference-outlined" class="mr-4"></AppIcon>
{{ data.paragraph_list?.length || 0 }}</el-button {{ $t('chat.KnowledgeSource.referenceParagraph') }}
> {{ data.paragraph_list?.length || 0 }}</el-button
</div> >
<div class="mt-8" v-if="!isWorkFlow(props.type)"> </div>
<el-row :gutter="8" v-if="uniqueParagraphList?.length"> <div class="mt-8" v-if="!isWorkFlow(props.type)">
<template v-for="(item, index) in uniqueParagraphList" :key="index"> <el-row :gutter="8" v-if="uniqueParagraphList?.length">
<el-col :span="12" class="mb-8"> <template v-for="(item, index) in uniqueParagraphList" :key="index">
<el-card shadow="never" class="file-List-card" data-width="40"> <el-col :span="12" class="mb-8">
<div class="flex-between"> <el-card shadow="never" class="file-List-card" data-width="40">
<div class="flex"> <div class="flex-between">
<img :src="getImgUrl(item && item?.document_name)" alt="" width="20" /> <div class="flex">
<div class="ml-4 ellipsis-1" :title="item?.document_name" v-if="!item.source_url"> <img :src="getImgUrl(item && item?.document_name)" alt="" width="20" />
<p>{{ item && item?.document_name }}</p> <div class="ml-4 ellipsis-1" :title="item?.document_name" v-if="!item.source_url">
</div> <p>{{ item && item?.document_name }}</p>
<div class="ml-8" v-else> </div>
<a <div class="ml-8" v-else>
:href="getNormalizedUrl(item?.source_url)" <a
target="_blank" :href="getNormalizedUrl(item?.source_url)"
class="ellipsis" target="_blank"
:title="item?.document_name?.trim()" class="ellipsis"
> :title="item?.document_name?.trim()"
<span :title="item?.document_name?.trim()">{{ item?.document_name }}</span> >
</a> <span :title="item?.document_name?.trim()">{{ item?.document_name }}</span>
</a>
</div>
</div> </div>
</div> </div>
</div> </el-card>
</el-card> </el-col>
</el-col> </template>
</template> </el-row>
</el-row>
</div>
<div
class="border-t color-secondary flex-between mt-12"
style="padding-top: 12px; padding-bottom: 8px"
>
<div>
<span class="mr-8">
{{ $t('chat.KnowledgeSource.consume') }}: {{ data?.message_tokens + data?.answer_tokens }}
</span>
<span> {{ $t('chat.KnowledgeSource.consumeTime') }}: {{ data?.run_time?.toFixed(2) }} s</span>
</div> </div>
<el-button
v-if="isWorkFlow(props.type)" <div
type="primary" class="execution-details border-t color-secondary flex-between mt-12"
link style="padding-top: 12px; padding-bottom: 8px"
@click="openExecutionDetail(data.execution_details)"
>
<el-icon class="mr-4"><Document /></el-icon>
{{ $t('chat.executionDetails.title') }}</el-button
> >
<div>
<span class="mr-8">
{{ $t('chat.KnowledgeSource.consume') }}: {{ data?.message_tokens + data?.answer_tokens }}
</span>
<span>
{{ $t('chat.KnowledgeSource.consumeTime') }}: {{ data?.run_time?.toFixed(2) }} s</span
>
</div>
<el-button
v-if="isWorkFlow(props.type)"
type="primary"
link
@click="openExecutionDetail(data.execution_details)"
style="padding: 0;"
>
<el-icon class="mr-4"><Document /></el-icon>
{{ $t('chat.executionDetails.title') }}</el-button
>
</div>
<!-- 知识库引用 dialog -->
<ParagraphSourceDialog ref="ParagraphSourceDialogRef" />
<!-- 执行详情 dialog -->
<ExecutionDetailDialog ref="ExecutionDetailDialogRef" />
</div> </div>
<!-- 知识库引用 dialog -->
<ParagraphSourceDialog ref="ParagraphSourceDialogRef" />
<!-- 执行详情 dialog -->
<ExecutionDetailDialog ref="ExecutionDetailDialogRef" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
@ -107,13 +112,11 @@ const uniqueParagraphList = computed(() => {
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.source_dataset-button { @media only screen and (max-width: 430px) {
background: var(--app-text-color-light-1); .chat-knowledge-source {
border: 1px solid #ffffff; .execution-details {
&:hover { display: block;
border: 1px solid var(--el-color-primary); }
background: var(--el-color-primary-light-9);
color: var(--el-text-color-primary);
} }
} }
</style> </style>

View File

@ -34,9 +34,11 @@
{{ $t('chat.tip.answerLoading') }} <span class="dotting"></span> {{ $t('chat.tip.answerLoading') }} <span class="dotting"></span>
</p> </p>
<!-- 知识来源 --> <!-- 知识来源 -->
<div v-if="showSource(chatRecord) && index === chatRecord.answer_text_list.length - 1"> <KnowledgeSource
<KnowledgeSource :data="chatRecord" :type="application.type" /> :data="chatRecord"
</div> :type="application.type"
v-if="showSource(chatRecord) && index === chatRecord.answer_text_list.length - 1"
/>
</el-card> </el-card>
</div> </div>
</template> </template>

View File

@ -1,82 +1,88 @@
<template> <template>
<div> <div class="chat-operation-button flex-between">
<el-text type="info"> <el-text type="info">
<span class="ml-4">{{ datetimeFormat(data.create_time) }}</span> <span class="ml-4">{{ datetimeFormat(data.create_time) }}</span>
</el-text> </el-text>
<div>
<!-- 语音播放 -->
<span v-if="tts">
<el-tooltip
effect="dark"
:content="$t('chat.operation.play')"
placement="top"
v-if="!audioPlayerStatus"
>
<el-button text :disabled="!data?.write_ed" @click="playAnswerText(data?.answer_text)">
<AppIcon iconName="app-video-play"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip v-else effect="dark" :content="$t('chat.operation.pause')" placement="top">
<el-button type="primary" text :disabled="!data?.write_ed" @click="pausePlayAnswerText()">
<AppIcon iconName="app-video-pause"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
</span>
<span v-if="type == 'ai-chat' || type == 'log'">
<el-tooltip effect="dark" :content="$t('chat.operation.regeneration')" placement="top">
<el-button :disabled="chat_loading" text @click="regeneration">
<el-icon><RefreshRight /></el-icon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" :content="$t('common.copy')" placement="top">
<el-button text @click="copyClick(data?.answer_text.trim())">
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip
effect="dark"
:content="$t('chat.operation.like')"
placement="top"
v-if="buttonData?.vote_status === '-1'"
>
<el-button text @click="voteHandle('0')" :disabled="loading">
<AppIcon iconName="app-like"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip
effect="dark"
:content="$t('chat.operation.cancelLike')"
placement="top"
v-if="buttonData?.vote_status === '0'"
>
<el-button text @click="voteHandle('-1')" :disabled="loading">
<AppIcon iconName="app-like-color"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" v-if="buttonData?.vote_status === '-1'" />
<el-tooltip
effect="dark"
:content="$t('chat.operation.oppose')"
placement="top"
v-if="buttonData?.vote_status === '-1'"
>
<el-button text @click="voteHandle('1')" :disabled="loading">
<AppIcon iconName="app-oppose"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip
effect="dark"
:content="$t('chat.operation.cancelOppose')"
placement="top"
v-if="buttonData?.vote_status === '1'"
>
<el-button text @click="voteHandle('-1')" :disabled="loading">
<AppIcon iconName="app-oppose-color"></AppIcon>
</el-button>
</el-tooltip>
</span>
</div>
<!-- 先渲染不然不能播放 -->
<audio ref="audioPlayer" v-for="item in audioList" :key="item" controls hidden="hidden"></audio>
</div> </div>
<div>
<!-- 语音播放 -->
<span v-if="tts">
<el-tooltip effect="dark" :content="$t('chat.operation.play')" placement="top" v-if="!audioPlayerStatus">
<el-button text :disabled="!data?.write_ed" @click="playAnswerText(data?.answer_text)">
<AppIcon iconName="app-video-play"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip v-else effect="dark" :content="$t('chat.operation.pause')" placement="top">
<el-button type="primary" text :disabled="!data?.write_ed" @click="pausePlayAnswerText()">
<AppIcon iconName="app-video-pause"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
</span>
<span v-if="type == 'ai-chat' || type == 'log'">
<el-tooltip effect="dark" :content="$t('chat.operation.regeneration')" placement="top">
<el-button :disabled="chat_loading" text @click="regeneration">
<el-icon><RefreshRight /></el-icon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" :content="$t('common.copy')" placement="top">
<el-button text @click="copyClick(data?.answer_text.trim())">
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip
effect="dark"
:content="$t('chat.operation.like')"
placement="top"
v-if="buttonData?.vote_status === '-1'"
>
<el-button text @click="voteHandle('0')" :disabled="loading">
<AppIcon iconName="app-like"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip
effect="dark"
:content="$t('chat.operation.cancelLike')"
placement="top"
v-if="buttonData?.vote_status === '0'"
>
<el-button text @click="voteHandle('-1')" :disabled="loading">
<AppIcon iconName="app-like-color"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" v-if="buttonData?.vote_status === '-1'" />
<el-tooltip
effect="dark"
:content="$t('chat.operation.oppose')"
placement="top"
v-if="buttonData?.vote_status === '-1'"
>
<el-button text @click="voteHandle('1')" :disabled="loading">
<AppIcon iconName="app-oppose"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip
effect="dark"
:content="$t('chat.operation.cancelOppose')"
placement="top"
v-if="buttonData?.vote_status === '1'"
>
<el-button text @click="voteHandle('-1')" :disabled="loading">
<AppIcon iconName="app-oppose-color"></AppIcon>
</el-button>
</el-tooltip>
</span>
</div>
<!-- 先渲染不然不能播放 -->
<audio ref="audioPlayer" v-for="item in audioList" :key="item" controls hidden="hidden"></audio>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, ref } from 'vue' import { nextTick, onMounted, ref } from 'vue'
@ -158,9 +164,7 @@ function markdownToPlainText(md: string) {
} }
function removeFormRander(text: string) { function removeFormRander(text: string) {
return text return text.replace(/<form_rander>[\s\S]*?<\/form_rander>/g, '').trim()
.replace(/<form_rander>[\s\S]*?<\/form_rander>/g, '')
.trim()
} }
const playAnswerText = (text: string) => { const playAnswerText = (text: string) => {
@ -175,7 +179,7 @@ const playAnswerText = (text: string) => {
audioPlayerStatus.value = true audioPlayerStatus.value = true
// //
audioList.value = text.split(/(<audio[^>]*><\/audio>)/).filter((item) => item.trim().length > 0) audioList.value = text.split(/(<audio[^>]*><\/audio>)/).filter((item) => item.trim().length > 0)
nextTick(()=>{ nextTick(() => {
// console.log(audioList.value, audioPlayer.value) // console.log(audioList.value, audioPlayer.value)
playAnswerTextPart() playAnswerTextPart()
}) })
@ -190,7 +194,8 @@ const playAnswerTextPart = () => {
} }
if (audioList.value[currentAudioIndex.value].includes('<audio')) { if (audioList.value[currentAudioIndex.value].includes('<audio')) {
if (audioPlayer.value) { if (audioPlayer.value) {
audioPlayer.value[currentAudioIndex.value].src = audioList.value[currentAudioIndex.value].match(/src="([^"]*)"/)?.[1] || '' audioPlayer.value[currentAudioIndex.value].src =
audioList.value[currentAudioIndex.value].match(/src="([^"]*)"/)?.[1] || ''
audioPlayer.value[currentAudioIndex.value].play() // audioPlayer.value[currentAudioIndex.value].play() //
audioPlayer.value[currentAudioIndex.value].onended = () => { audioPlayer.value[currentAudioIndex.value].onended = () => {
currentAudioIndex.value += 1 currentAudioIndex.value += 1
@ -201,7 +206,10 @@ const playAnswerTextPart = () => {
if (audioList.value[currentAudioIndex.value] !== utterance.value?.text) { if (audioList.value[currentAudioIndex.value] !== utterance.value?.text) {
window.speechSynthesis.cancel() window.speechSynthesis.cancel()
} }
if (window.speechSynthesis.paused && audioList.value[currentAudioIndex.value] === utterance.value?.text) { if (
window.speechSynthesis.paused &&
audioList.value[currentAudioIndex.value] === utterance.value?.text
) {
window.speechSynthesis.resume() window.speechSynthesis.resume()
return return
} }
@ -225,7 +233,11 @@ const playAnswerTextPart = () => {
return return
} }
applicationApi applicationApi
.postTextToSpeech((props.applicationId as string) || (id as string), { text: audioList.value[currentAudioIndex.value] }, loading) .postTextToSpeech(
(props.applicationId as string) || (id as string),
{ text: audioList.value[currentAudioIndex.value] },
loading
)
.then(async (res: any) => { .then(async (res: any) => {
if (res.type === 'application/json') { if (res.type === 'application/json') {
const text = await res.text() const text = await res.text()
@ -284,9 +296,20 @@ onMounted(() => {
}) })
bus.emit('pause-autoplay') bus.emit('pause-autoplay')
// //
if (props.tts && props.tts_autoplay && buttonData.value.write_ed && !buttonData.value.update_time) { if (
props.tts &&
props.tts_autoplay &&
buttonData.value.write_ed &&
!buttonData.value.update_time
) {
playAnswerText(buttonData.value.answer_text) playAnswerText(buttonData.value.answer_text)
} }
}) })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
@media only screen and (max-width: 430px) {
.chat-operation-button {
display: block;
}
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div class="chat-operation-button">
<LogOperationButton <LogOperationButton
v-if="type === 'log'" v-if="type === 'log'"
v-bind:data="chatRecord" v-bind:data="chatRecord"
@ -10,37 +10,31 @@
:type="type" :type="type"
/> />
<div class="flex-between mt-8" v-else> <div class="mt-8" v-else>
<div> <el-button
<el-button type="primary"
type="primary" v-if="chatRecord.is_stop && !chatRecord.write_ed"
v-if="chatRecord.is_stop && !chatRecord.write_ed" @click="startChat(chatRecord)"
@click="startChat(chatRecord)" link
link >{{ $t('chat.operation.continue') }}
>{{ $t('chat.operation.continue') }} </el-button>
</el-button> <el-button type="primary" v-else-if="!chatRecord.write_ed" @click="stopChat(chatRecord)" link
<el-button >{{ $t('chat.operation.stopChat') }}
type="primary" </el-button>
v-else-if="!chatRecord.write_ed"
@click="stopChat(chatRecord)"
link
>{{ $t('chat.operation.stopChat') }}
</el-button>
</div>
</div>
<div v-if="chatRecord.write_ed && 500 != chatRecord.status" class="flex-between">
<ChatOperationButton
:tts="application.tts_model_enable"
:tts_type="application.tts_type"
:tts_autoplay="application.tts_autoplay"
:data="chatRecord"
:type="type"
:applicationId="application.id"
:chatId="chatRecord.chat_id"
:chat_loading="loading"
@regeneration="regenerationChart(chatRecord)"
/>
</div> </div>
<ChatOperationButton
v-if="chatRecord.write_ed && 500 != chatRecord.status"
:tts="application.tts_model_enable"
:tts_type="application.tts_type"
:tts_autoplay="application.tts_autoplay"
:data="chatRecord"
:type="type"
:applicationId="application.id"
:chatId="chatRecord.chat_id"
:chat_loading="loading"
@regeneration="regenerationChart(chatRecord)"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">