From 254816a712847b099184d84ca8631a50fb32f39e Mon Sep 17 00:00:00 2001 From: wujingjing <gersonwu@qq.com> Date: 星期一, 03 三月 2025 15:24:09 +0800 Subject: [PATCH] 初步对接 --- customer_list/common/static/fonts/ywiconfont/iconfont.ttf | 0 src/api/ai/chat.ts | 2 customer_list/common/static/fonts/ywiconfont/iconfont.woff | 0 src/api/attach/index.ts | 6 src/components/layout/AHMContainer.vue | 2 src/components/chat/Chat.vue | 19 ++- src/components/chat/components/playBar/businessTable/search/index.vue | 6 src/components/chat/components/playBar/PlayBar.vue | 19 ++ src/components/chat/components/playBar/hook/useUploadFile.ts | 46 ++++-- customer_list/common/static/fonts/ywiconfont/iconfont.css | 14 + customer_list/common/static/fonts/ywiconfont/iconfont.woff2 | 0 src/components/chat/components/playBar/businessTable/index.vue | 204 +++++++++++++++++++++++++++++----- 12 files changed, 248 insertions(+), 70 deletions(-) diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.css b/customer_list/common/static/fonts/ywiconfont/iconfont.css index b4ed7c8..bae2fb0 100644 --- a/customer_list/common/static/fonts/ywiconfont/iconfont.css +++ b/customer_list/common/static/fonts/ywiconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "ywifont"; /* Project id 4655417 */ - src: url('iconfont.woff2?t=1740909922081') format('woff2'), - url('iconfont.woff?t=1740909922081') format('woff'), - url('iconfont.ttf?t=1740909922081') format('truetype'); + src: url('iconfont.woff2?t=1740972827582') format('woff2'), + url('iconfont.woff?t=1740972827582') format('woff'), + url('iconfont.ttf?t=1740972827582') format('truetype'); } .ywifont { @@ -13,6 +13,14 @@ -moz-osx-font-smoothing: grayscale; } +.ywicon-TXTtubiao:before { + content: "\e64d"; +} + +.ywicon-csv:before { + content: "\e64b"; +} + .ywicon-fujian:before { content: "\e88a"; } diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.ttf b/customer_list/common/static/fonts/ywiconfont/iconfont.ttf index 9ecc5c9..c8a65de 100644 --- a/customer_list/common/static/fonts/ywiconfont/iconfont.ttf +++ b/customer_list/common/static/fonts/ywiconfont/iconfont.ttf Binary files differ diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.woff b/customer_list/common/static/fonts/ywiconfont/iconfont.woff index 034dfcf..3b6b673 100644 --- a/customer_list/common/static/fonts/ywiconfont/iconfont.woff +++ b/customer_list/common/static/fonts/ywiconfont/iconfont.woff Binary files differ diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.woff2 b/customer_list/common/static/fonts/ywiconfont/iconfont.woff2 index 3fd5b69..dfc1138 100644 --- a/customer_list/common/static/fonts/ywiconfont/iconfont.woff2 +++ b/customer_list/common/static/fonts/ywiconfont/iconfont.woff2 Binary files differ diff --git a/src/api/ai/chat.ts b/src/api/ai/chat.ts index 7b4d155..1e37ade 100644 --- a/src/api/ai/chat.ts +++ b/src/api/ai/chat.ts @@ -267,7 +267,7 @@ params: {}, data: params, headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'multipart/form-data', }, ...extraData, }, diff --git a/src/api/attach/index.ts b/src/api/attach/index.ts index 6c7deaf..17e6a54 100644 --- a/src/api/attach/index.ts +++ b/src/api/attach/index.ts @@ -3,14 +3,14 @@ export const getAttachTableList = () => { return request({ url: '/attach/get_attach_table_list', - method: 'get', + method: 'POST', }); }; export const queryAttachTableRecords = (params: any) => { return request({ url: '/attach/query_attach_table_records', - method: 'get', - params, + method: 'POST', + data: params, }); }; diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue index 86d78ed..1878d17 100644 --- a/src/components/chat/Chat.vue +++ b/src/components/chat/Chat.vue @@ -142,10 +142,10 @@ raw_mode: roomConfig.value?.[currentRouteId]?.isAnswerByLLM ?? false, ...judgeParams, } as any; - const formDataParams = toFormData(params); - for (const item of attachFileList.value) { - formDataParams.append('files', item.file); + if(businessTableData.value?.length > 0) { + params.tables = JSON.stringify(businessTableData.value); } + // if (!position) { // const loadingInstance = ElLoadingService({ // text: '鑾峰彇浣嶇疆涓�...', @@ -170,7 +170,10 @@ params.sample_id = currentSampleId; currentSampleId = ''; } - + const formDataParams = toFormData(params); + for (const item of attachFileList.value) { + formDataParams.append('files', item.file); + } let lastTimestamp = new Date().getTime(); questionRes = {}; let lastIsResult = false; @@ -190,7 +193,7 @@ return isEmpty; }; questionStreamByPost( - params, + formDataParams, (chunkRes) => { Logger.info('chunk response锛歕n\n' + JSON.stringify(chunkRes)); if (chunkRes.mode === 'result') { @@ -380,12 +383,15 @@ const content = parseContent(questionRes, true); return content; }; - +const playBarRef = useCompRef(PlayBar); +const businessTableData = computed(() => playBarRef.value?.businessTableData ?? []); const clearMessageContent = () => (messageContent.value = { type: AnswerType.Text, values: '', }); + playBarRef.value?.clearFileList(); + playBarRef.value?.clearBusinessTable(); let currentSampleId = ''; @@ -595,7 +601,6 @@ messageContent.value.values = content; }; //#endregion -const playBarRef = useCompRef(PlayBar); //鐢ㄦ埛闂璁剧疆涓哄父鐢ㄨ const setCommonPhraseClick = (item) => { playBarRef.value.addPhrase(item); diff --git a/src/components/chat/components/playBar/PlayBar.vue b/src/components/chat/components/playBar/PlayBar.vue index 020ec46..021966b 100644 --- a/src/components/chat/components/playBar/PlayBar.vue +++ b/src/components/chat/components/playBar/PlayBar.vue @@ -86,7 +86,10 @@ </el-button> <div class="flex-items-center gap-2"> <el-tooltip placement="top" content="鍏宠仈涓氬姟琛ㄦ牸"> - <div class="cursor-pointer size-[38px] relative !z-10 rounded flex-center hover:bg-[#f2f2f2]" @click="openBusinessTable"> + <div + class="cursor-pointer size-[38px] relative !z-10 rounded flex-center hover:bg-[#f2f2f2]" + @click="openBusinessTable" + > <span class="ywifont ywicon-biaoge !text-[24px]"></span> </div> </el-tooltip> @@ -129,9 +132,7 @@ ref="commonPhraseRef" @updateInput="updateInputValue" /> - <BusinessTable - v-model="businessTableIsShow" - /> + <BusinessTable v-model="businessTableIsShow" @submit="submitBusinessTable" /> </div> </div> </template> @@ -229,13 +230,21 @@ pastTarget: inputRef as any, }); +const businessTableData = ref([]); +const submitBusinessTable = (data) => { + businessTableData.value = data; +}; + +const clearBusinessTable = () => { + businessTableData.value = []; +}; //#region ====================== 涓氬姟琛ㄦ牸 ====================== const businessTableIsShow = ref(false); const openBusinessTable = () => { businessTableIsShow.value = true; }; //#endregion -defineExpose({ addPhrase, showSyncTip, attachFileList }); +defineExpose({ addPhrase, showSyncTip, attachFileList, clearFileList, businessTableData, clearBusinessTable }); </script> <style scoped lang="scss"> @use './index.scss'; diff --git a/src/components/chat/components/playBar/businessTable/index.vue b/src/components/chat/components/playBar/businessTable/index.vue index 696efef..427474e 100644 --- a/src/components/chat/components/playBar/businessTable/index.vue +++ b/src/components/chat/components/playBar/businessTable/index.vue @@ -1,12 +1,19 @@ <template> - <el-dialog :destroy-on-close="true" v-model="dialogIsShow" width="70%" :close-on-click-modal="false" @closed="closeDialog"> + <el-dialog + class="limit-height" + :destroy-on-close="true" + v-model="dialogIsShow" + width="70%" + :close-on-click-modal="false" + @closed="closeDialog" + > <template #header> <div style="color: #fff"> <SvgIcon name="ele-Document" :size="16" style="margin-right: 3px; display: inline; vertical-align: middle" /> <span>涓氬姟琛ㄥ叧鑱�</span> </div> </template> - <AHMContainer type="card"> + <AHMContainer type="card" class="h100" v-loading="submitLoading"> <template #aside> <!-- 鐩綍鏍� --> <LeftTreeByMgr @@ -20,7 +27,7 @@ }" defaultExpandAll :treedata="listTreeData" - title-name="鍦烘櫙鍒楄〃" + title-name="闄勪欢琛�" :show-more-operate="false" :show-add="false" :current-node-key="currentListID" @@ -32,7 +39,7 @@ <template #header> <el-form ref="queryFormRef" :inline="true" class="relative"> <el-form-item :label="item.title" prop="title" v-for="item in filterColumns as any" :key="item.name"> - <TableSearch v-model="item.value" :filter="item.filter" @input="debounceSearch(item)" /> + <TableSearch v-model="item.values" :filter="item.filter" @input="debounceSearch()" /> </el-form-item> </el-form> </template> @@ -49,7 +56,14 @@ highlight-current-row @sort-change="handleSortChange" > - <el-table-column v-for="item in tableColumns" :prop="item.name" :label="item.title" show-overflow-tooltip> + <el-table-column + v-for="item in tableColumns" + :prop="item.name" + sortable="custom" + :key="item.name" + :label="item.title" + show-overflow-tooltip + > </el-table-column> </el-table> </div> @@ -81,8 +95,29 @@ const closeDialog = () => { dialogIsShow.value = false; }; -const submitFormValue = () => { - emit('submit', tableData.value); +const submitLoading = ref(false); +const getSubmitData = async () => { + submitLoading.value = true; + await getUnGetData().finally(() => { + submitLoading.value = false; + }); + const tables = listTreeData.value.map((item) => { + return { + title: item.title, + columns: item.columns.map((item) => { + return item.title; + }), + values: item.tableData.map((item) => { + return Object.values(item); + }), + }; + }); + + return tables; +}; +const submitFormValue = async () => { + const data = await getSubmitData(); + emit('submit', data); dialogIsShow.value = false; }; //#region ====================== 宸︿晶鏍戞暟鎹紝tree init ====================== @@ -104,7 +139,7 @@ return { ...item, // 鍒濆鏌ヨ鍊� - value: '', + values: [''], }; }); } @@ -112,31 +147,47 @@ return treeData; }); +/** + * 璁板綍 orderMap + */ +const setOrderMap = (node) => { + if (!node.orderMap) { + node.orderMap = new Map(); + } +}; + const handleClickNode = (data) => { nextTick(() => { leftTreeRef.value?.treeRef.setCurrentKey(data.id); }); currentNode.value = data; + setOrderMap(data); getTableData(); }; const getListTreeData = async () => { - const res = await attachApi.getAttachTableList(); + treeLoading.value = true; + const res = await attachApi.getAttachTableList().finally(() => { + treeLoading.value = false; + }); listData.value = res.tables || []; const firstListTreeNode = listTreeData.value[0]; if (firstListTreeNode) { handleClickNode(firstListTreeNode); } else { - tableData.value = []; currentNode.value = null; } }; //#endregion //#region ====================== 鎸囨爣绠$悊琛ㄦ牸鏁版嵁锛宼able init ====================== const tableLoading = ref(false); -const tableData = ref([]); +const tableData = computed(() => { + return currentNode.value?.tableData || []; +}); const isDragStatus = ref(false); -const tableColumns = ref([]); +const tableColumns = computed(() => { + return currentNode.value?.columns || []; +}); const getTableData = async () => { // allTableData.value = (res.values || []).map((item) => { // item.create_time = item.create_time?.slice(0, 10); @@ -146,10 +197,21 @@ // const len = getLenById(allTableData.value, value.id, value); // value.title = `${value.title} (${len})`; // }); - tableColumns.value = currentNode.value.columns; + if (!currentNode.value.tableData) { + handleSearchInput(); + } }; //#endregion -const indexMapItem = computed(() => { + +const getIndexMapItem = (columns) => { + return new Map<number, any>( + columns.map((item, index) => { + return [index, item]; + }) + ); +}; + +const indexMapItem = computed<Map<number, any>>(() => { return new Map( tableColumns.value.map((item, index) => { return [index, item]; @@ -157,41 +219,111 @@ ); }); //#region ====================== 鏌ヨ ====================== -const handleSearchInput = async (item) => { - const params = { - id: currentNode.value.id, - } as any; +const getFilterColumns = (node) => { + return node?.columns?.filter((item) => item.filter) as any[]; +}; +const filterColumns = computed(() => { + return getFilterColumns(currentNode.value); +}); - if (filterColumns.value && filterColumns.value.length) { - params.filter = filterColumns.value.map((item) => { +const getSearchParams = (node) => { + const params = { + id: node.id, + } as any; + const filterColumns = getFilterColumns(node); + if (filterColumns && filterColumns.length) { + params.filter = filterColumns.map((item) => { return { col: item.name, filter: item.filter, - value: item.value, + values: item.values, }; }); } + setOrderMap(node); + const orderMap = node.orderMap; + const orderList = Array.from(orderMap.entries()).map(([key, value]) => { + return { + col: key, + order: value, + }; + }); + if (orderList?.length > 0) { + params.order = orderList; + } + return params; +}; - const res = await attachApi.queryAttachTableRecords(params); - tableData.value = (res.values || []).map((item, index) => { +const parseRecordData = (res, columns) => { + const indexMapItem = getIndexMapItem(columns); + return (res.values || []).map((item, index) => { const row = {} as any; - item?.forEach((item, index) => { - row[indexMapItem.value.get(index).name] = item; + row[indexMapItem.get(index).name] = item; }); return row; }); }; +const handleSearchItem = async (node, prop?, order?, column?) => { + const params = getSearchParams(node); + // 淇涓哄綋鍓嶈淇敼鐨� order + if (prop) { + // const foundItem = params.order.find((item) => item.col === prop); + // if (foundItem) { + // foundItem.order = order; + // } + // 鎺掑簭鍙細鏈変竴涓瓧娈� + params.order = [ + { + col: prop, + order: order, + }, + ]; + } + tableLoading.value = true; + const res = await attachApi.queryAttachTableRecords(params).finally(() => { + tableLoading.value = false; + }); + if (prop) { + const orderMap = node.orderMap; + orderMap.clear(); + orderMap.set(prop, order); + column.order = getEleOrder(order); + } + node.tableData = parseRecordData(res, tableColumns.value); +}; + +const handleSearchInput = async (prop?, order?, column?) => { + handleSearchItem(currentNode.value, prop, order, column); +}; + +const getUnGetData = async () => { + const getDataPromiseList = []; + for (const item of listTreeData.value) { + if (!item.tableData) { + getDataPromiseList.push(handleSearchItem(item)); + } + } + // 绛夊緟鎵�鏈夋暟鎹幏鍙栧畬鎴� + await Promise.all(getDataPromiseList); +}; + const debounceSearch = debounce(handleSearchInput, 400); //#endregion - -const filterColumns = computed(() => { - return tableColumns.value.filter((item) => item.filter) as any[]; -}); -const orderMap = new Map(); +const getEleOrder = (order) => { + if (order === 'DESC') { + return 'descending'; + } else if (order === 'ASC') { + return 'ascending'; + } else { + return ''; + } +}; const handleSortChange = ({ column, prop, order }) => { + setOrderMap(currentNode.value); + const orderMap = currentNode.value.orderMap; // 鎭㈠鍘熺姸锛屾洿鏂板悗鍐嶆樉绀烘帓搴忕姸鎬� const curOrder = orderMap.get(prop) ?? null; column.order = curOrder; @@ -204,6 +336,7 @@ } else { sendOrder = ''; } + handleSearchInput(prop, sendOrder, column); }; watch( @@ -230,4 +363,15 @@ font-size: 14px; } } + +:deep(.el-dialog__body) { + height: calc(90vh - 111px) !important; +} +</style> +<style lang="scss"> +.limit-height { + .el-dialog__body { + height: calc(90vh - 111px) !important; + } +} </style> diff --git a/src/components/chat/components/playBar/businessTable/search/index.vue b/src/components/chat/components/playBar/businessTable/search/index.vue index 2ba0974..fce0fd3 100644 --- a/src/components/chat/components/playBar/businessTable/search/index.vue +++ b/src/components/chat/components/playBar/businessTable/search/index.vue @@ -3,7 +3,7 @@ <el-input v-if="filter === 'like'" @input="(value) => input(value)" - v-model="modelValue" + v-model="modelValue[0]" style="width: 226.4px" clearable /> @@ -14,8 +14,8 @@ const props = defineProps(['filter']); const emit = defineEmits(['input']); const modelValue = defineModel({ - type: String, - default: '', + type: Array<string>, + default: [''], }); const input = (value) => { diff --git a/src/components/chat/components/playBar/hook/useUploadFile.ts b/src/components/chat/components/playBar/hook/useUploadFile.ts index 14f2943..912c6c0 100644 --- a/src/components/chat/components/playBar/hook/useUploadFile.ts +++ b/src/components/chat/components/playBar/hook/useUploadFile.ts @@ -6,8 +6,8 @@ pastTarget: Ref<HTMLElement | null>; }; -export type FileType = 'doc' | 'docx' | 'pdf' | 'md' | 'xls' | 'xlsx' | 'png' | 'jpg' | 'jpeg' | 'gif' | 'json'; -export type FileGroupType = 'word' | 'pdf' | 'excel' | 'image' | 'json' | 'md'; +export type FileType = 'doc' | 'docx' | 'pdf' | 'md' | 'xls' | 'xlsx' | 'png' | 'jpg' | 'jpeg' | 'gif' | 'json' | 'txt' | 'csv'; +export type FileGroupType = 'word' | 'pdf' | 'excel' | 'image' | 'json' | 'md' | 'csv' | 'txt'; export type UploadFile = { name: string; type: FileType; @@ -24,6 +24,10 @@ case 'doc': case 'docx': return 'word'; + case 'csv': + return 'csv'; + case 'txt': + return 'txt'; case 'md': return 'md'; @@ -52,6 +56,10 @@ return 'pdf'; case 'excel': return 'excel'; + case 'csv': + return 'csv'; + case 'txt': + return 'txt'; case 'json': return 'json'; @@ -70,6 +78,10 @@ return 'text-red-400'; case 'excel': return 'text-green-400'; + case 'csv': + return 'text-green-500'; + case 'txt': + return 'text-cyan-400'; case 'json': return 'text-yellow-400'; @@ -79,12 +91,13 @@ return ''; } }; - +// 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 supportFileType = ['doc', 'docx', 'md', 'xls', 'xlsx', 'png', 'jpg', 'jpeg', 'gif', 'json', 'pdf']; const attachFileList = ref<UploadFile[]>([]); - const parseFiles = (files:FileList) => { + const parseFiles = (files: FileList) => { const filterFiles: UploadFile[] = []; for (const file of files) { const suffix = file.name.split('.').pop() as FileType; @@ -114,7 +127,7 @@ } } attachFileList.value.push(...filterFiles); - } + }; /** * 瑙f瀽绮樿创鏉挎枃浠� @@ -124,7 +137,7 @@ event.stopPropagation(); const data = event.clipboardData || window.clipboardData; const files = data.files as FileList; - parseFiles(files) + parseFiles(files); }; const clearFileList = () => { @@ -132,22 +145,21 @@ }; const { files, - open:openFileDialog, - reset:resetOpenFileDialog, + open: openFileDialog, + reset: resetOpenFileDialog, onChange: onPickFileChange, } = useFileDialog({ accept: - 'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,text/markdown,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,image/png,image/jpeg,image/gif,application/json,application/pdf', // Set to accept only image files - reset:true + 'text/csv,text/plain', // Only accept csv and txt files + reset: true, }); - - const openFileClick = () =>{ + const openFileClick = () => { openFileDialog(); - } + }; onPickFileChange((files) => { - if(!files) return; - parseFiles(files) + if (!files) return; + parseFiles(files); }); const deleteIndexFile = (index) => { const file = attachFileList.value[index]; @@ -164,6 +176,6 @@ attachFileList, clearFileList, deleteIndexFile, - openFileClick + openFileClick, }; }; diff --git a/src/components/layout/AHMContainer.vue b/src/components/layout/AHMContainer.vue index d8fc4b7..bbebd64 100644 --- a/src/components/layout/AHMContainer.vue +++ b/src/components/layout/AHMContainer.vue @@ -8,7 +8,7 @@ <slot v-else name="aside"></slot> </el-col> <el-col :span="20" :xs="24" class="flex-column h100"> - <el-card class="yw-layout-card yw-layout-header" v-if="type === 'card'" shadow="hover" :body-style="{ paddingBottom: '0' }"> + <el-card class="yw-layout-card yw-layout-header flex-0" v-if="type === 'card'" shadow="hover" :body-style="{ paddingBottom: '0' }"> <slot name="header"> </slot> </el-card> <slot v-else name="header"> </slot> -- Gitblit v1.9.3