From 1075860848d14e3d6d1506b91d9c9039433bf4cc Mon Sep 17 00:00:00 2001 From: wujingjing <gersonwu@qq.com> Date: 星期一, 03 三月 2025 17:40:13 +0800 Subject: [PATCH] 合并附件和业务表格 --- vite.config.ts | 2 src/components/chat/Chat.vue | 34 +++++--- src/components/chat/components/playBar/PlayBar.vue | 78 +++++++++++++------ src/components/chat/components/playBar/hook/useUploadFile.ts | 42 ++++++---- src/components/chat/components/playBar/hook/useAttach.ts | 30 +++++++ 5 files changed, 133 insertions(+), 53 deletions(-) diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue index 1878d17..08badfb 100644 --- a/src/components/chat/Chat.vue +++ b/src/components/chat/Chat.vue @@ -82,7 +82,6 @@ import { getCurrentPosition } from '/@/utils/brower'; const containerRef = useCompRef(ChatContainer); const chatListDom = computed(() => containerRef.value?.chatListDom); -const attachFileList = computed(() => playBarRef.value?.attachFileList ?? []); const scrollToBottom = () => { containerRef.value?.scrollToBottom(); }; @@ -142,10 +141,11 @@ raw_mode: roomConfig.value?.[currentRouteId]?.isAnswerByLLM ?? false, ...judgeParams, } as any; - if(businessTableData.value?.length > 0) { - params.tables = JSON.stringify(businessTableData.value); + const tableList = attachList.value.filter((item) => item.type === 'table').map((item) => item.model); + if (tableList?.length > 0) { + params.tables = JSON.stringify(tableList); } - + // if (!position) { // const loadingInstance = ElLoadingService({ // text: '鑾峰彇浣嶇疆涓�...', @@ -171,9 +171,11 @@ currentSampleId = ''; } const formDataParams = toFormData(params); - for (const item of attachFileList.value) { + const fileList = attachList.value.filter((item) => item.type === 'file').map((item) => item.model); + for (const item of fileList) { formDataParams.append('files', item.file); } + clearAttach(); let lastTimestamp = new Date().getTime(); questionRes = {}; let lastIsResult = false; @@ -348,7 +350,9 @@ stepList.push(stepItem); } else { const lastItem = stepList.at(-1); - lastItem.title += chunkRes.value ?? ''; + if (lastItem) { + lastItem.title += chunkRes.value ?? ''; + } } if (chunkRes.mode === 'begin_stream') { @@ -384,14 +388,13 @@ return content; }; const playBarRef = useCompRef(PlayBar); -const businessTableData = computed(() => playBarRef.value?.businessTableData ?? []); -const clearMessageContent = () => - (messageContent.value = { +const attachList = computed(() => playBarRef.value?.attachList ?? []); +const clearMessageContent = () => { + messageContent.value = { type: AnswerType.Text, values: '', - }); - playBarRef.value?.clearFileList(); - playBarRef.value?.clearBusinessTable(); + }; +}; let currentSampleId = ''; @@ -442,6 +445,13 @@ return [userItem, assistantItem]; }; +/** + * 娓呴櫎闄勪欢 + */ +const clearAttach = () => { + playBarRef.value?.clearAttach(); +}; + const sendChatMessage = async (content: ChatContent = messageContent.value) => { if (!checkCanSend(content)) { return; diff --git a/src/components/chat/components/playBar/PlayBar.vue b/src/components/chat/components/playBar/PlayBar.vue index 021966b..260bb8c 100644 --- a/src/components/chat/components/playBar/PlayBar.vue +++ b/src/components/chat/components/playBar/PlayBar.vue @@ -26,28 +26,37 @@ </div> <div class="set-input flex-auto"> - <div v-if="attachFileList?.length > 0" class="flex gap-3.5 w-full overflow-x-auto px-2 pb-2"> + <div v-if="attachList?.length > 0" class="flex gap-3.5 w-full overflow-x-auto px-2 pb-2"> <div - v-for="(item, index) in attachFileList" + v-for="(item, index) in attachList" :key="index" class="flex items-center gap-2 bg-[#f5f5f5] px-2 py-3 rounded-lg w-[220px] relative group" > - <el-image - :zoom-rate="1.2" - fit="cover" - class="w-[24px] rounded cursor-pointer" - v-if="item.groupType === 'image'" - :src="item.previewUrl" - :preview-src-list="[item.previewUrl]" - /> - <div v-else class="ywifont !text-[24px] flex-0" :class="[`ywicon-${item.icon}`, item.iconClass]"></div> - <div class="flex flex-col gap-0.5 w-full flex-auto"> - <div class="font-bold over-ellipsis w-full">{{ item.name }}</div> - <div class="text-info text-sm over-ellipsis w-full">{{ `${item.type}锛�${item.size}` }}</div> - </div> + <template v-if="item.type === 'file'"> + <el-image + :zoom-rate="1.2" + fit="cover" + class="w-[24px] rounded cursor-pointer" + v-if="item.model.groupType === 'image'" + :src="item.model.previewUrl" + :preview-src-list="[item.model.previewUrl]" + /> + <div v-else class="ywifont !text-[24px] flex-0" :class="[`ywicon-${item.icon}`, item.iconClass]"></div> + <div class="flex flex-col gap-0.5 w-full flex-auto"> + <div class="font-bold over-ellipsis w-full">{{ item.title }}</div> + <div class="text-info text-sm over-ellipsis w-full">{{ `${item.model.type}锛�${item.model.size}` }}</div> + </div> + </template> + <template v-if="item.type === 'table'"> + <div class="ywifont !text-[24px] flex-0" :class="[`ywicon-${item.icon}`, item.iconClass]"></div> + <div class="flex flex-col gap-0.5 w-full flex-auto"> + <div class="font-bold over-ellipsis w-full">{{ item.title }}</div> + <div class="text-info text-sm over-ellipsis w-full">{{ `涓氬姟琛ㄦ牸锛�${item.model.values?.length} 鏉¤褰昤 }}</div> + </div> + </template> <div class="group-hover:visible invisible absolute right-0.5 top-0.5 bg-red-500 flex-center rounded-full p-0.5 cursor-pointer" - @click="deleteIndexFile(index)" + @click="deleteAttachInIndex(index)" > <span class="ywifont ywicon-guanbi text-white !text-[10px] font-bold"></span> </div> @@ -148,6 +157,7 @@ import { useCompRef } from '/@/utils/types'; import { useUploadFile } from './hook/useUploadFile'; import BusinessTable from './businessTable/index.vue'; +import { useAttach } from './hook/useAttach'; const emits = defineEmits(['sendClick', 'stopGenClick']); const props = defineProps({ isTalking: Boolean, @@ -226,25 +236,45 @@ }, syncMsgDuration); }; -const { clearFileList, openFileClick, attachFileList, deleteIndexFile } = useUploadFile({ +const { attachList, removeAttach, clearAttach } = useAttach(); + +const { openFileClick, deleteUploadFile } = useUploadFile({ pastTarget: inputRef as any, + attachFileList: attachList, }); - -const businessTableData = ref([]); +const deleteAttachInIndex = (index: number) => { + const attach = attachList.value[index]; + if (attach.type === 'file') { + deleteUploadFile(attach.model); + } + removeAttach(index); +}; const submitBusinessTable = (data) => { - businessTableData.value = data; + attachList.value.push( + ...data.map((item) => { + return { + title: item.title, + type: 'table', + model: item, + icon: 'biaoge', + iconClass: 'ywicon-biaoge text-[#c5e0ff]', + }; + }) + ); }; -const clearBusinessTable = () => { - businessTableData.value = []; -}; //#region ====================== 涓氬姟琛ㄦ牸 ====================== const businessTableIsShow = ref(false); const openBusinessTable = () => { businessTableIsShow.value = true; }; //#endregion -defineExpose({ addPhrase, showSyncTip, attachFileList, clearFileList, businessTableData, clearBusinessTable }); +defineExpose({ + addPhrase, + showSyncTip, + attachList, + clearAttach, +}); </script> <style scoped lang="scss"> @use './index.scss'; diff --git a/src/components/chat/components/playBar/hook/useAttach.ts b/src/components/chat/components/playBar/hook/useAttach.ts new file mode 100644 index 0000000..4b7c53e --- /dev/null +++ b/src/components/chat/components/playBar/hook/useAttach.ts @@ -0,0 +1,30 @@ +import { ref } from 'vue'; + +export type AttachType = 'file' | 'table'; +export type Attach<T = any> = { + title: string; + type: AttachType; + model: T; + icon?: string; + iconClass?: string; +}; + +export const useAttach = () => { + const attachList = ref<Attach[]>([]); + + + + const removeAttach = (index: number) => { + attachList.value.splice(index, 1); + }; + + const clearAttach = () => { + attachList.value = []; + }; + + return { + attachList, + removeAttach, + clearAttach, + }; +}; diff --git a/src/components/chat/components/playBar/hook/useUploadFile.ts b/src/components/chat/components/playBar/hook/useUploadFile.ts index 912c6c0..5f01e26 100644 --- a/src/components/chat/components/playBar/hook/useUploadFile.ts +++ b/src/components/chat/components/playBar/hook/useUploadFile.ts @@ -1,9 +1,11 @@ import { useEventListener, useFileDialog } from '@vueuse/core'; -import { ref, type Ref } from 'vue'; +import { type Ref } from 'vue'; +import type { Attach } from './useAttach'; import { convertFileSize } from '/@/utils/file'; export type UseUploadFileOptions = { pastTarget: Ref<HTMLElement | null>; + attachFileList: Ref<Attach<UploadFile>[]>; }; export type FileType = 'doc' | 'docx' | 'pdf' | 'md' | 'xls' | 'xlsx' | 'png' | 'jpg' | 'jpeg' | 'gif' | 'json' | 'txt' | 'csv'; @@ -93,10 +95,8 @@ }; // const supportFileType = ['doc', 'docx', 'md', 'xls', 'xlsx', 'png', 'jpg', 'jpeg', 'gif', 'json', 'pdf']; const supportFileType = ['csv', 'txt']; -const acceptFiles = [].join(',') export const useUploadFile = (options: UseUploadFileOptions) => { - const attachFileList = ref<UploadFile[]>([]); - + const { attachFileList } = options; const parseFiles = (files: FileList) => { const filterFiles: UploadFile[] = []; for (const file of files) { @@ -126,7 +126,24 @@ filterFiles.push(uploadFile); } } - attachFileList.value.push(...filterFiles); + attachFileList.value.push( + ...filterFiles.map( + (item) => + ({ + get title() { + return item.name; + }, + type: 'file', + model: item, + get icon() { + return item.icon; + }, + get iconClass() { + return item.iconClass; + }, + } as Attach<UploadFile>) + ) + ); }; /** @@ -140,17 +157,14 @@ parseFiles(files); }; - const clearFileList = () => { - attachFileList.value = []; - }; + const { files, open: openFileDialog, reset: resetOpenFileDialog, onChange: onPickFileChange, } = useFileDialog({ - accept: - 'text/csv,text/plain', // Only accept csv and txt files + accept: 'text/csv,text/plain', // Only accept csv and txt files reset: true, }); @@ -161,21 +175,17 @@ if (!files) return; parseFiles(files); }); - const deleteIndexFile = (index) => { - const file = attachFileList.value[index]; - // Revoke object URL for image files + const deleteUploadFile = (file: UploadFile) => { if (file.previewUrl) { URL.revokeObjectURL(file.previewUrl); } - attachFileList.value.splice(index, 1); }; useEventListener(options.pastTarget, 'paste', pasteUpload); return { attachFileList, - clearFileList, - deleteIndexFile, + deleteUploadFile, openFileClick, }; }; diff --git a/vite.config.ts b/vite.config.ts index 1c82ca0..27dae42 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -58,7 +58,7 @@ host: '0.0.0.0', port: env.VITE_PORT as unknown as number, open: JSON.parse(env.VITE_OPEN), - hmr: false, + hmr: true, proxy: { '/events': { target: 'http://localhost:3000', -- Gitblit v1.9.3