From 1b236e89bf36b1a8bd391ca8c19eea05c256b67e Mon Sep 17 00:00:00 2001 From: gjd Date: Mon, 9 Jun 2025 16:20:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E6=B7=BB=E5=8A=A0=20AI=20=E7=BB=98?= =?UTF-8?q?=E5=9B=BE=E5=92=8C=E6=80=9D=E7=BB=B4=E5=AF=BC=E5=9B=BE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 AI 绘图管理页面,包括绘画列表、搜索筛选和操作功能 - 实现 AI 思维导图生成功能,支持流式生成和已有内容生成 - 添加 AI 音乐和写作相关的 API 接口 - 更新常量文件,增加 AI 平台、图像生成状态等枚举 - 优化 AI 绘图和思维导图的组件结构,提高可维护性 --- apps/web-antd/src/api/ai/image/index.ts | 112 +++++++++++ apps/web-antd/src/api/ai/mindmap/index.ts | 65 +++++++ apps/web-antd/src/api/ai/music/index.ts | 44 +++++ apps/web-antd/src/api/ai/write/index.ts | 90 +++++++++ apps/web-antd/src/utils/constants.ts | 121 ++++++++++++ .../src/views/ai/image/manager/data.ts | 146 ++++++++++++++ .../src/views/ai/image/manager/index.vue | 146 ++++++++++++-- .../src/views/ai/mindmap/index/index.vue | 99 ++++++++-- .../views/ai/mindmap/index/modules/Left.vue | 77 ++++++++ .../views/ai/mindmap/index/modules/Right.vue | 167 ++++++++++++++++ .../src/views/ai/mindmap/manager/data.ts | 84 ++++++++ .../src/views/ai/mindmap/manager/index.vue | 117 ++++++++++-- .../src/views/ai/music/manager/data.ts | 175 +++++++++++++++++ .../src/views/ai/music/manager/index.vue | 180 ++++++++++++++++-- .../src/views/ai/write/manager/data.ts | 157 +++++++++++++++ .../src/views/ai/write/manager/index.vue | 117 ++++++++++-- 16 files changed, 1794 insertions(+), 103 deletions(-) create mode 100644 apps/web-antd/src/api/ai/image/index.ts create mode 100644 apps/web-antd/src/api/ai/mindmap/index.ts create mode 100644 apps/web-antd/src/api/ai/music/index.ts create mode 100644 apps/web-antd/src/api/ai/write/index.ts create mode 100644 apps/web-antd/src/views/ai/image/manager/data.ts create mode 100644 apps/web-antd/src/views/ai/mindmap/index/modules/Left.vue create mode 100644 apps/web-antd/src/views/ai/mindmap/index/modules/Right.vue create mode 100644 apps/web-antd/src/views/ai/mindmap/manager/data.ts create mode 100644 apps/web-antd/src/views/ai/music/manager/data.ts create mode 100644 apps/web-antd/src/views/ai/write/manager/data.ts diff --git a/apps/web-antd/src/api/ai/image/index.ts b/apps/web-antd/src/api/ai/image/index.ts new file mode 100644 index 000000000..24699ac77 --- /dev/null +++ b/apps/web-antd/src/api/ai/image/index.ts @@ -0,0 +1,112 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace AiImageApi { + export interface ImageMidjourneyButtonsVO { + customId: string; // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识 + emoji: string; // 图标 emoji + label: string; // Make Variations 文本 + style: number; // 样式: 2(Primary)、3(Green) + } + // AI 绘图 VO + export interface ImageVO { + id: number; // 编号 + platform: string; // 平台 + model: string; // 模型 + prompt: string; // 提示词 + width: number; // 图片宽度 + height: number; // 图片高度 + status: number; // 状态 + publicStatus: boolean; // 公开状态 + picUrl: string; // 任务地址 + errorMessage: string; // 错误信息 + options: any; // 配置 Map + taskId: number; // 任务编号 + buttons: ImageMidjourneyButtonsVO[]; // mj 操作按钮 + createTime: Date; // 创建时间 + finishTime: Date; // 完成时间 + } + + export interface ImageDrawReqVO { + prompt: string; // 提示词 + modelId: number; // 模型 + style: string; // 图像生成的风格 + width: string; // 图片宽度 + height: string; // 图片高度 + options: object; // 绘制参数,Map + } + + export interface ImageMidjourneyImagineReqVO { + prompt: string; // 提示词 + modelId: number; // 模型 + base64Array: string[]; // size不能为空 + width: string; // 图片宽度 + height: string; // 图片高度 + version: string; // 版本 + } + + export interface ImageMidjourneyActionVO { + id: number; // 图片编号 + customId: string; // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识 + } +} + +// 获取【我的】绘图分页 +export function getImagePageMy(params: PageParam) { + return requestClient.get>( + '/ai/image/my-page', + { params }, + ); +} + +// 获取【我的】绘图记录 +export function getImageMy(id: number) { + return requestClient.get(`/ai/image/get-my?id=${id}`); +} + +// 获取【我的】绘图记录列表 +export function getImageListMyByIds(ids: number[]) { + return requestClient.get(`/ai/image/my-list-by-ids`, { + params: { ids: ids.join(',') }, + }); +} + +// 生成图片 +export function drawImage(data: AiImageApi.ImageDrawReqVO) { + return requestClient.post(`/ai/image/draw`, data); +} + +// 删除【我的】绘画记录 +export function deleteImageMy(id: number) { + return requestClient.delete(`/ai/image/delete-my?id=${id}`); +} + +// ================ midjourney 专属 ================ +// 【Midjourney】生成图片 +export function midjourneyImagine( + data: AiImageApi.ImageMidjourneyImagineReqVO, +) { + return requestClient.post(`/ai/image/midjourney/imagine`, data); +} + +// 【Midjourney】Action 操作(二次生成图片) +export function midjourneyAction(data: AiImageApi.ImageMidjourneyActionVO) { + return requestClient.post(`/ai/image/midjourney/action`, data); +} + +// ================ 绘图管理 ================ +// 查询绘画分页 +export function getImagePage(params: any) { + return requestClient.get(`/ai/image/page`, { params }); +} + +// 更新绘画发布状态 +export function updateImage(data: any) { + return requestClient.put(`/ai/image/update`, data); +} + +// 删除绘画 +export function deleteImage(id: number) { + return requestClient.delete(`/ai/image/delete?id=${id}`); +} diff --git a/apps/web-antd/src/api/ai/mindmap/index.ts b/apps/web-antd/src/api/ai/mindmap/index.ts new file mode 100644 index 000000000..496199ed0 --- /dev/null +++ b/apps/web-antd/src/api/ai/mindmap/index.ts @@ -0,0 +1,65 @@ +import { fetchEventSource } from '@vben/request'; +import { useAccessStore } from '@vben/stores'; + +import { requestClient } from '#/api/request'; + +const accessStore = useAccessStore(); +export namespace AiMindmapApi { + // AI 思维导图 VO + export interface MindMapVO { + id: number; // 编号 + userId: number; // 用户编号 + prompt: string; // 生成内容提示 + generatedContent: string; // 生成的思维导图内容 + platform: string; // 平台 + model: string; // 模型 + errorMessage: string; // 错误信息 + } + + // AI 思维导图生成 VO + export interface AiMindMapGenerateReqVO { + prompt: string; + } +} + +export function generateMindMap({ + data, + onClose, + onMessage, + onError, + ctrl, +}: { + ctrl: AbortController; + data: AiMindmapApi.AiMindMapGenerateReqVO; + onClose?: (...args: any[]) => void; + onError?: (...args: any[]) => void; + onMessage?: (res: any) => void; +}) { + const token = accessStore.accessToken; + return fetchEventSource( + `${import.meta.env.VITE_BASE_URL}/ai/mind-map/generate-stream`, + { + method: 'post', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + openWhenHidden: true, + body: JSON.stringify(data), + onmessage: onMessage, + onerror: onError, + onclose: onClose, + signal: ctrl.signal, + }, + ); +} + +// 查询思维导图分页 +export function getMindMapPage(params: any) { + return requestClient.get(`/ai/mind-map/page`, { params }); +} + +// 删除思维导图 +export function deleteMindMap(id: number) { + return requestClient.delete(`/ai/mind-map/delete?id=${id}`); +} diff --git a/apps/web-antd/src/api/ai/music/index.ts b/apps/web-antd/src/api/ai/music/index.ts new file mode 100644 index 000000000..a3a60ffc2 --- /dev/null +++ b/apps/web-antd/src/api/ai/music/index.ts @@ -0,0 +1,44 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace AiMusicApi { + // AI 音乐 VO + export interface MusicVO { + id: number; // 编号 + userId: number; // 用户编号 + title: string; // 音乐名称 + lyric: string; // 歌词 + imageUrl: string; // 图片地址 + audioUrl: string; // 音频地址 + videoUrl: string; // 视频地址 + status: number; // 音乐状态 + gptDescriptionPrompt: string; // 描述词 + prompt: string; // 提示词 + platform: string; // 模型平台 + model: string; // 模型 + generateMode: number; // 生成模式 + tags: string; // 音乐风格标签 + duration: number; // 音乐时长 + publicStatus: boolean; // 是否发布 + taskId: string; // 任务id + errorMessage: string; // 错误信息 + } +} + +// 查询音乐分页 +export function getMusicPage(params: PageParam) { + return requestClient.get>(`/ai/music/page`, { + params, + }); +} + +// 更新音乐 +export function updateMusic(data: any) { + return requestClient.put('/ai/music/update', data); +} + +// 删除音乐 +export function deleteMusic(id: number) { + return requestClient.delete(`/ai/music/delete?id=${id}`); +} diff --git a/apps/web-antd/src/api/ai/write/index.ts b/apps/web-antd/src/api/ai/write/index.ts new file mode 100644 index 000000000..21585cbda --- /dev/null +++ b/apps/web-antd/src/api/ai/write/index.ts @@ -0,0 +1,90 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import type { AiWriteTypeEnum } from '#/utils/constants'; + +import { fetchEventSource } from '@vben/request'; +import { useAccessStore } from '@vben/stores'; + +import { requestClient } from '#/api/request'; + +const accessStore = useAccessStore(); +export namespace AiWriteApi { + export interface WriteVO { + type: AiWriteTypeEnum.REPLY | AiWriteTypeEnum.WRITING; // 1:撰写 2:回复 + prompt: string; // 写作内容提示 1。撰写 2回复 + originalContent: string; // 原文 + length: number; // 长度 + format: number; // 格式 + tone: number; // 语气 + language: number; // 语言 + userId?: number; // 用户编号 + platform?: string; // 平台 + model?: string; // 模型 + generatedContent?: string; // 生成的内容 + errorMessage?: string; // 错误信息 + createTime?: Date; // 创建时间 + } + + export interface AiWritePageReqVO extends PageParam { + userId?: number; // 用户编号 + type?: AiWriteTypeEnum; // 写作类型 + platform?: string; // 平台 + createTime?: [string, string]; // 创建时间 + } + + export interface AiWriteRespVo { + id: number; + userId: number; + type: number; + platform: string; + model: string; + prompt: string; + generatedContent: string; + originalContent: string; + length: number; + format: number; + tone: number; + language: number; + errorMessage: string; + createTime: string; + } +} + +export function writeStream( + data: any, + onClose: any, + onMessage: any, + onError: any, + ctrl: any, +) { + const token = accessStore.accessToken; + return fetchEventSource( + `${import.meta.env.VITE_BASE_URL}/ai/write/generate-stream`, + { + method: 'post', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + openWhenHidden: true, + body: JSON.stringify(data), + onmessage: onMessage, + onerror: onError, + onclose: onClose, + signal: ctrl.signal, + }, + ); +} + +// 获取写作列表 +export function getWritePage(params: any) { + return requestClient.get>( + `/ai/write/page`, + { params }, + ); +} + +// 删除音乐 +export function deleteWrite(id: number) { + return requestClient.delete(`/ai/write/delete`, { params: { id } }); +} diff --git a/apps/web-antd/src/utils/constants.ts b/apps/web-antd/src/utils/constants.ts index 611d992d9..761df6fdf 100644 --- a/apps/web-antd/src/utils/constants.ts +++ b/apps/web-antd/src/utils/constants.ts @@ -5,6 +5,23 @@ * 枚举类 */ +/** + * AI 平台的枚举 + */ +export const AiPlatformEnum = { + TONG_YI: 'TongYi', // 阿里 + YI_YAN: 'YiYan', // 百度 + DEEP_SEEK: 'DeepSeek', // DeepSeek + ZHI_PU: 'ZhiPu', // 智谱 AI + XING_HUO: 'XingHuo', // 讯飞 + SiliconFlow: 'SiliconFlow', // 硅基流动 + OPENAI: 'OpenAI', + Ollama: 'Ollama', + STABLE_DIFFUSION: 'StableDiffusion', // Stability AI + MIDJOURNEY: 'Midjourney', // Midjourney + SUNO: 'Suno', // Suno AI +}; + export const AiModelTypeEnum = { CHAT: 1, // 聊天 IMAGE: 2, // 图像 @@ -13,6 +30,31 @@ export const AiModelTypeEnum = { EMBEDDING: 5, // 向量 RERANK: 6, // 重排 }; + +/** + * AI 图像生成状态的枚举 + */ +export const AiImageStatusEnum = { + IN_PROGRESS: 10, // 进行中 + SUCCESS: 20, // 已完成 + FAIL: 30, // 已失败 +}; +/** + * AI 音乐生成状态的枚举 + */ +export const AiMusicStatusEnum = { + IN_PROGRESS: 10, // 进行中 + SUCCESS: 20, // 已完成 + FAIL: 30, // 已失败 +}; + +/** + * AI 写作类型的枚举 + */ +export enum AiWriteTypeEnum { + WRITING = 1, // 撰写 + REPLY, // 回复 +} // ========== COMMON 模块 ========== // 全局通用状态枚举 export const CommonStatusEnum = { @@ -733,3 +775,82 @@ OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.DELEGATE, '委派'); OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.ADD_SIGN, '加签'); OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.RETURN, '退回'); OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.COPY, '抄送'); + +// ========== 【写作 UI】相关的枚举 ========== + +/** 写作点击示例时的数据 */ +export const WriteExample = { + write: { + prompt: 'vue', + data: 'Vue.js 是一种用于构建用户界面的渐进式 JavaScript 框架。它的核心库只关注视图层,易于上手,同时也便于与其他库或已有项目整合。\n\nVue.js 的特点包括:\n- 响应式的数据绑定:Vue.js 会自动将数据与 DOM 同步,使得状态管理变得更加简单。\n- 组件化:Vue.js 允许开发者通过小型、独立和通常可复用的组件构建大型应用。\n- 虚拟 DOM:Vue.js 使用虚拟 DOM 实现快速渲染,提高了性能。\n\n在 Vue.js 中,一个典型的应用结构可能包括:\n1. 根实例:每个 Vue 应用都需要一个根实例作为入口点。\n2. 组件系统:可以创建自定义的可复用组件。\n3. 指令:特殊的带有前缀 v- 的属性,为 DOM 元素提供特殊的行为。\n4. 插值:用于文本内容,将数据动态地插入到 HTML。\n5. 计算属性和侦听器:用于处理数据的复杂逻辑和响应数据变化。\n6. 条件渲染:根据条件决定元素的渲染。\n7. 列表渲染:用于显示列表数据。\n8. 事件处理:响应用户交互。\n9. 表单输入绑定:处理表单输入和验证。\n10. 组件生命周期钩子:在组件的不同阶段执行特定的函数。\n\nVue.js 还提供了官方的路由器 Vue Router 和状态管理库 Vuex,以支持构建复杂的单页应用(SPA)。\n\n在开发过程中,开发者通常会使用 Vue CLI,这是一个强大的命令行工具,用于快速生成 Vue 项目脚手架,集成了诸如 Babel、Webpack 等现代前端工具,以及热重载、代码检测等开发体验优化功能。\n\nVue.js 的生态系统还包括大量的第三方库和插件,如 Vuetify(UI 组件库)、Vue Test Utils(测试工具)等,这些都极大地丰富了 Vue.js 的开发生态。\n\n总的来说,Vue.js 是一个灵活、高效的前端框架,适合从小型项目到大型企业级应用的开发。它的易用性、灵活性和强大的社区支持使其成为许多开发者的首选框架之一。', + }, + reply: { + originalContent: '领导,我想请假', + prompt: '不批', + data: '您的请假申请已收悉,经核实和考虑,暂时无法批准您的请假申请。\n\n如有特殊情况或紧急事务,请及时与我联系。\n\n祝工作顺利。\n\n谢谢。', + }, +}; + +// ========== 【思维导图 UI】相关的枚举 ========== + +/** 思维导图已有内容生成示例 */ +export const MindMapContentExample = `# Java 技术栈 + +## 核心技术 +### Java SE +### Java EE + +## 框架 +### Spring +#### Spring Boot +#### Spring MVC +#### Spring Data +### Hibernate +### MyBatis + +## 构建工具 +### Maven +### Gradle + +## 版本控制 +### Git +### SVN + +## 测试工具 +### JUnit +### Mockito +### Selenium + +## 应用服务器 +### Tomcat +### Jetty +### WildFly + +## 数据库 +### MySQL +### PostgreSQL +### Oracle +### MongoDB + +## 消息队列 +### Kafka +### RabbitMQ +### ActiveMQ + +## 微服务 +### Spring Cloud +### Dubbo + +## 容器化 +### Docker +### Kubernetes + +## 云服务 +### AWS +### Azure +### Google Cloud + +## 开发工具 +### IntelliJ IDEA +### Eclipse +### Visual Studio Code`; diff --git a/apps/web-antd/src/views/ai/image/manager/data.ts b/apps/web-antd/src/views/ai/image/manager/data.ts new file mode 100644 index 000000000..ec6fabee2 --- /dev/null +++ b/apps/web-antd/src/views/ai/image/manager/data.ts @@ -0,0 +1,146 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { getSimpleUserList } from '#/api/system/user'; +import { DICT_TYPE, getDictOptions } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + 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.AI_IMAGE_STATUS, 'number'), + }, + }, + { + fieldName: 'publicStatus', + label: '是否发布', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + allowClear: true, + }, + }, + { + 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: '编号', + minWidth: 180, + fixed: 'left', + }, + { + title: '图片', + minWidth: 110, + fixed: 'left', + slots: { default: 'picUrl' }, + }, + { + minWidth: 180, + title: '用户', + slots: { default: 'userId' }, + }, + { + field: 'platform', + title: '平台', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_PLATFORM }, + }, + }, + { + field: 'model', + title: '模型', + minWidth: 180, + }, + { + field: 'status', + title: '绘画状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_IMAGE_STATUS }, + }, + }, + { + minWidth: 100, + title: '是否发布', + slots: { default: 'publicStatus' }, + }, + { + field: 'prompt', + title: '提示词', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'width', + title: '宽度', + minWidth: 180, + }, + { + field: 'height', + title: '高度', + minWidth: 180, + }, + { + field: 'errorMessage', + title: '错误信息', + minWidth: 180, + }, + { + field: 'taskId', + title: '任务编号', + minWidth: 180, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/ai/image/manager/index.vue b/apps/web-antd/src/views/ai/image/manager/index.vue index 3411ad599..3baafb34d 100644 --- a/apps/web-antd/src/views/ai/image/manager/index.vue +++ b/apps/web-antd/src/views/ai/image/manager/index.vue @@ -1,31 +1,135 @@ diff --git a/apps/web-antd/src/views/ai/mindmap/index/index.vue b/apps/web-antd/src/views/ai/mindmap/index/index.vue index cf47ebd8c..12ace1b87 100644 --- a/apps/web-antd/src/views/ai/mindmap/index/index.vue +++ b/apps/web-antd/src/views/ai/mindmap/index/index.vue @@ -1,28 +1,85 @@ diff --git a/apps/web-antd/src/views/ai/mindmap/index/modules/Left.vue b/apps/web-antd/src/views/ai/mindmap/index/modules/Left.vue new file mode 100644 index 000000000..750514c90 --- /dev/null +++ b/apps/web-antd/src/views/ai/mindmap/index/modules/Left.vue @@ -0,0 +1,77 @@ + +