From c30db7d1ca50e2e3d8630600df5ae20505eee781 Mon Sep 17 00:00:00 2001 From: XIE7654 <765462425@qq.com> Date: Fri, 18 Jul 2025 11:34:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=81=8A=E5=A4=A9=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/ai/urls.py | 2 + backend/ai/views/__init__.py | 4 +- backend/ai/views/chat_conversation.py | 39 ++++ .../management/commands/tpl/viewset.py.tpl | 6 +- .../web-antd/src/locales/langs/en-US/ai.json | 4 + .../web-antd/src/locales/langs/zh-CN/ai.json | 4 + .../src/models/ai/chat_conversation.ts | 30 +++ .../web-antd/src/views/ai/api_key/data.ts | 2 +- .../src/views/ai/chat_conversation/data.ts | 181 ++++++++++++++++++ .../src/views/ai/chat_conversation/list.vue | 123 ++++++++++++ .../ai/chat_conversation/modules/form.vue | 79 ++++++++ 11 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 backend/ai/views/chat_conversation.py create mode 100644 web/apps/web-antd/src/models/ai/chat_conversation.ts create mode 100644 web/apps/web-antd/src/views/ai/chat_conversation/data.ts create mode 100644 web/apps/web-antd/src/views/ai/chat_conversation/list.vue create mode 100644 web/apps/web-antd/src/views/ai/chat_conversation/modules/form.vue diff --git a/backend/ai/urls.py b/backend/ai/urls.py index 73a489d..5021ef7 100644 --- a/backend/ai/urls.py +++ b/backend/ai/urls.py @@ -8,6 +8,8 @@ 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) +router.register(r'chat_conversation', views.ChatConversationViewSet) + urlpatterns = [ path('', include(router.urls)), diff --git a/backend/ai/views/__init__.py b/backend/ai/views/__init__.py index 66c8d61..fac792d 100644 --- a/backend/ai/views/__init__.py +++ b/backend/ai/views/__init__.py @@ -3,9 +3,11 @@ __all__ = [ 'AIModelViewSet', 'ToolViewSet', 'KnowledgeViewSet', + 'ChatConversationViewSet', ] from ai.views.ai_api_key import AIApiKeyViewSet from ai.views.ai_model import AIModelViewSet from ai.views.tool import ToolViewSet -from ai.views.knowledge import KnowledgeViewSet \ No newline at end of file +from ai.views.knowledge import KnowledgeViewSet +from ai.views.chat_conversation import ChatConversationViewSet \ No newline at end of file diff --git a/backend/ai/views/chat_conversation.py b/backend/ai/views/chat_conversation.py new file mode 100644 index 0000000..3a41b5b --- /dev/null +++ b/backend/ai/views/chat_conversation.py @@ -0,0 +1,39 @@ +from rest_framework import serializers + +from ai.models import ChatConversation +from utils.serializers import CustomModelSerializer +from utils.custom_model_viewSet import CustomModelViewSet +from django_filters import rest_framework as filters + + +class ChatConversationSerializer(CustomModelSerializer): + username = serializers.CharField(source='user.username', read_only=True) + + """ + AI 聊天对话 序列化器 + """ + class Meta: + model = ChatConversation + fields = '__all__' + read_only_fields = ['id', 'create_time', 'update_time'] + +class ChatConversationFilter(filters.FilterSet): + + class Meta: + model = ChatConversation + fields = ['id', 'remark', 'creator', 'modifier', 'is_deleted', 'title', 'pinned', 'model', + 'system_message', 'max_tokens', 'max_contexts'] + + +class ChatConversationViewSet(CustomModelViewSet): + """ + AI 聊天对话 视图集 + """ + queryset = ChatConversation.objects.filter(is_deleted=False).order_by('-id') + serializer_class = ChatConversationSerializer + filterset_class = ChatConversationFilter + search_fields = ['name'] # 根据实际字段调整 + ordering_fields = ['create_time', 'id'] + ordering = ['-create_time'] + +# 移入urls中 diff --git a/backend/system/management/commands/tpl/viewset.py.tpl b/backend/system/management/commands/tpl/viewset.py.tpl index 85a83b8..8ecba6b 100644 --- a/backend/system/management/commands/tpl/viewset.py.tpl +++ b/backend/system/management/commands/tpl/viewset.py.tpl @@ -14,7 +14,7 @@ class ${model_name}Serializer(CustomModelSerializer): read_only_fields = ['id', 'create_time', 'update_time'] -class $model_nameFilter(filters.FilterSet): +class ${model_name}Filter(filters.FilterSet): class Meta: model = $model_name @@ -27,10 +27,12 @@ class ${model_name}ViewSet(CustomModelViewSet): """ queryset = $model_name.objects.filter(is_deleted=False).order_by('-id') serializer_class = ${model_name}Serializer - filterset_class = [$filterset_fields] + filterset_class = ${model_name}Filter search_fields = ['name'] # 根据实际字段调整 ordering_fields = ['create_time', 'id'] ordering = ['-create_time'] # 移入urls中 # router.register(r'${model_name_snake}', views.${model_name}ViewSet) +# 移入 __init__.py +# from ${app_name}.views.${model_name_snake} import ${model_name}ViewSet \ No newline at end of file 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 5f59222..775e14c 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 @@ -19,5 +19,9 @@ "chat": { "title": "AI CHAT", "name": "AI CHAT" + }, + "chat_conversation": { + "title": "CHAT Management", + "name": "CHAT 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 53f2fe8..b630a70 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 @@ -19,5 +19,9 @@ "chat": { "title": "AI对话", "name": "AI对话" + }, + "chat_conversation": { + "title": "对话列表", + "name": "对话列表" } } diff --git a/web/apps/web-antd/src/models/ai/chat_conversation.ts b/web/apps/web-antd/src/models/ai/chat_conversation.ts new file mode 100644 index 0000000..36deb8e --- /dev/null +++ b/web/apps/web-antd/src/models/ai/chat_conversation.ts @@ -0,0 +1,30 @@ +import { BaseModel } from '#/models/base'; + +export namespace AiChatConversationApi { + export interface AiChatConversation { + id: number; + remark: string; + creator: string; + modifier: string; + update_time: string; + create_time: string; + is_deleted: boolean; + title: string; + pinned: boolean; + pinned_time: string; + user: number; + role: number; + model_id: number; + model: string; + system_message: string; + temperature: any; + max_tokens: number; + max_contexts: number; + } +} + +export class AiChatConversationModel extends BaseModel { + constructor() { + super('/ai/chat_conversation/'); + } +} diff --git a/web/apps/web-antd/src/views/ai/api_key/data.ts b/web/apps/web-antd/src/views/ai/api_key/data.ts index dcfb537..8ee82a6 100644 --- a/web/apps/web-antd/src/views/ai/api_key/data.ts +++ b/web/apps/web-antd/src/views/ai/api_key/data.ts @@ -151,7 +151,7 @@ export function useColumns( cellRender: { attrs: { nameField: 'name', - nameTitle: $t('ai.ai_api_key.name'), + nameTitle: $t('ai.api_key.name'), onClick: onActionClick, }, name: 'CellOperation', diff --git a/web/apps/web-antd/src/views/ai/chat_conversation/data.ts b/web/apps/web-antd/src/views/ai/chat_conversation/data.ts new file mode 100644 index 0000000..e853f39 --- /dev/null +++ b/web/apps/web-antd/src/views/ai/chat_conversation/data.ts @@ -0,0 +1,181 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { AiChatConversationApi } from '#/models/ai/chat_conversation'; + +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: 'title', + label: '对话标题', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['对话标题'])) + .max(100, $t('ui.formRules.maxLength', ['对话标题', 100])), + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'pinned', + label: '是否置顶', + }, + { + component: 'Input', + fieldName: 'pinned_time', + label: '置顶时间', + }, + { + component: 'Input', + fieldName: 'user', + label: '用户', + }, + { + component: 'Input', + fieldName: 'role', + label: '聊天角色', + }, + { + component: 'Input', + fieldName: 'model_id', + label: '向量模型编号', + }, + { + component: 'Input', + fieldName: 'model', + label: '模型标识', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['模型标识'])) + .max(100, $t('ui.formRules.maxLength', ['模型标识', 100])), + }, + { + component: 'Input', + fieldName: 'system_message', + 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: '上下文的最大 Message 数量', + }, + { + component: 'Input', + fieldName: 'remark', + label: '备注', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['备注'])) + .max(100, $t('ui.formRules.maxLength', ['备注', 100])), + }, + ]; +} + +/** + * 获取编辑表单的字段配置 + */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'title', + label: '对话标题', + }, + { + component: 'Input', + fieldName: 'user', + label: '用户', + }, + { + component: 'Input', + fieldName: 'model', + label: '模型标识', + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: 'ID', + }, + { + field: 'username', + title: '用户', + }, + { + field: 'role', + title: '聊天角色', + }, + { + field: 'model_id', + title: '向量模型编号', + }, + { + field: 'model', + title: '模型标识', + }, + { + field: 'system_message', + title: '角色设定', + }, + { + align: 'center', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('ai.chat_conversation.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + // op('ai:chat_conversation:edit', 'edit'), + // op('ai:chat_conversation:delete', 'delete'), + ], + }, + field: 'action', + fixed: 'right', + title: '操作', + width: 120, + }, + ]; +} diff --git a/web/apps/web-antd/src/views/ai/chat_conversation/list.vue b/web/apps/web-antd/src/views/ai/chat_conversation/list.vue new file mode 100644 index 0000000..b603c86 --- /dev/null +++ b/web/apps/web-antd/src/views/ai/chat_conversation/list.vue @@ -0,0 +1,123 @@ + + + diff --git a/web/apps/web-antd/src/views/ai/chat_conversation/modules/form.vue b/web/apps/web-antd/src/views/ai/chat_conversation/modules/form.vue new file mode 100644 index 0000000..9ee5dec --- /dev/null +++ b/web/apps/web-antd/src/views/ai/chat_conversation/modules/form.vue @@ -0,0 +1,79 @@ + + + +