正在显示
11 个修改的文件
包含
870 行增加
和
11 行删除
| 1 | +import { defHttp } from '/@/utils/http/axios'; | ||
| 2 | +import { useMessage } from '/@/hooks/web/useMessage'; | ||
| 3 | + | ||
| 4 | +const { createConfirm } = useMessage(); | ||
| 5 | + | ||
| 6 | +enum Api { | ||
| 7 | + list = '/question/embedding/list', | ||
| 8 | + save = '/question/embedding/add', | ||
| 9 | + edit = '/question/embedding/edit', | ||
| 10 | + deleteOne = '/question/embedding/delete', | ||
| 11 | + deleteBatch = '/question/embedding/deleteBatch', | ||
| 12 | + importExcel = '/question/embedding/importZip', | ||
| 13 | + uploadZip = '/question/embedding/uploadZip', | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +export const getExportUrl = Api.importExcel; | ||
| 17 | +export const getImportZipUrl = Api.uploadZip; | ||
| 18 | + | ||
| 19 | +export const list = async (params) => { | ||
| 20 | + try { | ||
| 21 | + const res = await defHttp.get({ | ||
| 22 | + url: Api.list, | ||
| 23 | + params: { ...params, size: 1000 } | ||
| 24 | + }); | ||
| 25 | + | ||
| 26 | + if (res?.records && Array.isArray(res.records)) { | ||
| 27 | + res.records = res.records.map(item => ({ | ||
| 28 | + ...item, | ||
| 29 | + ...item.metadata, | ||
| 30 | + question: item.question || '', | ||
| 31 | + answer: item.answer || '' | ||
| 32 | + })); | ||
| 33 | + } | ||
| 34 | + return res; | ||
| 35 | + } catch (error) { | ||
| 36 | + console.error("Error fetching question embeddings:", error); | ||
| 37 | + return { | ||
| 38 | + records: [], | ||
| 39 | + total: 0, | ||
| 40 | + size: 10, | ||
| 41 | + current: 1, | ||
| 42 | + pages: 0 | ||
| 43 | + }; | ||
| 44 | + } | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +export const deleteOne = (params, handleSuccess) => { | ||
| 48 | + return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => { | ||
| 49 | + handleSuccess(); | ||
| 50 | + }); | ||
| 51 | +}; | ||
| 52 | + | ||
| 53 | +export const batchDelete = (params, handleSuccess) => { | ||
| 54 | + createConfirm({ | ||
| 55 | + iconType: 'warning', | ||
| 56 | + title: '确认删除', | ||
| 57 | + content: '是否删除选中问答数据', | ||
| 58 | + okText: '确认', | ||
| 59 | + cancelText: '取消', | ||
| 60 | + onOk: () => { | ||
| 61 | + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { | ||
| 62 | + handleSuccess(); | ||
| 63 | + }); | ||
| 64 | + }, | ||
| 65 | + }); | ||
| 66 | +}; | ||
| 67 | + | ||
| 68 | +export const saveOrUpdate = (params, isUpdate) => { | ||
| 69 | + const url = isUpdate ? Api.edit : Api.save; | ||
| 70 | + return defHttp.post({ url: url, params }); | ||
| 71 | +}; |
| 1 | +import { BasicColumn } from '/@/components/Table'; | ||
| 2 | +import { FormSchema } from '/@/components/Table'; | ||
| 3 | + | ||
| 4 | +export const columns: BasicColumn[] = [ | ||
| 5 | + { | ||
| 6 | + title: 'ID', | ||
| 7 | + align: 'center', | ||
| 8 | + dataIndex: 'id', | ||
| 9 | + width: 200, | ||
| 10 | + }, | ||
| 11 | + { | ||
| 12 | + title: '问题', | ||
| 13 | + align: 'center', | ||
| 14 | + dataIndex: 'question', | ||
| 15 | + width: 250 | ||
| 16 | + }, | ||
| 17 | + { | ||
| 18 | + title: '回答', | ||
| 19 | + align: 'center', | ||
| 20 | + dataIndex: 'answer', | ||
| 21 | + width: 300 | ||
| 22 | + }, | ||
| 23 | + { | ||
| 24 | + title: '原文', | ||
| 25 | + align: 'center', | ||
| 26 | + dataIndex: 'text', | ||
| 27 | + width: 300 | ||
| 28 | + }, | ||
| 29 | + { | ||
| 30 | + title: '元数据', | ||
| 31 | + align: 'center', | ||
| 32 | + dataIndex: 'metadata', | ||
| 33 | + width: 200, | ||
| 34 | + customRender: ({ text }) => JSON.stringify(text || {}) | ||
| 35 | + } | ||
| 36 | +]; | ||
| 37 | + | ||
| 38 | +export const searchFormSchema: FormSchema[] = [ | ||
| 39 | + { | ||
| 40 | + field: 'question', | ||
| 41 | + label: '问题', | ||
| 42 | + component: 'Input', | ||
| 43 | + colProps: { span: 8 } | ||
| 44 | + }, | ||
| 45 | + { | ||
| 46 | + field: 'answer', | ||
| 47 | + label: '回答', | ||
| 48 | + component: 'Input', | ||
| 49 | + colProps: { span: 8 } | ||
| 50 | + } | ||
| 51 | +]; | ||
| 52 | + | ||
| 53 | +export const formSchema: FormSchema[] = [ | ||
| 54 | + { | ||
| 55 | + field: 'id', | ||
| 56 | + label: 'ID', | ||
| 57 | + component: 'Input', | ||
| 58 | + show: false | ||
| 59 | + }, | ||
| 60 | + { | ||
| 61 | + field: 'question', | ||
| 62 | + label: '问题', | ||
| 63 | + component: 'InputTextArea', | ||
| 64 | + required: true, | ||
| 65 | + colProps: { span: 24 } | ||
| 66 | + }, | ||
| 67 | + { | ||
| 68 | + field: 'answer', | ||
| 69 | + label: '回答', | ||
| 70 | + component: 'InputTextArea', | ||
| 71 | + required: true, | ||
| 72 | + colProps: { span: 24 } | ||
| 73 | + }, | ||
| 74 | + { | ||
| 75 | + field: 'text', | ||
| 76 | + label: '原文', | ||
| 77 | + component: 'InputTextArea', | ||
| 78 | + colProps: { span: 24 } | ||
| 79 | + }, | ||
| 80 | + { | ||
| 81 | + field: 'metadata', | ||
| 82 | + label: '元数据', | ||
| 83 | + component: 'InputTextArea', | ||
| 84 | + colProps: { span: 24 } | ||
| 85 | + } | ||
| 86 | +]; | ||
| 87 | + | ||
| 88 | +export const superQuerySchema = { | ||
| 89 | + question: { title: '问题', order: 0, view: 'text', type: 'string' }, | ||
| 90 | + answer: { title: '回答', order: 1, view: 'text', type: 'string' } | ||
| 91 | +}; | ||
| 92 | + | ||
| 93 | +export function getBpmFormSchema(_formData): FormSchema[] { | ||
| 94 | + return formSchema; | ||
| 95 | +} |
| 1 | +<template> | ||
| 2 | + <div> | ||
| 3 | + <BasicTable @register="registerTable" :rowSelection="rowSelection"> | ||
| 4 | + <template #tableTitle> | ||
| 5 | + <a-button type="primary" @click="handleAdd" preIcon="ant-design:plus-outlined">新增</a-button> | ||
| 6 | + <a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button> | ||
| 7 | + <j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button> | ||
| 8 | + <a-dropdown v-if="selectedRowKeys.length > 0"> | ||
| 9 | + <template #overlay> | ||
| 10 | + <a-menu> | ||
| 11 | + <a-menu-item key="1" @click="batchHandleDelete"> | ||
| 12 | + <Icon icon="ant-design:delete-outlined" />删除 | ||
| 13 | + </a-menu-item> | ||
| 14 | + </a-menu> | ||
| 15 | + </template> | ||
| 16 | + <a-button>批量操作<Icon icon="mdi:chevron-down" /></a-button> | ||
| 17 | + </a-dropdown> | ||
| 18 | + <super-query :config="superQueryConfig" @search="handleSuperQuery" /> | ||
| 19 | + </template> | ||
| 20 | + <template #action="{ record }"> | ||
| 21 | + <TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" /> | ||
| 22 | + </template> | ||
| 23 | + </BasicTable> | ||
| 24 | + <QuestionEmbeddingModal @register="registerModal" @success="handleSuccess" /> | ||
| 25 | + </div> | ||
| 26 | +</template> | ||
| 27 | + | ||
| 28 | +<script lang="ts" setup> | ||
| 29 | +import { ref, reactive } from 'vue'; | ||
| 30 | +import { BasicTable, useTable, TableAction } from '/@/components/Table'; | ||
| 31 | +import { useModal } from '/@/components/Modal'; | ||
| 32 | +import { useListPage } from '/@/hooks/system/useListPage'; | ||
| 33 | +import QuestionEmbeddingModal from './components/QuestionEmbeddingModal.vue'; | ||
| 34 | +import { columns, searchFormSchema, superQuerySchema } from './QuestionEmbedding.data'; | ||
| 35 | +import { list, deleteOne, batchDelete, getImportZipUrl, getExportUrl } from './QuestionEmbedding.api'; | ||
| 36 | + | ||
| 37 | +const queryParam = reactive<any>({}); | ||
| 38 | +const [registerModal, { openModal }] = useModal(); | ||
| 39 | + | ||
| 40 | +const { tableContext, onExportXls, onImportXls } = useListPage({ | ||
| 41 | + tableProps: { | ||
| 42 | + title: '问答向量库', | ||
| 43 | + api: list, | ||
| 44 | + columns, | ||
| 45 | + formConfig: { | ||
| 46 | + schemas: searchFormSchema, | ||
| 47 | + autoSubmitOnEnter: true, | ||
| 48 | + showAdvancedButton: true | ||
| 49 | + }, | ||
| 50 | + actionColumn: { | ||
| 51 | + width: 120, | ||
| 52 | + fixed: 'right' | ||
| 53 | + }, | ||
| 54 | + beforeFetch: (params) => Object.assign(params, queryParam) | ||
| 55 | + }, | ||
| 56 | + exportConfig: { | ||
| 57 | + name: '问答向量库', | ||
| 58 | + url: getExportUrl | ||
| 59 | + }, | ||
| 60 | + importConfig: { | ||
| 61 | + url: getImportZipUrl, | ||
| 62 | + success: handleSuccess | ||
| 63 | + } | ||
| 64 | +}); | ||
| 65 | + | ||
| 66 | +const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext; | ||
| 67 | +const superQueryConfig = reactive(superQuerySchema); | ||
| 68 | + | ||
| 69 | +function handleSuperQuery(params) { | ||
| 70 | + Object.assign(queryParam, params); | ||
| 71 | + reload(); | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +function handleAdd() { | ||
| 75 | + openModal(true, { isUpdate: false, showFooter: true }); | ||
| 76 | +} | ||
| 77 | + | ||
| 78 | +function handleEdit(record) { | ||
| 79 | + openModal(true, { record, isUpdate: true, showFooter: true }); | ||
| 80 | +} | ||
| 81 | + | ||
| 82 | +function handleDetail(record) { | ||
| 83 | + openModal(true, { record, isUpdate: true, showFooter: false }); | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +async function handleDelete(record) { | ||
| 87 | + await deleteOne({ id: record.id }, handleSuccess); | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +async function batchHandleDelete() { | ||
| 91 | + await batchDelete({ ids: selectedRowKeys.value }, handleSuccess); | ||
| 92 | +} | ||
| 93 | + | ||
| 94 | +function handleSuccess() { | ||
| 95 | + selectedRowKeys.value = []; | ||
| 96 | + reload(); | ||
| 97 | +} | ||
| 98 | + | ||
| 99 | +function getTableAction(record) { | ||
| 100 | + return [ | ||
| 101 | + { label: '编辑', onClick: handleEdit.bind(null, record) } | ||
| 102 | + ]; | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +function getDropDownAction(record) { | ||
| 106 | + return [ | ||
| 107 | + { label: '详情', onClick: handleDetail.bind(null, record) }, | ||
| 108 | + { | ||
| 109 | + label: '删除', | ||
| 110 | + popConfirm: { | ||
| 111 | + title: '确认删除此问答?', | ||
| 112 | + confirm: handleDelete.bind(null, record) | ||
| 113 | + } | ||
| 114 | + } | ||
| 115 | + ]; | ||
| 116 | +} | ||
| 117 | +</script> |
| 1 | +<template> | ||
| 2 | + <BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="800" @ok="handleSubmit"> | ||
| 3 | + <BasicForm @register="registerForm" name="QuestionEmbeddingForm" /> | ||
| 4 | + </BasicModal> | ||
| 5 | +</template> | ||
| 6 | + | ||
| 7 | +<script lang="ts" setup> | ||
| 8 | +import { ref, computed, unref } from 'vue'; | ||
| 9 | +import { BasicModal, useModalInner } from '/@/components/Modal'; | ||
| 10 | +import { BasicForm, useForm } from '/@/components/Form/index'; | ||
| 11 | +import { formSchema } from '../QuestionEmbedding.data'; | ||
| 12 | +import { saveOrUpdate } from '../QuestionEmbedding.api'; | ||
| 13 | + | ||
| 14 | +const emit = defineEmits(['register', 'success']); | ||
| 15 | +const isUpdate = ref(true); | ||
| 16 | +const isDetail = ref(false); | ||
| 17 | + | ||
| 18 | +const [registerForm, { setProps, resetFields, setFieldsValue, validate }] = useForm({ | ||
| 19 | + labelWidth: 150, | ||
| 20 | + schemas: formSchema, | ||
| 21 | + showActionButtonGroup: false, | ||
| 22 | + baseColProps: { span: 24 } | ||
| 23 | +}); | ||
| 24 | + | ||
| 25 | +const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { | ||
| 26 | + await resetFields(); | ||
| 27 | + setModalProps({ | ||
| 28 | + confirmLoading: false, | ||
| 29 | + showCancelBtn: !!data?.showFooter, | ||
| 30 | + showOkBtn: !!data?.showFooter | ||
| 31 | + }); | ||
| 32 | + | ||
| 33 | + isUpdate.value = !!data?.isUpdate; | ||
| 34 | + isDetail.value = !!data?.showFooter; | ||
| 35 | + | ||
| 36 | + if (unref(isUpdate)) { | ||
| 37 | + await setFieldsValue({ | ||
| 38 | + ...data.record, | ||
| 39 | + }); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + setProps({ disabled: !data?.showFooter }); | ||
| 43 | +}); | ||
| 44 | + | ||
| 45 | +const title = computed(() => (!unref(isUpdate) ? '新增问答' : !unref(isDetail) ? '问答详情' : '编辑问答')); | ||
| 46 | + | ||
| 47 | +async function handleSubmit() { | ||
| 48 | + try { | ||
| 49 | + const values = await validate(); | ||
| 50 | + setModalProps({ confirmLoading: true }); | ||
| 51 | + await saveOrUpdate(values, isUpdate.value); | ||
| 52 | + closeModal(); | ||
| 53 | + emit('success'); | ||
| 54 | + } finally { | ||
| 55 | + setModalProps({ confirmLoading: false }); | ||
| 56 | + } | ||
| 57 | +} | ||
| 58 | +</script> |
| @@ -12,20 +12,53 @@ enum Api { | @@ -12,20 +12,53 @@ enum Api { | ||
| 12 | importExcel = '/embeddings/embeddings/importExcel', | 12 | importExcel = '/embeddings/embeddings/importExcel', |
| 13 | exportXls = '/embeddings/embeddings/exportXls', | 13 | exportXls = '/embeddings/embeddings/exportXls', |
| 14 | } | 14 | } |
| 15 | + | ||
| 15 | /** | 16 | /** |
| 16 | * 导出api | 17 | * 导出api |
| 17 | * @param params | 18 | * @param params |
| 18 | */ | 19 | */ |
| 19 | export const getExportUrl = Api.exportXls; | 20 | export const getExportUrl = Api.exportXls; |
| 21 | + | ||
| 20 | /** | 22 | /** |
| 21 | * 导入api | 23 | * 导入api |
| 22 | */ | 24 | */ |
| 23 | export const getImportUrl = Api.importExcel; | 25 | export const getImportUrl = Api.importExcel; |
| 26 | + | ||
| 24 | /** | 27 | /** |
| 25 | * 列表接口 | 28 | * 列表接口 |
| 26 | * @param params | 29 | * @param params |
| 27 | */ | 30 | */ |
| 28 | -export const list = (params) => defHttp.get({ url: Api.list, params }); | 31 | +export const list = async (params) => { |
| 32 | + try { | ||
| 33 | + // 设置足够大的size获取所有数据 | ||
| 34 | + const res = await defHttp.get({ | ||
| 35 | + url: Api.list, | ||
| 36 | + params: { ...params, size: 1000 } | ||
| 37 | + }); | ||
| 38 | + | ||
| 39 | + if (res?.records && Array.isArray(res.records)) { | ||
| 40 | + // 将metadata中的字段提取到顶层 | ||
| 41 | + res.records = res.records.map(item => ({ | ||
| 42 | + ...item, | ||
| 43 | + ...item.metadata, // 将metadata中的字段展开 | ||
| 44 | + docName: item.metadata?.docName || '', | ||
| 45 | + knowledgeId: item.metadata?.knowledgeId || '', | ||
| 46 | + docId: item.metadata?.docId || '', | ||
| 47 | + index: item.metadata?.index || '', | ||
| 48 | + })); | ||
| 49 | + } | ||
| 50 | + return res; | ||
| 51 | + } catch (error) { | ||
| 52 | + console.error("Error fetching data:", error); | ||
| 53 | + return { | ||
| 54 | + records: [], | ||
| 55 | + total: 0, | ||
| 56 | + size: 10, | ||
| 57 | + current: 1, | ||
| 58 | + pages: 0 | ||
| 59 | + }; | ||
| 60 | + } | ||
| 61 | +} | ||
| 29 | 62 | ||
| 30 | /** | 63 | /** |
| 31 | * 删除单个 | 64 | * 删除单个 |
| @@ -35,6 +68,7 @@ export const deleteOne = (params, handleSuccess) => { | @@ -35,6 +68,7 @@ export const deleteOne = (params, handleSuccess) => { | ||
| 35 | handleSuccess(); | 68 | handleSuccess(); |
| 36 | }); | 69 | }); |
| 37 | }; | 70 | }; |
| 71 | + | ||
| 38 | /** | 72 | /** |
| 39 | * 批量删除 | 73 | * 批量删除 |
| 40 | * @param params | 74 | * @param params |
| @@ -53,6 +87,7 @@ export const batchDelete = (params, handleSuccess) => { | @@ -53,6 +87,7 @@ export const batchDelete = (params, handleSuccess) => { | ||
| 53 | }, | 87 | }, |
| 54 | }); | 88 | }); |
| 55 | }; | 89 | }; |
| 90 | + | ||
| 56 | /** | 91 | /** |
| 57 | * 保存或者更新 | 92 | * 保存或者更新 |
| 58 | * @param params | 93 | * @param params |
| 1 | import { BasicColumn } from '/@/components/Table'; | 1 | import { BasicColumn } from '/@/components/Table'; |
| 2 | import { FormSchema } from '/@/components/Table'; | 2 | import { FormSchema } from '/@/components/Table'; |
| 3 | -//列表数据 | 3 | + |
| 4 | +// 列表数据 | ||
| 4 | export const columns: BasicColumn[] = [ | 5 | export const columns: BasicColumn[] = [ |
| 5 | { | 6 | { |
| 6 | - title: 'name', | 7 | + title: 'ID', |
| 8 | + align: 'center', | ||
| 9 | + dataIndex: 'id', | ||
| 10 | + width: 200, | ||
| 11 | + }, | ||
| 12 | + { | ||
| 13 | + title: '文本内容', | ||
| 7 | align: 'center', | 14 | align: 'center', |
| 8 | - dataIndex: 'name', | 15 | + dataIndex: 'text', |
| 16 | + width: 300 | ||
| 17 | + }, | ||
| 18 | + { | ||
| 19 | + title: '文件名称', | ||
| 20 | + align: 'center', | ||
| 21 | + dataIndex: 'docName', | ||
| 22 | + width: 150 | ||
| 23 | + }, | ||
| 24 | + { | ||
| 25 | + title: '知识ID', | ||
| 26 | + align: 'center', | ||
| 27 | + dataIndex: 'knowledgeId', | ||
| 28 | + width: 150 | ||
| 29 | + }, | ||
| 30 | + { | ||
| 31 | + title: '文档ID', | ||
| 32 | + align: 'center', | ||
| 33 | + dataIndex: 'docId', | ||
| 34 | + width: 150 | ||
| 35 | + }, | ||
| 36 | + { | ||
| 37 | + title: '索引', | ||
| 38 | + align: 'center', | ||
| 39 | + dataIndex: 'index', | ||
| 40 | + width: 80 | ||
| 41 | + }, | ||
| 42 | + { | ||
| 43 | + title: '相似度', | ||
| 44 | + align: 'center', | ||
| 45 | + dataIndex: 'similarity', | ||
| 46 | + width: 100 | ||
| 9 | }, | 47 | }, |
| 10 | ]; | 48 | ]; |
| 11 | -//查询数据 | 49 | + |
| 50 | +// 查询数据 | ||
| 12 | export const searchFormSchema: FormSchema[] = []; | 51 | export const searchFormSchema: FormSchema[] = []; |
| 13 | -//表单数据 | 52 | + |
| 53 | +// 表单数据 | ||
| 14 | export const formSchema: FormSchema[] = [ | 54 | export const formSchema: FormSchema[] = [ |
| 15 | { | 55 | { |
| 16 | - label: 'name', | ||
| 17 | - field: 'name', | 56 | + label: 'text', |
| 57 | + field: 'text', | ||
| 18 | component: 'Input', | 58 | component: 'Input', |
| 19 | }, | 59 | }, |
| 20 | - // TODO 主键隐藏字段,目前写死为ID | ||
| 21 | { | 60 | { |
| 22 | label: '', | 61 | label: '', |
| 23 | field: 'id', | 62 | field: 'id', |
| @@ -28,7 +67,7 @@ export const formSchema: FormSchema[] = [ | @@ -28,7 +67,7 @@ export const formSchema: FormSchema[] = [ | ||
| 28 | 67 | ||
| 29 | // 高级查询数据 | 68 | // 高级查询数据 |
| 30 | export const superQuerySchema = { | 69 | export const superQuerySchema = { |
| 31 | - name: { title: 'name', order: 0, view: 'text', type: 'string' }, | 70 | + name: { title: 'text', order: 0, view: 'text', type: 'string' }, |
| 32 | }; | 71 | }; |
| 33 | 72 | ||
| 34 | /** | 73 | /** |
| @@ -36,6 +75,5 @@ export const superQuerySchema = { | @@ -36,6 +75,5 @@ export const superQuerySchema = { | ||
| 36 | * @param param | 75 | * @param param |
| 37 | */ | 76 | */ |
| 38 | export function getBpmFormSchema(_formData): FormSchema[] { | 77 | export function getBpmFormSchema(_formData): FormSchema[] { |
| 39 | - // 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema | ||
| 40 | return formSchema; | 78 | return formSchema; |
| 41 | } | 79 | } |
src/views/super/airag/zdyrag/ZdyRag.api.ts
0 → 100644
| 1 | +import { defHttp } from '/@/utils/http/axios'; | ||
| 2 | +import { useMessage } from '/@/hooks/web/useMessage'; | ||
| 3 | + | ||
| 4 | +const { createMessage } = useMessage(); | ||
| 5 | + | ||
| 6 | +enum Api { | ||
| 7 | + send = '/airag/zdyRag/send', | ||
| 8 | +} | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 发送消息 | ||
| 12 | + * @param params | ||
| 13 | + */ | ||
| 14 | +export const sendMessage = async (params: { questionText: string }) => { | ||
| 15 | + try { | ||
| 16 | + const res = await defHttp.get({ | ||
| 17 | + url: Api.send, | ||
| 18 | + params, | ||
| 19 | + }); | ||
| 20 | + return res; | ||
| 21 | + } catch (error) { | ||
| 22 | + console.error("Error sending message:", error); | ||
| 23 | + createMessage.error('发送消息失败'); | ||
| 24 | + return null; | ||
| 25 | + } | ||
| 26 | +}; | ||
| 27 | + | ||
| 28 | +// export const sendMessage = (params) => { | ||
| 29 | +// return defHttp.get({ url: Api.send, params }, { isTransformResponse: false }); | ||
| 30 | +// }; | ||
| 31 | + | ||
| 32 | +// 保留其他API方法,但聊天界面可能不需要它们 | ||
| 33 | +export const list = async () => ({ records: [] }); | ||
| 34 | +export const deleteOne = async () => {}; | ||
| 35 | +export const batchDelete = async () => {}; | ||
| 36 | +export const getImportUrl = Api.send; | ||
| 37 | +export const getExportUrl = Api.send; |
src/views/super/airag/zdyrag/ZdyRag.data.ts
0 → 100644
| 1 | +import { BasicColumn } from '/@/components/Table'; | ||
| 2 | +import { FormSchema } from '/@/components/Table'; | ||
| 3 | + | ||
| 4 | +// 列表数据 | ||
| 5 | +export const columns: BasicColumn[] = [ | ||
| 6 | + { | ||
| 7 | + title: 'ID', | ||
| 8 | + align: 'center', | ||
| 9 | + dataIndex: 'id', | ||
| 10 | + width: 200, | ||
| 11 | + }, | ||
| 12 | + { | ||
| 13 | + title: '文本内容', | ||
| 14 | + align: 'center', | ||
| 15 | + dataIndex: 'text', | ||
| 16 | + width: 300 | ||
| 17 | + }, | ||
| 18 | + { | ||
| 19 | + title: '文件名称', | ||
| 20 | + align: 'center', | ||
| 21 | + dataIndex: 'docName', | ||
| 22 | + width: 150 | ||
| 23 | + }, | ||
| 24 | + { | ||
| 25 | + title: '知识ID', | ||
| 26 | + align: 'center', | ||
| 27 | + dataIndex: 'knowledgeId', | ||
| 28 | + width: 150 | ||
| 29 | + }, | ||
| 30 | + { | ||
| 31 | + title: '文档ID', | ||
| 32 | + align: 'center', | ||
| 33 | + dataIndex: 'docId', | ||
| 34 | + width: 150 | ||
| 35 | + }, | ||
| 36 | + { | ||
| 37 | + title: '索引', | ||
| 38 | + align: 'center', | ||
| 39 | + dataIndex: 'index', | ||
| 40 | + width: 80 | ||
| 41 | + }, | ||
| 42 | + { | ||
| 43 | + title: '相似度', | ||
| 44 | + align: 'center', | ||
| 45 | + dataIndex: 'similarity', | ||
| 46 | + width: 100 | ||
| 47 | + }, | ||
| 48 | +]; | ||
| 49 | + | ||
| 50 | +// 查询数据 | ||
| 51 | +export const searchFormSchema: FormSchema[] = []; | ||
| 52 | + | ||
| 53 | +// 表单数据 | ||
| 54 | +export const formSchema: FormSchema[] = [ | ||
| 55 | + { | ||
| 56 | + label: 'text', | ||
| 57 | + field: 'text', | ||
| 58 | + component: 'Input', | ||
| 59 | + }, | ||
| 60 | + { | ||
| 61 | + label: '', | ||
| 62 | + field: 'id', | ||
| 63 | + component: 'Input', | ||
| 64 | + show: false, | ||
| 65 | + }, | ||
| 66 | +]; | ||
| 67 | + | ||
| 68 | +// 高级查询数据 | ||
| 69 | +export const superQuerySchema = { | ||
| 70 | + name: { title: 'text', order: 0, view: 'text', type: 'string' }, | ||
| 71 | +}; | ||
| 72 | + | ||
| 73 | +/** | ||
| 74 | + * 流程表单调用这个方法获取formSchema | ||
| 75 | + * @param param | ||
| 76 | + */ | ||
| 77 | +export function getBpmFormSchema(_formData): FormSchema[] { | ||
| 78 | + return formSchema; | ||
| 79 | +} |
src/views/super/airag/zdyrag/ZdyRagList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="chat-container"> | ||
| 3 | + <!-- 聊天消息区域 --> | ||
| 4 | + <div class="chat-messages" ref="messagesContainer"> | ||
| 5 | + <div v-for="(message, index) in messages" :key="index" :class="['message', message.type]"> | ||
| 6 | + <div class="message-content"> | ||
| 7 | + <div class="message-text">{{ message.text }}</div> | ||
| 8 | + <div v-if="message.type === 'assistant' && message.similarity" class="message-meta"> | ||
| 9 | + 相似度: {{ (message.similarity * 100).toFixed(2) }}% | ||
| 10 | + </div> | ||
| 11 | + </div> | ||
| 12 | + </div> | ||
| 13 | + <div v-if="loading" class="message assistant"> | ||
| 14 | + <div class="message-content"> | ||
| 15 | + <div class="message-text">思考中...</div> | ||
| 16 | + </div> | ||
| 17 | + </div> | ||
| 18 | + </div> | ||
| 19 | + | ||
| 20 | + <!-- 输入区域 --> | ||
| 21 | + <div class="chat-input"> | ||
| 22 | + <a-input | ||
| 23 | + v-model:value="inputMessage" | ||
| 24 | + placeholder="请输入您的问题..." | ||
| 25 | + @pressEnter="sendMessage" | ||
| 26 | + :disabled="loading" | ||
| 27 | + /> | ||
| 28 | + <a-button | ||
| 29 | + type="primary" | ||
| 30 | + @click="sendMessage" | ||
| 31 | + :loading="loading" | ||
| 32 | + :disabled="!inputMessage.trim()" | ||
| 33 | + > | ||
| 34 | + 发送 | ||
| 35 | + </a-button> | ||
| 36 | + </div> | ||
| 37 | + </div> | ||
| 38 | +</template> | ||
| 39 | + | ||
| 40 | +<script lang="ts" name="zdy-rag-chat" setup> | ||
| 41 | +import { ref, reactive, nextTick } from 'vue'; | ||
| 42 | +import { list, sendMessage as apiSendMessage } from './ZdyRag.api'; | ||
| 43 | +import { BasicTable, useTable } from '/@/components/Table'; | ||
| 44 | +import { useMessage } from '/@/hooks/web/useMessage'; | ||
| 45 | + | ||
| 46 | +const { createMessage } = useMessage(); | ||
| 47 | + | ||
| 48 | +// 聊天消息数据 | ||
| 49 | +const messages = reactive([ | ||
| 50 | + { | ||
| 51 | + type: 'assistant', | ||
| 52 | + text: '您好!我是智能助手,请问有什么可以帮您?', | ||
| 53 | + similarity: null | ||
| 54 | + } | ||
| 55 | +]); | ||
| 56 | + | ||
| 57 | +const inputMessage = ref(''); | ||
| 58 | +const loading = ref(false); | ||
| 59 | +const messagesContainer = ref<HTMLElement>(); | ||
| 60 | + | ||
| 61 | +// 发送消息 | ||
| 62 | +const sendMessage = async () => { | ||
| 63 | + const text = inputMessage.value.trim(); | ||
| 64 | + if (!text || loading.value) return; | ||
| 65 | + | ||
| 66 | + // 添加用户消息 | ||
| 67 | + messages.push({ | ||
| 68 | + type: 'user', | ||
| 69 | + text: text, | ||
| 70 | + similarity: null | ||
| 71 | + }); | ||
| 72 | + | ||
| 73 | + inputMessage.value = ''; | ||
| 74 | + loading.value = true; | ||
| 75 | + | ||
| 76 | + try { | ||
| 77 | + // 调用API发送消息 | ||
| 78 | + const res = await apiSendMessage({ questionText: text }); | ||
| 79 | + console.log("res....",res) | ||
| 80 | + if (res?.answer) { | ||
| 81 | + messages.push({ | ||
| 82 | + type: 'assistant', | ||
| 83 | + text: res.answer, | ||
| 84 | + similarity: res.similarity || null | ||
| 85 | + }); | ||
| 86 | + } else { | ||
| 87 | + createMessage.error('获取回答失败'); | ||
| 88 | + } | ||
| 89 | + } catch (error) { | ||
| 90 | + console.error('发送消息失败:', error); | ||
| 91 | + createMessage.error('发送消息失败'); | ||
| 92 | + } finally { | ||
| 93 | + loading.value = false; | ||
| 94 | + scrollToBottom(); | ||
| 95 | + } | ||
| 96 | +}; | ||
| 97 | + | ||
| 98 | +// 滚动到底部 | ||
| 99 | +const scrollToBottom = () => { | ||
| 100 | + nextTick(() => { | ||
| 101 | + if (messagesContainer.value) { | ||
| 102 | + messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; | ||
| 103 | + } | ||
| 104 | + }); | ||
| 105 | +}; | ||
| 106 | +</script> | ||
| 107 | + | ||
| 108 | +<style lang="less" scoped> | ||
| 109 | +.chat-container { | ||
| 110 | + display: flex; | ||
| 111 | + flex-direction: column; | ||
| 112 | + height: calc(100vh - 120px); | ||
| 113 | + max-width: 800px; | ||
| 114 | + margin: 0 auto; | ||
| 115 | + padding: 20px; | ||
| 116 | + background-color: #f5f5f5; | ||
| 117 | + border-radius: 8px; | ||
| 118 | +} | ||
| 119 | + | ||
| 120 | +.chat-messages { | ||
| 121 | + flex: 1; | ||
| 122 | + overflow-y: auto; | ||
| 123 | + padding: 10px; | ||
| 124 | + margin-bottom: 20px; | ||
| 125 | + background-color: white; | ||
| 126 | + border-radius: 8px; | ||
| 127 | + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +.message { | ||
| 131 | + margin-bottom: 15px; | ||
| 132 | + display: flex; | ||
| 133 | + | ||
| 134 | + &.user { | ||
| 135 | + justify-content: flex-end; | ||
| 136 | + | ||
| 137 | + .message-content { | ||
| 138 | + background-color: #1890ff; | ||
| 139 | + color: white; | ||
| 140 | + border-radius: 18px 18px 0 18px; | ||
| 141 | + } | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + &.assistant { | ||
| 145 | + justify-content: flex-start; | ||
| 146 | + | ||
| 147 | + .message-content { | ||
| 148 | + background-color: #f0f0f0; | ||
| 149 | + color: #333; | ||
| 150 | + border-radius: 18px 18px 18px 0; | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | +} | ||
| 154 | + | ||
| 155 | +.message-content { | ||
| 156 | + max-width: 70%; | ||
| 157 | + padding: 10px 15px; | ||
| 158 | + word-wrap: break-word; | ||
| 159 | +} | ||
| 160 | + | ||
| 161 | +.message-meta { | ||
| 162 | + font-size: 12px; | ||
| 163 | + color: #666; | ||
| 164 | + margin-top: 5px; | ||
| 165 | + text-align: right; | ||
| 166 | +} | ||
| 167 | + | ||
| 168 | +.chat-input { | ||
| 169 | + display: flex; | ||
| 170 | + gap: 10px; | ||
| 171 | + | ||
| 172 | + :deep(.ant-input) { | ||
| 173 | + flex: 1; | ||
| 174 | + border-radius: 20px; | ||
| 175 | + padding: 10px 15px; | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + .ant-btn { | ||
| 179 | + border-radius: 20px; | ||
| 180 | + padding: 0 20px; | ||
| 181 | + } | ||
| 182 | +} | ||
| 183 | +</style> |
| 1 | +<template> | ||
| 2 | + <div style="min-height: 400px"> | ||
| 3 | + <BasicForm @register="registerForm"></BasicForm> | ||
| 4 | + <div style="width: 100%;text-align: center" v-if="!formDisabled"> | ||
| 5 | + <a-button @click="submitForm" pre-icon="ant-design:check" type="primary">提 交</a-button> | ||
| 6 | + </div> | ||
| 7 | + </div> | ||
| 8 | +</template> | ||
| 9 | + | ||
| 10 | +<script lang="ts"> | ||
| 11 | + import {BasicForm, useForm} from '/@/components/Form/index'; | ||
| 12 | + import {computed, defineComponent} from 'vue'; | ||
| 13 | + import {defHttp} from '/@/utils/http/axios'; | ||
| 14 | + import { propTypes } from '/@/utils/propTypes'; | ||
| 15 | + import {getBpmFormSchema} from '../ZdyRag.data'; | ||
| 16 | + import {saveOrUpdate} from '../ZdyRag.api'; | ||
| 17 | + | ||
| 18 | + export default defineComponent({ | ||
| 19 | + name: "TestForm", | ||
| 20 | + components:{ | ||
| 21 | + BasicForm | ||
| 22 | + }, | ||
| 23 | + props:{ | ||
| 24 | + formData: propTypes.object.def({}), | ||
| 25 | + formBpm: propTypes.bool.def(true), | ||
| 26 | + }, | ||
| 27 | + setup(props){ | ||
| 28 | + const [registerForm, { setFieldsValue, setProps, getFieldsValue }] = useForm({ | ||
| 29 | + labelWidth: 150, | ||
| 30 | + schemas: getBpmFormSchema(props.formData), | ||
| 31 | + showActionButtonGroup: false, | ||
| 32 | + baseColProps: {span: 24} | ||
| 33 | + }); | ||
| 34 | + | ||
| 35 | + const formDisabled = computed(()=>{ | ||
| 36 | + if(props.formData.disabled === false){ | ||
| 37 | + return false; | ||
| 38 | + } | ||
| 39 | + return true; | ||
| 40 | + }); | ||
| 41 | + | ||
| 42 | + let formData = {}; | ||
| 43 | + const queryByIdUrl = '/test/test/queryById'; | ||
| 44 | + async function initFormData(){ | ||
| 45 | + let params = {id: props.formData.dataId}; | ||
| 46 | + const data = await defHttp.get({url: queryByIdUrl, params}); | ||
| 47 | + formData = {...data} | ||
| 48 | + //设置表单的值 | ||
| 49 | + await setFieldsValue(formData); | ||
| 50 | + //默认是禁用 | ||
| 51 | + await setProps({disabled: formDisabled.value}) | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + async function submitForm() { | ||
| 55 | + let data = getFieldsValue(); | ||
| 56 | + let params = Object.assign({}, formData, data); | ||
| 57 | + console.log('表单数据', params) | ||
| 58 | + await saveOrUpdate(params, true) | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + initFormData(); | ||
| 62 | + | ||
| 63 | + return { | ||
| 64 | + registerForm, | ||
| 65 | + formDisabled, | ||
| 66 | + submitForm, | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | + }); | ||
| 70 | +</script> |
| 1 | +<template> | ||
| 2 | + <BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="800" @ok="handleSubmit"> | ||
| 3 | + <BasicForm @register="registerForm" name="TestForm" /> | ||
| 4 | + </BasicModal> | ||
| 5 | +</template> | ||
| 6 | + | ||
| 7 | +<script lang="ts" setup> | ||
| 8 | + import {ref, computed, unref} from 'vue'; | ||
| 9 | + import {BasicModal, useModalInner} from '/@/components/Modal'; | ||
| 10 | + import {BasicForm, useForm} from '/@/components/Form/index'; | ||
| 11 | + import {formSchema} from '../ZdyRag.data'; | ||
| 12 | + import {saveOrUpdate} from '../ZdyRag.api'; | ||
| 13 | + // Emits声明 | ||
| 14 | + const emit = defineEmits(['register','success']); | ||
| 15 | + const isUpdate = ref(true); | ||
| 16 | + const isDetail = ref(false); | ||
| 17 | + //表单配置 | ||
| 18 | + const [registerForm, { setProps,resetFields, setFieldsValue, validate, scrollToField }] = useForm({ | ||
| 19 | + labelWidth: 150, | ||
| 20 | + schemas: formSchema, | ||
| 21 | + showActionButtonGroup: false, | ||
| 22 | + baseColProps: {span: 24} | ||
| 23 | + }); | ||
| 24 | + //表单赋值 | ||
| 25 | + const [registerModal, {setModalProps, closeModal}] = useModalInner(async (data) => { | ||
| 26 | + //重置表单 | ||
| 27 | + await resetFields(); | ||
| 28 | + setModalProps({confirmLoading: false,showCancelBtn:!!data?.showFooter,showOkBtn:!!data?.showFooter}); | ||
| 29 | + isUpdate.value = !!data?.isUpdate; | ||
| 30 | + isDetail.value = !!data?.showFooter; | ||
| 31 | + if (unref(isUpdate)) { | ||
| 32 | + //表单赋值 | ||
| 33 | + await setFieldsValue({ | ||
| 34 | + ...data.record, | ||
| 35 | + }); | ||
| 36 | + } | ||
| 37 | + // 隐藏底部时禁用整个表单 | ||
| 38 | + setProps({ disabled: !data?.showFooter }) | ||
| 39 | + }); | ||
| 40 | + //设置标题 | ||
| 41 | + const title = computed(() => (!unref(isUpdate) ? '新增' : !unref(isDetail) ? '详情' : '编辑')); | ||
| 42 | + //表单提交事件 | ||
| 43 | + async function handleSubmit(v) { | ||
| 44 | + try { | ||
| 45 | + let values = await validate(); | ||
| 46 | + setModalProps({confirmLoading: true}); | ||
| 47 | + //提交表单 | ||
| 48 | + await saveOrUpdate(values, isUpdate.value); | ||
| 49 | + //关闭弹窗 | ||
| 50 | + closeModal(); | ||
| 51 | + //刷新列表 | ||
| 52 | + emit('success'); | ||
| 53 | + } catch ({ errorFields }) { | ||
| 54 | + if (errorFields) { | ||
| 55 | + const firstField = errorFields[0]; | ||
| 56 | + if (firstField) { | ||
| 57 | + scrollToField(firstField.name, { behavior: 'smooth', block: 'center' }); | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + return Promise.reject(errorFields); | ||
| 61 | + } finally { | ||
| 62 | + setModalProps({confirmLoading: false}); | ||
| 63 | + } | ||
| 64 | + } | ||
| 65 | +</script> | ||
| 66 | + | ||
| 67 | +<style lang="less" scoped> | ||
| 68 | + /** 时间和数字输入框样式 */ | ||
| 69 | + :deep(.ant-input-number) { | ||
| 70 | + width: 100%; | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + :deep(.ant-calendar-picker) { | ||
| 74 | + width: 100%; | ||
| 75 | + } | ||
| 76 | +</style> |
-
请 注册 或 登录 后发表评论