Commit 520e97cc authored by nick zheng's avatar nick zheng

Merge branch 'beta' into 'master'

feat: 支持上传文件进行对话

See merge request !87
parents f3d95013 b9b62616
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
name="viewport" name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/> />
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_4711453_a5ytfgvaagl.css" /> <link rel="stylesheet" href="//at.alicdn.com/t/c/font_4711453_f5muspehl1h.css" />
<title>Model Link</title> <title>Model Link</title>
</head> </head>
......
import { request } from '@/utils/request' import { request } from '@/utils/request'
import { AxiosProgressEvent } from 'axios'
export function fetchUpload<T>(formdata: FormData) { export function fetchUpload<T>(
formdata: FormData,
config?: { onUploadProgress?: (progressEvent: AxiosProgressEvent) => void },
) {
return request.post<T>('/bosRest/upload.json', formdata, { return request.post<T>('/bosRest/upload.json', formdata, {
headers: { 'Content-Type': 'multipart/form-data' }, headers: { 'Content-Type': 'multipart/form-data' },
timeout: 120000, timeout: 120000,
...(config?.onUploadProgress ? { onUploadProgress: config.onUploadProgress } : {}),
}) })
} }
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive, ref } from 'vue' import { onMounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useLayoutConfig } from '@/composables/useLayoutConfig'
const { t } = useI18n() const { t } = useI18n()
const { isMobile } = useLayoutConfig()
const isShowModal = ref(false) const isShowModal = ref(false)
const modalOptions = reactive({ const modalOptions = reactive({
...@@ -45,14 +48,17 @@ function handleShowModal(content: string, title?: string) { ...@@ -45,14 +48,17 @@ function handleShowModal(content: string, title?: string) {
<template> <template>
<n-modal v-model:show="isShowModal"> <n-modal v-model:show="isShowModal">
<div class="min-w-[420px] max-w-[600px] rounded-[10px] bg-[#fff] p-[30px]"> <div
class="rounded-[10px] bg-[#fff]"
:class="isMobile ? 'max-w-[calc(100%-40px)] p-[20px]' : 'min-w-[420px] max-w-[600px] p-[30px]'"
>
<div> <div>
<h2> <h2 class="flex items-baseline">
<i class="iconfont icon-tishi text-[18px] text-[#f25744]"></i> <i class="iconfont icon-tishi text-[18px] text-[#f25744]"></i>
<span class="font-600 ml-[5px] text-[18px]">{{ modalOptions.title || t('common_module.tip') }}</span> <span class="font-600 ml-[5px] text-[18px]">{{ modalOptions.title || t('common_module.tip') }}</span>
</h2> </h2>
<div class="mt-[20px] indent-4 text-[16px]">{{ modalOptions.content }}</div> <div class="mt-[20px] pl-4 text-[16px]">{{ modalOptions.content }}</div>
</div> </div>
<div class="mt-[50px] text-end"> <div class="mt-[50px] text-end">
......
import { reactive, ref } from 'vue'
import { UploadFileInfo } from 'naive-ui'
import { UploadStatus } from '@/enums/upload-status'
import { fetchUpload } from '@/apis/upload'
import { AxiosProgressEvent } from 'axios'
import i18n from '@/locales'
interface FileInfoItem {
id: string
name: string
size: number
status: 'pending' | 'uploading' | 'finished' | 'removed' | 'error'
url: string
percentage: number
type?: string
}
const { t } = i18n.global
export function useDialogueFile() {
const uploadFileList = ref<FileInfoItem[]>([])
function handleLimitUpload(data: { file: UploadFileInfo }) {
const fileType = (data.file.file && data.file.file?.name.split('.')?.pop()?.toLowerCase()) || ''
if (data.file.file && data.file.file?.size === 0) {
window.$message.error(
t('personal_space_module.knowledge_module.upload_document_module.empty_document_content_message'),
)
const fileData = reactive({
id: data.file.id,
name: data.file.name,
status: UploadStatus.ERROR,
size: data.file.file?.size || 0,
type: fileType,
percentage: 0,
url: '',
})
uploadFileList.value = []
uploadFileList.value.push(fileData)
return false
}
if (data.file.file && data.file.file?.size > 10 * 1024 * 1024) {
window.$message.error(
t('personal_space_module.knowledge_module.upload_document_module.upload_size_error_message'),
)
const fileData = reactive({
id: data.file.id,
name: data.file.name,
status: UploadStatus.ERROR,
size: data.file.file?.size || 0,
url: '',
percentage: 0,
type: fileType,
})
uploadFileList.value = []
uploadFileList.value.push(fileData)
return false
}
return true
}
async function handleUpload(file: any) {
const formData = new FormData()
formData.append('file', file.file.file)
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(),
percentage: 0,
url: '',
})
if (uploadFileList.value.length <= 1) {
uploadFileList.value = []
await uploadFileList.value.push(fileData)
fetchUpload(formData, {
onUploadProgress: (progressEvent: AxiosProgressEvent) => {
if (progressEvent.total) {
fileData.percentage = Number(((progressEvent.loaded / progressEvent.total) * 100).toFixed(1))
} else {
fileData.percentage = 0
}
},
})
.then((res) => {
if (res.code === 0) {
fileData.status = UploadStatus.FINISHED
fileData.url = res.data as string
}
})
.catch(() => {
fileData.status = UploadStatus.ERROR
})
}
}
function handleRemoveFile(id: string) {
uploadFileList.value = uploadFileList.value.filter((fileItem) => fileItem.id !== id)
}
return { uploadFileList, handleLimitUpload, handleUpload, handleRemoveFile }
}
export enum UploadStatus {
PENDING = 'pending',
UPLOADING = 'uploading',
FINISHED = 'finished',
REMOVED = 'removed',
ERROR = 'error',
}
...@@ -100,6 +100,9 @@ common_module: ...@@ -100,6 +100,9 @@ common_module:
clear_message_popover_message: 'Clear history session' clear_message_popover_message: 'Clear history session'
clear_message_dialog_title: 'Are you sure you want to clear the conversation?' clear_message_dialog_title: 'Are you sure you want to clear the conversation?'
clear_message_dialog_content: 'Clearing the session will clear all the history of the session in the debug area. Are you sure to clear the session?' clear_message_dialog_content: 'Clearing the session will clear all the history of the session in the debug area. Are you sure to clear the session?'
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'
overwrite_file_tip: 'The newly uploaded file overwrites the original file, whether to continue uploading'
data_table_module: data_table_module:
action: 'Controls' action: 'Controls'
...@@ -216,7 +219,7 @@ personal_space_module: ...@@ -216,7 +219,7 @@ personal_space_module:
topP: 'Top P' topP: 'Top P'
topP_popover_message: 'When the model generates the output, it starts with the words with the highest probability until the total probability of these words accumulates to a value of Top p. This limits the model to choosing only these high-probability terms, thereby controlling the diversity of output content.' topP_popover_message: 'When the model generates the output, it starts with the words with the highest probability until the total probability of these words accumulates to a value of Top p. This limits the model to choosing only these high-probability terms, thereby controlling the diversity of output content.'
temperature: 'Generative randomness' temperature: 'Generative randomness'
temperature_popover_message: 'Used to control the diversity of model outputs. The recommended value is 0, and the larger the value, the greater the difference in the output content of the model推荐值为 0,数值越大,模型每次输出内容的差异性越大' temperature_popover_message: 'Used to control the diversity of model outputs. The recommended value is 0, and the larger the value, the greater the difference in the output content of the model'
communication_turn: 'Refer to session rounds' communication_turn: 'Refer to session rounds'
communication_turn_popover_message: 'The maximum number of session rounds passed into the large model context. The recommended value is 2, the higher the value, the stronger the context correlation in multiple rounds of conversations, but the more Tokens are consumed' communication_turn_popover_message: 'The maximum number of session rounds passed into the large model context. The recommended value is 2, the higher the value, the stronger the context correlation in multiple rounds of conversations, but the more Tokens are consumed'
agent_setting: 'Application setting' agent_setting: 'Application setting'
...@@ -246,6 +249,8 @@ personal_space_module: ...@@ -246,6 +249,8 @@ personal_space_module:
knowledge: 'Knowledge' knowledge: 'Knowledge'
knowledge_base: 'Knowledge base' knowledge_base: 'Knowledge base'
knowledge_base_desc: 'Reference text data, tabular knowledge data (including FAQ questions, multi-column index questions) and web data to achieve knowledge base questions and answers. The application can be associated with a maximum of 5 knowledge bases. Please fill in the detailed description of the knowledge base to improve the accuracy of questions and answers' knowledge_base_desc: 'Reference text data, tabular knowledge data (including FAQ questions, multi-column index questions) and web data to achieve knowledge base questions and answers. The application can be associated with a maximum of 5 knowledge bases. Please fill in the detailed description of the knowledge base to improve the accuracy of questions and answers'
upload_file: 'Upload file'
upload_file_desc: 'Enable the user to upload files for chat, support TXT, MD, PDF, DOC, DOCX format files'
dialogue: 'Dialogue' dialogue: 'Dialogue'
preamble: 'Opening remarks' preamble: 'Opening remarks'
preamble_input_placeholder: 'Please enter an opening statement' preamble_input_placeholder: 'Please enter an opening statement'
......
...@@ -99,6 +99,9 @@ common_module: ...@@ -99,6 +99,9 @@ common_module:
clear_message_popover_message: '清空历史会话' clear_message_popover_message: '清空历史会话'
clear_message_dialog_title: '确认要清空对话吗?' clear_message_dialog_title: '确认要清空对话吗?'
clear_message_dialog_content: '清空对话将清空调试区域所有历史对话内容,确定清空对话吗?' clear_message_dialog_content: '清空对话将清空调试区域所有历史对话内容,确定清空对话吗?'
cancel_associate_file_tip: '不再围绕这个文件回答'
upload_file_limit: '仅支持上传单个文件,支持PDF、DOC、DOCX、MD、TXT格式,最大10MB'
overwrite_file_tip: '新上传的文件会覆盖原有文件,是否继续上传'
data_table_module: data_table_module:
action: '操作' action: '操作'
...@@ -244,6 +247,8 @@ personal_space_module: ...@@ -244,6 +247,8 @@ personal_space_module:
knowledge: '知识' knowledge: '知识'
knowledge_base: '知识库' knowledge_base: '知识库'
knowledge_base_desc: '引用文本数据、表格型知识数据(含FAQ问答,多列索引问答)以及网页数据,实现知识库问答,应用最多可关联5个知识库,请详细填写知识库描述信息以提高问答准确率' knowledge_base_desc: '引用文本数据、表格型知识数据(含FAQ问答,多列索引问答)以及网页数据,实现知识库问答,应用最多可关联5个知识库,请详细填写知识库描述信息以提高问答准确率'
upload_file: '上传文件'
upload_file_desc: '开启后支持用户上传文件进行对话聊天, 支持TXT、MD、PDF、DOC、DOCX格式的文件'
dialogue: '对话' dialogue: '对话'
preamble: '开场白' preamble: '开场白'
preamble_input_placeholder: '请输入开场白' preamble_input_placeholder: '请输入开场白'
......
...@@ -99,6 +99,9 @@ common_module: ...@@ -99,6 +99,9 @@ common_module:
clear_message_popover_message: '清空歷史會話' clear_message_popover_message: '清空歷史會話'
clear_message_dialog_title: '確認要清空對話嗎?' clear_message_dialog_title: '確認要清空對話嗎?'
clear_message_dialog_content: '清空對話將清空調試區域所有歷史對話內容,確定清空對話嗎?' clear_message_dialog_content: '清空對話將清空調試區域所有歷史對話內容,確定清空對話嗎?'
cancel_associate_file_tip: '不再圍繞這個文件回答'
upload_file_limit: '僅支持上傳單個文件,支持PDF、DOC、DOCX、MD、TXT格式,最大10MB'
overwrite_file_tip: '新上傳的文件會覆蓋原有文件,是否繼續上傳'
data_table_module: data_table_module:
action: '操作' action: '操作'
...@@ -244,6 +247,8 @@ personal_space_module: ...@@ -244,6 +247,8 @@ personal_space_module:
knowledge: '知識' knowledge: '知識'
knowledge_base: '知識庫' knowledge_base: '知識庫'
knowledge_base_desc: '引用文本數據、表格型知識數據(含FAQ問答,多列索引問答)以及網頁數據,實現知識庫問答,應用最多可關聯5個知識庫,請詳細填寫知識庫描述信息以提高問答準確率' knowledge_base_desc: '引用文本數據、表格型知識數據(含FAQ問答,多列索引問答)以及網頁數據,實現知識庫問答,應用最多可關聯5個知識庫,請詳細填寫知識庫描述信息以提高問答準確率'
upload_file: '上傳文件'
upload_file_desc: '開啓後支持用户上傳文件進行對話聊天, 支持TXT、MD、PDF、DOC、DOCX格式的文件'
dialogue: '對話' dialogue: '對話'
preamble: '開場白' preamble: '開場白'
preamble_input_placeholder: '請輸入開場白' preamble_input_placeholder: '請輸入開場白'
......
...@@ -24,6 +24,7 @@ export function defaultPersonalAppConfigState(): PersonalAppConfigState { ...@@ -24,6 +24,7 @@ export function defaultPersonalAppConfigState(): PersonalAppConfigState {
}, },
knowledgeConfig: { knowledgeConfig: {
knowledgeIds: [], knowledgeIds: [],
isDocumentParsing: 'N',
}, },
commModelConfig: { commModelConfig: {
largeModel: '文心4.0 (8K)', largeModel: '文心4.0 (8K)',
......
...@@ -28,6 +28,7 @@ export interface PersonalAppConfigState { ...@@ -28,6 +28,7 @@ export interface PersonalAppConfigState {
} }
knowledgeConfig: { knowledgeConfig: {
knowledgeIds: number[] //知识库ID knowledgeIds: number[] //知识库ID
isDocumentParsing: 'Y' | 'N' //是否开启文档解析 Y-开启 N-关闭
} }
commModelConfig: { commModelConfig: {
largeModel: string //大模型 largeModel: string //大模型
......
...@@ -6,12 +6,15 @@ import { throttle } from 'lodash-es' ...@@ -6,12 +6,15 @@ import { throttle } from 'lodash-es'
import CMessage from './c-message' import CMessage from './c-message'
import { MessageItemInterface, MultiModelDialogueItem, QuestionMessageItem } from '../types' import { MessageItemInterface, MultiModelDialogueItem, QuestionMessageItem } from '../types'
import { fetchEventStreamSource } from '../utils/fetch-event-stream-source' import { fetchEventStreamSource } from '../utils/fetch-event-stream-source'
import { UploadStatus } from '@/enums/upload-status'
import { useDialogueFile } from '@/composables/useDialogueFile'
const { t } = useI18n() const { t } = useI18n()
interface Props { interface Props {
agentId: string agentId: string
isAnswerResponseWait: boolean isAnswerResponseWait: boolean
isEnableDocumentParse: boolean
} }
const props = defineProps<Props>() const props = defineProps<Props>()
...@@ -25,6 +28,8 @@ const emit = defineEmits<{ ...@@ -25,6 +28,8 @@ const emit = defineEmits<{
clearAllMessage: [] clearAllMessage: []
}>() }>()
const { uploadFileList, handleLimitUpload, handleUpload, handleRemoveFile } = useDialogueFile()
const multiModelDialogueList = defineModel<MultiModelDialogueItem[]>('multiModelDialogueList', { required: true }) const multiModelDialogueList = defineModel<MultiModelDialogueItem[]>('multiModelDialogueList', { required: true })
const questionContent = ref('') const questionContent = ref('')
...@@ -41,6 +46,18 @@ const isAllowClearAllMessage = computed(() => { ...@@ -41,6 +46,18 @@ const isAllowClearAllMessage = computed(() => {
return multiModelDialogueList.value.some((modelItem) => modelItem.messageList.size > 0) return multiModelDialogueList.value.some((modelItem) => modelItem.messageList.size > 0)
}) })
const isInputMessageDisabled = computed(() => {
return uploadFileList.value.some((fileItem) => fileItem.status !== UploadStatus.FINISHED)
})
const isUploadFileDisabled = computed(() => {
return uploadFileList.value.length === 1
})
const uploadFileIcon = (type: string) => {
return `https://gsst-poe-sit.gz.bcebos.com/icon/${type}.svg`
}
const messageListScrollToBottomThrottle = throttle(() => { const messageListScrollToBottomThrottle = throttle(() => {
emit('messageListScrollToBottom') emit('messageListScrollToBottom')
}, 1000) }, 1000)
...@@ -62,7 +79,7 @@ function messageItemFactory() { ...@@ -62,7 +79,7 @@ function messageItemFactory() {
} }
function handleQuestionSubmit() { function handleQuestionSubmit() {
if (isQuestionSubmitDisabled.value || props.isAnswerResponseWait) return if (isQuestionSubmitDisabled.value || props.isAnswerResponseWait || isInputMessageDisabled.value) return
if (!isAllSelectedModelName.value) { if (!isAllSelectedModelName.value) {
window.$message.warning(t('multi_model_dialogue_module.please_select_model_first')) window.$message.warning(t('multi_model_dialogue_module.please_select_model_first'))
...@@ -116,6 +133,7 @@ function handleQuestionSubmit() { ...@@ -116,6 +133,7 @@ function handleQuestionSubmit() {
path: '/api/rest/agentApplicationInfoRest/preview.json', path: '/api/rest/agentApplicationInfoRest/preview.json',
payload: { payload: {
agentId: props.agentId, agentId: props.agentId,
fileUrls: uploadFileList.value.map((item) => item.url),
messages, messages,
topP, topP,
temperature, temperature,
...@@ -167,12 +185,24 @@ function errorMessageResponse(questionMessageId: string, answerMessageId: string ...@@ -167,12 +185,24 @@ function errorMessageResponse(questionMessageId: string, answerMessageId: string
emit('deleteMessageItem', questionMessageId, modelIndex) emit('deleteMessageItem', questionMessageId, modelIndex)
emit('deleteMessageItem', answerMessageId, modelIndex) emit('deleteMessageItem', answerMessageId, modelIndex)
} }
function handleSelectFile(cb: () => void) {
if (isUploadFileDisabled.value) {
window.$message.ctWarning('', t('common_module.dialogue_module.overwrite_file_tip')).then(() => {
cb()
})
return
}
cb()
}
</script> </script>
<template> <template>
<footer class="flex flex-col items-center"> <footer class="flex flex-col items-center">
<div class="mb-4 flex items-center gap-[18px]"> <div class="mb-4 flex items-end gap-[18px]">
<NPopover trigger="hover"> <div class="flex gap-3">
<n-popover trigger="hover">
<template #trigger> <template #trigger>
<div <div
class="border-inactive-border-color flex h-[54px] w-[54px] shrink-0 items-center justify-center rounded-full border bg-white" class="border-inactive-border-color flex h-[54px] w-[54px] shrink-0 items-center justify-center rounded-full border bg-white"
...@@ -187,19 +217,90 @@ function errorMessageResponse(questionMessageId: string, answerMessageId: string ...@@ -187,19 +217,90 @@ function errorMessageResponse(questionMessageId: string, answerMessageId: string
</div> </div>
</template> </template>
<span class="text-xs"> {{ t('common_module.dialogue_module.clear_message_popover_message') }}</span> <span class="text-xs"> {{ t('common_module.dialogue_module.clear_message_popover_message') }}</span>
</NPopover> </n-popover>
<n-upload
:show-file-list="false"
accept=".doc, .pdf, .docx, .txt, .md"
abstract
@before-upload="handleLimitUpload"
@change="handleUpload"
>
<n-upload-trigger #="{ handleClick }" abstract>
<n-popover style="width: 210px" trigger="hover">
<template #trigger>
<div
v-show="isEnableDocumentParse"
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="handleSelectFile(handleClick)"
>
<i class="iconfont icon-upload text-xl leading-none" />
</div>
</template>
<span class="text-xs"> {{ t('common_module.dialogue_module.upload_file_limit') }} </span>
</n-popover>
</n-upload-trigger>
</n-upload>
</div>
<div class="flex flex-col gap-1">
<ul v-show="uploadFileList.length > 0" class="mb-1.5 grid gap-1.5">
<li
v-for="uploadFileItem in uploadFileList"
:key="uploadFileItem.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'"
>
<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" />
<div class="mx-2.5 flex flex-1 flex-col overflow-hidden">
<n-ellipsis>
{{ uploadFileItem.name }}
</n-ellipsis>
</div>
</div>
<n-progress
v-show="!['finished', 'error'].includes(uploadFileItem.status)"
class="left-13.5 w-[calc(100%-78px)]! absolute bottom-0"
type="line"
rail-color="#F3F3F3"
:height="4"
:percentage="uploadFileItem.percentage"
:show-indicator="false"
/>
<div v-show="['finished', 'error'].includes(uploadFileItem.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)"
/>
</template>
<span>{{ t('common_module.dialogue_module.cancel_associate_file_tip') }}</span>
</n-popover>
</div>
</div>
</li>
</ul>
<div class="relative"> <div class="relative">
<NInput <n-input
v-model:value="questionContent" v-model:value="questionContent"
:placeholder="t('common_module.dialogue_module.question_input_placeholder')" :placeholder="t('common_module.dialogue_module.question_input_placeholder')"
class="rounded-[26px]! w-[725px]! border-[#9EA3FF]! border py-[10px] pl-3 pr-[44px]" :disabled="isInputMessageDisabled"
class="rounded-theme! w-[725px]! border-[#9EA3FF]! border py-[10px] pl-3 pr-[44px]"
@keydown.enter="handleQuestionSubmit" @keydown.enter="handleQuestionSubmit"
/> />
<i <i
class="iconfont icon-send-icon absolute right-6 top-[18px] text-xl leading-none" class="iconfont icon-send-icon absolute right-6 top-[18px] text-xl leading-none"
:class=" :class="
isQuestionSubmitDisabled || isAnswerResponseWait isQuestionSubmitDisabled || isAnswerResponseWait || isInputMessageDisabled
? 'text-hover-theme-color cursor-not-allowed' ? 'text-hover-theme-color cursor-not-allowed'
: 'text-theme-color cursor-pointer hover:opacity-80' : 'text-theme-color cursor-pointer hover:opacity-80'
" "
...@@ -207,6 +308,7 @@ function errorMessageResponse(questionMessageId: string, answerMessageId: string ...@@ -207,6 +308,7 @@ function errorMessageResponse(questionMessageId: string, answerMessageId: string
/> />
</div> </div>
</div> </div>
</div>
<span class="text-gray-font-color select-none"> <span class="text-gray-font-color select-none">
{{ t('common_module.dialogue_module.generate_warning_message') }} {{ t('common_module.dialogue_module.generate_warning_message') }}
</span> </span>
......
...@@ -45,6 +45,10 @@ const isHasMessageList = computed(() => { ...@@ -45,6 +45,10 @@ const isHasMessageList = computed(() => {
return multiModelDialogueList.value.some((modelItem) => modelItem.messageList.size > 0) return multiModelDialogueList.value.some((modelItem) => modelItem.messageList.size > 0)
}) })
const isEnableDocumentParse = computed(() => {
return agentApplicationConfig.value.knowledgeConfig.isDocumentParsing === 'Y'
})
onMounted(async () => { onMounted(async () => {
if (!currentRoute.params.agentId) { if (!currentRoute.params.agentId) {
window.$message.warning(t('multi_model_dialogue_module.not_find_agent')) window.$message.warning(t('multi_model_dialogue_module.not_find_agent'))
...@@ -320,6 +324,7 @@ function handleBlockMessageResponse() { ...@@ -320,6 +324,7 @@ function handleBlockMessageResponse() {
v-model:multi-model-dialogue-list="multiModelDialogueList" v-model:multi-model-dialogue-list="multiModelDialogueList"
:agent-id="agentId" :agent-id="agentId"
:is-answer-response-wait="isAnswerResponseWait" :is-answer-response-wait="isAnswerResponseWait"
:is-enable-document-parse="isEnableDocumentParse"
@add-question-message-item="handleAddQuestionMessageItem" @add-question-message-item="handleAddQuestionMessageItem"
@add-answer-message-item="handleAddAnswerMessageItem" @add-answer-message-item="handleAddAnswerMessageItem"
@update-message-item="handleUpdateSpecifyMessageItem" @update-message-item="handleUpdateSpecifyMessageItem"
......
...@@ -38,6 +38,8 @@ const isHoverKnowledgeItem = computed(() => (kdId: number) => { ...@@ -38,6 +38,8 @@ const isHoverKnowledgeItem = computed(() => (kdId: number) => {
return hoverKdId.value === kdId return hoverKdId.value === kdId
}) })
const isOpenDocumentParsing = computed(() => knowledgeConfig.value.isDocumentParsing === 'Y')
async function handleGetKnowledgeListByIds() { async function handleGetKnowledgeListByIds() {
const res = await fetchGetKnowledgeListByKdIds<KnowledgeItem[]>(knowledgeConfig.value.knowledgeIds) const res = await fetchGetKnowledgeListByKdIds<KnowledgeItem[]>(knowledgeConfig.value.knowledgeIds)
...@@ -81,6 +83,10 @@ function handleCloseAssociatedKnowledgeModal() { ...@@ -81,6 +83,10 @@ function handleCloseAssociatedKnowledgeModal() {
knowledgeConfigExpandedNames.value = ['knowledge'] knowledgeConfigExpandedNames.value = ['knowledge']
handleGetKnowledgeListByIds() handleGetKnowledgeListByIds()
} }
function handleUpdateDocumentParsing(value: boolean) {
knowledgeConfig.value.isDocumentParsing = value ? 'Y' : 'N'
}
</script> </script>
<template> <template>
...@@ -156,6 +162,28 @@ function handleCloseAssociatedKnowledgeModal() { ...@@ -156,6 +162,28 @@ function handleCloseAssociatedKnowledgeModal() {
{{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.knowledge_base_desc') }} {{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.knowledge_base_desc') }}
</span> </span>
</NCollapseItem> </NCollapseItem>
<NCollapseItem name="uploadFile" class="my-[13px]!">
<template #header>
<span class="mr-[5px] min-w-[60px]">
{{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.upload_file') }}
</span>
</template>
<template #header-extra>
<n-switch :value="isOpenDocumentParsing" size="small" @update:value="handleUpdateDocumentParsing">
<template #checked> {{ t('common_module.open') }} </template>
<template #unchecked> {{ t('common_module.close') }} </template>
</n-switch>
</template>
<div>
<div class="text-xs text-[#84868c]">
{{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.upload_file_desc') }}
</div>
<div class="flex flex-1 flex-wrap items-center gap-[12px] overflow-hidden"></div>
</div>
</NCollapseItem>
</NCollapse> </NCollapse>
</div> </div>
</section> </section>
......
...@@ -62,16 +62,15 @@ function handleUpdatePageScroll() { ...@@ -62,16 +62,15 @@ function handleUpdatePageScroll() {
} }
function handleClearAllMessage() { function handleClearAllMessage() {
window.$dialog.warning({ window.$message
title: t('common_module.dialogue_module.clear_message_dialog_title'), .ctWarning(
content: t('common_module.dialogue_module.clear_message_dialog_content'), t('common_module.dialogue_module.clear_message_dialog_content'),
negativeText: t('common_module.cancel_btn_text'), t('common_module.dialogue_module.clear_message_dialog_title'),
positiveText: t('common_module.confirm_btn_text'), )
onPositiveClick: () => { .then(() => {
footerInputRef.value?.blockMessageResponse() footerInputRef.value?.blockMessageResponse()
messageList.value = [] messageList.value = []
window.$message.success(t('common_module.clear_success_message')) window.$message.success(t('common_module.clear_success_message'))
},
}) })
} }
......
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject, onMounted, onUnmounted, ref } from 'vue' import { computed, inject, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue'
import { Emitter } from 'mitt' import { Emitter } from 'mitt'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import OverwriteMessageTipModal from './overwrite-message-tip-modal.vue'
import { fetchCustomEventSource } from '@/composables/useEventSource' import { fetchCustomEventSource } from '@/composables/useEventSource'
import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config' import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config'
import { UploadStatus } from '@/enums/upload-status'
import { useDialogueFile } from '@/composables/useDialogueFile'
interface Props { interface Props {
messageList: ConversationMessageItem[] messageList: ConversationMessageItem[]
...@@ -26,10 +29,13 @@ const emit = defineEmits<{ ...@@ -26,10 +29,13 @@ const emit = defineEmits<{
const personalAppConfigStore = usePersonalAppConfigStore() const personalAppConfigStore = usePersonalAppConfigStore()
const { uploadFileList, handleLimitUpload, handleUpload, handleRemoveFile } = useDialogueFile()
const messageTipModalRef = useTemplateRef<InstanceType<typeof OverwriteMessageTipModal>>('messageTipModalRef')
const emitter = inject<Emitter<MittEvents>>('emitter') const emitter = inject<Emitter<MittEvents>>('emitter')
const inputMessageContent = ref('') const inputMessageContent = ref('')
const isAnswerResponseWait = ref(false) const isAnswerResponseWait = ref(false)
let controller: AbortController | null = null let controller: AbortController | null = null
...@@ -50,6 +56,29 @@ const isSendBtnDisabled = computed(() => { ...@@ -50,6 +56,29 @@ const isSendBtnDisabled = computed(() => {
return !inputMessageContent.value.trim() return !inputMessageContent.value.trim()
}) })
const isInputMessageDisabled = computed(() => {
return uploadFileList.value.some((fileItem) => fileItem.status !== UploadStatus.FINISHED)
})
const isEnableDocumentParse = computed(() => {
return personalAppConfigStore.knowledgeConfig.isDocumentParsing === 'Y'
})
const isUploadFileDisabled = computed(() => {
return uploadFileList.value.length === 1
})
const uploadFileIcon = (type: string) => {
return `https://gsst-poe-sit.gz.bcebos.com/icon/${type}.svg`
}
watch(
() => isEnableDocumentParse.value,
() => {
uploadFileList.value = []
},
)
onUnmounted(() => { onUnmounted(() => {
blockMessageResponse() blockMessageResponse()
emitter?.off('selectQuestion') emitter?.off('selectQuestion')
...@@ -73,18 +102,8 @@ function messageItemFactory() { ...@@ -73,18 +102,8 @@ function messageItemFactory() {
} as const } as const
} }
function handleEnterKeypress(event: KeyboardEvent) {
if (event.code === 'Enter' && !event.shiftKey) {
event.preventDefault()
if (!inputMessageContent.value.trim() || isAnswerResponseWait.value) return ''
handleMessageSend()
}
}
function handleMessageSend() { function handleMessageSend() {
if (!inputMessageContent.value.trim() || isAnswerResponseWait.value) return '' if (!inputMessageContent.value.trim() || isAnswerResponseWait.value || isInputMessageDisabled.value) return ''
const messages: { const messages: {
content: { content: {
...@@ -136,6 +155,7 @@ function handleMessageSend() { ...@@ -136,6 +155,7 @@ function handleMessageSend() {
path: '/api/rest/agentApplicationInfoRest/preview.json', path: '/api/rest/agentApplicationInfoRest/preview.json',
payload: { payload: {
agentId: agentId.value, agentId: agentId.value,
fileUrls: uploadFileList.value.map((item) => item.url),
messages, messages,
}, },
controller, controller,
...@@ -195,6 +215,17 @@ function blockMessageResponse() { ...@@ -195,6 +215,17 @@ function blockMessageResponse() {
isAnswerResponseWait.value = false isAnswerResponseWait.value = false
} }
function handleSelectFile(cb: () => void) {
if (isUploadFileDisabled.value) {
messageTipModalRef.value?.handleShowModal().then(() => {
cb()
})
return
}
cb()
}
defineExpose({ defineExpose({
blockMessageResponse, blockMessageResponse,
}) })
...@@ -202,45 +233,121 @@ defineExpose({ ...@@ -202,45 +233,121 @@ defineExpose({
<template> <template>
<div class="mb-3 mt-5 px-5"> <div class="mb-3 mt-5 px-5">
<div class="flex"> <div class="flex items-end gap-2.5">
<div class="mr-2 flex h-8 w-8 items-center justify-center"> <div class="flex-1">
<NPopover trigger="hover"> <ul v-show="uploadFileList.length > 0" class="mb-1.5 grid gap-1.5">
<li
v-for="uploadFileItem in uploadFileList"
:key="uploadFileItem.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'"
>
<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" />
<div class="mx-2.5 flex flex-1 flex-col overflow-hidden">
<n-ellipsis>
{{ uploadFileItem.name }}
</n-ellipsis>
</div>
</div>
<n-progress
v-show="!['finished', 'error'].includes(uploadFileItem.status)"
class="left-13.5 w-[calc(100%-78px)]! absolute bottom-0"
type="line"
rail-color="#F3F3F3"
:height="4"
:percentage="uploadFileItem.percentage"
:show-indicator="false"
/>
<div v-show="['finished', 'error'].includes(uploadFileItem.status)" class="hidden group-hover:block">
<n-popover trigger="hover" placement="top-end" :show-arrow="false">
<template #trigger> <template #trigger>
<i <i
class="iconfont icon-clear text-base leading-none" class="iconfont icon-close cursor-pointer hover:opacity-80"
:class=" :class="uploadFileItem.status === 'error' ? 'text-error-font-color' : 'text-font-color'"
isAllowClearMessage @click="handleRemoveFile(uploadFileItem.id)"
? 'hover:text-theme-color cursor-pointer text-[#5c5f66]'
: 'cursor-not-allowed text-[#b8babf]'
"
@click="handleClearAllMessage"
/> />
</template> </template>
<span class="text-xs"> <span>{{ t('common_module.dialogue_module.cancel_associate_file_tip') }}</span>
{{ t('common_module.dialogue_module.clear_message_popover_message') }} </n-popover>
</span> </div>
</NPopover>
</div> </div>
</li>
</ul>
<div class="relative flex-1"> <div class="relative flex-1">
<NInput <n-input
v-model:value="inputMessageContent" v-model:value="inputMessageContent"
:placeholder="t('common_module.dialogue_module.question_input_placeholder')" :placeholder="t('common_module.dialogue_module.question_input_placeholder')"
:disabled="isInputMessageDisabled"
class="rounded-xl! shadow-[0_1px_#09122105,0_1px_1px_#09122105,0_3px_3px_#09122103,0_9px_9px_#09122103]! py-[4px] pr-[50px]" class="rounded-xl! shadow-[0_1px_#09122105,0_1px_1px_#09122105,0_3px_3px_#09122103,0_9px_9px_#09122103]! py-[4px] pr-[50px]"
@keypress="handleEnterKeypress" @keydown.enter="handleMessageSend"
/> />
<div <div
class="bg-px-send-png absolute bottom-2 right-[20px] h-[24px] w-[24px]" class="bg-px-send-png absolute bottom-2 right-[20px] h-[24px] w-[24px]"
:class="isSendBtnDisabled || isAnswerResponseWait ? 'opacity-60' : 'cursor-pointer'" :class="
isSendBtnDisabled || isAnswerResponseWait || isInputMessageDisabled ? 'opacity-60' : 'cursor-pointer'
"
@click="handleMessageSend" @click="handleMessageSend"
/> />
</div> </div>
</div> </div>
<div class="mt-[9px] pl-10"> <n-upload
:show-file-list="false"
accept=".doc, .pdf, .docx, .txt, .md"
abstract
@before-upload="handleLimitUpload"
@change="handleUpload"
>
<n-upload-trigger #="{ handleClick }" abstract>
<n-popover style="width: 210px" trigger="hover">
<template #trigger>
<div
v-show="isEnableDocumentParse"
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="handleSelectFile(handleClick)"
>
<i class="iconfont icon-upload flex h-4 w-4 items-center justify-center" />
</div>
</template>
<span class="text-xs"> {{ t('common_module.dialogue_module.upload_file_limit') }} </span>
</n-popover>
</n-upload-trigger>
</n-upload>
<n-popover trigger="hover">
<template #trigger>
<div
class="h-7.5 w-7.5 mb-1 flex cursor-pointer items-center justify-center rounded-full bg-white"
@click="handleClearAllMessage"
>
<i
class="iconfont icon-clear text-base leading-none"
:class="
isAllowClearMessage ? 'hover:text-theme-color text-font-color' : 'cursor-not-allowed text-[#b8babf]'
"
/>
</div>
</template>
<span class="text-xs">
{{ t('common_module.dialogue_module.clear_message_popover_message') }}
</span>
</n-popover>
</div>
<div class="mt-[9px] pl-2">
<span class="text-xs text-[#84868c]"> <span class="text-xs text-[#84868c]">
{{ t('common_module.dialogue_module.generate_warning_message') }} {{ t('common_module.dialogue_module.generate_warning_message') }}
</span> </span>
</div> </div>
</div> </div>
<OverwriteMessageTipModal ref="messageTipModalRef" />
</template> </template>
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const isShowModal = ref(false)
const pageWidth = ref(window.innerWidth)
const modalOptions = reactive({
title: t('common_module.dialogue_module.overwrite_file_tip'),
content: '',
})
let _modalStatusResolve = (_value?: any) => {}
let _modalStatusReject = (_reason?: any) => {}
const modalFixRight = computed(() => {
return ((pageWidth.value / 5) * 2 - 443) / 2
})
watch(
() => isShowModal.value,
() => {
pageWidth.value = window.innerWidth
},
)
function handleCancel() {
isShowModal.value = false
_modalStatusReject(new Error('cancel show modal'))
}
function handleConfirm() {
isShowModal.value = false
_modalStatusResolve(true)
}
function handleShowModal() {
isShowModal.value = true
return new Promise<boolean | Error>((resolve, reject) => {
_modalStatusResolve = resolve
_modalStatusReject = reject
})
}
defineExpose({
handleShowModal,
})
</script>
<template>
<n-modal v-model:show="isShowModal">
<div class="fixed! w-[443px] rounded-[10px] bg-[#fff] p-[30px]" :style="{ right: modalFixRight + 'px' }">
<div>
<h2 class="flex items-baseline">
<i class="iconfont icon-tishi text-[18px] text-[#f25744]"></i>
<span class="font-600 ml-[5px] text-[18px]">{{ modalOptions.title || t('common_module.tip') }}</span>
</h2>
<div class="mt-[20px] pl-4 text-[16px]">{{ modalOptions.content }}</div>
</div>
<div class="mt-[50px] text-end">
<n-button color="#F5F5F5" round class="!px-[34px] !py-[10px] !text-[14px] !text-[#333]" @click="handleCancel">
{{ t('common_module.cancel_btn_text') }}
</n-button>
<n-button color="#6F77FF" round class="!ml-[12px] !px-[34px] !py-[10px] !text-[14px]" @click="handleConfirm">
{{ t('common_module.confirm_btn_text') }}
</n-button>
</div>
</div>
</n-modal>
</template>
...@@ -4,6 +4,8 @@ import { Emitter } from 'mitt' ...@@ -4,6 +4,8 @@ import { Emitter } from 'mitt'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { fetchCustomEventSource } from '@/composables/useEventSource' import { fetchCustomEventSource } from '@/composables/useEventSource'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { UploadStatus } from '@/enums/upload-status'
import { useDialogueFile } from '@/composables/useDialogueFile'
import { useLayoutConfig } from '@/composables/useLayoutConfig' import { useLayoutConfig } from '@/composables/useLayoutConfig'
interface Props { interface Props {
...@@ -11,6 +13,7 @@ interface Props { ...@@ -11,6 +13,7 @@ interface Props {
dialogsId: string dialogsId: string
messageList: ConversationMessageItem[] messageList: ConversationMessageItem[]
continuousQuestionStatus: 'default' | 'close' continuousQuestionStatus: 'default' | 'close'
isEnableDocumentParse: boolean
} }
const { t } = useI18n() const { t } = useI18n()
...@@ -32,6 +35,8 @@ const { isMobile } = useLayoutConfig() ...@@ -32,6 +35,8 @@ const { isMobile } = useLayoutConfig()
const userStore = useUserStore() const userStore = useUserStore()
const { uploadFileList, handleLimitUpload, handleUpload, handleRemoveFile } = useDialogueFile()
const emitter = inject<Emitter<MittEvents>>('emitter') const emitter = inject<Emitter<MittEvents>>('emitter')
const inputMessageContent = ref('') const inputMessageContent = ref('')
...@@ -60,6 +65,18 @@ const isCreateContinueQuestions = computed(() => { ...@@ -60,6 +65,18 @@ const isCreateContinueQuestions = computed(() => {
return props.continuousQuestionStatus === 'default' return props.continuousQuestionStatus === 'default'
}) })
const isInputMessageDisabled = computed(() => {
return uploadFileList.value.some((fileItem) => fileItem.status !== UploadStatus.FINISHED)
})
const isUploadFileDisabled = computed(() => {
return uploadFileList.value.length === 1
})
const uploadFileIcon = (type: string) => {
return `https://gsst-poe-sit.gz.bcebos.com/icon/${type}.svg`
}
onMounted(() => { onMounted(() => {
emitter?.on('selectQuestion', (featuredQuestion) => { emitter?.on('selectQuestion', (featuredQuestion) => {
if (!isLogin.value) { if (!isLogin.value) {
...@@ -88,23 +105,13 @@ function messageItemFactory() { ...@@ -88,23 +105,13 @@ function messageItemFactory() {
} as const } as const
} }
function handleEnterKeypress(event: KeyboardEvent) {
if (event.code === 'Enter' && !event.shiftKey) {
event.preventDefault()
if (!inputMessageContent.value.trim() || isAnswerResponseWait.value) return ''
handleMessageSend()
}
}
function handleMessageSend() { function handleMessageSend() {
if (!isLogin.value) { if (!isLogin.value) {
window.$message.warning(t('common_module.not_login_text')) window.$message.warning(t('common_module.not_login_text'))
return return
} }
if (!inputMessageContent.value.trim() || isAnswerResponseWait.value) return '' if (!inputMessageContent.value.trim() || isAnswerResponseWait.value || isInputMessageDisabled.value) return ''
emit('resetContinueQuestionList') emit('resetContinueQuestionList')
emit('addMessageItem', { ...messageItemFactory(), textContent: inputMessageContent.value }) emit('addMessageItem', { ...messageItemFactory(), textContent: inputMessageContent.value })
...@@ -131,6 +138,7 @@ function handleMessageSend() { ...@@ -131,6 +138,7 @@ function handleMessageSend() {
payload: { payload: {
agentId: props.agentId, agentId: props.agentId,
dialogsId: props.dialogsId, dialogsId: props.dialogsId,
fileUrls: uploadFileList.value.map((item) => item.url),
input, input,
}, },
controller, controller,
...@@ -194,6 +202,17 @@ function handleToLogin() { ...@@ -194,6 +202,17 @@ function handleToLogin() {
emit('toLogin') emit('toLogin')
} }
function handleSelectFile(cb: () => void) {
if (isUploadFileDisabled.value) {
window.$message.ctWarning('', t('common_module.dialogue_module.overwrite_file_tip')).then(() => {
cb()
})
return
}
cb()
}
defineExpose({ defineExpose({
blockMessageResponse, blockMessageResponse,
}) })
...@@ -201,35 +220,70 @@ defineExpose({ ...@@ -201,35 +220,70 @@ defineExpose({
<template> <template>
<div class="my-5"> <div class="my-5">
<div class="flex items-center"> <div class="flex items-end gap-2.5">
<div class="mr-2 flex items-center justify-center" :class="isMobile ? 'h-6 w-6' : 'h-8 w-8'"> <div class="flex flex-1 flex-col">
<NPopover trigger="hover"> <ul v-show="uploadFileList.length > 0" class="mb-1.5 grid gap-1.5">
<li
v-for="uploadFileItem in uploadFileList"
:key="uploadFileItem.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'"
>
<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" />
<div class="mx-2.5 flex flex-1 flex-col overflow-hidden">
<n-ellipsis>
{{ uploadFileItem.name }}
</n-ellipsis>
</div>
</div>
<n-progress
v-show="!['finished', 'error'].includes(uploadFileItem.status)"
class="left-13.5 w-[calc(100%-78px)]! absolute bottom-0"
type="line"
rail-color="#F3F3F3"
:height="4"
:percentage="uploadFileItem.percentage"
:show-indicator="false"
/>
<div
v-show="['finished', 'error'].includes(uploadFileItem.status)"
class="group-hover:block"
:class="isMobile ? 'block' : 'hidden'"
>
<n-popover trigger="hover" placement="top-end" :show-arrow="false">
<template #trigger> <template #trigger>
<i <i
class="iconfont icon-clear text-base leading-none" class="iconfont icon-close cursor-pointer hover:opacity-80"
:class=" :class="uploadFileItem.status === 'error' ? 'text-error-font-color' : 'text-font-color'"
isAllowClearMessage @click="handleRemoveFile(uploadFileItem.id)"
? 'hover:text-theme-color cursor-pointer text-[#5c5f66]'
: 'cursor-not-allowed text-[#b8babf]'
"
@click="handleClearAllMessage"
/> />
</template> </template>
<span class="text-xs"> {{ t('common_module.dialogue_module.clear_message_popover_message') }}</span> <span>{{ t('common_module.dialogue_module.cancel_associate_file_tip') }}</span>
</NPopover> </n-popover>
</div> </div>
</div>
</li>
</ul>
<div class="relative flex-1"> <div class="relative flex-1">
<NInput <n-input
v-model:value="inputMessageContent" v-model:value="inputMessageContent"
:placeholder="inputPlaceholder" :placeholder="inputPlaceholder"
:disabled="!isLogin" :disabled="!isLogin || isInputMessageDisabled"
class="rounded-xl! shadow-[0_1px_#09122105,0_1px_1px_#09122105,0_3px_3px_#09122103,0_9px_9px_#09122103]! py-[4px] pr-[50px]" class="rounded-xl! shadow-[0_1px_#09122105,0_1px_1px_#09122105,0_3px_3px_#09122103,0_9px_9px_#09122103]! py-[4px] pr-[50px]"
@keypress="handleEnterKeypress" @keydown.enter="handleMessageSend"
/> />
<div <div
class="bg-px-send-png absolute bottom-2 right-[20px] h-[24px] w-[24px]" class="bg-px-send-png absolute bottom-2 right-[20px] h-[24px] w-[24px]"
:class="isSendBtnDisabled || isAnswerResponseWait || !isLogin ? 'opacity-60' : 'cursor-pointer'" :class="
isSendBtnDisabled || isAnswerResponseWait || !isLogin || isInputMessageDisabled
? 'opacity-60'
: 'cursor-pointer'
"
@click="handleMessageSend" @click="handleMessageSend"
/> />
...@@ -243,6 +297,53 @@ defineExpose({ ...@@ -243,6 +297,53 @@ defineExpose({
</div> </div>
</div> </div>
<n-upload
:show-file-list="false"
accept=".doc, .pdf, .docx, .txt, .md"
:disabled="!isLogin"
abstract
@before-upload="handleLimitUpload"
@change="handleUpload"
>
<n-upload-trigger #="{ handleClick }" abstract>
<n-popover style="width: 210px" trigger="hover">
<template #trigger>
<div
v-show="isEnableDocumentParse"
class="h-7.5 w-7.5 mb-1 flex items-center justify-center rounded-full bg-white"
:class="
isLogin
? 'hover:text-theme-color text-font-color cursor-pointer'
: 'cursor-not-allowed text-[#b8babf]'
"
@click="handleSelectFile(handleClick)"
>
<i class="iconfont icon-upload flex h-4 w-4 items-center justify-center" />
</div>
</template>
<span class="text-xs"> {{ t('common_module.dialogue_module.upload_file_limit') }} </span>
</n-popover>
</n-upload-trigger>
</n-upload>
<n-popover trigger="hover">
<template #trigger>
<div
class="h-7.5 w-7.5 mb-1 flex items-center justify-center rounded-full bg-white"
:class="
isAllowClearMessage
? 'hover:text-theme-color text-font-color cursor-pointer'
: 'cursor-not-allowed text-[#b8babf]'
"
>
<i class="iconfont icon-clear text-base leading-none" @click="handleClearAllMessage" />
</div>
</template>
<span class="text-xs"> {{ t('common_module.dialogue_module.clear_message_popover_message') }}</span>
</n-popover>
</div>
<div class="mt-[9px]"> <div class="mt-[9px]">
<span class="flex w-full justify-center text-xs text-[#b8babf]"> <span class="flex w-full justify-center text-xs text-[#b8babf]">
{{ t('common_module.dialogue_module.generate_warning_message') }} {{ t('common_module.dialogue_module.generate_warning_message') }}
......
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import PageHeader from './components/mobile-page-header.vue' import PageHeader from './components/mobile-page-header.vue'
...@@ -34,6 +34,10 @@ const messageList = ref<ConversationMessageItem[]>([]) ...@@ -34,6 +34,10 @@ const messageList = ref<ConversationMessageItem[]>([])
const continuousQuestionStatus = ref<'default' | 'close'>('default') const continuousQuestionStatus = ref<'default' | 'close'>('default')
const continueQuestionList = ref<string[]>([]) const continueQuestionList = ref<string[]>([])
const isEnableDocumentParse = computed(() => {
return agentApplicationConfig.value.knowledgeConfig.isDocumentParsing === 'Y'
})
onMounted(async () => { onMounted(async () => {
if (router.currentRoute.value.params.agentId) { if (router.currentRoute.value.params.agentId) {
agentId.value = router.currentRoute.value.params.agentId as string agentId.value = router.currentRoute.value.params.agentId as string
...@@ -113,16 +117,15 @@ function handleUpdatePageScroll() { ...@@ -113,16 +117,15 @@ function handleUpdatePageScroll() {
} }
function handleClearAllMessage() { function handleClearAllMessage() {
window.$dialog.warning({ window.$message
title: t('common_module.dialogue_module.clear_message_dialog_title'), .ctWarning(
content: t('common_module.dialogue_module.clear_message_dialog_content'), t('common_module.dialogue_module.clear_message_dialog_content'),
negativeText: t('common_module.cancel_btn_text'), t('common_module.dialogue_module.clear_message_dialog_title'),
positiveText: t('common_module.confirm_btn_text'), )
onPositiveClick: () => { .then(() => {
footerInputRef.value?.blockMessageResponse() footerInputRef.value?.blockMessageResponse()
messageList.value = [] messageList.value = []
window.$message.success(t('common_module.clear_success_message')) window.$message.success(t('common_module.clear_success_message'))
},
}) })
} }
...@@ -172,6 +175,7 @@ function handleResetContinueQuestionList() { ...@@ -172,6 +175,7 @@ function handleResetContinueQuestionList() {
:dialogs-id="dialogsId" :dialogs-id="dialogsId"
:agent-id="agentApplicationConfig.baseInfo.agentId" :agent-id="agentApplicationConfig.baseInfo.agentId"
:continuous-question-status="continuousQuestionStatus" :continuous-question-status="continuousQuestionStatus"
:is-enable-document-parse="isEnableDocumentParse"
@add-message-item="handleAddMessageItem" @add-message-item="handleAddMessageItem"
@update-specify-message-item="handleUpdateSpecifyMessageItem" @update-specify-message-item="handleUpdateSpecifyMessageItem"
@delete-last-message-item="handleDeleteLastMessageItem" @delete-last-message-item="handleDeleteLastMessageItem"
......
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import PageHeader from './components/web-page-header.vue' import PageHeader from './components/web-page-header.vue'
...@@ -37,6 +37,10 @@ const messageList = ref<ConversationMessageItem[]>([]) ...@@ -37,6 +37,10 @@ const messageList = ref<ConversationMessageItem[]>([])
const continuousQuestionStatus = ref<'default' | 'close'>('default') const continuousQuestionStatus = ref<'default' | 'close'>('default')
const continueQuestionList = ref<string[]>([]) const continueQuestionList = ref<string[]>([])
const isEnableDocumentParse = computed(() => {
return agentApplicationConfig.value.knowledgeConfig.isDocumentParsing === 'Y'
})
onMounted(async () => { onMounted(async () => {
if (router.currentRoute.value.params.agentId) { if (router.currentRoute.value.params.agentId) {
agentId.value = router.currentRoute.value.params.agentId as string agentId.value = router.currentRoute.value.params.agentId as string
...@@ -133,16 +137,15 @@ function handleUpdatePageScroll() { ...@@ -133,16 +137,15 @@ function handleUpdatePageScroll() {
} }
function handleClearAllMessage() { function handleClearAllMessage() {
window.$dialog.warning({ window.$message
title: t('common_module.dialogue_module.clear_message_dialog_title'), .ctWarning(
content: t('common_module.dialogue_module.clear_message_dialog_content'), t('common_module.dialogue_module.clear_message_dialog_content'),
negativeText: t('common_module.cancel_btn_text'), t('common_module.dialogue_module.clear_message_dialog_title'),
positiveText: t('common_module.confirm_btn_text'), )
onPositiveClick: () => { .then(() => {
footerInputRef.value?.blockMessageResponse() footerInputRef.value?.blockMessageResponse()
messageList.value = [] messageList.value = []
window.$message.success(t('common_module.clear_success_message')) window.$message.success(t('common_module.clear_success_message'))
},
}) })
} }
...@@ -210,6 +213,7 @@ function handleResetContinueQuestionList() { ...@@ -210,6 +213,7 @@ function handleResetContinueQuestionList() {
:dialogs-id="dialogsId" :dialogs-id="dialogsId"
:agent-id="agentApplicationConfig.baseInfo.agentId" :agent-id="agentApplicationConfig.baseInfo.agentId"
:continuous-question-status="continuousQuestionStatus" :continuous-question-status="continuousQuestionStatus"
:is-enable-document-parse="isEnableDocumentParse"
@add-message-item="handleAddMessageItem" @add-message-item="handleAddMessageItem"
@update-specify-message-item="handleUpdateSpecifyMessageItem" @update-specify-message-item="handleUpdateSpecifyMessageItem"
@delete-last-message-item="handleDeleteLastMessageItem" @delete-last-message-item="handleDeleteLastMessageItem"
......
...@@ -100,6 +100,9 @@ declare namespace I18n { ...@@ -100,6 +100,9 @@ declare namespace I18n {
clear_message_popover_message: string clear_message_popover_message: string
clear_message_dialog_title: string clear_message_dialog_title: string
clear_message_dialog_content: string clear_message_dialog_content: string
cancel_associate_file_tip: string
upload_file_limit: string
overwrite_file_tip: string
} }
data_table_module: { data_table_module: {
...@@ -246,6 +249,8 @@ declare namespace I18n { ...@@ -246,6 +249,8 @@ declare namespace I18n {
knowledge: string knowledge: string
knowledge_base: string knowledge_base: string
knowledge_base_desc: string knowledge_base_desc: string
upload_file: string
upload_file_desc: string
dialogue: string dialogue: string
preamble: string preamble: string
preamble_input_placeholder: string preamble_input_placeholder: string
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment