From 3ef362508a6b954f2b6ae7272050292a1f606324 Mon Sep 17 00:00:00 2001 From: gjd Date: Fri, 6 Jun 2025 17:09:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E6=B7=BB=E5=8A=A0=20AI=20=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E8=81=8A=E5=A4=A9=E5=92=8C=20API=20=E5=AF=86=E9=92=A5?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 AI 对话聊天管理页面,包括对话列表和消息列表 - 新增 API 密钥管理页面,包括密钥列表和表单 - 添加相关 API 接口和数据模型 - 集成表单和表格组件,实现基本的 CRUD 操作 --- .../src/api/ai/chat/conversation/index.ts | 75 +++++ .../web-antd/src/api/ai/chat/message/index.ts | 96 ++++++ .../src/api/ai/knowledge/knowledge/index.ts | 50 ++++ .../web-antd/src/api/ai/model/apiKey/index.ts | 50 ++++ .../src/api/ai/model/chatRole/index.ts | 85 ++++++ apps/web-antd/src/api/ai/model/model/index.ts | 55 ++++ apps/web-antd/src/api/ai/model/tool/index.ts | 43 +++ apps/web-antd/src/utils/constants.ts | 8 + apps/web-antd/src/utils/dict.ts | 2 +- .../src/views/ai/chat/manager/data.ts | 196 +++++++++++++ .../src/views/ai/chat/manager/index.vue | 39 ++- .../manager/modules/ChatConversationList.vue | 113 +++++++ .../chat/manager/modules/ChatMessageList.vue | 110 +++++++ .../src/views/ai/model/apiKey/data.ts | 126 ++++++++ .../src/views/ai/model/apiKey/index.vue | 140 +++++++-- .../views/ai/model/apiKey/modules/form.vue | 82 ++++++ .../src/views/ai/model/chatRole/data.ts | 277 ++++++++++++++++++ .../src/views/ai/model/chatRole/index.vue | 151 ++++++++-- .../views/ai/model/chatRole/modules/form.vue | 88 ++++++ .../web-antd/src/views/ai/model/model/data.ts | 248 ++++++++++++++++ .../src/views/ai/model/model/index.vue | 154 ++++++++-- .../src/views/ai/model/model/modules/form.vue | 83 ++++++ apps/web-antd/src/views/ai/model/tool/data.ts | 111 +++++++ .../src/views/ai/model/tool/index.vue | 140 +++++++-- .../src/views/ai/model/tool/modules/form.vue | 82 ++++++ packages/effects/request/package.json | 1 + packages/effects/request/src/index.ts | 1 + pnpm-lock.yaml | 8 + 28 files changed, 2509 insertions(+), 105 deletions(-) create mode 100644 apps/web-antd/src/api/ai/chat/conversation/index.ts create mode 100644 apps/web-antd/src/api/ai/chat/message/index.ts create mode 100644 apps/web-antd/src/api/ai/knowledge/knowledge/index.ts create mode 100644 apps/web-antd/src/api/ai/model/apiKey/index.ts create mode 100644 apps/web-antd/src/api/ai/model/chatRole/index.ts create mode 100644 apps/web-antd/src/api/ai/model/model/index.ts create mode 100644 apps/web-antd/src/api/ai/model/tool/index.ts create mode 100644 apps/web-antd/src/views/ai/chat/manager/data.ts create mode 100644 apps/web-antd/src/views/ai/chat/manager/modules/ChatConversationList.vue create mode 100644 apps/web-antd/src/views/ai/chat/manager/modules/ChatMessageList.vue create mode 100644 apps/web-antd/src/views/ai/model/apiKey/data.ts create mode 100644 apps/web-antd/src/views/ai/model/apiKey/modules/form.vue create mode 100644 apps/web-antd/src/views/ai/model/chatRole/data.ts create mode 100644 apps/web-antd/src/views/ai/model/chatRole/modules/form.vue create mode 100644 apps/web-antd/src/views/ai/model/model/data.ts create mode 100644 apps/web-antd/src/views/ai/model/model/modules/form.vue create mode 100644 apps/web-antd/src/views/ai/model/tool/data.ts create mode 100644 apps/web-antd/src/views/ai/model/tool/modules/form.vue diff --git a/apps/web-antd/src/api/ai/chat/conversation/index.ts b/apps/web-antd/src/api/ai/chat/conversation/index.ts new file mode 100644 index 000000000..c15f80893 --- /dev/null +++ b/apps/web-antd/src/api/ai/chat/conversation/index.ts @@ -0,0 +1,75 @@ +import type { PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace AiChatConversationApi { + export interface ChatConversationVO { + id: number; // ID 编号 + userId: number; // 用户编号 + title: string; // 对话标题 + pinned: boolean; // 是否置顶 + roleId: number; // 角色编号 + modelId: number; // 模型编号 + model: string; // 模型标志 + temperature: number; // 温度参数 + maxTokens: number; // 单条回复的最大 Token 数量 + maxContexts: number; // 上下文的最大 Message 数量 + createTime?: Date; // 创建时间 + // 额外字段 + systemMessage?: string; // 角色设定 + modelName?: string; // 模型名字 + roleAvatar?: string; // 角色头像 + modelMaxTokens?: string; // 模型的单条回复的最大 Token 数量 + modelMaxContexts?: string; // 模型的上下文的最大 Message 数量 + } +} + +// 获得【我的】聊天对话 +export function getChatConversationMy(id: number) { + return requestClient.get< + PageResult + >(`/ai/chat/conversation/get-my?id=${id}`); +} + +// 新增【我的】聊天对话 +export function createChatConversationMy( + data: AiChatConversationApi.ChatConversationVO, +) { + return requestClient.post('/ai/chat/conversation/create-my', data); +} + +// 更新【我的】聊天对话 +export function updateChatConversationMy( + data: AiChatConversationApi.ChatConversationVO, +) { + return requestClient.put(`/ai/chat/conversation/update-my`, data); +} + +// 删除【我的】聊天对话 +export function deleteChatConversationMy(id: string) { + return requestClient.delete(`/ai/chat/conversation/delete-my?id=${id}`); +} + +// 删除【我的】所有对话,置顶除外 +export function deleteChatConversationMyByUnpinned() { + return requestClient.delete(`/ai/chat/conversation/delete-by-unpinned`); +} + +// 获得【我的】聊天对话列表 +export function getChatConversationMyList() { + return requestClient.get( + `/ai/chat/conversation/my-list`, + ); +} + +// 获得【我的】聊天对话列表 +export function getChatConversationPage(params: any) { + return requestClient.get< + PageResult + >(`/ai/chat/conversation/page`, { params }); +} + +// 管理员删除消息 +export function deleteChatConversationByAdmin(id: number) { + return requestClient.delete(`/ai/chat/conversation/delete-by-admin?id=${id}`); +} diff --git a/apps/web-antd/src/api/ai/chat/message/index.ts b/apps/web-antd/src/api/ai/chat/message/index.ts new file mode 100644 index 000000000..2232c7f1d --- /dev/null +++ b/apps/web-antd/src/api/ai/chat/message/index.ts @@ -0,0 +1,96 @@ +import type { PageResult } from '@vben/request'; + +import { fetchEventSource } from '@vben/request'; +import { useAccessStore } from '@vben/stores'; + +import { requestClient } from '#/api/request'; + +const accessStore = useAccessStore(); +export namespace AiChatMessageApi { + export interface ChatMessageVO { + id: number; // 编号 + conversationId: number; // 对话编号 + type: string; // 消息类型 + userId: string; // 用户编号 + roleId: string; // 角色编号 + model: number; // 模型标志 + modelId: number; // 模型编号 + content: string; // 聊天内容 + tokens: number; // 消耗 Token 数量 + segmentIds?: number[]; // 段落编号 + segments?: { + content: string; // 段落内容 + documentId: number; // 文档编号 + documentName: string; // 文档名称 + id: number; // 段落编号 + }[]; + createTime: Date; // 创建时间 + roleAvatar: string; // 角色头像 + userAvatar: string; // 用户头像 + } +} + +// 消息列表 +export function getChatMessageListByConversationId( + conversationId: null | number, +) { + return requestClient.get( + `/ai/chat/message/list-by-conversation-id?conversationId=${conversationId}`, + ); +} + +// 发送 Stream 消息 +export function sendChatMessageStream( + conversationId: number, + content: string, + ctrl: any, + enableContext: boolean, + onMessage: any, + onError: any, + onClose: any, +) { + const token = accessStore.accessToken; + return fetchEventSource( + `${import.meta.env.VITE_BASE_URL}/ai/chat/message/send-stream`, + { + method: 'post', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + openWhenHidden: true, + body: JSON.stringify({ + conversationId, + content, + useContext: enableContext, + }), + onmessage: onMessage, + onerror: onError, + onclose: onClose, + signal: ctrl.signal, + }, + ); +} + +// 删除消息 +export function deleteChatMessage(id: string) { + return requestClient.delete(`/ai/chat/message/delete?id=${id}`); +} + +// 删除指定对话的消息 +export function deleteByConversationId(conversationId: number) { + return requestClient.delete( + `/ai/chat/message/delete-by-conversation-id?conversationId=${conversationId}`, + ); +} +// 获得消息分页 +export function getChatMessagePage(params: any) { + return requestClient.get>( + '/ai/chat/message/page', + { params }, + ); +} +// 管理员删除消息 +export function deleteChatMessageByAdmin(id: number) { + return requestClient.delete(`/ai/chat/message/delete-by-admin?id=${id}`); +} diff --git a/apps/web-antd/src/api/ai/knowledge/knowledge/index.ts b/apps/web-antd/src/api/ai/knowledge/knowledge/index.ts new file mode 100644 index 000000000..0ff7281fb --- /dev/null +++ b/apps/web-antd/src/api/ai/knowledge/knowledge/index.ts @@ -0,0 +1,50 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace AiKnowledgeKnowledgeApi { + export interface KnowledgeVO { + id: number; // 编号 + name: string; // 知识库名称 + description: string; // 知识库描述 + embeddingModelId: number; // 嵌入模型编号,高质量模式时维护 + topK: number; // topK + similarityThreshold: number; // 相似度阈值 + } +} + +// 查询知识库分页 +export function getKnowledgePage(params: PageParam) { + return requestClient.get>( + '/ai/knowledge/page', + { params }, + ); +} + +// 查询知识库详情 +export function getKnowledge(id: number) { + return requestClient.get( + `/ai/knowledge/get?id=${id}`, + ); +} +// 新增知识库 +export function createKnowledge(data: AiKnowledgeKnowledgeApi.KnowledgeVO) { + return requestClient.post('/ai/knowledge/create', data); +} + +// 修改知识库 +export function updateKnowledge(data: AiKnowledgeKnowledgeApi.KnowledgeVO) { + return requestClient.put('/ai/knowledge/update', data); +} + +// 删除知识库 +export function deleteKnowledge(id: number) { + return requestClient.delete(`/ai/knowledge/delete?id=${id}`); +} + +// 获取知识库简单列表 +export function getSimpleKnowledgeList() { + return requestClient.get( + '/ai/knowledge/simple-list', + ); +} diff --git a/apps/web-antd/src/api/ai/model/apiKey/index.ts b/apps/web-antd/src/api/ai/model/apiKey/index.ts new file mode 100644 index 000000000..6ae5a1fac --- /dev/null +++ b/apps/web-antd/src/api/ai/model/apiKey/index.ts @@ -0,0 +1,50 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace AiModelApiKeyApi { + export interface ApiKeyVO { + id: number; // 编号 + name: string; // 名称 + apiKey: string; // 密钥 + platform: string; // 平台 + url: string; // 自定义 API 地址 + status: number; // 状态 + } +} + +// 查询 API 密钥分页 +export function getApiKeyPage(params: PageParam) { + return requestClient.get>( + '/ai/api-key/page', + { params }, + ); +} + +// 获得 API 密钥列表 +export function getApiKeySimpleList() { + return requestClient.get( + '/ai/api-key/simple-list', + ); +} + +// 查询 API 密钥详情 +export function getApiKey(id: number) { + return requestClient.get( + `/ai/api-key/get?id=${id}`, + ); +} +// 新增 API 密钥 +export function createApiKey(data: AiModelApiKeyApi.ApiKeyVO) { + return requestClient.post('/ai/api-key/create', data); +} + +// 修改 API 密钥 +export function updateApiKey(data: AiModelApiKeyApi.ApiKeyVO) { + return requestClient.put('/ai/api-key/update', data); +} + +// 删除 API 密钥 +export function deleteApiKey(id: number) { + return requestClient.delete(`/ai/api-key/delete?id=${id}`); +} diff --git a/apps/web-antd/src/api/ai/model/chatRole/index.ts b/apps/web-antd/src/api/ai/model/chatRole/index.ts new file mode 100644 index 000000000..1c6306c33 --- /dev/null +++ b/apps/web-antd/src/api/ai/model/chatRole/index.ts @@ -0,0 +1,85 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace AiModelChatRoleApi { + export interface ChatRoleVO { + id: number; // 角色编号 + modelId: number; // 模型编号 + name: string; // 角色名称 + avatar: string; // 角色头像 + category: string; // 角色类别 + sort: number; // 角色排序 + description: string; // 角色描述 + systemMessage: string; // 角色设定 + welcomeMessage: string; // 角色设定 + publicStatus: boolean; // 是否公开 + status: number; // 状态 + knowledgeIds?: number[]; // 引用的知识库 ID 列表 + toolIds?: number[]; // 引用的工具 ID 列表 + } + + // AI 聊天角色 分页请求 vo + export interface ChatRolePageReqVO { + name?: string; // 角色名称 + category?: string; // 角色类别 + publicStatus: boolean; // 是否公开 + pageNo: number; // 是否公开 + pageSize: number; // 是否公开 + } +} + +// 查询聊天角色分页 +export function getChatRolePage(params: PageParam) { + return requestClient.get>( + '/ai/chat-role/page', + { params }, + ); +} + +// 查询聊天角色详情 +export function getChatRole(id: number) { + return requestClient.get( + `/ai/chat-role/get?id=${id}`, + ); +} +// 新增聊天角色 +export function createChatRole(data: AiModelChatRoleApi.ChatRoleVO) { + return requestClient.post('/ai/chat-role/create', data); +} + +// 修改聊天角色 +export function updateChatRole(data: AiModelChatRoleApi.ChatRoleVO) { + return requestClient.put('/ai/chat-role/update', data); +} + +// 删除聊天角色 +export function deleteChatRole(id: number) { + return requestClient.delete(`/ai/chat-role/delete?id=${id}`); +} + +// ======= chat 聊天 +// 获取 my role +export function getMyPage(params: AiModelChatRoleApi.ChatRolePageReqVO) { + return requestClient.get('/ai/chat-role/my-page', { params }); +} + +// 获取角色分类 +export function getCategoryList() { + return requestClient.get('/ai/chat-role/category-list'); +} + +// 创建角色 +export function createMy(data: AiModelChatRoleApi.ChatRoleVO) { + return requestClient.post('/ai/chat-role/create-my', data); +} + +// 更新角色 +export function updateMy(data: AiModelChatRoleApi.ChatRoleVO) { + return requestClient.put('/ai/chat-role/update', data); +} + +// 删除角色 my +export function deleteMy(id: number) { + return requestClient.delete(`/ai/chat-role/delete-my?id=${id}`); +} diff --git a/apps/web-antd/src/api/ai/model/model/index.ts b/apps/web-antd/src/api/ai/model/model/index.ts new file mode 100644 index 000000000..eefa0878f --- /dev/null +++ b/apps/web-antd/src/api/ai/model/model/index.ts @@ -0,0 +1,55 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace AiModelModelApi { + export interface ModelVO { + id: number; // 编号 + keyId: number; // API 秘钥编号 + name: string; // 模型名字 + model: string; // 模型标识 + platform: string; // 模型平台 + type: number; // 模型类型 + sort: number; // 排序 + status: number; // 状态 + temperature?: number; // 温度参数 + maxTokens?: number; // 单条回复的最大 Token 数量 + maxContexts?: number; // 上下文的最大 Message 数量 + } +} + +// 查询模型分页 +export function getModelPage(params: PageParam) { + return requestClient.get>( + '/ai/model/page', + { params }, + ); +} + +// 获得模型列表 +export function getModelSimpleList(type?: number) { + return requestClient.get('/ai/model/simple-list', { + params: { + type, + }, + }); +} + +// 查询模型详情 +export function getModel(id: number) { + return requestClient.get(`/ai/model/get?id=${id}`); +} +// 新增模型 +export function createModel(data: AiModelModelApi.ModelVO) { + return requestClient.post('/ai/model/create', data); +} + +// 修改模型 +export function updateModel(data: AiModelModelApi.ModelVO) { + return requestClient.put('/ai/model/update', data); +} + +// 删除模型 +export function deleteModel(id: number) { + return requestClient.delete(`/ai/model/delete?id=${id}`); +} diff --git a/apps/web-antd/src/api/ai/model/tool/index.ts b/apps/web-antd/src/api/ai/model/tool/index.ts new file mode 100644 index 000000000..a6a878f92 --- /dev/null +++ b/apps/web-antd/src/api/ai/model/tool/index.ts @@ -0,0 +1,43 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace AiModelToolApi { + export interface ToolVO { + id: number; // 工具编号 + name: string; // 工具名称 + description: string; // 工具描述 + status: number; // 状态 + } +} + +// 查询工具分页 +export function getToolPage(params: PageParam) { + return requestClient.get>('/ai/tool/page', { + params, + }); +} + +// 查询工具详情 +export function getTool(id: number) { + return requestClient.get(`/ai/tool/get?id=${id}`); +} +// 新增工具 +export function createTool(data: AiModelToolApi.ToolVO) { + return requestClient.post('/ai/tool/create', data); +} + +// 修改工具 +export function updateTool(data: AiModelToolApi.ToolVO) { + return requestClient.put('/ai/tool/update', data); +} + +// 删除工具 +export function deleteTool(id: number) { + return requestClient.delete(`/ai/tool/delete?id=${id}`); +} + +// 获取工具简单列表 +export function getToolSimpleList() { + return requestClient.get('/ai/tool/simple-list'); +} diff --git a/apps/web-antd/src/utils/constants.ts b/apps/web-antd/src/utils/constants.ts index ed4f97a8e..611d992d9 100644 --- a/apps/web-antd/src/utils/constants.ts +++ b/apps/web-antd/src/utils/constants.ts @@ -5,6 +5,14 @@ * 枚举类 */ +export const AiModelTypeEnum = { + CHAT: 1, // 聊天 + IMAGE: 2, // 图像 + VOICE: 3, // 音频 + VIDEO: 4, // 视频 + EMBEDDING: 5, // 向量 + RERANK: 6, // 重排 +}; // ========== COMMON 模块 ========== // 全局通用状态枚举 export const CommonStatusEnum = { diff --git a/apps/web-antd/src/utils/dict.ts b/apps/web-antd/src/utils/dict.ts index 3393f9fa7..1571f04da 100644 --- a/apps/web-antd/src/utils/dict.ts +++ b/apps/web-antd/src/utils/dict.ts @@ -143,10 +143,10 @@ export const getBoolDictOptions = (dictType: string) => { enum DICT_TYPE { AI_GENERATE_MODE = 'ai_generate_mode', // AI 生成模式 AI_IMAGE_STATUS = 'ai_image_status', // AI 图片状态 + AI_MODEL_TYPE = 'ai_model_type', // AI 模型类型 AI_MUSIC_STATUS = 'ai_music_status', // AI 音乐状态 // ========== AI - 人工智能模块 ========== AI_PLATFORM = 'ai_platform', // AI 平台 - AI_WRITE_FORMAT = 'ai_write_format', // AI 写作格式 AI_WRITE_LANGUAGE = 'ai_write_language', // AI 写作语言 AI_WRITE_LENGTH = 'ai_write_length', // AI 写作长度 diff --git a/apps/web-antd/src/views/ai/chat/manager/data.ts b/apps/web-antd/src/views/ai/chat/manager/data.ts new file mode 100644 index 000000000..5e1f844e8 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/manager/data.ts @@ -0,0 +1,196 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { getSimpleUserList } from '#/api/system/user'; +import { DICT_TYPE } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchemaConversation(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + }, + { + fieldName: 'title', + label: '聊天标题', + component: 'Input', + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + placeholder: ['开始时间', '结束时间'], + valueFormat: 'YYYY-MM-DD HH:mm:ss', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumnsConversation(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '对话编号', + fixed: 'left', + minWidth: 180, + }, + { + field: 'title', + title: '对话标题', + minWidth: 180, + fixed: 'left', + }, + { + title: '用户', + width: 180, + slots: { default: 'userId' }, + }, + { + field: 'roleName', + title: '角色', + minWidth: 180, + }, + { + field: 'model', + title: '模型标识', + minWidth: 180, + }, + { + field: 'messageCount', + title: '消息数', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'temperature', + title: '温度参数', + minWidth: 80, + }, + { + title: '回复数 Token 数', + field: 'maxTokens', + minWidth: 120, + }, + { + title: '上下文数量', + field: 'maxContexts', + minWidth: 120, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchemaMessage(): VbenFormSchema[] { + return [ + { + fieldName: 'conversationId', + label: '对话编号', + component: 'Input', + }, + { + fieldName: 'userId', + label: '用户编号', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + placeholder: ['开始时间', '结束时间'], + valueFormat: 'YYYY-MM-DD HH:mm:ss', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumnsMessage(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '消息编号', + fixed: 'left', + minWidth: 180, + }, + { + field: 'conversationId', + title: '对话编号', + minWidth: 180, + fixed: 'left', + }, + { + title: '用户', + width: 180, + slots: { default: 'userId' }, + }, + { + field: 'roleName', + title: '角色', + minWidth: 180, + }, + { + field: 'type', + title: '消息类型', + minWidth: 100, + }, + { + field: 'model', + title: '模型标识', + minWidth: 180, + }, + { + field: 'content', + title: '消息内容', + minWidth: 300, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'replyId', + title: '回复消息编号', + minWidth: 180, + }, + { + title: '携带上下文', + field: 'useContext', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + minWidth: 100, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/ai/chat/manager/index.vue b/apps/web-antd/src/views/ai/chat/manager/index.vue index 9bc7fbd0c..b892dd23a 100644 --- a/apps/web-antd/src/views/ai/chat/manager/index.vue +++ b/apps/web-antd/src/views/ai/chat/manager/index.vue @@ -1,31 +1,30 @@ diff --git a/apps/web-antd/src/views/ai/chat/manager/modules/ChatConversationList.vue b/apps/web-antd/src/views/ai/chat/manager/modules/ChatConversationList.vue new file mode 100644 index 000000000..7b23f7912 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/manager/modules/ChatConversationList.vue @@ -0,0 +1,113 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/manager/modules/ChatMessageList.vue b/apps/web-antd/src/views/ai/chat/manager/modules/ChatMessageList.vue new file mode 100644 index 000000000..0752b6bca --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/manager/modules/ChatMessageList.vue @@ -0,0 +1,110 @@ + + + diff --git a/apps/web-antd/src/views/ai/model/apiKey/data.ts b/apps/web-antd/src/views/ai/model/apiKey/data.ts new file mode 100644 index 000000000..03b5ccc92 --- /dev/null +++ b/apps/web-antd/src/views/ai/model/apiKey/data.ts @@ -0,0 +1,126 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { z } from '#/adapter/form'; +import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'platform', + label: '所属平台', + component: 'Select', + componentProps: { + placeholder: '请选择所属平台', + options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'), + allowClear: true, + }, + rules: z.string().min(1, { message: '请输入平台' }), + }, + { + component: 'Input', + fieldName: 'name', + label: '名称', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'apiKey', + label: '密钥', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'url', + label: '自定义 API URL', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名称', + component: 'Input', + }, + { + fieldName: 'platform', + label: '平台', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'), + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'platform', + title: '所属平台', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_PLATFORM }, + }, + }, + { + field: 'name', + title: '名称', + }, + { + field: 'apiKey', + title: '密钥', + }, + { + field: 'url', + title: '自定义 API URL', + }, + { + field: 'status', + title: '状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/ai/model/apiKey/index.vue b/apps/web-antd/src/views/ai/model/apiKey/index.vue index 2d916433a..ce3749e63 100644 --- a/apps/web-antd/src/views/ai/model/apiKey/index.vue +++ b/apps/web-antd/src/views/ai/model/apiKey/index.vue @@ -1,31 +1,129 @@ diff --git a/apps/web-antd/src/views/ai/model/apiKey/modules/form.vue b/apps/web-antd/src/views/ai/model/apiKey/modules/form.vue new file mode 100644 index 000000000..68376f41f --- /dev/null +++ b/apps/web-antd/src/views/ai/model/apiKey/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-antd/src/views/ai/model/chatRole/data.ts b/apps/web-antd/src/views/ai/model/chatRole/data.ts new file mode 100644 index 000000000..a20434ba6 --- /dev/null +++ b/apps/web-antd/src/views/ai/model/chatRole/data.ts @@ -0,0 +1,277 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { z } from '#/adapter/form'; +import { getSimpleKnowledgeList } from '#/api/ai/knowledge/knowledge'; +import { getModelSimpleList } from '#/api/ai/model/model'; +import { getToolSimpleList } from '#/api/ai/model/tool'; +import { + AiModelTypeEnum, + CommonStatusEnum, + DICT_TYPE, + getDictOptions, +} from '#/utils'; +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'formType', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '角色名称', + rules: 'required', + }, + { + component: 'ImageUpload', + fieldName: 'avatar', + label: '角色头像', + componentProps: { + maxSize: 1, + }, + rules: 'required', + }, + { + fieldName: 'modelId', + label: '绑定模型', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择绑定模型', + api: () => getModelSimpleList(AiModelTypeEnum.CHAT), + labelField: 'name', + valueField: 'id', + allowClear: true, + }, + dependencies: { + triggerFields: ['formType'], + show: (values) => { + return values.formType === 'create' || values.formType === 'update'; + }, + }, + }, + { + component: 'Input', + fieldName: 'category', + label: '角色类别', + rules: 'required', + dependencies: { + triggerFields: ['formType'], + show: (values) => { + return values.formType === 'create' || values.formType === 'update'; + }, + }, + }, + { + component: 'Textarea', + fieldName: 'description', + label: '角色描述', + componentProps: { + placeholder: '请输入角色描述', + }, + rules: 'required', + }, + { + fieldName: 'systemMessage', + label: '角色设定', + component: 'Textarea', + componentProps: { + placeholder: '请输入角色设定', + }, + rules: 'required', + }, + { + fieldName: 'knowledgeIds', + label: '引用知识库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择引用知识库', + api: getSimpleKnowledgeList, + labelField: 'name', + mode: 'multiple', + valueField: 'id', + allowClear: true, + }, + }, + { + fieldName: 'toolIds', + label: '引用工具', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择引用工具', + api: getToolSimpleList, + mode: 'multiple', + labelField: 'name', + valueField: 'id', + allowClear: true, + }, + }, + { + fieldName: 'publicStatus', + label: '是否公开', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + buttonStyle: 'solid', + optionType: 'button', + }, + defaultValue: true, + dependencies: { + triggerFields: ['formType'], + show: (values) => { + return values.formType === 'create' || values.formType === 'update'; + }, + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '角色排序', + component: 'InputNumber', + componentProps: { + controlsPosition: 'right', + placeholder: '请输入角色排序', + class: 'w-full', + }, + dependencies: { + triggerFields: ['formType'], + show: (values) => { + return values.formType === 'create' || values.formType === 'update'; + }, + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + dependencies: { + triggerFields: ['formType'], + show: (values) => { + return values.formType === 'create' || values.formType === 'update'; + }, + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '角色名称', + component: 'Input', + }, + { + fieldName: 'category', + label: '角色类别', + component: 'Input', + }, + { + fieldName: 'publicStatus', + label: '是否公开', + component: 'Select', + componentProps: { + placeholder: '请选择是否公开', + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + allowClear: true, + }, + defaultValue: true, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '角色名称', + minWidth: 100, + }, + { + title: '绑定模型', + field: 'modelName', + minWidth: 100, + }, + { + title: '角色头像', + slots: { default: 'avatar' }, + minWidth: 140, + }, + { + title: '角色类别', + field: 'category', + minWidth: 100, + }, + { + title: '角色描述', + field: 'description', + minWidth: 100, + }, + { + title: '角色设定', + field: 'systemMessage', + minWidth: 100, + }, + { + title: '知识库', + slots: { default: 'knowledgeIds' }, + minWidth: 100, + }, + { + title: '工具', + slots: { default: 'toolIds' }, + minWidth: 100, + }, + { + field: 'publicStatus', + title: '是否公开', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + minWidth: 80, + }, + { + field: 'status', + title: '状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + minWidth: 80, + }, + { + title: '角色排序', + field: 'sort', + minWidth: 80, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/ai/model/chatRole/index.vue b/apps/web-antd/src/views/ai/model/chatRole/index.vue index 45f09d3ea..0e9ad1557 100644 --- a/apps/web-antd/src/views/ai/model/chatRole/index.vue +++ b/apps/web-antd/src/views/ai/model/chatRole/index.vue @@ -1,31 +1,140 @@ diff --git a/apps/web-antd/src/views/ai/model/chatRole/modules/form.vue b/apps/web-antd/src/views/ai/model/chatRole/modules/form.vue new file mode 100644 index 000000000..56773a16a --- /dev/null +++ b/apps/web-antd/src/views/ai/model/chatRole/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-antd/src/views/ai/model/model/data.ts b/apps/web-antd/src/views/ai/model/model/data.ts new file mode 100644 index 000000000..54fa41905 --- /dev/null +++ b/apps/web-antd/src/views/ai/model/model/data.ts @@ -0,0 +1,248 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { z } from '#/adapter/form'; +import { getApiKeySimpleList } from '#/api/ai/model/apiKey'; +import { + AiModelTypeEnum, + CommonStatusEnum, + DICT_TYPE, + getDictOptions, +} from '#/utils'; +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'platform', + label: '所属平台', + component: 'Select', + componentProps: { + placeholder: '请选择所属平台', + options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'), + allowClear: true, + }, + rules: z.string().min(1, { message: '请输入平台' }), + }, + { + fieldName: 'type', + label: '模型类型', + component: 'Select', + componentProps: (values) => { + return { + placeholder: '请输入模型类型', + disabled: !!values.id, + options: getDictOptions(DICT_TYPE.AI_MODEL_TYPE, 'number'), + allowClear: true, + }; + }, + rules: 'required', + }, + { + fieldName: 'keyId', + label: 'API 秘钥', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择API 秘钥', + api: getApiKeySimpleList, + labelField: 'name', + valueField: 'id', + allowClear: true, + }, + rules: 'required', + }, + { + component: 'Input', + fieldName: 'name', + label: '模型名字', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'model', + label: '模型标识', + rules: 'required', + }, + { + fieldName: 'sort', + label: '模型排序', + component: 'InputNumber', + componentProps: { + controlsPosition: 'right', + placeholder: '请输入模型排序', + class: 'w-full', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'temperature', + label: '温度参数', + component: 'InputNumber', + componentProps: { + controlsPosition: 'right', + placeholder: '请输入温度参数', + class: 'w-full', + min: 0, + max: 2, + }, + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [AiModelTypeEnum.CHAT].includes(values.type); + }, + }, + rules: 'required', + }, + { + fieldName: 'maxTokens', + label: '回复数 Token 数', + component: 'InputNumber', + componentProps: { + min: 0, + max: 8192, + controlsPosition: 'right', + placeholder: '请输入回复数 Token 数', + class: 'w-full', + }, + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [AiModelTypeEnum.CHAT].includes(values.type); + }, + }, + rules: 'required', + }, + { + fieldName: 'maxContexts', + label: '上下文数量', + component: 'InputNumber', + componentProps: { + min: 0, + max: 20, + controlsPosition: 'right', + placeholder: '请输入上下文数量', + class: 'w-full', + }, + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [AiModelTypeEnum.CHAT].includes(values.type); + }, + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '模型名字', + component: 'Input', + }, + { + fieldName: 'model', + label: '模型标识', + component: 'Input', + }, + { + fieldName: 'platform', + label: '模型平台', + component: 'Input', + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'platform', + title: '所属平台', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_PLATFORM }, + }, + minWidth: 100, + }, + { + field: 'type', + title: '模型类型', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_MODEL_TYPE }, + }, + minWidth: 100, + }, + { + field: 'name', + title: '模型名字', + minWidth: 180, + }, + { + title: '模型标识', + field: 'model', + minWidth: 180, + }, + { + title: 'API 秘钥', + slots: { default: 'keyId' }, + minWidth: 140, + }, + { + title: '排序', + field: 'sort', + minWidth: 80, + }, + { + field: 'status', + title: '状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + minWidth: 80, + }, + { + field: 'temperature', + title: '温度参数', + minWidth: 80, + }, + { + title: '回复数 Token 数', + field: 'maxTokens', + minWidth: 140, + }, + { + title: '上下文数量', + field: 'maxContexts', + minWidth: 100, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/ai/model/model/index.vue b/apps/web-antd/src/views/ai/model/model/index.vue index 827703350..80fd45f49 100644 --- a/apps/web-antd/src/views/ai/model/model/index.vue +++ b/apps/web-antd/src/views/ai/model/model/index.vue @@ -1,31 +1,143 @@ diff --git a/apps/web-antd/src/views/ai/model/model/modules/form.vue b/apps/web-antd/src/views/ai/model/model/modules/form.vue new file mode 100644 index 000000000..c88857648 --- /dev/null +++ b/apps/web-antd/src/views/ai/model/model/modules/form.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/web-antd/src/views/ai/model/tool/data.ts b/apps/web-antd/src/views/ai/model/tool/data.ts new file mode 100644 index 000000000..df1eba38c --- /dev/null +++ b/apps/web-antd/src/views/ai/model/tool/data.ts @@ -0,0 +1,111 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + + { + component: 'Input', + fieldName: 'name', + label: '工具名称', + rules: 'required', + }, + { + component: 'Textarea', + fieldName: 'description', + label: '工具描述', + componentProps: { + placeholder: '请输入工具描述', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + defaultValue: CommonStatusEnum.ENABLE, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '工具名称', + component: 'Input', + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + placeholder: ['开始时间', '结束时间'], + valueFormat: 'YYYY-MM-DD HH:mm:ss', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '工具编号', + }, + { + field: 'name', + title: '工具名称', + }, + { + field: 'description', + title: '工具描述', + }, + { + field: 'status', + title: '状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/ai/model/tool/index.vue b/apps/web-antd/src/views/ai/model/tool/index.vue index daf18f8cc..54fc5a08a 100644 --- a/apps/web-antd/src/views/ai/model/tool/index.vue +++ b/apps/web-antd/src/views/ai/model/tool/index.vue @@ -1,34 +1,132 @@ diff --git a/apps/web-antd/src/views/ai/model/tool/modules/form.vue b/apps/web-antd/src/views/ai/model/tool/modules/form.vue new file mode 100644 index 000000000..de16dc275 --- /dev/null +++ b/apps/web-antd/src/views/ai/model/tool/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/packages/effects/request/package.json b/packages/effects/request/package.json index 527f6d904..08e672b39 100644 --- a/packages/effects/request/package.json +++ b/packages/effects/request/package.json @@ -20,6 +20,7 @@ } }, "dependencies": { + "@microsoft/fetch-event-source": "^2.0.1", "@vben/locales": "workspace:*", "@vben/utils": "workspace:*", "axios": "catalog:", diff --git a/packages/effects/request/src/index.ts b/packages/effects/request/src/index.ts index fae1b058d..d4a3ed157 100644 --- a/packages/effects/request/src/index.ts +++ b/packages/effects/request/src/index.ts @@ -1,2 +1,3 @@ export * from './request-client'; +export * from '@microsoft/fetch-event-source'; export * from 'axios'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dabf51bdb..493986cbe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1824,6 +1824,9 @@ importers: packages/effects/request: dependencies: + '@microsoft/fetch-event-source': + specifier: ^2.0.1 + version: 2.0.1 '@vben/locales': specifier: workspace:* version: link:../../locales @@ -3915,6 +3918,9 @@ packages: resolution: {integrity: sha512-cszYIcjiNscDoMB1CIKZ3My61+JOhpERGlGr54i6bocvGLrcL/wo9o+RNXMBrb7XgLtKaizZWUpqRduQuHQLdg==} hasBin: true + '@microsoft/fetch-event-source@2.0.1': + resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==} + '@microsoft/tsdoc-config@0.17.1': resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} @@ -13780,6 +13786,8 @@ snapshots: transitivePeerDependencies: - '@types/node' + '@microsoft/fetch-event-source@2.0.1': {} + '@microsoft/tsdoc-config@0.17.1': dependencies: '@microsoft/tsdoc': 0.15.1