diff --git a/apps/web-antd/public/static/dall2.jpg b/apps/web-antd/public/static/dall2.jpg new file mode 100644 index 00000000..c07374dc Binary files /dev/null and b/apps/web-antd/public/static/dall2.jpg differ diff --git a/apps/web-antd/public/static/dall3.jpg b/apps/web-antd/public/static/dall3.jpg new file mode 100644 index 00000000..7f45803b Binary files /dev/null and b/apps/web-antd/public/static/dall3.jpg differ diff --git a/apps/web-antd/public/static/qingxi.jpg b/apps/web-antd/public/static/qingxi.jpg new file mode 100644 index 00000000..d76b8156 Binary files /dev/null and b/apps/web-antd/public/static/qingxi.jpg differ diff --git a/apps/web-antd/public/static/ziran.jpg b/apps/web-antd/public/static/ziran.jpg new file mode 100644 index 00000000..62907242 Binary files /dev/null and b/apps/web-antd/public/static/ziran.jpg differ diff --git a/apps/web-antd/src/api/ai/image/index.ts b/apps/web-antd/src/api/ai/image/index.ts index 24699ac7..8e2856d7 100644 --- a/apps/web-antd/src/api/ai/image/index.ts +++ b/apps/web-antd/src/api/ai/image/index.ts @@ -40,7 +40,7 @@ export namespace AiImageApi { export interface ImageMidjourneyImagineReqVO { prompt: string; // 提示词 modelId: number; // 模型 - base64Array: string[]; // size不能为空 + base64Array?: string[]; // size不能为空 width: string; // 图片宽度 height: string; // 图片高度 version: string; // 版本 @@ -62,7 +62,7 @@ export function getImagePageMy(params: PageParam) { // 获取【我的】绘图记录 export function getImageMy(id: number) { - return requestClient.get(`/ai/image/get-my?id=${id}`); + return requestClient.get(`/ai/image/get-my?id=${id}`); } // 获取【我的】绘图记录列表 diff --git a/apps/web-antd/src/router/routes/modules/ai.ts b/apps/web-antd/src/router/routes/modules/ai.ts index 9e1feb92..67f9f69d 100644 --- a/apps/web-antd/src/router/routes/modules/ai.ts +++ b/apps/web-antd/src/router/routes/modules/ai.ts @@ -9,6 +9,18 @@ const routes: RouteRecordRaw[] = [ hideInMenu: true, }, children: [ + { + path: 'image/square', + component: () => import('#/views/ai/image/square/index.vue'), + name: 'AiImageSquare', + meta: { + noCache: true, + hidden: true, + canTo: true, + title: '绘图作品', + activePath: '/ai/image', + }, + }, { path: 'knowledge/document', component: () => import('#/views/ai/knowledge/document/index.vue'), diff --git a/apps/web-antd/src/utils/constants.ts b/apps/web-antd/src/utils/constants.ts index 761df6fd..a9d192e6 100644 --- a/apps/web-antd/src/utils/constants.ts +++ b/apps/web-antd/src/utils/constants.ts @@ -30,7 +30,29 @@ export const AiModelTypeEnum = { EMBEDDING: 5, // 向量 RERANK: 6, // 重排 }; - +export interface ImageModelVO { + key: string; + name: string; + image?: string; +} +export const OtherPlatformEnum: ImageModelVO[] = [ + { + key: AiPlatformEnum.TONG_YI, + name: '通义万相', + }, + { + key: AiPlatformEnum.YI_YAN, + name: '百度千帆', + }, + { + key: AiPlatformEnum.ZHI_PU, + name: '智谱 AI', + }, + { + key: AiPlatformEnum.SiliconFlow, + name: '硅基流动', + }, +]; /** * AI 图像生成状态的枚举 */ @@ -55,6 +77,172 @@ export enum AiWriteTypeEnum { WRITING = 1, // 撰写 REPLY, // 回复 } + +// ========== 【图片 UI】相关的枚举 ========== + +export const ImageHotWords = [ + '中国旗袍', + '古装美女', + '卡通头像', + '机甲战士', + '童话小屋', + '中国长城', +]; // 图片热词 + +export const ImageHotEnglishWords = [ + 'Chinese Cheongsam', + 'Ancient Beauty', + 'Cartoon Avatar', + 'Mech Warrior', + 'Fairy Tale Cottage', + 'The Great Wall of China', +]; // 图片热词(英文) + +export const StableDiffusionSamplers: ImageModelVO[] = [ + { + key: 'DDIM', + name: 'DDIM', + }, + { + key: 'DDPM', + name: 'DDPM', + }, + { + key: 'K_DPMPP_2M', + name: 'K_DPMPP_2M', + }, + { + key: 'K_DPMPP_2S_ANCESTRAL', + name: 'K_DPMPP_2S_ANCESTRAL', + }, + { + key: 'K_DPM_2', + name: 'K_DPM_2', + }, + { + key: 'K_DPM_2_ANCESTRAL', + name: 'K_DPM_2_ANCESTRAL', + }, + { + key: 'K_EULER', + name: 'K_EULER', + }, + { + key: 'K_EULER_ANCESTRAL', + name: 'K_EULER_ANCESTRAL', + }, + { + key: 'K_HEUN', + name: 'K_HEUN', + }, + { + key: 'K_LMS', + name: 'K_LMS', + }, +]; + +export const StableDiffusionStylePresets: ImageModelVO[] = [ + { + key: '3d-model', + name: '3d-model', + }, + { + key: 'analog-film', + name: 'analog-film', + }, + { + key: 'anime', + name: 'anime', + }, + { + key: 'cinematic', + name: 'cinematic', + }, + { + key: 'comic-book', + name: 'comic-book', + }, + { + key: 'digital-art', + name: 'digital-art', + }, + { + key: 'enhance', + name: 'enhance', + }, + { + key: 'fantasy-art', + name: 'fantasy-art', + }, + { + key: 'isometric', + name: 'isometric', + }, + { + key: 'line-art', + name: 'line-art', + }, + { + key: 'low-poly', + name: 'low-poly', + }, + { + key: 'modeling-compound', + name: 'modeling-compound', + }, + // neon-punk origami photographic pixel-art tile-texture + { + key: 'neon-punk', + name: 'neon-punk', + }, + { + key: 'origami', + name: 'origami', + }, + { + key: 'photographic', + name: 'photographic', + }, + { + key: 'pixel-art', + name: 'pixel-art', + }, + { + key: 'tile-texture', + name: 'tile-texture', + }, +]; + +export const StableDiffusionClipGuidancePresets: ImageModelVO[] = [ + { + key: 'NONE', + name: 'NONE', + }, + { + key: 'FAST_BLUE', + name: 'FAST_BLUE', + }, + { + key: 'FAST_GREEN', + name: 'FAST_GREEN', + }, + { + key: 'SIMPLE', + name: 'SIMPLE', + }, + { + key: 'SLOW', + name: 'SLOW', + }, + { + key: 'SLOWER', + name: 'SLOWER', + }, + { + key: 'SLOWEST', + name: 'SLOWEST', + }, +]; // ========== COMMON 模块 ========== // 全局通用状态枚举 export const CommonStatusEnum = { @@ -142,7 +330,136 @@ export const InfraApiErrorLogProcessStatusEnum = { DONE: 1, // 已处理 IGNORE: 2, // 已忽略 }; +export interface ImageSizeVO { + key: string; + name?: string; + style: string; + width: string; + height: string; +} +export const Dall3SizeList: ImageSizeVO[] = [ + { + key: '1024x1024', + name: '1:1', + width: '1024', + height: '1024', + style: 'width: 30px; height: 30px;background-color: #dcdcdc;', + }, + { + key: '1024x1792', + name: '3:5', + width: '1024', + height: '1792', + style: 'width: 30px; height: 50px;background-color: #dcdcdc;', + }, + { + key: '1792x1024', + name: '5:3', + width: '1792', + height: '1024', + style: 'width: 50px; height: 30px;background-color: #dcdcdc;', + }, +]; +export const Dall3Models: ImageModelVO[] = [ + { + key: 'dall-e-3', + name: 'DALL·E 3', + image: `/static/dall2.jpg`, + }, + { + key: 'dall-e-2', + name: 'DALL·E 2', + image: `/static/dall3.jpg`, + }, +]; + +export const Dall3StyleList: ImageModelVO[] = [ + { + key: 'vivid', + name: '清晰', + image: `/static/qingxi.jpg`, + }, + { + key: 'natural', + name: '自然', + image: `/static/ziran.jpg`, + }, +]; +export const MidjourneyModels: ImageModelVO[] = [ + { + key: 'midjourney', + name: 'MJ', + image: 'https://bigpt8.com/pc/_nuxt/mj.34a61377.png', + }, + { + key: 'niji', + name: 'NIJI', + image: 'https://bigpt8.com/pc/_nuxt/nj.ca79b143.png', + }, +]; +export const MidjourneyVersions = [ + { + value: '6.0', + label: 'v6.0', + }, + { + value: '5.2', + label: 'v5.2', + }, + { + value: '5.1', + label: 'v5.1', + }, + { + value: '5.0', + label: 'v5.0', + }, + { + value: '4.0', + label: 'v4.0', + }, +]; + +export const NijiVersionList = [ + { + value: '5', + label: 'v5', + }, +]; + +export const MidjourneySizeList: ImageSizeVO[] = [ + { + key: '1:1', + width: '1', + height: '1', + style: 'width: 30px; height: 30px;background-color: #dcdcdc;', + }, + { + key: '3:4', + width: '3', + height: '4', + style: 'width: 30px; height: 40px;background-color: #dcdcdc;', + }, + { + key: '4:3', + width: '4', + height: '3', + style: 'width: 40px; height: 30px;background-color: #dcdcdc;', + }, + { + key: '9:16', + width: '9', + height: '16', + style: 'width: 30px; height: 50px;background-color: #dcdcdc;', + }, + { + key: '16:9', + width: '16', + height: '9', + style: 'width: 50px; height: 30px;background-color: #dcdcdc;', + }, +]; // ========== PAY 模块 ========== /** * 支付渠道枚举 diff --git a/apps/web-antd/src/utils/formatTime.ts b/apps/web-antd/src/utils/formatTime.ts index 45fe6442..9f2f30a8 100644 --- a/apps/web-antd/src/utils/formatTime.ts +++ b/apps/web-antd/src/utils/formatTime.ts @@ -1,3 +1,79 @@ +import dayjs from 'dayjs'; + +/** + * 时间日期转换 + * @param date 当前时间,new Date() 格式 + * @param format 需要转换的时间格式字符串 + * @description format 字符串随意,如 `YYYY-MM、YYYY-MM-DD` + * @description format 季度:"YYYY-MM-DD HH:mm:ss QQQQ" + * @description format 星期:"YYYY-MM-DD HH:mm:ss WWW" + * @description format 几周:"YYYY-MM-DD HH:mm:ss ZZZ" + * @description format 季度 + 星期 + 几周:"YYYY-MM-DD HH:mm:ss WWW QQQQ ZZZ" + * @returns 返回拼接后的时间字符串 + */ +export function formatDate(date: Date, format?: string): string { + // 日期不存在,则返回空 + if (!date) { + return ''; + } + // 日期存在,则进行格式化 + return date ? dayjs(date).format(format ?? 'YYYY-MM-DD HH:mm:ss') : ''; +} + +/** + * 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前` + * @param param 当前时间,new Date() 格式或者字符串时间格式 + * @param format 需要转换的时间格式字符串 + * @description param 10秒: 10 * 1000 + * @description param 1分: 60 * 1000 + * @description param 1小时: 60 * 60 * 1000 + * @description param 24小时:60 * 60 * 24 * 1000 + * @description param 3天: 60 * 60* 24 * 1000 * 3 + * @returns 返回拼接后的时间字符串 + */ +export function formatPast( + param: Date | string, + format = 'YYYY-MM-DD HH:mm:ss', +): string { + // 传入格式处理、存储转换值 + let s: number, t: any; + // 获取js 时间戳 + let time: number = Date.now(); + // 是否是对象 + typeof param === 'string' || typeof param === 'object' + ? (t = new Date(param).getTime()) + : (t = param); + // 当前时间戳 - 传入时间戳 + time = Number.parseInt(`${time - t}`); + if (time < 10_000) { + // 10秒内 + return '刚刚'; + } else if (time < 60_000 && time >= 10_000) { + // 超过10秒少于1分钟内 + s = Math.floor(time / 1000); + return `${s}秒前`; + } else if (time < 3_600_000 && time >= 60_000) { + // 超过1分钟少于1小时 + s = Math.floor(time / 60_000); + return `${s}分钟前`; + } else if (time < 86_400_000 && time >= 3_600_000) { + // 超过1小时少于24小时 + s = Math.floor(time / 3_600_000); + return `${s}小时前`; + } else if (time < 259_200_000 && time >= 86_400_000) { + // 超过1天少于3天内 + s = Math.floor(time / 86_400_000); + return `${s}天前`; + } else { + // 超过3天 + const date = + typeof param === 'string' || typeof param === 'object' + ? new Date(param) + : param; + return formatDate(date, format); + } +} + /** * 将毫秒,转换成时间字符串。例如说,xx 分钟 * @@ -30,3 +106,39 @@ export function formatPast2(ms: number): string { } return second > 0 ? `${second} 秒` : `${0} 秒`; } + +/** + * @param {Date | number | string} time 需要转换的时间 + * @param {string} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss + */ +export function formatTime(time: Date | number | string, fmt: string) { + if (time) { + const date = new Date(time); + const o = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'H+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + 'q+': Math.floor((date.getMonth() + 3) / 3), + S: date.getMilliseconds(), + }; + if (/(y+)/.test(fmt)) { + fmt = fmt.replace( + RegExp.$1, + `${date.getFullYear()}`.slice(4 - RegExp.$1.length), + ); + } + for (const k in o) { + if (new RegExp(`(${k})`).test(fmt)) { + fmt = fmt.replace( + RegExp.$1, + RegExp.$1.length === 1 ? o[k] : `00${o[k]}`.slice(`${o[k]}`.length), + ); + } + } + return fmt; + } else { + return ''; + } +} diff --git a/apps/web-antd/src/utils/index.ts b/apps/web-antd/src/utils/index.ts index 9e82ab96..4fa686cc 100644 --- a/apps/web-antd/src/utils/index.ts +++ b/apps/web-antd/src/utils/index.ts @@ -7,3 +7,4 @@ export * from './formCreate'; export * from './rangePickerProps'; export * from './routerHelper'; export * from './upload'; +export * from './utils'; diff --git a/apps/web-antd/src/utils/utils.ts b/apps/web-antd/src/utils/utils.ts new file mode 100644 index 00000000..d08eb778 --- /dev/null +++ b/apps/web-antd/src/utils/utils.ts @@ -0,0 +1,13 @@ +/** + * Created by 芋道源码 + * + * AI 枚举类 + * + * 问题:为什么不放在 src/utils/common-utils.ts 呢? + * 回答:主要 AI 是可选模块,考虑到独立、解耦,所以放在了 /views/ai/utils/common-utils.ts + */ + +/** 判断字符串是否包含中文 */ +export const hasChinese = (str: string) => { + return /[\u4E00-\u9FA5]/.test(str); +}; diff --git a/apps/web-antd/src/views/ai/image/index/components/ImageCard.vue b/apps/web-antd/src/views/ai/image/index/components/ImageCard.vue new file mode 100644 index 00000000..4223edc3 --- /dev/null +++ b/apps/web-antd/src/views/ai/image/index/components/ImageCard.vue @@ -0,0 +1,175 @@ + + + + diff --git a/apps/web-antd/src/views/ai/image/index/components/ImageDetail.vue b/apps/web-antd/src/views/ai/image/index/components/ImageDetail.vue new file mode 100644 index 00000000..f408b722 --- /dev/null +++ b/apps/web-antd/src/views/ai/image/index/components/ImageDetail.vue @@ -0,0 +1,236 @@ + + + + diff --git a/apps/web-antd/src/views/ai/image/index/components/ImageList.vue b/apps/web-antd/src/views/ai/image/index/components/ImageList.vue new file mode 100644 index 00000000..dcd44fae --- /dev/null +++ b/apps/web-antd/src/views/ai/image/index/components/ImageList.vue @@ -0,0 +1,250 @@ + + + + diff --git a/apps/web-antd/src/views/ai/image/index/components/common/index.vue b/apps/web-antd/src/views/ai/image/index/components/common/index.vue new file mode 100644 index 00000000..05b4ca59 --- /dev/null +++ b/apps/web-antd/src/views/ai/image/index/components/common/index.vue @@ -0,0 +1,248 @@ + + +