Commit d6e6f419 authored by nick zheng's avatar nick zheng

feat: agent应用插件内容返回显示

parent 026511a5
......@@ -46,6 +46,7 @@
"vant": "^4.9.18",
"vue": "^3.5.13",
"vue-i18n": "^9.14.0",
"vue-json-viewer": "^3.0.4",
"vue-router": "^4.4.5"
},
"devDependencies": {
......
......@@ -98,6 +98,9 @@ importers:
vue-i18n:
specifier: ^9.14.0
version: 9.14.0(vue@3.5.13(typescript@5.6.2))
vue-json-viewer:
specifier: ^3.0.4
version: 3.0.4(vue@3.5.13(typescript@5.6.2))
vue-router:
specifier: ^4.4.5
version: 4.4.5(vue@3.5.13(typescript@5.6.2))
......@@ -251,7 +254,7 @@ importers:
version: 5.4.6(@types/node@20.16.5)(sass@1.79.1)(terser@5.39.0)
vite-plugin-checker:
specifier: ^0.7.2
version: 0.7.2(eslint@9.10.0(jiti@2.4.2))(optionator@0.9.4)(stylelint@16.9.0(typescript@5.6.2))(typescript@5.6.2)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)(terser@5.39.0))(vue-tsc@2.0.29(typescript@5.6.2))
version: 0.7.2(eslint@9.10.0(jiti@2.4.2))(meow@13.2.0)(optionator@0.9.4)(stylelint@16.9.0(typescript@5.6.2))(typescript@5.6.2)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)(terser@5.39.0))(vue-tsc@2.0.29(typescript@5.6.2))
vite-svg-loader:
specifier: ^5.1.0
version: 5.1.0(vue@3.5.13(typescript@5.6.2))
......@@ -1568,6 +1571,9 @@ packages:
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
engines: {node: '>=18'}
clipboard@2.0.11:
resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==}
clipboardy@4.0.0:
resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==}
engines: {node: '>=18'}
......@@ -1756,6 +1762,9 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
delegate@3.2.0:
resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
destr@2.0.3:
resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==}
......@@ -2128,6 +2137,9 @@ packages:
globjoin@0.1.4:
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
good-listener@1.2.2:
resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
......@@ -2956,6 +2968,9 @@ packages:
seemly@0.3.8:
resolution: {integrity: sha512-MW8Qs6vbzo0pHmDpFSYPna+lwpZ6Zk1ancbajw/7E8TKtHdV+1DfZZD+kKJEhG/cAoB/i+LiT+5msZOqj0DwRA==}
select@1.1.2:
resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
......@@ -3190,6 +3205,9 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
tiny-emitter@2.1.0:
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
......@@ -3486,6 +3504,11 @@ packages:
peerDependencies:
vue: ^3.0.0
vue-json-viewer@3.0.4:
resolution: {integrity: sha512-pnC080rTub6YjccthVSNQod2z9Sl5IUUq46srXtn6rxwhW8QM4rlYn+CTSLFKXWfw+N3xv77Cioxw7B4XUKIbQ==}
peerDependencies:
vue: ^3.2.2
vue-router@4.4.5:
resolution: {integrity: sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==}
peerDependencies:
......@@ -5000,6 +5023,12 @@ snapshots:
slice-ansi: 5.0.0
string-width: 7.2.0
clipboard@2.0.11:
dependencies:
good-listener: 1.2.2
select: 1.1.2
tiny-emitter: 2.1.0
clipboardy@4.0.0:
dependencies:
execa: 8.0.1
......@@ -5165,6 +5194,8 @@ snapshots:
delayed-stream@1.0.0: {}
delegate@3.2.0: {}
destr@2.0.3: {}
dir-glob@3.0.1:
......@@ -5596,6 +5627,10 @@ snapshots:
globjoin@0.1.4: {}
good-listener@1.2.2:
dependencies:
delegate: 3.2.0
gopd@1.2.0: {}
graceful-fs@4.2.11: {}
......@@ -6286,6 +6321,8 @@ snapshots:
seemly@0.3.8: {}
select@1.1.2: {}
semver@6.3.1: {}
semver@7.6.3: {}
......@@ -6565,6 +6602,8 @@ snapshots:
through@2.3.8: {}
tiny-emitter@2.1.0: {}
tiny-invariant@1.3.3: {}
tinyexec@0.3.0: {}
......@@ -6766,7 +6805,7 @@ snapshots:
evtd: 0.2.4
vue: 3.5.13(typescript@5.6.2)
vite-plugin-checker@0.7.2(eslint@9.10.0(jiti@2.4.2))(optionator@0.9.4)(stylelint@16.9.0(typescript@5.6.2))(typescript@5.6.2)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)(terser@5.39.0))(vue-tsc@2.0.29(typescript@5.6.2)):
vite-plugin-checker@0.7.2(eslint@9.10.0(jiti@2.4.2))(meow@13.2.0)(optionator@0.9.4)(stylelint@16.9.0(typescript@5.6.2))(typescript@5.6.2)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)(terser@5.39.0))(vue-tsc@2.0.29(typescript@5.6.2)):
dependencies:
'@babel/code-frame': 7.24.7
ansi-escapes: 4.3.2
......@@ -6785,6 +6824,7 @@ snapshots:
vscode-uri: 3.0.8
optionalDependencies:
eslint: 9.10.0(jiti@2.4.2)
meow: 13.2.0
optionator: 0.9.4
stylelint: 16.9.0(typescript@5.6.2)
typescript: 5.6.2
......@@ -6858,6 +6898,11 @@ snapshots:
'@vue/devtools-api': 6.6.4
vue: 3.5.13(typescript@5.6.2)
vue-json-viewer@3.0.4(vue@3.5.13(typescript@5.6.2)):
dependencies:
clipboard: 2.0.11
vue: 3.5.13(typescript@5.6.2)
vue-router@4.4.5(vue@3.5.13(typescript@5.6.2)):
dependencies:
'@vue/devtools-api': 6.6.4
......
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { CheckOne } from '@icon-park/vue-next'
import JsonViewer from 'vue-json-viewer'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
import { useLayoutConfig } from '@/composables/useLayoutConfig'
interface Props {
displayFormat: 'json' | 'markdown' | 'none'
name: string
arguments: string
content: string
pluginLoading: boolean
}
const props = defineProps<Props>()
const { isMobile } = useLayoutConfig()
const { t } = useI18n()
const isFolding = ref(true)
const parsedPluginArguments = computed(() => {
try {
if (props.arguments) {
return JSON.parse(props.arguments)
}
return {}
} catch (error) {
return { error: 'Invalid JSON' }
}
})
const parsedPluginContent = computed(() => {
try {
if (props.content) {
return JSON.parse(props.content)
}
return {}
} catch (error) {
return { error: 'Invalid JSON' }
}
})
const parsedMarkdownPluginContent = computed(() => {
try {
if (props.content) {
return JSON.parse(props.content)
}
return ''
} catch (error) {
return props.content
}
})
</script>
<template>
<div
class="flex w-full flex-col bg-[#f3f5f9]"
:class="
isMobile ? 'rounded-[2.66667vw] px-[3.2vw] py-[2.66667vw] text-[3.2vw]' : 'rounded-[10px] px-[12px] py-[10px]'
"
>
<div class="flex flex-1 items-center justify-between gap-[10px] overflow-hidden">
<div class="flex flex-1 items-center gap-[6px] overflow-hidden">
<div
v-show="pluginLoading"
class="bg-px-plugin_loading-gif bg-contain bg-center bg-no-repeat"
:class="isMobile ? 'h-[3.73333vw] w-[3.73333vw]' : 'h-[14px] w-[14px]'"
/>
<CheckOne v-show="!pluginLoading" theme="outline" size="16" fill="#40bd23" />
<n-ellipsis class="text-gray-font-color w-full leading-5" :line-clamp="1" :tooltip="{ with: 'trigger' }">
{{
pluginLoading
? t('common_module.plugin_in_progress', { pluginName: name })
: t('common_module.plugin_executed_successfully', {
pluginName: name,
})
}}
</n-ellipsis>
</div>
<span
v-show="!pluginLoading"
class="iconfont icon-left text-font-color transform cursor-pointer text-[12px] duration-200 ease-in-out hover:text-[#777ef9]"
:class="isFolding ? 'rotate-[270deg]' : 'rotate-[90deg]'"
@click="isFolding = !isFolding"
/>
</div>
<div class="flex transform flex-col duration-200 ease-in-out" :class="isFolding ? 'h-0 overflow-hidden' : 'h-fit'">
<!-- json格式 -->
<div v-if="displayFormat === 'json'">
<div class="mt-[10px]">
<span
class="rounded-theme inline-block bg-[#E8EAFF] text-[#0072FF]"
:class="
isMobile
? 'h-[7.2vw] px-[3.2vw] text-[2.93333vw] leading-[7.2vw]'
: 'h-[29px] px-[12px] text-[12px] leading-[29px]'
"
>
{{ t('common_module.input_parameter') }}
</span>
<JsonViewer :value="parsedPluginArguments" :expand-depth="3" />
</div>
<div class="mt-[10px]">
<span
class="rounded-theme inline-block bg-[#E8EAFF] text-[#0072FF]"
:class="
isMobile
? 'h-[7.2vw] px-[3.2vw] text-[2.93333vw] leading-[7.2vw]'
: 'h-[29px] px-[12px] text-[12px] leading-[29px]'
"
>
{{ t('common_module.output_parameter') }}
</span>
<JsonViewer :value="parsedPluginContent" :expand-depth="3" />
</div>
</div>
<!-- markdown格式 -->
<div v-if="displayFormat === 'markdown'">
<div class="mt-[10px]">
<MarkdownRender :raw-text-content="parsedMarkdownPluginContent" :font-size="isMobile ? '3.2vw' : '14px'" />
</div>
</div>
<!-- none格式 -->
<div v-if="displayFormat === 'none'">
<span>{{ content }}</span>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
:deep(.markdown-render-container .markdown-render-inner) {
table {
color: #fff !important;
}
}
:deep(.jv-container.jv-light) {
font-family: 'Microsoft YaHei UI';
font-size: 14px;
.jv-code.open {
padding-bottom: 0;
}
.jv-code {
padding: 10px 0 0;
background-color: #f3f5f9;
}
.jv-item.jv-string {
color: #999;
}
.jv-item.jv-number {
color: #0072ff;
}
.jv-key {
color: #000;
}
}
</style>
......@@ -12,7 +12,8 @@ interface FileInfoItem {
status: 'pending' | 'uploading' | 'finished' | 'removed' | 'error'
url: string
percentage: number
type?: string
type: string
format: 'document' | 'image'
}
const { t } = i18n.global
......@@ -20,11 +21,12 @@ export function useDialogueFile() {
const uploadFileList = ref<FileInfoItem[]>([])
function handleLimitUpload(data: { file: UploadFileInfo }) {
const allowTypeList = ['md', 'doc', 'docx', 'pdf', 'txt']
const documentTypeList = ['md', 'doc', 'docx', 'pdf', 'txt'] // 文件类型
const imageTypeList = ['png', 'jpeg', 'jpg', 'webp'] // 图片类型
const fileType = (data.file.file && data.file.file?.name.split('.')?.pop()?.toLowerCase()) || ''
if (fileType && !allowTypeList.includes(fileType)) {
if (fileType && !documentTypeList.includes(fileType) && !imageTypeList.includes(fileType)) {
window.$message.error(
t('personal_space_module.knowledge_module.upload_document_module.upload_format_error_message'),
)
......@@ -44,6 +46,7 @@ export function useDialogueFile() {
type: fileType,
percentage: 0,
url: '',
format: documentTypeList.includes(fileType) ? ('document' as const) : ('image' as const),
})
uploadFileList.value = []
......@@ -64,6 +67,7 @@ export function useDialogueFile() {
url: '',
percentage: 0,
type: fileType,
format: documentTypeList.includes(fileType) ? ('document' as const) : ('image' as const),
})
uploadFileList.value = []
......@@ -78,14 +82,18 @@ export function useDialogueFile() {
const formData = new FormData()
formData.append('file', file.file.file)
const fileFormat = file.file?.name.split('.')?.pop()?.toLowerCase()
const documentTypeList = ['md', 'doc', 'docx', 'pdf', 'txt']
const fileData = reactive({
id: file.file.id,
name: file.file.name,
status: UploadStatus.UPLOADING,
size: file.file?.file?.size || 0,
type: file.file?.name.split('.')?.pop()?.toLowerCase(),
type: fileFormat,
percentage: 0,
url: '',
format: documentTypeList.includes(fileFormat) ? ('document' as const) : ('image' as const),
})
if (uploadFileList.value.length <= 1) {
......
......@@ -8,7 +8,7 @@ import { languageKeyTransform } from '@/utils/language-key-transform'
interface ResponseData {
message: string
reasoningContent: string
function: { name: string }
function: { displayFormat: 'json' | 'markdown' | 'none'; name: string; result: string; arguments: string }
knowledgeContentResult: KnowledgeContentResultItem[]
dbChainResult: DBChainResultItem[]
}
......
......@@ -161,6 +161,8 @@ common_module:
add_database_successfully: 'Database set {0} was added successfully'
remove_database_successfully: 'Database set {0} was removed successfully'
database_QA_executed_successfully: 'Database Q&A executed successfully'
input_parameter: 'Input parameter'
output_parameter: 'Output parameter'
dialogue_module:
continue_question_message: 'You can keep asking questions'
......@@ -171,7 +173,7 @@ common_module:
clear_message_dialog_title: 'Are you sure you want to clear the conversation?'
clear_message_dialog_content: 'Clearing the conversation will clear all the current conversation content. Are you sure to clear the conversation?'
cancel_associate_file_tip: 'No longer answer around this file'
upload_file_limit: 'Only a single file can be uploaded in PDF, DOC, DOCX, MD, TXT format, up to 10MB'
upload_file_limit: 'Only a single file can be uploaded in PDF, DOC, DOCX, MD, TXT, PNG, JPG, JPEG, WEBP format, up to 10MB'
overwrite_file_tip: 'The newly uploaded file overwrites the original file, whether to continue uploading'
stop_playing_and_then_operate: 'When the audio is playing, stop playing and then perform the operation'
do_not_operate_until_the_reply_is_complete: 'Do not operate until the reply is complete'
......@@ -386,7 +388,7 @@ personal_space_module:
only_one_database_can_be_added: 'Only one database can be added'
associated_database_desc: 'You can upload local spreadsheet data or connect to a business database to build your dataset. When users ask numerical questions, the application can query, compute, and analyze the data to provide answers. Note: The app supports association with only 1 database at most'
upload_file: 'Upload file'
upload_file_desc: 'Enable the user to upload files for chat, support TXT, MD, PDF, DOC, DOCX format files'
upload_file_desc: 'Enable the user to upload files for chat, support TXT, MD, PDF, DOC, DOCX, PNG, JPG, JPEG, WEBP format files'
dialogue: 'Dialogue'
preamble: 'Opening remarks'
preamble_input_placeholder: 'Please enter an opening statement'
......
......@@ -160,6 +160,8 @@ common_module:
add_database_successfully: '数据库 {0} 添加成功'
remove_database_successfully: '数据库 {0} 移除成功'
database_QA_executed_successfully: '数据库问答执行成功'
input_parameter: '输入参数'
output_parameter: '输出参数'
dialogue_module:
continue_question_message: '你可以继续提问'
......@@ -170,7 +172,7 @@ common_module:
clear_message_dialog_title: '确认要清空对话吗?'
clear_message_dialog_content: '清空对话将清空当前所有对话内容,确定清空对话吗?'
cancel_associate_file_tip: '不再围绕这个文件回答'
upload_file_limit: '仅支持上传单个文件,支持PDF、DOC、DOCX、MD、TXT格式,最大10MB'
upload_file_limit: '仅支持上传单个文件,支持PDF、DOC、DOCX、MD、TXT、PNG、JPG、JPEG、WEBP格式,最大10MB'
overwrite_file_tip: '新上传的文件会覆盖原有文件,是否继续上传'
stop_playing_and_then_operate: '音频播放中,请停止播放后再操作'
do_not_operate_until_the_reply_is_complete: '回复完成后再操作'
......@@ -384,7 +386,7 @@ personal_space_module:
only_one_database_can_be_added: '仅支持添加一个数据库'
associated_database_desc: '可上传本地表格数据或连接业务数据库构建数据库。用户询问数值类问题时,应用能够查询、计算和分析数据并答复。应用最多可关联1个数据库'
upload_file: '上传文件'
upload_file_desc: '开启后支持用户上传文件进行对话聊天, 支持TXT、MD、PDF、DOC、DOCX格式的文件'
upload_file_desc: '开启后支持用户上传文件进行对话聊天, 支持TXT、MD、PDF、DOC、DOCX、PNG、JPG、JPEG、WEBP格式的文件'
dialogue: '对话'
preamble: '开场白'
preamble_input_placeholder: '请输入开场白'
......
......@@ -160,6 +160,8 @@ common_module:
add_database_successfully: '數據庫 {0} 添加成功'
remove_database_successfully: '數據庫 {0} 移除成功'
database_QA_executed_successfully: '數據庫問答執行成功'
input_parameter: '輸入參數'
output_parameter: '輸出參數'
dialogue_module:
continue_question_message: '你可以繼續提問'
......@@ -170,7 +172,7 @@ common_module:
clear_message_dialog_title: '確認要清空對話嗎?'
clear_message_dialog_content: '清空對話將清空當前所有對話內容,確定清空對話嗎?'
cancel_associate_file_tip: '不再圍繞這個文件回答'
upload_file_limit: '僅支持上傳單個文件,支持PDF、DOC、DOCX、MD、TXT格式,最大10MB'
upload_file_limit: '僅支持上傳單個文件,支持PDF、DOC、DOCX、MD、TXT、PNG、JPG、JPEG、WEBP格式,最大10MB'
overwrite_file_tip: '新上傳的文件會覆蓋原有文件,是否繼續上傳'
stop_playing_and_then_operate: '音頻播放中,請停止播放後再操作'
do_not_operate_until_the_reply_is_complete: '回覆完成後再操作'
......@@ -384,7 +386,7 @@ personal_space_module:
only_one_database_can_be_added: '僅支持添加一個數據庫'
associated_database_desc: '可上傳本地表格數據或連接業務數據庫構建數據庫。用户詢問數值類問題時,應用能夠查詢、計算和分析數據並答覆。應用最多可關聯1個數據庫'
upload_file: '上傳文件'
upload_file_desc: '開啓後支持用户上傳文件進行對話聊天, 支持TXT、MD、PDF、DOC、DOCX格式的文件'
upload_file_desc: '開啓後支持用户上傳文件進行對話聊天, 支持TXT、MD、PDF、DOC、DOCX、PNG、JPG、JPEG、WEBP格式的文件'
dialogue: '對話'
preamble: '開場白'
preamble_input_placeholder: '請輸入開場白'
......
......@@ -11,7 +11,6 @@ import { UploadStatus } from '@/enums/upload-status'
import { ChannelType } from '@/enums/channel'
import { useDialogueFile } from '@/composables/useDialogueFile'
import { useUserStore } from '@/store/modules/user'
import { useUploadImage } from '@/composables/useUploadImage'
const { t } = useI18n()
......@@ -19,7 +18,6 @@ interface Props {
agentId: string
isAnswerResponseWait: boolean
isEnableDocumentParse: boolean
isEnableUploadImage: boolean
}
const props = defineProps<Props>()
......@@ -36,7 +34,6 @@ const emit = defineEmits<{
const userStore = useUserStore()
const { uploadFileList, handleLimitUpload, handleUpload, handleRemoveFile } = useDialogueFile()
const { uploadImageList, handleLimitUploadImage, handleUploadImage, handleRemoveUploadImage } = useUploadImage()
const multiModelDialogueList = defineModel<MultiModelDialogueItem[]>('multiModelDialogueList', { required: true })
......@@ -61,6 +58,14 @@ const isInputMessageDisabled = computed(() => {
)
})
const uploadDocumentList = computed(() => {
return uploadFileList.value.filter((fileItem) => fileItem.format === 'document')
})
const uploadImageList = computed(() => {
return uploadFileList.value.filter((fileItem) => fileItem.format === 'image')
})
const isUploadFileDisabled = computed(() => {
return uploadFileList.value.length === 1
})
......@@ -97,11 +102,16 @@ function messageItemFactory() {
timestamp: Date.now(),
isTextContentLoading: false,
isAnswerResponseLoading: false,
pluginName: '',
imageUrl: '',
reasoningContent: '',
knowledgeContentResult: [],
dbChainSQLContent: '',
pluginResult: {
displayFormat: 'none',
pluginName: '',
arguments: '',
pluginContent: '',
},
} as MessageItemInterface
}
......@@ -142,7 +152,7 @@ function handleQuestionSubmit() {
type: 'text',
text: messageItem.content,
image_url: {
url: messageItem?.imageUrl || '',
url: '',
},
},
],
......@@ -195,7 +205,19 @@ function handleQuestionSubmit() {
// 插件
if (data.function && data.function.name) {
emit('updateMessageItem', answerMessageId, { pluginName: data.function.name }, modelIndex)
emit(
'updateMessageItem',
answerMessageId,
{
pluginResult: {
displayFormat: data.function.displayFormat,
pluginName: data.function.name,
arguments: data.function.arguments,
pluginContent: data.function.result,
},
},
modelIndex,
)
emit('messageListScrollToBottom')
}
......@@ -262,7 +284,7 @@ function handleQuestionSubmit() {
})
questionContent.value = ''
uploadImageList.value = []
uploadFileList.value = uploadFileList.value.filter((item) => item.format === 'document')
})
}
......@@ -281,17 +303,6 @@ function handleSelectFile(cb: () => void) {
cb()
}
function handleSelectUploadImage(cb: () => void) {
if (uploadImageList.value.length > 0) {
window.$message.ctWarning('', t('common_module.dialogue_module.overwrite_image_tip')).then(() => {
cb()
})
return
}
cb()
}
</script>
<template>
......@@ -317,31 +328,7 @@ function handleSelectUploadImage(cb: () => void) {
<n-upload
:show-file-list="false"
accept="image/png, image/jpeg, image/jpg, image/webp"
abstract
@before-upload="handleLimitUploadImage"
@change="handleUploadImage"
>
<n-upload-trigger #="{ handleClick }" abstract>
<n-popover style="width: 210px" trigger="hover">
<template #trigger>
<div
v-show="isEnableUploadImage"
class="border-inactive-border-color text-font-color hover:text-theme-color flex h-[54px] w-[54px] shrink-0 cursor-pointer items-center justify-center rounded-full border bg-white"
@click="handleSelectUploadImage(handleClick)"
>
<i class="iconfont icon-upload-image text-xl leading-none" />
</div>
</template>
<span class="text-xs"> {{ t('common_module.dialogue_module.upload_image_limit') }} </span>
</n-popover>
</n-upload-trigger>
</n-upload>
<n-upload
:show-file-list="false"
accept=".doc, .pdf, .docx, .txt, .md"
accept=".doc, .pdf, .docx, .txt, .md, image/png, image/jpeg, image/jpg, image/webp"
abstract
@before-upload="handleLimitUpload"
@change="handleUpload"
......@@ -374,7 +361,7 @@ function handleSelectUploadImage(cb: () => void) {
>
<div
class="absolute right-[-4px] top-[-4px] flex h-4 w-4 cursor-pointer items-center justify-center rounded-full bg-[rgba(0,0,0,0.55)] hover:opacity-80"
@click="handleRemoveUploadImage(uploadImageItem.id)"
@click="handleRemoveFile(uploadImageItem.id)"
>
<CloseSmall theme="outline" size="16" fill="#fff" />
</div>
......@@ -394,41 +381,41 @@ function handleSelectUploadImage(cb: () => void) {
</div>
</div>
<ul v-show="uploadFileList.length > 0" class="mb-1.5 grid gap-1.5">
<ul v-show="uploadDocumentList.length > 0" class="mb-1.5 grid gap-1.5">
<li
v-for="uploadFileItem in uploadFileList"
:key="uploadFileItem.id"
v-for="uploadDocumentItem in uploadDocumentList"
:key="uploadDocumentItem.id"
class="rounded-theme group relative flex h-[42px] w-full items-center overflow-hidden border bg-white/70"
:class="uploadFileItem.status === 'error' ? 'border-error-font-color' : 'border-transparent'"
:class="uploadDocumentItem.status === 'error' ? 'border-error-font-color' : 'border-transparent'"
>
<div class="flex w-full items-center justify-between px-3.5">
<div class="flex w-full items-center overflow-hidden">
<img :src="uploadFileIcon(uploadFileItem.type!)" class="h-7 w-7" />
<img :src="uploadFileIcon(uploadDocumentItem.type!)" class="h-7 w-7" />
<div class="mx-2.5 flex flex-1 flex-col overflow-hidden">
<n-ellipsis>
{{ uploadFileItem.name }}
{{ uploadDocumentItem.name }}
</n-ellipsis>
</div>
</div>
<n-progress
v-show="!['finished', 'error'].includes(uploadFileItem.status)"
v-show="!['finished', 'error'].includes(uploadDocumentItem.status)"
class="left-13.5 w-[calc(100%-78px)]! absolute bottom-0"
type="line"
rail-color="#F3F3F3"
:height="4"
:percentage="uploadFileItem.percentage"
:percentage="uploadDocumentItem.percentage"
:show-indicator="false"
/>
<div v-show="['finished', 'error'].includes(uploadFileItem.status)" class="hidden group-hover:block">
<div v-show="['finished', 'error'].includes(uploadDocumentItem.status)" class="hidden group-hover:block">
<n-popover trigger="hover" placement="top-end" :show-arrow="false">
<template #trigger>
<i
class="iconfont icon-close cursor-pointer hover:opacity-80"
:class="uploadFileItem.status === 'error' ? 'text-error-font-color' : 'text-font-color'"
@click="handleRemoveFile(uploadFileItem.id)"
:class="uploadDocumentItem.status === 'error' ? 'text-error-font-color' : 'text-font-color'"
@click="handleRemoveFile(uploadDocumentItem.id)"
/>
</template>
<span>{{ t('common_module.dialogue_module.cancel_associate_file_tip') }}</span>
......
<script setup lang="ts">
import { computed, readonly, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { CheckOne, Down } from '@icon-park/vue-next'
import { Down } from '@icon-park/vue-next'
import type { MessageItemInterface } from '../types'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
import ExecuteCodeRender from '@/components/execute-code-render/execute-code-render.vue'
import ExecutePluginRender from '@/components/execute-plugin-render/execute-plugin-render.vue'
import MessageBubbleLoading from './message-bubble-loading.vue'
interface Props {
......@@ -84,25 +85,6 @@ function handleShowReasoningContentSwitch() {
{{ props.messageItem.nickName }}
</div>
<div
v-show="isAssistant && messageItem.pluginName"
class="mb-[7px] flex items-center gap-[5px] font-['Microsoft_YaHei_UI'] text-[#999]"
>
<div
v-show="messageItem.isTextContentLoading"
class="bg-px-plugin_loading-gif h-[14px] w-[14px] bg-contain bg-center bg-no-repeat"
/>
<CheckOne v-show="!messageItem.isTextContentLoading" theme="outline" size="16" fill="#40bd23" />
<span class="leading-5">
{{
messageItem.isTextContentLoading
? t('common_module.plugin_in_progress', { pluginName: messageItem.pluginName })
: t('common_module.plugin_executed_successfully', { pluginName: messageItem.pluginName })
}}
</span>
</div>
<div class="flex min-w-[40px] max-w-full flex-col items-start overflow-hidden">
<div
class="min-h-[21px] w-full flex-wrap rounded-[10px] border border-[#9EA3FF] px-[15px] py-[12px] text-justify"
......@@ -118,7 +100,18 @@ function handleShowReasoningContentSwitch() {
class="max-h-[120px]! mb-[12px] rounded-[10px] object-contain"
/>
<div v-if="messageItem.isTextContentLoading" class="flex h-[21px] w-full items-center justify-center">
<!-- 插件返回结果 -->
<div v-show="isAssistant && messageItem?.pluginResult?.pluginName" class="mb-[11px] w-full">
<ExecutePluginRender
:display-format="messageItem.pluginResult?.displayFormat || 'none'"
:name="messageItem.pluginResult?.pluginName!"
:content="messageItem.pluginResult?.pluginContent!"
:arguments="messageItem.pluginResult?.arguments!"
:plugin-loading="messageItem.isTextContentLoading"
/>
</div>
<div v-if="messageItem.isTextContentLoading" class="flex px-4 py-1.5">
<MessageBubbleLoading :active-color="isAssistant ? '#fff' : '#192338'" />
</div>
......@@ -166,7 +159,7 @@ function handleShowReasoningContentSwitch() {
<style lang="scss" scoped>
:deep(.markdown-render-container .markdown-render-inner) {
table {
color: #192338 !important;
color: #fff !important;
}
}
</style>
......@@ -33,11 +33,16 @@ export interface MessageItemInterface {
timestamp: number
isTextContentLoading: boolean
isAnswerResponseLoading: boolean
pluginName?: string
imageUrl?: string
reasoningContent: string
knowledgeContentResult: KnowledgeContentResultItem[]
dbChainSQLContent: string
pluginResult?: {
displayFormat: 'json' | 'markdown' | 'none'
pluginName: string
arguments: string
pluginContent: string
}
}
export interface LargeModelItem {
......
......@@ -8,7 +8,7 @@ import { languageKeyTransform } from '@/utils/language-key-transform'
interface ResponseData {
message: string
reasoningContent: string
function: { name: string }
function: { displayFormat: 'json' | 'markdown' | 'none'; name: string; result: string; arguments: string }
knowledgeContentResult: KnowledgeContentResultItem[]
dbChainResult: DBChainResultItem[]
}
......
......@@ -13,8 +13,6 @@ import { TEXTTOSPEECH_WS_URL } from '@/config/base-url'
import WebSocketCtr from '@/utils/web-socket-ctr'
import { ChannelType } from '@/enums/channel'
import { useUserStore } from '@/store/modules/user'
import { useUploadImage } from '@/composables/useUploadImage'
import { PluginType } from '@/enums/plugin'
interface Props {
messageList: Map<string, ConversationMessageItem>
......@@ -48,7 +46,6 @@ const overwriteMessageTipOptions = reactive({
})
const { uploadFileList, handleLimitUpload, handleUpload, handleRemoveFile } = useDialogueFile()
const { uploadImageList, handleLimitUploadImage, handleUploadImage, handleRemoveUploadImage } = useUploadImage()
const messageTipModalRef = useTemplateRef<InstanceType<typeof OverwriteMessageTipModal>>('messageTipModalRef')
const isAnswerResponseLoading = defineModel<boolean>('isAnswerResponseLoading', { required: true })
......@@ -87,8 +84,7 @@ const isSendBtnDisabled = computed(() => {
const isInputMessageDisabled = computed(() => {
return (
uploadFileList.value.some((fileItem) => fileItem.status !== UploadStatus.FINISHED) ||
!personalAppConfigStore.baseInfo.agentId ||
uploadImageList.value.some((imageItem) => imageItem.status !== UploadStatus.FINISHED)
!personalAppConfigStore.baseInfo.agentId
)
})
......@@ -100,8 +96,14 @@ const isUploadFileDisabled = computed(() => {
return uploadFileList.value.length === 1
})
const isEnableUploadImage = computed(() => {
return personalAppConfigStore.unitIds?.includes(PluginType.IMAGE_OCR) || false
// 上传文档列表
const uploadDocumentList = computed(() => {
return uploadFileList.value.filter((fileItem) => fileItem.format === 'document')
})
// 上传图片列表
const uploadImageList = computed(() => {
return uploadFileList.value.filter((fileItem) => fileItem.format === 'image')
})
const uploadFileIcon = (type: string) => {
......@@ -146,12 +148,17 @@ function messageItemFactory(): ConversationMessageItem {
isVoicePlaying: false,
voiceFragmentUrlList: [],
isVoiceEnabled: false,
pluginName: '',
imageUrl: '',
reasoningContent: '',
modelName: '',
knowledgeContentResult: [],
dbChainSQLContent: '',
pluginResult: {
displayFormat: 'none',
pluginName: '',
pluginContent: '',
arguments: '',
},
}
}
......@@ -208,7 +215,7 @@ function handleMessageSend() {
type: 'text',
text: messageItem.textContent,
image_url: {
url: messageItem.imageUrl || '',
url: '',
},
},
],
......@@ -264,7 +271,14 @@ function handleMessageSend() {
// 插件
if (data.function && data.function.name) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { pluginName: data.function.name })
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
pluginResult: {
displayFormat: data.function.displayFormat,
pluginName: data.function.name,
pluginContent: data.function.result,
arguments: data.function.arguments,
},
})
emit('updatePageScroll')
}
......@@ -328,7 +342,7 @@ function handleMessageSend() {
})
inputMessageContent.value = ''
uploadImageList.value = []
uploadFileList.value = uploadFileList.value.filter((item) => item.format === 'document')
}
function errorMessageResponse() {
......@@ -368,19 +382,6 @@ function handleSelectFile(cb: () => void) {
cb()
}
function handleSelectImage(cb: () => void) {
if (uploadImageList.value.length > 0) {
overwriteMessageTipOptions.title = t('common_module.dialogue_module.overwrite_image_tip')
messageTipModalRef.value?.handleShowModal().then(() => {
cb()
})
return
}
cb()
}
function sentenceExtract(messageId: string) {
const symbolRegExp = /[。!?;.!?;~]/g
let sentenceDraft = assistantFullAnswerContent.value
......@@ -502,7 +503,7 @@ defineExpose({
>
<div
class="absolute right-[-4px] top-[-4px] flex h-4 w-4 cursor-pointer items-center justify-center rounded-full bg-[rgba(0,0,0,0.55)] hover:opacity-80"
@click="handleRemoveUploadImage(uploadImageItem.id)"
@click="handleRemoveFile(uploadImageItem.id)"
>
<CloseSmall theme="outline" size="16" fill="#fff" />
</div>
......@@ -521,41 +522,41 @@ defineExpose({
</div>
</div>
<ul v-show="uploadFileList.length > 0" class="mb-1.5 grid gap-1.5">
<ul v-show="uploadDocumentList.length > 0" class="mb-1.5 grid gap-1.5">
<li
v-for="uploadFileItem in uploadFileList"
:key="uploadFileItem.id"
v-for="uploadDocumentItem in uploadDocumentList"
:key="uploadDocumentItem.id"
class="group relative flex h-[42px] w-full items-center overflow-hidden rounded-[10px] border bg-white/70"
:class="uploadFileItem.status === 'error' ? 'border-error-font-color' : 'border-transparent'"
:class="uploadDocumentItem.status === 'error' ? 'border-error-font-color' : 'border-transparent'"
>
<div class="flex w-full items-center justify-between px-3.5">
<div class="flex w-full items-center overflow-hidden">
<img :src="uploadFileIcon(uploadFileItem.type!)" class="h-7 w-7" />
<img :src="uploadFileIcon(uploadDocumentItem.type!)" class="h-7 w-7" />
<div class="mx-2.5 flex flex-1 flex-col overflow-hidden">
<n-ellipsis>
{{ uploadFileItem.name }}
{{ uploadDocumentItem.name }}
</n-ellipsis>
</div>
</div>
<n-progress
v-show="!['finished', 'error'].includes(uploadFileItem.status)"
v-show="!['finished', 'error'].includes(uploadDocumentItem.status)"
class="left-13.5 w-[calc(100%-78px)]! absolute bottom-0"
type="line"
rail-color="#F3F3F3"
:height="4"
:percentage="uploadFileItem.percentage"
:percentage="uploadDocumentItem.percentage"
:show-indicator="false"
/>
<div v-show="['finished', 'error'].includes(uploadFileItem.status)" class="hidden group-hover:block">
<div v-show="['finished', 'error'].includes(uploadDocumentItem.status)" class="hidden group-hover:block">
<n-popover trigger="hover" placement="top-end" :show-arrow="false">
<template #trigger>
<i
class="iconfont icon-close cursor-pointer hover:opacity-80"
:class="uploadFileItem.status === 'error' ? 'text-error-font-color' : 'text-font-color'"
@click="handleRemoveFile(uploadFileItem.id)"
:class="uploadDocumentItem.status === 'error' ? 'text-error-font-color' : 'text-font-color'"
@click="handleRemoveFile(uploadDocumentItem.id)"
/>
</template>
<span>{{ t('common_module.dialogue_module.cancel_associate_file_tip') }}</span>
......@@ -594,7 +595,7 @@ defineExpose({
<n-upload
:show-file-list="false"
accept=".doc, .pdf, .docx, .txt, .md"
accept=".doc, .pdf, .docx, .txt, .md, image/png, image/jpeg, image/jpg, image/webp"
abstract
@before-upload="handleLimitUpload"
@change="handleUpload"
......@@ -616,30 +617,6 @@ defineExpose({
</n-upload-trigger>
</n-upload>
<n-upload
:show-file-list="false"
accept="image/png, image/jpeg, image/jpg, image/webp"
abstract
@before-upload="handleLimitUploadImage"
@change="handleUploadImage"
>
<n-upload-trigger #="{ handleClick }" abstract>
<n-popover style="width: 210px" trigger="hover">
<template #trigger>
<div
v-show="isEnableUploadImage"
class="h-7.5 w-7.5 hover:text-theme-color text-font-color mb-1 flex cursor-pointer items-center justify-center rounded-full bg-white"
@click="handleSelectImage(handleClick)"
>
<i class="iconfont icon-upload-image flex h-4 w-4 items-center justify-center" />
</div>
</template>
<span class="text-xs"> {{ t('common_module.dialogue_module.upload_image_limit') }} </span>
</n-popover>
</n-upload-trigger>
</n-upload>
<n-popover trigger="hover">
<template #trigger>
<div
......
......@@ -2,11 +2,12 @@
import { computed, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { throttle } from 'lodash-es'
import { CheckOne, Down } from '@icon-park/vue-next'
import { 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'
import ExecuteCodeRender from '@/components/execute-code-render/execute-code-render.vue'
import ExecutePluginRender from '@/components/execute-plugin-render/execute-plugin-render.vue'
import { useUserStore } from '@/store/modules/user'
import { asBlob } from 'html-docx-js-typescript'
import { downloadFile } from '@/utils/download-file'
......@@ -156,7 +157,7 @@ const handleContentCopy = throttle(
<div class="flex min-w-[80px] max-w-full flex-col items-start overflow-x-auto">
<div
class="w-full flex-wrap rounded-xl border border-[#e8e9eb] px-4 py-[11px]"
:class="role === 'user' ? 'user-content-container bg-[#4b87ff] text-white' : 'bg-white text-[#333]'"
:class="role === 'user' ? 'user-content-container bg-[#777EF9] text-white' : 'bg-white text-[#333]'"
>
<img
v-show="role === 'user' && messageItem.imageUrl"
......@@ -164,26 +165,18 @@ const handleContentCopy = throttle(
class="max-h-[120px]! mb-[11px] rounded-[10px] object-contain"
/>
<div
v-show="role === 'assistant' && messageItem.pluginName"
class="mb-[8px] flex items-center gap-[5px] font-['Microsoft_YaHei_UI'] text-[#999]"
>
<div
v-show="messageItem.isTextContentLoading"
class="bg-px-plugin_loading-gif h-[14px] w-[14px] bg-contain bg-center bg-no-repeat"
<!-- 插件返回结果 -->
<div v-show="role === 'assistant' && messageItem?.pluginResult?.pluginName" class="mb-[11px] w-full">
<ExecutePluginRender
:display-format="messageItem.pluginResult?.displayFormat || 'none'"
:name="messageItem.pluginResult?.pluginName!"
:content="messageItem.pluginResult?.pluginContent!"
:arguments="messageItem.pluginResult?.arguments!"
:plugin-loading="messageItem.isTextContentLoading"
/>
<CheckOne v-show="!messageItem.isTextContentLoading" theme="outline" size="16" fill="#40bd23" />
<span class="leading-5">
{{
messageItem.isTextContentLoading
? t('common_module.plugin_in_progress', { pluginName: messageItem.pluginName })
: t('common_module.plugin_executed_successfully', { pluginName: messageItem.pluginName })
}}
</span>
</div>
<div v-if="messageItem.isTextContentLoading" class="flex justify-center py-1.5">
<div v-if="messageItem.isTextContentLoading" class="flex px-4 py-1.5">
<CustomLoading />
</div>
......@@ -283,4 +276,10 @@ const handleContentCopy = throttle(
color: #fff !important;
}
}
:deep(.markdown-render-container .markdown-render-inner) {
table {
color: #fff !important;
}
}
</style>
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import { computed, nextTick, ref, useTemplateRef, watch } from 'vue'
import { useElementVisibility } from '@vueuse/core'
import MessageItem from './message-item.vue'
import ContinueQuestion from './continue-question.vue'
import { useScroll } from '@/composables/useScroll'
import { useBackBottom } from '@/composables/useBackBottom'
import HitKnowledge from './hit-knowledge.vue'
interface Props {
......@@ -22,8 +22,8 @@ defineEmits<{
}>()
const { scrollRef, scrollToBottom } = useScroll()
const { visible, clickBackBottom, throttleScrollContainer } = useBackBottom(scrollRef, scrollToBottom)
const backBottomBtnFlagRef = useTemplateRef<HTMLDivElement | null>('backBottomBtnFlagRef')
const isNotShowBackBottomBtn = useElementVisibility(backBottomBtnFlagRef)
const isShowKnowledgeContent = ref(false)
const currentKnowledgeContentResult = ref<KnowledgeContentResultItem[]>([])
......@@ -48,9 +48,13 @@ defineExpose({
scrollToBottom: handleScrollToBottom,
})
function clickBackBottom() {
scrollToBottom()
}
function handleScrollToBottom() {
nextTick(() => {
!visible.value && scrollToBottom()
isNotShowBackBottomBtn.value && scrollToBottom()
})
}
......@@ -61,8 +65,8 @@ function handleShowKnowledgeResult(knowledgeContentResult: KnowledgeContentResul
</script>
<template>
<main ref="scrollRef" class="h-full overflow-y-auto overflow-x-hidden px-5" @scroll="throttleScrollContainer">
<div>
<main ref="scrollRef" class="h-full overflow-y-auto overflow-x-hidden px-5">
<div class="relative">
<MessageItem
v-for="[key, messageItem] in messageList"
:key="key"
......@@ -72,19 +76,23 @@ function handleShowKnowledgeResult(knowledgeContentResult: KnowledgeContentResul
@audio-pause="() => $emit('audioPause')"
@show-knowledge-result="handleShowKnowledgeResult(messageItem.knowledgeContentResult)"
/>
</div>
<div v-show="isShowContinueQuestion">
<ContinueQuestion :continuous-question-list="continuousQuestionList" />
</div>
<div ref="backBottomBtnFlagRef" class="-z-1 absolute bottom-0 h-[100px] w-full"></div>
</div>
<Transition name="back-bottom-btn" mode="out-in">
<div
v-show="visible"
v-show="!isNotShowBackBottomBtn"
class="flex-center hover:text-theme-color absolute bottom-5 right-5 h-[26px] w-[26px] cursor-pointer rounded-full bg-white shadow-[0_0_0_1px_#ededed]"
@click.stop="clickBackBottom"
>
<i class="iconfont icon-huidaodibu text-[12px]" />
</div>
</Transition>
<HitKnowledge
v-model:is-show-drawer="isShowKnowledgeContent"
......@@ -92,3 +100,17 @@ function handleShowKnowledgeResult(knowledgeContentResult: KnowledgeContentResul
/>
</main>
</template>
<style lang="scss" scoped>
.back-bottom-btn-enter-active,
.back-bottom-btn-leave-active {
transition-timing-function: ease-in-out;
transition-duration: 0.2s;
transition-property: opacity;
}
.back-bottom-btn-enter-from,
.back-bottom-btn-leave-to {
opacity: 0;
}
</style>
......@@ -13,7 +13,6 @@ import { useLayoutConfig } from '@/composables/useLayoutConfig'
import { TEXTTOSPEECH_WS_URL } from '@/config/base-url'
import WebSocketCtr from '@/utils/web-socket-ctr'
import { ChannelType } from '@/enums/channel'
import { useUploadImage } from '@/composables/useUploadImage'
interface Props {
agentId: string
......@@ -21,7 +20,6 @@ interface Props {
messageList: Map<string, ConversationMessageItem>
continuousQuestionStatus: 'default' | 'close'
isEnableDocumentParse: boolean
isEnableUploadImage: boolean
isEnableVoice: boolean
answerAudioAutoPlay: boolean
answerAudioPlaying: boolean
......@@ -52,7 +50,6 @@ const { isMobile } = useLayoutConfig()
const userStore = useUserStore()
const { uploadFileList, handleLimitUpload, handleUpload, handleRemoveFile } = useDialogueFile()
const { uploadImageList, handleLimitUploadImage, handleUploadImage, handleRemoveUploadImage } = useUploadImage()
const isAnswerResponseWait = defineModel<boolean>('isAnswerResponseLoading', { required: true })
const isAnswerResponseInterrupt = defineModel<boolean>('isAnswerResponseInterrupt', { required: true })
......@@ -97,6 +94,14 @@ const isInputMessageDisabled = computed(() => {
)
})
const uploadDocumentList = computed(() => {
return uploadFileList.value.filter((fileItem) => fileItem.format === 'document')
})
const uploadImageList = computed(() => {
return uploadFileList.value.filter((fileItem) => fileItem.format === 'image')
})
const isUploadFileDisabled = computed(() => {
return uploadFileList.value.length === 1
})
......@@ -140,11 +145,16 @@ function messageItemFactory(): ConversationMessageItem {
isVoiceLoading: false,
isVoicePlaying: false,
voiceFragmentUrlList: [],
pluginName: '',
imageUrl: '',
reasoningContent: '',
knowledgeContentResult: [],
dbChainSQLContent: '',
pluginResult: {
displayFormat: 'none',
pluginName: '',
arguments: '',
pluginContent: '',
},
}
}
......@@ -236,7 +246,14 @@ function handleMessageSend(lastQuestionContent?: string) {
// 插件
if (data.function && data.function.name) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { pluginName: data.function.name })
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
pluginResult: {
displayFormat: data.function.displayFormat,
pluginName: data.function.name,
arguments: data.function.arguments,
pluginContent: data.function.result,
},
})
emit('updatePageScroll')
}
......@@ -292,7 +309,7 @@ function handleMessageSend(lastQuestionContent?: string) {
})
inputMessageContent.value = ''
uploadImageList.value = []
uploadFileList.value = uploadFileList.value.filter((item) => item.format === 'document')
}
function errorMessageResponse() {
......@@ -334,16 +351,16 @@ function handleSelectFile(cb: () => void) {
cb()
}
function handleSelectImage(cb: () => void) {
if (uploadImageList.value.length > 0) {
window.$message.ctWarning('', t('common_module.dialogue_module.overwrite_file_tip')).then(() => {
cb()
})
return
}
// function handleSelectImage(cb: () => void) {
// if (uploadImageList.value.length > 0) {
// window.$message.ctWarning('', t('common_module.dialogue_module.overwrite_file_tip')).then(() => {
// cb()
// })
// return
// }
cb()
}
// cb()
// }
function sentenceExtract(messageId: string) {
const symbolRegExp = /[。!?;.!?;~]/g
......@@ -468,7 +485,7 @@ defineExpose({
>
<div
class="absolute right-[-4px] top-[-4px] flex h-4 w-4 cursor-pointer items-center justify-center rounded-full bg-[rgba(0,0,0,0.55)] hover:opacity-80"
@click="handleRemoveUploadImage(uploadImageItem.id)"
@click="handleRemoveFile(uploadImageItem.id)"
>
<CloseSmall theme="outline" size="16" fill="#fff" />
</div>
......@@ -487,36 +504,36 @@ defineExpose({
</div>
</div>
<ul v-show="uploadFileList.length > 0" class="mb-1.5 grid gap-1.5">
<ul v-show="uploadDocumentList.length > 0" class="mb-1.5 grid gap-1.5">
<li
v-for="uploadFileItem in uploadFileList"
:key="uploadFileItem.id"
v-for="uploadDocumentItem in uploadDocumentList"
:key="uploadDocumentItem.id"
class="group relative flex h-[42px] w-full items-center overflow-hidden rounded-[10px] border bg-white/70"
:class="uploadFileItem.status === 'error' ? 'border-error-font-color' : 'border-transparent'"
:class="uploadDocumentItem.status === 'error' ? 'border-error-font-color' : 'border-transparent'"
>
<div class="flex w-full items-center justify-between px-3.5">
<div class="flex w-full items-center overflow-hidden">
<img :src="uploadFileIcon(uploadFileItem.type!)" class="h-7 w-7" />
<img :src="uploadFileIcon(uploadDocumentItem.type!)" class="h-7 w-7" />
<div class="mx-2.5 flex flex-1 flex-col overflow-hidden">
<n-ellipsis>
{{ uploadFileItem.name }}
{{ uploadDocumentItem.name }}
</n-ellipsis>
</div>
</div>
<n-progress
v-show="!['finished', 'error'].includes(uploadFileItem.status)"
v-show="!['finished', 'error'].includes(uploadDocumentItem.status)"
class="left-13.5 w-[calc(100%-78px)]! absolute bottom-0"
type="line"
rail-color="#F3F3F3"
:height="4"
:percentage="uploadFileItem.percentage"
:percentage="uploadDocumentItem.percentage"
:show-indicator="false"
/>
<div
v-show="['finished', 'error'].includes(uploadFileItem.status)"
v-show="['finished', 'error'].includes(uploadDocumentItem.status)"
class="group-hover:block"
:class="isMobile ? 'block' : 'hidden'"
>
......@@ -524,8 +541,8 @@ defineExpose({
<template #trigger>
<i
class="iconfont icon-close cursor-pointer hover:opacity-80"
:class="uploadFileItem.status === 'error' ? 'text-error-font-color' : 'text-font-color'"
@click="handleRemoveFile(uploadFileItem.id)"
:class="uploadDocumentItem.status === 'error' ? 'text-error-font-color' : 'text-font-color'"
@click="handleRemoveFile(uploadDocumentItem.id)"
/>
</template>
<span>{{ t('common_module.dialogue_module.cancel_associate_file_tip') }}</span>
......@@ -571,7 +588,7 @@ defineExpose({
<n-upload
:show-file-list="false"
accept=".doc, .pdf, .docx, .txt, .md"
accept=".doc, .pdf, .docx, .txt, .md, image/png, image/jpeg, image/jpg, image/webp"
:disabled="!isLogin"
abstract
@before-upload="handleLimitUpload"
......@@ -599,30 +616,6 @@ defineExpose({
</n-upload-trigger>
</n-upload>
<n-upload
:show-file-list="false"
accept="image/png, image/jpeg, image/jpg, image/webp"
abstract
@before-upload="handleLimitUploadImage"
@change="handleUploadImage"
>
<n-upload-trigger #="{ handleClick }" abstract>
<n-popover style="width: 210px" trigger="hover">
<template #trigger>
<div
v-show="isEnableUploadImage"
class="h-7.5 w-7.5 hover:text-theme-color text-font-color mb-1 flex cursor-pointer items-center justify-center rounded-full bg-white"
@click="handleSelectImage(handleClick)"
>
<i class="iconfont icon-upload-image flex h-4 w-4 items-center justify-center" />
</div>
</template>
<span class="text-xs"> {{ t('common_module.dialogue_module.upload_image_limit') }} </span>
</n-popover>
</n-upload-trigger>
</n-upload>
<n-popover trigger="hover">
<template #trigger>
<div
......
......@@ -2,11 +2,12 @@
import { computed, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { throttle } from 'lodash-es'
import { CheckOne, Down } from '@icon-park/vue-next'
import { 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'
import EditorDrawer from '@/components/editor-drawer/editor-drawer.vue'
import ExecutePluginRender from '@/components/execute-plugin-render/execute-plugin-render.vue'
import ExecuteCodeRender from '@/components/execute-code-render/execute-code-render.vue'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { useLayoutConfig } from '@/composables/useLayoutConfig'
......@@ -174,7 +175,7 @@ const handleContentCopy = throttle(
<div class="flex min-w-[80px] flex-col" :class="[isMobile ? 'max-w-[calc(100%-20px)]' : 'max-w-full']">
<div
class="w-full flex-wrap rounded-xl border border-[#e8e9eb] px-4 py-[11px]"
:class="[role === 'user' ? 'user-content-container bg-theme-color text-white' : 'bg-white text-[#333]']"
:class="[role === 'user' ? 'user-content-container bg-[#777EF9] text-white' : 'bg-white text-[#333]']"
>
<img
v-show="role === 'user' && messageItem.imageUrl"
......@@ -182,23 +183,15 @@ const handleContentCopy = throttle(
class="max-h-[120px]! mb-[11px] rounded-[10px] object-contain"
/>
<div
v-show="role === 'assistant' && messageItem.pluginName"
class="mb-[8px] flex items-center gap-[5px] font-['Microsoft_YaHei_UI'] text-[#999]"
>
<div
v-show="messageItem.isTextContentLoading"
class="bg-px-plugin_loading-gif h-[14px] w-[14px] bg-contain bg-center bg-no-repeat"
<!-- 插件返回结果 -->
<div v-show="role === 'assistant' && messageItem?.pluginResult?.pluginName" class="mb-[11px] w-full">
<ExecutePluginRender
:display-format="messageItem.pluginResult?.displayFormat || 'none'"
:name="messageItem.pluginResult?.pluginName!"
:content="messageItem.pluginResult?.pluginContent!"
:arguments="messageItem.pluginResult?.arguments!"
:plugin-loading="messageItem.isTextContentLoading"
/>
<CheckOne v-show="!messageItem.isTextContentLoading" theme="outline" size="16" fill="#40bd23" />
<span class="leading-5">
{{
messageItem.isTextContentLoading
? t('common_module.plugin_in_progress', { pluginName: messageItem.pluginName })
: t('common_module.plugin_executed_successfully', { pluginName: messageItem.pluginName })
}}
</span>
</div>
<div v-if="messageItem.isTextContentLoading" class="py-1.5 pl-4">
......@@ -313,4 +306,10 @@ const handleContentCopy = throttle(
color: #fff !important;
}
}
:deep(.markdown-render-container .markdown-render-inner) {
table {
color: #fff !important;
}
}
</style>
<script setup lang="ts">
import { computed, nextTick, watch } from 'vue'
import { computed, nextTick, useTemplateRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useElementVisibility } from '@vueuse/core'
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 { useBackBottom } from '@/composables/useBackBottom'
import { useLayoutConfig } from '@/composables/useLayoutConfig'
interface Props {
......@@ -30,8 +30,8 @@ const { t } = useI18n()
const { isMobile } = useLayoutConfig()
const { scrollRef, scrollToBottom } = useScroll()
const { visible, clickBackBottom, throttleScrollContainer } = useBackBottom(scrollRef, scrollToBottom)
const backBottomBtnFlagRef = useTemplateRef<HTMLDivElement | null>('backBottomBtnFlagRef')
const isNotShowBackBottomBtn = useElementVisibility(backBottomBtnFlagRef)
const isShowContinueQuestion = computed(() => {
return (
......@@ -54,16 +54,20 @@ defineExpose({
scrollToBottom: handleScrollToBottom,
})
function clickBackBottom() {
scrollToBottom()
}
function handleScrollToBottom() {
nextTick(() => {
!visible.value && scrollToBottom()
isNotShowBackBottomBtn.value && scrollToBottom()
})
}
</script>
<template>
<main ref="scrollRef" class="h-full overflow-y-auto overflow-x-hidden px-5" @scroll="throttleScrollContainer">
<div>
<main ref="scrollRef" class="h-full overflow-y-auto overflow-x-hidden px-5">
<div class="relative">
<MessageItem
v-for="[key, messageItem] in messageList"
:key="key"
......@@ -73,7 +77,6 @@ function handleScrollToBottom() {
@audio-play="() => $emit('audioPlay', messageItem)"
@audio-pause="() => $emit('audioPause')"
/>
</div>
<p v-show="isMobile && isAnswerResponseLoading" class="ml-1 mt-[7px] text-xs text-[#84868c]">
{{ t('common_module.dialogue_module.do_not_exit_page') }}
......@@ -83,9 +86,12 @@ function handleScrollToBottom() {
<ContinueQuestion :continuous-question-list="continuousQuestionList" />
</div>
<div ref="backBottomBtnFlagRef" class="-z-1 absolute bottom-0 h-[100px] w-full"></div>
</div>
<Transition name="back-bottom-btn" mode="out-in">
<div
v-show="visible"
v-show="!isNotShowBackBottomBtn"
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"
>
......
......@@ -13,7 +13,6 @@ import { useLayoutConfig } from '@/composables/useLayoutConfig'
import { TEXTTOSPEECH_WS_URL } from '@/config/base-url'
import WebSocketCtr from '@/utils/web-socket-ctr'
import { ChannelType } from '@/enums/channel'
import { useUploadImage } from '@/composables/useUploadImage'
import { showDialog } from 'vant'
interface Props {
......@@ -22,7 +21,6 @@ interface Props {
messageList: Map<string, ConversationMessageItem>
continuousQuestionStatus: 'default' | 'close'
isEnableDocumentParse: boolean
isEnableUploadImage: boolean
isEnableVoice: boolean
answerAudioAutoPlay: boolean
answerAudioPlaying: boolean
......@@ -53,7 +51,6 @@ const { isMobile } = useLayoutConfig()
const userStore = useUserStore()
const { uploadFileList, handleLimitUpload, handleUpload, handleRemoveFile } = useDialogueFile()
const { uploadImageList, handleLimitUploadImage, handleUploadImage, handleRemoveUploadImage } = useUploadImage()
const isAnswerResponseWait = defineModel<boolean>('isAnswerResponseLoading', { required: true })
const isAnswerResponseInterrupt = defineModel<boolean>('isAnswerResponseInterrupt', { required: true })
......@@ -98,6 +95,14 @@ const isInputMessageDisabled = computed(() => {
)
})
const uploadDocumentList = computed(() => {
return uploadFileList.value.filter((fileItem) => fileItem.format === 'document')
})
const uploadImageList = computed(() => {
return uploadFileList.value.filter((fileItem) => fileItem.format === 'image')
})
const isUploadFileDisabled = computed(() => {
return uploadFileList.value.length === 1
})
......@@ -141,11 +146,16 @@ function messageItemFactory(): ConversationMessageItem {
isVoiceLoading: false,
isVoicePlaying: false,
voiceFragmentUrlList: [],
pluginName: '',
imageUrl: '',
reasoningContent: '',
knowledgeContentResult: [],
dbChainSQLContent: '',
pluginResult: {
displayFormat: 'none',
pluginName: '',
arguments: '',
pluginContent: '',
},
}
}
......@@ -237,7 +247,14 @@ function handleMessageSend(lastQuestionContent?: string) {
// 插件
if (data.function && data.function.name) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { pluginName: data.function.name })
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
pluginResult: {
displayFormat: data.function.displayFormat,
pluginName: data.function.name,
arguments: data.function.arguments,
pluginContent: data.function.result,
},
})
emit('updatePageScroll')
}
......@@ -293,7 +310,7 @@ function handleMessageSend(lastQuestionContent?: string) {
})
inputMessageContent.value = ''
uploadImageList.value = []
uploadFileList.value = uploadFileList.value.filter((item) => item.format === 'document')
}
function errorMessageResponse() {
......@@ -326,17 +343,6 @@ function handleToLogin() {
function handleSelectFile(cb: () => void) {
if (isUploadFileDisabled.value) {
window.$message.ctWarning('', t('common_module.dialogue_module.overwrite_file_tip')).then(() => {
cb()
})
return
}
cb()
}
function handleSelectImage(cb: () => void) {
if (uploadImageList.value.length > 0) {
showDialog({
title: '',
message: t('common_module.dialogue_module.overwrite_file_tip'),
......@@ -347,6 +353,7 @@ function handleSelectImage(cb: () => void) {
}).then(() => {
cb()
})
return
}
......@@ -468,7 +475,7 @@ defineExpose({
<div class="footer-main-container">
<n-upload
:show-file-list="false"
accept=".doc, .pdf, .docx, .txt, .md"
accept=".doc, .pdf, .docx, .txt, .md, image/png, image/jpeg, image/jpg, image/webp"
:disabled="!isLogin"
abstract
@before-upload="handleLimitUpload"
......@@ -494,33 +501,6 @@ defineExpose({
</n-upload-trigger>
</n-upload>
<n-upload
:show-file-list="false"
accept="image/png, image/jpeg, image/jpg, image/webp"
abstract
@before-upload="handleLimitUploadImage"
@change="handleUploadImage"
>
<n-upload-trigger #="{ handleClick }" abstract>
<n-popover style="width: 210px" trigger="hover">
<template #trigger>
<div
v-show="isEnableUploadImage"
class="upload-image-btn"
:class="
isLogin ? 'text-theme-color cursor-pointer hover:opacity-80' : 'cursor-not-allowed text-[#b8babf]'
"
@click="handleSelectImage(handleClick)"
>
<i class="iconfont icon-upload-image" />
</div>
</template>
<span class="btn-tip"> {{ t('common_module.dialogue_module.upload_image_limit') }} </span>
</n-popover>
</n-upload-trigger>
</n-upload>
<n-popover trigger="hover">
<template #trigger>
<div
......@@ -546,7 +526,7 @@ defineExpose({
class="upload-image-item"
:class="{ 'upload-image-item-error': uploadImageItem.status === UploadStatus.ERROR }"
>
<div class="close-btn" @click="handleRemoveUploadImage(uploadImageItem.id)">
<div class="close-btn" @click="handleRemoveFile(uploadImageItem.id)">
<CloseSmall theme="outline" size="16" fill="#fff" />
</div>
......@@ -564,36 +544,36 @@ defineExpose({
</div>
</div>
<ul v-show="uploadFileList.length > 0" class="upload-file-list-container">
<ul v-show="uploadDocumentList.length > 0" class="upload-file-list-container">
<li
v-for="uploadFileItem in uploadFileList"
:key="uploadFileItem.id"
v-for="uploadDocumentItem in uploadDocumentList"
:key="uploadDocumentItem.id"
class="upload-file-item"
:class="uploadFileItem.status === 'error' ? 'border-error-font-color' : 'border-transparent'"
:class="uploadDocumentItem.status === 'error' ? 'border-error-font-color' : 'border-transparent'"
>
<div class="upload-file-item-container">
<div class="upload-file-item-content">
<img :src="uploadFileIcon(uploadFileItem.type!)" class="upload-file-icon" />
<img :src="uploadFileIcon(uploadDocumentItem.type!)" class="upload-file-icon" />
<div class="upload-file-name">
<n-ellipsis>
{{ uploadFileItem.name }}
{{ uploadDocumentItem.name }}
</n-ellipsis>
</div>
</div>
<n-progress
v-show="!['finished', 'error'].includes(uploadFileItem.status)"
v-show="!['finished', 'error'].includes(uploadDocumentItem.status)"
class="upload-file-item-progress"
type="line"
rail-color="#F3F3F3"
:height="4"
:percentage="uploadFileItem.percentage"
:percentage="uploadDocumentItem.percentage"
:show-indicator="false"
/>
<div
v-show="['finished', 'error'].includes(uploadFileItem.status)"
v-show="['finished', 'error'].includes(uploadDocumentItem.status)"
class="group-hover:block"
:class="isMobile ? 'block' : 'hidden'"
>
......@@ -601,8 +581,10 @@ defineExpose({
<template #trigger>
<i
class="iconfont icon-close close-upload-file-icon"
:class="uploadFileItem.status === 'error' ? 'upload-file-error-icon' : 'upload-file-success-icon'"
@click="handleRemoveFile(uploadFileItem.id)"
:class="
uploadDocumentItem.status === 'error' ? 'upload-file-error-icon' : 'upload-file-success-icon'
"
@click="handleRemoveFile(uploadDocumentItem.id)"
/>
</template>
<span>{{ t('common_module.dialogue_module.cancel_associate_file_tip') }}</span>
......
......@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
import { CheckOne, Down } from '@icon-park/vue-next'
import CustomLoading from '../custom-loading.vue'
import MusicWavesLoading from './music-waves-loading.vue'
import ExecutePluginRender from '@/components/execute-plugin-render/execute-plugin-render.vue'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
......@@ -117,17 +118,15 @@ function handleShowReasoningContentSwitch() {
>
<img v-show="role === 'user' && messageItem.imageUrl" :src="messageItem.imageUrl" class="upload-image" />
<div v-show="role === 'assistant' && messageItem.pluginName" class="plugin-container">
<div v-show="messageItem.isTextContentLoading" class="plugin-loading" />
<CheckOne v-show="!messageItem.isTextContentLoading" theme="outline" size="16" fill="#40bd23" />
<span class="plugin-name">
{{
messageItem.isTextContentLoading
? t('common_module.plugin_in_progress', { pluginName: messageItem.pluginName })
: t('common_module.plugin_executed_successfully', { pluginName: messageItem.pluginName })
}}
</span>
<!-- 插件返回结果 -->
<div v-show="role === 'assistant' && messageItem?.pluginResult?.pluginName" class="plugin-container">
<ExecutePluginRender
:display-format="messageItem.pluginResult?.displayFormat || 'none'"
:name="messageItem.pluginResult?.pluginName!"
:content="messageItem.pluginResult?.pluginContent!"
:arguments="messageItem.pluginResult?.arguments!"
:plugin-loading="messageItem.isTextContentLoading"
/>
</div>
<div v-if="messageItem.isTextContentLoading" class="content-loading">
......@@ -225,15 +224,7 @@ function handleShowReasoningContentSwitch() {
}
.plugin-container {
@apply mb-[8px] flex items-center gap-[5px] font-['Microsoft_YaHei_UI'] text-[#999];
.plugin-loading {
@apply bg-px-plugin_loading-gif h-[14px] w-[14px] bg-contain bg-center bg-no-repeat;
}
.plugin-name {
@apply text-[12px] leading-4;
}
@apply mb-[8px] w-full;
}
.database-container {
......@@ -275,4 +266,18 @@ function handleShowReasoningContentSwitch() {
}
}
}
:deep(.markdown-render-container .markdown-render-inner) {
table {
color: #fff !important;
}
}
:deep(.jv-container.jv-light) {
font-size: 12px;
.jv-code {
padding: 10px 0 0 !important;
}
}
</style>
<script setup lang="ts">
import { computed, nextTick, watch } from 'vue'
import { computed, nextTick, useTemplateRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useElementVisibility } from '@vueuse/core'
import { useScroll } from '@/composables/useScroll'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { useBackBottom } from '@/composables/useBackBottom'
import MessageItem from './message-item.vue'
import ContinueQuestion from './continue-question.vue'
......@@ -26,8 +26,8 @@ defineEmits<{
const { t } = useI18n()
const { scrollRef, scrollToBottom } = useScroll()
const { visible, clickBackBottom, throttleScrollContainer } = useBackBottom(scrollRef, scrollToBottom)
const backBottomBtnFlagRef = useTemplateRef<HTMLDivElement | null>('backBottomBtnFlagRef')
const isNotShowBackBottomBtn = useElementVisibility(backBottomBtnFlagRef)
const continuousQuestionList = defineModel<string[]>('continuousQuestionList', { required: true })
......@@ -52,16 +52,20 @@ defineExpose({
scrollToBottom: handleScrollToBottom,
})
function clickBackBottom() {
scrollToBottom()
}
function handleScrollToBottom() {
nextTick(() => {
!visible.value && scrollToBottom()
isNotShowBackBottomBtn.value && scrollToBottom()
})
}
</script>
<template>
<main ref="scrollRef" class="message-list-container" @scroll="throttleScrollContainer">
<div>
<main ref="scrollRef" class="message-list-container">
<div class="relative">
<MessageItem
v-for="[key, messageItem] in messageList"
:key="key"
......@@ -71,7 +75,6 @@ function handleScrollToBottom() {
@audio-play="() => $emit('audioPlay', messageItem)"
@audio-pause="() => $emit('audioPause')"
/>
</div>
<p v-show="isAnswerResponseLoading" class="answer-response-loading-text">
{{ t('common_module.dialogue_module.do_not_exit_page') }}
......@@ -81,8 +84,11 @@ function handleScrollToBottom() {
<ContinueQuestion v-model:continuous-question-list="continuousQuestionList" :type="'continuous'" />
</div>
<div ref="backBottomBtnFlagRef" class="back-bottom-flag"></div>
</div>
<Transition name="back-bottom-btn" mode="out-in">
<div v-show="visible" class="back-bottom-btn" @click.stop="clickBackBottom">
<div v-show="!isNotShowBackBottomBtn" class="back-bottom-btn" @click.stop="clickBackBottom">
<i class="iconfont icon-huidaodibu back-bottom-btn-icon" />
</div>
</Transition>
......@@ -97,6 +103,10 @@ function handleScrollToBottom() {
@apply my-[7px] ml-[4px] text-[12px] text-[#84868c];
}
.back-bottom-flag {
@apply -z-1 absolute bottom-0 h-[70px] w-full;
}
.back-bottom-btn {
@apply flex-center hover:text-theme-color absolute bottom-[20px] right-[20px] h-[24px] w-[24px] cursor-pointer rounded-full bg-white shadow-[0_0_0_1px_#ededed];
......
......@@ -23,7 +23,6 @@ import {
} from '@/apis/agent-application'
import { useLayoutConfig } from '@/composables/useLayoutConfig'
import { validBrowser } from '@/utils/browser-detection'
import { PluginType } from '@/enums/plugin'
const { t } = useI18n()
......@@ -85,10 +84,6 @@ const isEnableDocumentParse = computed(() => {
return agentApplicationConfig.value.knowledgeConfig.isDocumentParsing === 'Y'
})
const isEnableUploadImage = computed(() => {
return agentApplicationConfig.value.unitIds?.includes(PluginType.IMAGE_OCR) || false
})
const isEnableVoice = computed(() => {
return !!agentApplicationConfig.value.voiceConfig.timbreId
})
......@@ -422,7 +417,6 @@ function handleToLogoutPage() {
:agent-id="agentApplicationConfig.baseInfo.agentId"
:continuous-question-status="continuousQuestionStatus"
:is-enable-document-parse="isEnableDocumentParse"
:is-enable-upload-image="isEnableUploadImage"
:is-enable-voice="isEnableVoice"
:answer-audio-auto-play="answerAudioAutoPlay"
:answer-audio-playing="answerAudioPlaying"
......
......@@ -22,7 +22,6 @@ import { useLayoutConfig } from '@/composables/useLayoutConfig'
import { fetchGetMemberInfoById } from '@/apis/user'
import { UserInfo } from '@/store/types/user'
import { validBrowser } from '@/utils/browser-detection'
import { PluginType } from '@/enums/plugin'
const { t } = useI18n()
......@@ -60,10 +59,6 @@ const isEnableDocumentParse = computed(() => {
return agentApplicationConfig.value.knowledgeConfig.isDocumentParsing === 'Y'
})
const isEnableUploadImage = computed(() => {
return agentApplicationConfig.value.unitIds?.includes(PluginType.IMAGE_OCR) || false
})
const isEnableVoice = computed(() => {
return !!agentApplicationConfig.value.voiceConfig.timbreId
})
......@@ -371,7 +366,6 @@ function handleAudioPause(isClearMessageList = false) {
:agent-id="agentApplicationConfig.baseInfo.agentId"
:continuous-question-status="continuousQuestionStatus"
:is-enable-document-parse="isEnableDocumentParse"
:is-enable-upload-image="isEnableUploadImage"
:is-enable-voice="isEnableVoice"
:answer-audio-auto-play="answerAudioAutoPlay"
:answer-audio-playing="answerAudioPlaying"
......
......@@ -23,10 +23,15 @@ declare interface ConversationMessageItem {
isVoicePlaying: boolean
voiceFragmentUrlList: string[]
isVoiceEnabled?: boolean
pluginName?: string
imageUrl?: string
reasoningContent: string
modelName?: string
knowledgeContentResult: KnowledgeContentResultItem[]
dbChainSQLContent: string
pluginResult?: {
displayFormat: 'json' | 'markdown' | 'none'
pluginName: string
arguments: string
pluginContent: string
}
}
......@@ -154,6 +154,14 @@ declare namespace I18n {
cancel_authorization: string
get_code: string
database: string
no_database_table: string
view_database_table: string
total_database_table: string
add_database_successfully: string
remove_database_successfully: string
database_QA_executed_successfully: string
input_parameter: string
output_parameter: string
dialogue_module: {
continue_question_message: string
......
declare module 'vue-json-viewer'
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