Commit a4cf1c15 authored by tyyin lan's avatar tyyin lan

feat(首页聊天): 支持文件上传

parent 1d423a43
import { request } from '@/utils/request' import { request } from '@/utils/request'
import type { AxiosProgressEvent } from 'axios'
export function fetchAgentApplicationSelectList<T>() { export function fetchAgentApplicationSelectList<T>() {
return request.post<T>('/agentApplicationRest/getDefaultList.json', { return request.post<T>('/agentApplicationRest/getDefaultList.json', {
...@@ -28,3 +29,17 @@ export function fetchMessageRecordList<T>(dialogueId: string) { ...@@ -28,3 +29,17 @@ export function fetchMessageRecordList<T>(dialogueId: string) {
timeout: 12000, timeout: 12000,
}) })
} }
export function fetchFileUpload<T>(
formData: FormData,
config: { onUploadProgress: (progressEvent?: AxiosProgressEvent) => void; signal?: AbortSignal } = {
onUploadProgress: () => {},
},
) {
return request.post<T>(`/bosRest/upload.json`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 0,
onUploadProgress: config.onUploadProgress,
signal: config.signal,
})
}
...@@ -462,3 +462,6 @@ personal_settings_module: ...@@ -462,3 +462,6 @@ personal_settings_module:
please_enter_the_correct_verification_code: 'Please enter the correct verification code' please_enter_the_correct_verification_code: 'Please enter the correct verification code'
binding_successful: 'Binding successful' binding_successful: 'Binding successful'
obtaining_the_verification_code: 'Obtaining the verification code' obtaining_the_verification_code: 'Obtaining the verification code'
upload_file: 'Upload file'
file_type_restriction_doc: 'Only.pdf,.txt, .md, .doc, and.docx file types are supported'
the_file_type_cannot_be_uploaded: 'The file type cannot be uploaded'
...@@ -460,3 +460,6 @@ personal_settings_module: ...@@ -460,3 +460,6 @@ personal_settings_module:
please_enter_the_correct_verification_code: '请输入正确验证码' please_enter_the_correct_verification_code: '请输入正确验证码'
binding_successful: '绑定成功' binding_successful: '绑定成功'
obtaining_the_verification_code: '获取验证码方式' obtaining_the_verification_code: '获取验证码方式'
upload_file: '上传文件'
file_type_restriction_doc: '只支持.pdf, .txt,.md,.doc,.docx文件类型'
the_file_type_cannot_be_uploaded: '暂不支持上传该文件类型'
...@@ -460,3 +460,6 @@ personal_settings_module: ...@@ -460,3 +460,6 @@ personal_settings_module:
please_enter_the_correct_verification_code: '請輸入正確驗證碼' please_enter_the_correct_verification_code: '請輸入正確驗證碼'
binding_successful: '綁定成功' binding_successful: '綁定成功'
obtaining_the_verification_code: '獲取驗證碼方式' obtaining_the_verification_code: '獲取驗證碼方式'
upload_file: '上傳文件'
file_type_restriction_doc: '只支持.pdf, .txt,.md,.doc,.docx文件類型'
the_file_type_cannot_be_uploaded: '暫不支持上傳該文件類型'
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, ref, toValue } from 'vue' import { computed, nextTick, ref, shallowRef, toValue, useTemplateRef } from 'vue'
import type { AgentApplicationRecordItem, MessageItemInterface } from '../types' import type { AgentApplicationRecordItem, MessageItemInterface } from '../types'
import { fetchAgentApplicationSelectList } from '@/apis/home-agent' import { fetchAgentApplicationSelectList, fetchFileUpload } from '@/apis/home-agent'
import { nanoid } from 'nanoid' import { nanoid } from 'nanoid'
import fetchEventStreamSource from '../utils/fetch-event-stream-source' import fetchEventStreamSource from '../utils/fetch-event-stream-source'
import { throttle } from 'lodash-es' import { throttle } from 'lodash-es'
...@@ -31,13 +31,24 @@ const currentFetchEventSourceController = defineModel<AbortController | null>('c ...@@ -31,13 +31,24 @@ const currentFetchEventSourceController = defineModel<AbortController | null>('c
const { t } = useI18n() const { t } = useI18n()
const inputFileRef = useTemplateRef<HTMLInputElement | null>('inputFileRef')
let fileUploadController = shallowRef<AbortController | null>(null)
const isShowApplicationSelectMenu = ref(false) const isShowApplicationSelectMenu = ref(false)
const agentApplicationSelectList = ref<AgentApplicationRecordItem[]>([]) const agentApplicationSelectList = ref<AgentApplicationRecordItem[]>([])
const currentLatestMessageItemKeyMap = ref(new Map<'assistant' | 'user', string>()) const currentLatestMessageItemKeyMap = ref(new Map<'assistant' | 'user', string>())
const currentInputFileInfo = ref({
url: '',
fileName: '',
percentage: 5,
uploading: false,
})
const isQuestionSubmitBtnDisabled = computed(() => { const isQuestionSubmitBtnDisabled = computed(() => {
return questionContent.value.trim().length === 0 || isAgentResponding.value return questionContent.value.trim().length === 0 || isAgentResponding.value || currentInputFileInfo.value.uploading
}) })
;(function () { ;(function () {
...@@ -150,6 +161,7 @@ function questionSubmit() { ...@@ -150,6 +161,7 @@ function questionSubmit() {
dialogsId: props.currentSessionId, //会话ID dialogsId: props.currentSessionId, //会话ID
agentId: currentAgentApplication.value.agentId, //应用ID agentId: currentAgentApplication.value.agentId, //应用ID
input: questionContent.value.trim(), //提问文本 input: questionContent.value.trim(), //提问文本
fileUrls: currentInputFileInfo.value.url ? [currentInputFileInfo.value.url] : [],
}, },
{ {
onmessage: (message) => { onmessage: (message) => {
...@@ -190,11 +202,75 @@ function handleQuestionSubmitEnter(event: KeyboardEvent) { ...@@ -190,11 +202,75 @@ function handleQuestionSubmitEnter(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) { if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault() event.preventDefault()
if (questionContent.value.trim().length > 0) { if (questionContent.value.trim().length > 0 && !isAgentResponding.value && !currentInputFileInfo.value.uploading) {
questionSubmit() questionSubmit()
} }
} }
} }
function handleFileUploadPopup() {
inputFileRef.value && inputFileRef.value.click()
}
function handleFileUpload(e: Event) {
const target = e.target as HTMLInputElement
const file = target.files && target.files[0]
if (file) {
if (!['.pdf', '.txt', '.md', '.doc', '.docx'].some((fileType) => file.name.endsWith(fileType))) {
window.$message.warning(t('personal_settings_module.the_file_type_cannot_be_uploaded'))
return
}
currentInputFileInfo.value = {
fileName: file.name,
url: URL.createObjectURL(file),
percentage: 5,
uploading: true,
}
fileUploadController.value && fileUploadController.value.abort()
fileUploadController.value = new AbortController()
const formData = new FormData()
formData.append('file', file)
fetchFileUpload<string>(formData, {
onUploadProgress: (progressEvent) => {
const percentage = Number.parseInt((progressEvent?.progress || 0).toFixed(2)) * 100
currentInputFileInfo.value.percentage = percentage < 5 ? 5 : percentage
if (progressEvent?.progress === 1) {
currentInputFileInfo.value.uploading = false
}
},
signal: fileUploadController.value.signal,
}).then((res) => {
currentInputFileInfo.value.url = res.data
})
}
}
function handleFileUploadCancel() {
currentInputFileInfo.value = {
url: '',
fileName: '',
percentage: 0,
uploading: false,
}
if (fileUploadController.value) {
fileUploadController.value.abort()
fileUploadController.value = null
}
}
function handleFileUploadReplace() {
handleFileUploadPopup()
}
</script> </script>
<template> <template>
...@@ -281,12 +357,77 @@ function handleQuestionSubmitEnter(event: KeyboardEvent) { ...@@ -281,12 +357,77 @@ function handleQuestionSubmitEnter(event: KeyboardEvent) {
</Transition> </Transition>
</div> </div>
<n-button class="application-select-btn !h-[34px] !rounded-[10px] !p-0" @click="handleCreateNewSession"> <div class="flex items-center">
<div class="box-border flex w-full items-center justify-between px-[12px]"> <Transition name="file-upload" mode="out-in">
<i class="iconfont icon-session mr-[5px] text-[14px]"></i> <div v-if="!currentInputFileInfo.fileName">
<span class="text-[14px]">{{ t('home_module.starting_a_new_session') }}</span> <n-popover trigger="hover">
</div> <template #trigger>
</n-button> <n-button
class="application-select-btn !mr-[14px] !h-[34px] !rounded-[10px] !p-0"
@click="handleFileUploadPopup"
>
<div class="box-border flex w-full items-center justify-between px-[12px]">
<i class="iconfont icon-upload mr-[5px] text-[14px]"></i>
<span class="text-[14px]">{{ t('personal_settings_module.upload_file') }}</span>
</div>
</n-button>
</template>
<span>{{ t('personal_settings_module.file_type_restriction_doc') }}</span>
</n-popover>
</div>
<div v-else class="relative !mr-[14px] flex !h-[34px] items-center rounded-[10px] bg-[#ECEFFF] px-[14px]">
<div
class="mr-[5px] h-[18px] w-[18px] bg-[url('https://gsst-poe-sit.gz.bcebos.com/icon/doc.svg')] bg-contain bg-no-repeat"
></div>
<div class="w-[260px] text-[14px]">
<n-ellipsis :tooltip="{ width: 400 }">
{{ currentInputFileInfo.fileName }}
</n-ellipsis>
</div>
<div class="ml-[10px]">
<i
class="iconfont icon-huanyihuan hover:text-theme-color mr-[10px] cursor-pointer text-[14px] transition"
@click="handleFileUploadReplace"
></i>
<i
class="iconfont icon-close hover:text-theme-color cursor-pointer text-[14px] transition"
@click="handleFileUploadCancel"
></i>
</div>
<div v-show="currentInputFileInfo.uploading" class="absolute bottom-[1px] left-[40px] w-[86%]">
<n-progress
type="line"
:percentage="currentInputFileInfo.percentage"
:show-indicator="false"
processing
:fill-border-radius="0"
:border-radius="0"
:height="3"
rail-color="#DCDCDC"
/>
</div>
</div>
</Transition>
<n-button class="application-select-btn !h-[34px] !rounded-[10px] !p-0" @click="handleCreateNewSession">
<div class="box-border flex w-full items-center justify-between px-[12px]">
<i class="iconfont icon-session mr-[5px] text-[14px]"></i>
<span class="text-[14px]">{{ t('home_module.starting_a_new_session') }}</span>
</div>
</n-button>
<input
ref="inputFileRef"
type="file"
class="hidden"
accept=".pdf,.txt,.md,.doc,.docx"
@change="handleFileUpload"
/>
</div>
</div> </div>
<div> <div>
...@@ -340,4 +481,16 @@ function handleQuestionSubmitEnter(event: KeyboardEvent) { ...@@ -340,4 +481,16 @@ function handleQuestionSubmitEnter(event: KeyboardEvent) {
opacity: 0; opacity: 0;
scale: 0.8; scale: 0.8;
} }
.file-upload-enter-active,
.file-upload-leave-active {
transition-timing-function: ease-in-out;
transition-duration: 0.2s;
transition-property: opacity;
}
.file-upload-enter-from,
.file-upload-leave-to {
opacity: 0;
}
</style> </style>
...@@ -476,6 +476,9 @@ declare namespace I18n { ...@@ -476,6 +476,9 @@ declare namespace I18n {
please_enter_the_correct_verification_code: string please_enter_the_correct_verification_code: string
binding_successful: string binding_successful: string
obtaining_the_verification_code: string obtaining_the_verification_code: string
upload_file: string
file_type_restriction_doc: string
the_file_type_cannot_be_uploaded: 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