diff --git a/backend/ai/models.py b/backend/ai/models.py index 7415569..f99a125 100644 --- a/backend/ai/models.py +++ b/backend/ai/models.py @@ -54,7 +54,7 @@ class AIModel(CoreModel): class Meta: db_table = "ai_model" - verbose_name = "AI 模型" + verbose_name = "模型配置" verbose_name_plural = verbose_name def __str__(self): diff --git a/backend/ai/urls.py b/backend/ai/urls.py index 08299ad..da7f3a0 100644 --- a/backend/ai/urls.py +++ b/backend/ai/urls.py @@ -5,6 +5,8 @@ from . import views router = routers.DefaultRouter() router.register(r'ai_api_key', views.AIApiKeyViewSet) +router.register(r'ai_model', views.AIModelViewSet) + urlpatterns = [ path('', include(router.urls)), diff --git a/backend/ai/views/__init__.py b/backend/ai/views/__init__.py index 348726c..0d4de63 100644 --- a/backend/ai/views/__init__.py +++ b/backend/ai/views/__init__.py @@ -1,5 +1,7 @@ __all__ = [ - 'AIApiKeyViewSet' + 'AIApiKeyViewSet', + 'AIModelViewSet', ] -from ai.views.ai_api_key import AIApiKeyViewSet \ No newline at end of file +from ai.views.ai_api_key import AIApiKeyViewSet +from ai.views.ai_model import AIModelViewSet \ No newline at end of file diff --git a/backend/ai/views/ai_model.py b/backend/ai/views/ai_model.py new file mode 100644 index 0000000..4768228 --- /dev/null +++ b/backend/ai/views/ai_model.py @@ -0,0 +1,28 @@ +from rest_framework import serializers + +from ai.models import AIModel +from utils.serializers import CustomModelSerializer +from utils.custom_model_viewSet import CustomModelViewSet + +class AIModelSerializer(CustomModelSerializer): + api_key_name = serializers.CharField(source='key.name', read_only=True) + """ + AI 模型 序列化器 + """ + class Meta: + model = AIModel + fields = '__all__' + read_only_fields = ['id', 'create_time', 'update_time'] + +class AIModelViewSet(CustomModelViewSet): + """ + AI 模型 视图集 + """ + queryset = AIModel.objects.filter(is_deleted=False).select_related('key').order_by('-id') + serializer_class = AIModelSerializer + filterset_fields = ['id', 'remark', 'creator', 'modifier', 'is_deleted', 'name', 'sort', 'status', 'platform', + 'model', 'max_tokens', 'max_contexts'] + search_fields = ['name'] # 根据实际字段调整 + ordering_fields = ['create_time', 'id'] + ordering = ['-create_time'] + diff --git a/backend/system/management/commands/tpl/frontend_data.ts.tpl b/backend/system/management/commands/tpl/frontend_data.ts.tpl index e0ce5c5..006ee07 100644 --- a/backend/system/management/commands/tpl/frontend_data.ts.tpl +++ b/backend/system/management/commands/tpl/frontend_data.ts.tpl @@ -27,7 +27,6 @@ ${grid_form_fields} ]; } - /** * 获取表格列配置 * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 diff --git a/backend/system/management/commands/tpl/frontend_list.vue.tpl b/backend/system/management/commands/tpl/frontend_list.vue.tpl index 02bbb3b..a70d2f2 100644 --- a/backend/system/management/commands/tpl/frontend_list.vue.tpl +++ b/backend/system/management/commands/tpl/frontend_list.vue.tpl @@ -133,7 +133,7 @@ function refreshGrid() { v-permission="'${app_name}:${model_name_snake}:create'" > - {{ $$t('ui.actionTitle.create', [$$t('${app_name}.${model_name_snake}.name')]) }} + {{ $t('ui.actionTitle.create') }} 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 915be92..43d6c48 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 @@ -3,5 +3,9 @@ "ai_api_key": { "title": "API KEY", "name": "API KEY" + }, + "ai_model": { + "title": "MODEL CONFIG", + "name": "MODEL CONFIG" } } 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 337b444..b2dc137 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 @@ -3,5 +3,9 @@ "ai_api_key": { "title": "API 密钥", "name": "API 密钥" + }, + "ai_model": { + "title": "模型配置", + "name": "模型配置" } } diff --git a/web/apps/web-antd/src/models/ai/ai_model.ts b/web/apps/web-antd/src/models/ai/ai_model.ts new file mode 100644 index 0000000..6fd4184 --- /dev/null +++ b/web/apps/web-antd/src/models/ai/ai_model.ts @@ -0,0 +1,28 @@ +import { BaseModel } from '#/models/base'; + +export namespace AiAIModelApi { + export interface AiAIModel { + id: number; + remark: string; + creator: string; + modifier: string; + update_time: string; + create_time: string; + is_deleted: boolean; + name: string; + sort: number; + status: number; + key: number; + platform: string; + model: string; + temperature: any; + max_tokens: number; + max_contexts: number; + } +} + +export class AiAIModelModel extends BaseModel { + constructor() { + super('/ai/ai_model/'); + } +} 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 new file mode 100644 index 0000000..f3d0937 --- /dev/null +++ b/web/apps/web-antd/src/views/ai/ai_model/data.ts @@ -0,0 +1,206 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { AiAIModelApi } from '#/models/ai/ai_model'; + +import { z } from '#/adapter/form'; +import { $t } from '#/locales'; +import { AiAIApiKeyModel } from '#/models/ai/ai_api_key'; +import { op } from '#/utils/permission'; + +const AiKeyModel = new AiAIApiKeyModel(); + +/** + * 获取编辑表单的字段配置 + */ +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: '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: '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', + label: '温度参数', + }, + { + component: 'InputNumber', + fieldName: 'max_tokens', + label: '回复数 Token 数', + }, + { + component: 'InputNumber', + fieldName: 'max_contexts', + label: '上下文数量', + }, + { + 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 }, + ], + }, + }, + { + component: 'Input', + fieldName: 'platform', + label: '模型平台', + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: 'ID', + }, + { + field: 'name', + title: '模型名字', + }, + { + field: 'sort', + title: '排序', + }, + { + cellRender: { + name: 'CellTag', + }, + field: 'status', + title: '状态', + }, + { + field: 'api_key_name', + title: 'API 秘钥', + }, + { + field: 'platform', + title: '模型平台', + }, + { + field: 'model', + title: '模型标识', + }, + { + field: 'temperature', + title: '温度参数', + }, + { + field: 'max_tokens', + title: 'Token 数量', + }, + { + field: 'max_contexts', + title: 'Message 数量', + }, + { + field: 'remark', + title: '备注', + }, + { + align: 'center', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('ai.ai_model.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + op('ai:ai_model:edit', 'edit'), + op('ai:ai_model:delete', 'delete'), + ], + }, + field: 'action', + fixed: 'right', + title: '操作', + width: 120, + }, + ]; +} diff --git a/web/apps/web-antd/src/views/ai/ai_model/list.vue b/web/apps/web-antd/src/views/ai/ai_model/list.vue new file mode 100644 index 0000000..56fa5de --- /dev/null +++ b/web/apps/web-antd/src/views/ai/ai_model/list.vue @@ -0,0 +1,141 @@ + + + + + + + + + + {{ $t('ui.actionTitle.create') }} + + + + + diff --git a/web/apps/web-antd/src/views/ai/ai_model/modules/form.vue b/web/apps/web-antd/src/views/ai/ai_model/modules/form.vue new file mode 100644 index 0000000..6aed2f4 --- /dev/null +++ b/web/apps/web-antd/src/views/ai/ai_model/modules/form.vue @@ -0,0 +1,79 @@ + + + + + + + + + {{ $t('common.reset') }} + + + + + +