customer_list/common/static/fonts/ywiconfont/iconfont.css | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
customer_list/common/static/fonts/ywiconfont/iconfont.ttf | 补丁 | 查看 | 原始文档 | blame | 历史 | |
customer_list/common/static/fonts/ywiconfont/iconfont.woff | 补丁 | 查看 | 原始文档 | blame | 历史 | |
customer_list/common/static/fonts/ywiconfont/iconfont.woff2 | 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/api/ai/chat.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/api/attach/index.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/chat/Chat.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/chat/components/playBar/PlayBar.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/chat/components/playBar/businessTable/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/chat/components/playBar/businessTable/search/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/chat/components/playBar/hook/useUploadFile.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/layout/AHMContainer.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
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"; } customer_list/common/static/fonts/ywiconfont/iconfont.ttfBinary files differ
customer_list/common/static/fonts/ywiconfont/iconfont.woffBinary files differ
customer_list/common/static/fonts/ywiconfont/iconfont.woff2Binary files differ
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, }, 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, }); }; 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); 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'; 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 ====================== 指标管理表格数据,table 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> 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) => { 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); } }; /** * 解析粘贴板文件 @@ -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, }; }; 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>