From b82b38940b74a0a8c06f9aadc68f905940a26bea Mon Sep 17 00:00:00 2001 From: XIE7654 <765462425@qq.com> Date: Tue, 15 Jul 2025 10:21:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=9F=A5=E8=AF=86=E5=BA=93?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/ai/urls.py | 1 + backend/ai/views/__init__.py | 4 +- backend/ai/views/knowledge.py | 24 +++ .../web-antd/src/locales/langs/en-US/ai.json | 4 + .../web-antd/src/locales/langs/zh-CN/ai.json | 4 + web/apps/web-antd/src/models/ai/knowledge.ts | 26 +++ .../web-antd/src/views/ai/ai_model/data.ts | 70 ++++---- .../web-antd/src/views/ai/knowledge/data.ts | 149 ++++++++++++++++++ .../web-antd/src/views/ai/knowledge/list.vue | 141 +++++++++++++++++ .../src/views/ai/knowledge/modules/form.vue | 79 ++++++++++ .../src/views/system/dict_data/data.ts | 24 +-- .../views/system/dict_data/modules/form.vue | 2 +- .../src/views/system/dict_type/data.ts | 10 +- .../views/system/dict_type/modules/form.vue | 2 +- 14 files changed, 474 insertions(+), 66 deletions(-) create mode 100644 backend/ai/views/knowledge.py create mode 100644 web/apps/web-antd/src/models/ai/knowledge.ts create mode 100644 web/apps/web-antd/src/views/ai/knowledge/data.ts create mode 100644 web/apps/web-antd/src/views/ai/knowledge/list.vue create mode 100644 web/apps/web-antd/src/views/ai/knowledge/modules/form.vue diff --git a/backend/ai/urls.py b/backend/ai/urls.py index 3da09a7..73a489d 100644 --- a/backend/ai/urls.py +++ b/backend/ai/urls.py @@ -7,6 +7,7 @@ router = routers.DefaultRouter() router.register(r'ai_api_key', views.AIApiKeyViewSet) router.register(r'ai_model', views.AIModelViewSet) router.register(r'tool', views.ToolViewSet) +router.register(r'knowledge', views.KnowledgeViewSet) urlpatterns = [ path('', include(router.urls)), diff --git a/backend/ai/views/__init__.py b/backend/ai/views/__init__.py index 92691aa..66c8d61 100644 --- a/backend/ai/views/__init__.py +++ b/backend/ai/views/__init__.py @@ -2,8 +2,10 @@ __all__ = [ 'AIApiKeyViewSet', 'AIModelViewSet', 'ToolViewSet', + 'KnowledgeViewSet', ] from ai.views.ai_api_key import AIApiKeyViewSet from ai.views.ai_model import AIModelViewSet -from ai.views.tool import ToolViewSet \ No newline at end of file +from ai.views.tool import ToolViewSet +from ai.views.knowledge import KnowledgeViewSet \ No newline at end of file diff --git a/backend/ai/views/knowledge.py b/backend/ai/views/knowledge.py new file mode 100644 index 0000000..ad419db --- /dev/null +++ b/backend/ai/views/knowledge.py @@ -0,0 +1,24 @@ +from ai.models import Knowledge +from utils.serializers import CustomModelSerializer +from utils.custom_model_viewSet import CustomModelViewSet + +class KnowledgeSerializer(CustomModelSerializer): + """ + AI 知识库 序列化器 + """ + class Meta: + model = Knowledge + fields = '__all__' + read_only_fields = ['id', 'create_time', 'update_time'] + + +class KnowledgeViewSet(CustomModelViewSet): + """ + AI 知识库 视图集 + """ + queryset = Knowledge.objects.filter(is_deleted=False).order_by('-id') + serializer_class = KnowledgeSerializer + filterset_fields = ['id', 'remark', 'creator', 'modifier', 'is_deleted', 'name', 'embedding_model', 'top_k', 'status'] + search_fields = ['name'] # 根据实际字段调整 + ordering_fields = ['create_time', 'id'] + ordering = ['-create_time'] diff --git a/web/apps/web-antd/src/locales/langs/en-US/ai.json b/web/apps/web-antd/src/locales/langs/en-US/ai.json index 1a6b41a..2505bb1 100644 --- a/web/apps/web-antd/src/locales/langs/en-US/ai.json +++ b/web/apps/web-antd/src/locales/langs/en-US/ai.json @@ -11,5 +11,9 @@ "tool": { "title": "TOOL Management", "name": "TOOL Management" + }, + "knowledge": { + "title": "KNOWLEDGE Management", + "name": "KNOWLEDGE Management" } } diff --git a/web/apps/web-antd/src/locales/langs/zh-CN/ai.json b/web/apps/web-antd/src/locales/langs/zh-CN/ai.json index f093252..47116d7 100644 --- a/web/apps/web-antd/src/locales/langs/zh-CN/ai.json +++ b/web/apps/web-antd/src/locales/langs/zh-CN/ai.json @@ -11,5 +11,9 @@ "tool": { "title": "工具管理", "name": "工具管理" + }, + "knowledge": { + "title": "知识库管理", + "name": "知识库管理" } } diff --git a/web/apps/web-antd/src/models/ai/knowledge.ts b/web/apps/web-antd/src/models/ai/knowledge.ts new file mode 100644 index 0000000..72cfed8 --- /dev/null +++ b/web/apps/web-antd/src/models/ai/knowledge.ts @@ -0,0 +1,26 @@ +import { BaseModel } from '#/models/base'; + +export namespace AiKnowledgeApi { + export interface AiKnowledge { + id: number; + remark: string; + creator: string; + modifier: string; + update_time: string; + create_time: string; + is_deleted: boolean; + name: string; + description: string; + embedding_model_id: number; + embedding_model: string; + top_k: number; + similarity_threshold: any; + status: number; + } +} + +export class AiKnowledgeModel extends BaseModel { + constructor() { + super('/ai/knowledge/'); + } +} diff --git a/web/apps/web-antd/src/views/ai/ai_model/data.ts b/web/apps/web-antd/src/views/ai/ai_model/data.ts index f3d0937..f340860 100644 --- a/web/apps/web-antd/src/views/ai/ai_model/data.ts +++ b/web/apps/web-antd/src/views/ai/ai_model/data.ts @@ -16,6 +16,27 @@ const AiKeyModel = new AiAIApiKeyModel(); */ export function useSchema(): VbenFormSchema[] { return [ + { + component: 'ApiSelect', + componentProps: { + api: () => AiKeyModel.list(), + class: 'w-full', + resultField: 'items', + labelField: 'name', + valueField: 'id', + }, + fieldName: 'key', + label: 'API 秘钥', + }, + { + component: 'Input', + fieldName: 'platform', + label: '模型平台', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['模型平台'])) + .max(100, $t('ui.formRules.maxLength', ['模型平台', 100])), + }, { component: 'Input', fieldName: 'name', @@ -25,6 +46,20 @@ export function useSchema(): VbenFormSchema[] { .min(1, $t('ui.formRules.required', ['模型名字'])) .max(100, $t('ui.formRules.maxLength', ['模型名字', 100])), }, + { + component: 'Input', + fieldName: 'model', + label: '模型标识', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['模型标识'])) + .max(100, $t('ui.formRules.maxLength', ['模型标识', 100])), + }, + { + component: 'InputNumber', + fieldName: 'sort', + label: '排序', + }, { component: 'RadioGroup', componentProps: { @@ -39,41 +74,6 @@ export function useSchema(): VbenFormSchema[] { fieldName: 'status', label: $t('system.status'), }, - { - component: 'ApiSelect', - componentProps: { - api: () => AiKeyModel.list(), - class: 'w-full', - resultField: 'items', - labelField: 'name', - valueField: 'id', - }, - fieldName: 'key', - label: 'API 秘钥', - }, - { - component: 'InputNumber', - fieldName: 'sort', - label: '排序', - }, - { - component: 'Input', - fieldName: 'platform', - label: '模型平台', - rules: z - .string() - .min(1, $t('ui.formRules.required', ['模型平台'])) - .max(100, $t('ui.formRules.maxLength', ['模型平台', 100])), - }, - { - component: 'Input', - fieldName: 'model', - label: '模型标识', - rules: z - .string() - .min(1, $t('ui.formRules.required', ['模型标识'])) - .max(100, $t('ui.formRules.maxLength', ['模型标识', 100])), - }, { component: 'Input', fieldName: 'temperature', diff --git a/web/apps/web-antd/src/views/ai/knowledge/data.ts b/web/apps/web-antd/src/views/ai/knowledge/data.ts new file mode 100644 index 0000000..9ab94dd --- /dev/null +++ b/web/apps/web-antd/src/views/ai/knowledge/data.ts @@ -0,0 +1,149 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { AiKnowledgeApi } from '#/models/ai/knowledge'; + +import { z } from '#/adapter/form'; +import { $t } from '#/locales'; +import { format_datetime } from '#/utils/date'; +import { op } from '#/utils/permission'; + +/** + * 获取编辑表单的字段配置 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: '知识库名称', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['知识库名称'])) + .max(100, $t('ui.formRules.maxLength', ['知识库名称', 100])), + }, + { + component: 'Input', + componentProps: { rows: 3, showCount: true }, + fieldName: 'description', + label: '知识库描述', + rules: z + .string() + .max(500, $t('ui.formRules.maxLength', ['知识库描述', 500])) + .optional(), + }, + { + component: 'Input', + fieldName: 'embedding_model_id', + label: '向量模型编号', + }, + { + component: 'Input', + fieldName: 'embedding_model', + label: '向量模型标识', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['向量模型标识'])) + .max(100, $t('ui.formRules.maxLength', ['向量模型标识', 100])), + }, + { component: 'InputNumber', fieldName: 'top_k', label: 'topK' }, + { + component: 'Input', + fieldName: 'similarity_threshold', + label: '相似度阈值', + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: $t('common.enabled'), value: 1 }, + { label: $t('common.disabled'), value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'status', + label: $t('system.status'), + }, + { + component: 'Input', + fieldName: 'remark', + label: '备注', + }, + ]; +} + +/** + * 获取编辑表单的字段配置 + */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { component: 'Input', fieldName: 'name', label: '知识库名称' }, + { + component: 'Select', + fieldName: 'status', + label: '状态', + componentProps: { + allowClear: true, + options: [ + { label: '启用', value: 1 }, + { label: '禁用', value: 0 }, + ], + }, + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { field: 'id', title: 'ID' }, + { field: 'name', title: '知识库名称' }, + { field: 'description', title: '知识库描述' }, + { field: 'embedding_model_id', title: '向量模型编号' }, + { field: 'embedding_model', title: '向量模型标识' }, + { field: 'top_k', title: 'topK' }, + { field: 'similarity_threshold', title: '相似度阈值' }, + { field: 'status', title: '状态', cellRender: { name: 'CellTag' } }, + { field: 'remark', title: '备注' }, + { + field: 'update_time', + title: '修改时间', + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + field: 'create_time', + title: '创建时间', + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + align: 'center', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('ai.knowledge.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + op('ai:knowledge:edit', 'edit'), + op('ai:knowledge:delete', 'delete'), + ], + }, + field: 'action', + fixed: 'right', + title: '操作', + width: 120, + }, + ]; +} diff --git a/web/apps/web-antd/src/views/ai/knowledge/list.vue b/web/apps/web-antd/src/views/ai/knowledge/list.vue new file mode 100644 index 0000000..f7e2ed8 --- /dev/null +++ b/web/apps/web-antd/src/views/ai/knowledge/list.vue @@ -0,0 +1,141 @@ + + + diff --git a/web/apps/web-antd/src/views/ai/knowledge/modules/form.vue b/web/apps/web-antd/src/views/ai/knowledge/modules/form.vue new file mode 100644 index 0000000..970063a --- /dev/null +++ b/web/apps/web-antd/src/views/ai/knowledge/modules/form.vue @@ -0,0 +1,79 @@ + + + + diff --git a/web/apps/web-antd/src/views/system/dict_data/data.ts b/web/apps/web-antd/src/views/system/dict_data/data.ts index 788738b..e240103 100644 --- a/web/apps/web-antd/src/views/system/dict_data/data.ts +++ b/web/apps/web-antd/src/views/system/dict_data/data.ts @@ -27,35 +27,25 @@ export function useSchema(): VbenFormSchema[] { }, fieldName: 'dict_type', label: '字典类型', + rules: z.any(), }, { component: 'Input', fieldName: 'label', label: '字典标签', - rules: z - .string() - .min(2, $t('ui.formRules.minLength', [$t('system.dict_data.type'), 2])) - .max( - 20, - $t('ui.formRules.maxLength', [$t('system.dict_data.type'), 20]), - ), + rules: z.string(), }, { component: 'Input', fieldName: 'value', label: '字典键值', - rules: z - .string() - .min(2, $t('ui.formRules.minLength', [$t('system.dict_data.type'), 2])) - .max( - 50, - $t('ui.formRules.maxLength', [$t('system.dict_data.type'), 50]), - ), + rules: z.string(), }, { component: 'InputNumber', fieldName: 'sort', label: '字典排序', + rules: z.number(), }, { component: 'ApiSelect', @@ -119,10 +109,6 @@ export function useSchema(): VbenFormSchema[] { }, fieldName: 'remark', label: '备注', - rules: z - .string() - .max(50, $t('ui.formRules.maxLength', [$t('system.remark'), 50])) - .optional(), }, ]; } @@ -140,7 +126,7 @@ export function useColumns( align: 'left', field: 'id', fixed: 'left', - title: '字典编码', + title: 'id', treeNode: true, width: 150, }, diff --git a/web/apps/web-antd/src/views/system/dict_data/modules/form.vue b/web/apps/web-antd/src/views/system/dict_data/modules/form.vue index fb65d56..a552666 100644 --- a/web/apps/web-antd/src/views/system/dict_data/modules/form.vue +++ b/web/apps/web-antd/src/views/system/dict_data/modules/form.vue @@ -23,7 +23,7 @@ const getTitle = computed(() => { }); const route = useRoute(); const [Form, formApi] = useVbenForm({ - layout: 'vertical', + layout: 'horizontal', schema: useSchema(), showDefaultActions: false, }); diff --git a/web/apps/web-antd/src/views/system/dict_type/data.ts b/web/apps/web-antd/src/views/system/dict_type/data.ts index 02c7514..b962d3d 100644 --- a/web/apps/web-antd/src/views/system/dict_type/data.ts +++ b/web/apps/web-antd/src/views/system/dict_type/data.ts @@ -7,7 +7,7 @@ import type { SystemDictTypeApi } from '#/api/system/dict_type'; import { z } from '#/adapter/form'; import { $t } from '#/locales'; import { format_datetime } from '#/utils/date'; -import {op} from "#/utils/permission"; +import { op } from "#/utils/permission"; /** * 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量 @@ -30,13 +30,6 @@ export function useSchema(): VbenFormSchema[] { component: 'Input', fieldName: 'value', label: '字典类型', - rules: z - .string() - .min(2, $t('ui.formRules.minLength', [$t('system.dict_type.value'), 2])) - .max( - 20, - $t('ui.formRules.maxLength', [$t('system.dict_type.value'), 20]), - ), }, { component: 'RadioGroup', @@ -98,7 +91,6 @@ export function useColumns( { cellRender: { name: 'CellTag', - }, field: 'status', title: '状态', diff --git a/web/apps/web-antd/src/views/system/dict_type/modules/form.vue b/web/apps/web-antd/src/views/system/dict_type/modules/form.vue index 6c952df..a14498a 100644 --- a/web/apps/web-antd/src/views/system/dict_type/modules/form.vue +++ b/web/apps/web-antd/src/views/system/dict_type/modules/form.vue @@ -22,7 +22,7 @@ const getTitle = computed(() => { }); const [Form, formApi] = useVbenForm({ - layout: 'vertical', + layout: 'horizontal', schema: useSchema(), showDefaultActions: false, });