mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
fix: Optimize small screen dialogue style
This commit is contained in:
parent
470105f895
commit
96562b9f16
|
|
@ -1,66 +1,71 @@
|
|||
<template>
|
||||
<div class="flex align-center mt-16" v-if="!isWorkFlow(props.type)">
|
||||
<span class="mr-4 color-secondary">{{ $t('chat.KnowledgeSource.title') }}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" class="mr-8" link @click="openParagraph(data)">
|
||||
<AppIcon iconName="app-reference-outlined" class="mr-4"></AppIcon>
|
||||
{{ $t('chat.KnowledgeSource.referenceParagraph') }}
|
||||
{{ data.paragraph_list?.length || 0 }}</el-button
|
||||
>
|
||||
</div>
|
||||
<div class="mt-8" v-if="!isWorkFlow(props.type)">
|
||||
<el-row :gutter="8" v-if="uniqueParagraphList?.length">
|
||||
<template v-for="(item, index) in uniqueParagraphList" :key="index">
|
||||
<el-col :span="12" class="mb-8">
|
||||
<el-card shadow="never" class="file-List-card" data-width="40">
|
||||
<div class="flex-between">
|
||||
<div class="flex">
|
||||
<img :src="getImgUrl(item && item?.document_name)" alt="" width="20" />
|
||||
<div class="ml-4 ellipsis-1" :title="item?.document_name" v-if="!item.source_url">
|
||||
<p>{{ item && item?.document_name }}</p>
|
||||
</div>
|
||||
<div class="ml-8" v-else>
|
||||
<a
|
||||
:href="getNormalizedUrl(item?.source_url)"
|
||||
target="_blank"
|
||||
class="ellipsis"
|
||||
:title="item?.document_name?.trim()"
|
||||
>
|
||||
<span :title="item?.document_name?.trim()">{{ item?.document_name }}</span>
|
||||
</a>
|
||||
<div class="chat-knowledge-source">
|
||||
<div class="flex align-center mt-16" v-if="!isWorkFlow(props.type)">
|
||||
<span class="mr-4 color-secondary">{{ $t('chat.KnowledgeSource.title') }}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" class="mr-8" link @click="openParagraph(data)">
|
||||
<AppIcon iconName="app-reference-outlined" class="mr-4"></AppIcon>
|
||||
{{ $t('chat.KnowledgeSource.referenceParagraph') }}
|
||||
{{ data.paragraph_list?.length || 0 }}</el-button
|
||||
>
|
||||
</div>
|
||||
<div class="mt-8" v-if="!isWorkFlow(props.type)">
|
||||
<el-row :gutter="8" v-if="uniqueParagraphList?.length">
|
||||
<template v-for="(item, index) in uniqueParagraphList" :key="index">
|
||||
<el-col :span="12" class="mb-8">
|
||||
<el-card shadow="never" class="file-List-card" data-width="40">
|
||||
<div class="flex-between">
|
||||
<div class="flex">
|
||||
<img :src="getImgUrl(item && item?.document_name)" alt="" width="20" />
|
||||
<div class="ml-4 ellipsis-1" :title="item?.document_name" v-if="!item.source_url">
|
||||
<p>{{ item && item?.document_name }}</p>
|
||||
</div>
|
||||
<div class="ml-8" v-else>
|
||||
<a
|
||||
:href="getNormalizedUrl(item?.source_url)"
|
||||
target="_blank"
|
||||
class="ellipsis"
|
||||
:title="item?.document_name?.trim()"
|
||||
>
|
||||
<span :title="item?.document_name?.trim()">{{ item?.document_name }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</template>
|
||||
</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>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="isWorkFlow(props.type)"
|
||||
type="primary"
|
||||
link
|
||||
@click="openExecutionDetail(data.execution_details)"
|
||||
>
|
||||
<el-icon class="mr-4"><Document /></el-icon>
|
||||
{{ $t('chat.executionDetails.title') }}</el-button
|
||||
|
||||
<div
|
||||
class="execution-details 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>
|
||||
<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>
|
||||
<!-- 知识库引用 dialog -->
|
||||
<ParagraphSourceDialog ref="ParagraphSourceDialogRef" />
|
||||
<!-- 执行详情 dialog -->
|
||||
<ExecutionDetailDialog ref="ExecutionDetailDialogRef" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
|
@ -107,13 +112,11 @@ const uniqueParagraphList = computed(() => {
|
|||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.source_dataset-button {
|
||||
background: var(--app-text-color-light-1);
|
||||
border: 1px solid #ffffff;
|
||||
&:hover {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-text-color-primary);
|
||||
@media only screen and (max-width: 430px) {
|
||||
.chat-knowledge-source {
|
||||
.execution-details {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -34,9 +34,11 @@
|
|||
{{ $t('chat.tip.answerLoading') }} <span class="dotting"></span>
|
||||
</p>
|
||||
<!-- 知识来源 -->
|
||||
<div v-if="showSource(chatRecord) && index === chatRecord.answer_text_list.length - 1">
|
||||
<KnowledgeSource :data="chatRecord" :type="application.type" />
|
||||
</div>
|
||||
<KnowledgeSource
|
||||
:data="chatRecord"
|
||||
:type="application.type"
|
||||
v-if="showSource(chatRecord) && index === chatRecord.answer_text_list.length - 1"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,82 +1,88 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="chat-operation-button flex-between">
|
||||
<el-text type="info">
|
||||
<span class="ml-4">{{ datetimeFormat(data.create_time) }}</span>
|
||||
</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>
|
||||
<!-- 语音播放 -->
|
||||
<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>
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
|
|
@ -158,9 +164,7 @@ function markdownToPlainText(md: string) {
|
|||
}
|
||||
|
||||
function removeFormRander(text: string) {
|
||||
return text
|
||||
.replace(/<form_rander>[\s\S]*?<\/form_rander>/g, '')
|
||||
.trim()
|
||||
return text.replace(/<form_rander>[\s\S]*?<\/form_rander>/g, '').trim()
|
||||
}
|
||||
|
||||
const playAnswerText = (text: string) => {
|
||||
|
|
@ -175,7 +179,7 @@ const playAnswerText = (text: string) => {
|
|||
audioPlayerStatus.value = true
|
||||
// 分割成多份
|
||||
audioList.value = text.split(/(<audio[^>]*><\/audio>)/).filter((item) => item.trim().length > 0)
|
||||
nextTick(()=>{
|
||||
nextTick(() => {
|
||||
// console.log(audioList.value, audioPlayer.value)
|
||||
playAnswerTextPart()
|
||||
})
|
||||
|
|
@ -190,7 +194,8 @@ const playAnswerTextPart = () => {
|
|||
}
|
||||
if (audioList.value[currentAudioIndex.value].includes('<audio')) {
|
||||
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].onended = () => {
|
||||
currentAudioIndex.value += 1
|
||||
|
|
@ -201,7 +206,10 @@ const playAnswerTextPart = () => {
|
|||
if (audioList.value[currentAudioIndex.value] !== utterance.value?.text) {
|
||||
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()
|
||||
return
|
||||
}
|
||||
|
|
@ -225,7 +233,11 @@ const playAnswerTextPart = () => {
|
|||
return
|
||||
}
|
||||
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) => {
|
||||
if (res.type === 'application/json') {
|
||||
const text = await res.text()
|
||||
|
|
@ -284,9 +296,20 @@ onMounted(() => {
|
|||
})
|
||||
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)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
@media only screen and (max-width: 430px) {
|
||||
.chat-operation-button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="chat-operation-button">
|
||||
<LogOperationButton
|
||||
v-if="type === 'log'"
|
||||
v-bind:data="chatRecord"
|
||||
|
|
@ -10,37 +10,31 @@
|
|||
:type="type"
|
||||
/>
|
||||
|
||||
<div class="flex-between mt-8" v-else>
|
||||
<div>
|
||||
<el-button
|
||||
type="primary"
|
||||
v-if="chatRecord.is_stop && !chatRecord.write_ed"
|
||||
@click="startChat(chatRecord)"
|
||||
link
|
||||
>{{ $t('chat.operation.continue') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
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 class="mt-8" v-else>
|
||||
<el-button
|
||||
type="primary"
|
||||
v-if="chatRecord.is_stop && !chatRecord.write_ed"
|
||||
@click="startChat(chatRecord)"
|
||||
link
|
||||
>{{ $t('chat.operation.continue') }}
|
||||
</el-button>
|
||||
<el-button type="primary" v-else-if="!chatRecord.write_ed" @click="stopChat(chatRecord)" link
|
||||
>{{ $t('chat.operation.stopChat') }}
|
||||
</el-button>
|
||||
</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>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
|
|
|||
Loading…
Reference in New Issue