From 6cb1f16f56ca9250b3bc089326ecb4f3642227cc Mon Sep 17 00:00:00 2001 From: GeekMaster Date: Sat, 14 Jun 2025 20:44:45 +0800 Subject: [PATCH 01/14] update menu add label --- web/src/views/admin/Menu.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/views/admin/Menu.vue b/web/src/views/admin/Menu.vue index 1498263a..85a8b3ce 100644 --- a/web/src/views/admin/Menu.vue +++ b/web/src/views/admin/Menu.vue @@ -56,7 +56,7 @@ + + + + diff --git a/web/src/components/admin/AdminSidebar.vue b/web/src/components/admin/AdminSidebar.vue index 9375a8a5..8f852cdd 100644 --- a/web/src/components/admin/AdminSidebar.vue +++ b/web/src/components/admin/AdminSidebar.vue @@ -159,6 +159,11 @@ const items = [ index: '/admin/medias', title: '音视频记录', }, + { + icon: 'image', + index: '/admin/jimeng', + title: '即梦AI任务', + }, ], }, diff --git a/web/src/router.js b/web/src/router.js index 8e05f8ac..83093a66 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -109,6 +109,12 @@ const routes = [ meta: { title: '视频创作中心' }, component: () => import('@/views/Video.vue'), }, + { + name: 'jimeng', + path: '/jimeng', + meta: { title: '即梦AI' }, + component: () => import('@/views/Jimeng.vue'), + }, ], }, { @@ -252,6 +258,12 @@ const routes = [ meta: { title: '音视频管理' }, component: () => import('@/views/admin/records/Medias.vue'), }, + { + path: '/admin/jimeng', + name: 'admin-jimeng', + meta: { title: '即梦AI管理' }, + component: () => import('@/views/admin/JimengJobs.vue'), + }, { path: '/admin/powerLog', name: 'admin-power-log', diff --git a/web/src/store/jimeng.js b/web/src/store/jimeng.js new file mode 100644 index 00000000..33c0b2f9 --- /dev/null +++ b/web/src/store/jimeng.js @@ -0,0 +1,513 @@ +// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// * Copyright 2023 The Geek-AI Authors. All rights reserved. +// * Use of this source code is governed by a Apache-2.0 license +// * that can be found in the LICENSE file. +// * @Author yangjian102621@163.com +// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import nodata from '@/assets/img/no-data.png' +import { checkSession } from '@/store/cache' +import { closeLoading, showLoading, showMessageError, showMessageOK } from '@/utils/dialog' +import { httpGet, httpPost } from '@/utils/http' +import { replaceImg, substr, dateFormat } from '@/utils/libs' +import { ElMessage, ElMessageBox } from 'element-plus' +import { defineStore } from 'pinia' +import { computed, reactive, ref } from 'vue' + +export const useJimengStore = defineStore('jimeng', () => { + // 当前激活的功能分类和具体功能 + const activeCategory = ref('image_generation') + const activeFunction = ref('text_to_image') + const useImageInput = ref(false) + + // 共同状态 + const loading = ref(false) + const submitting = ref(false) + const list = ref([]) + const noData = ref(true) + const page = ref(1) + const pageSize = ref(20) + const total = ref(0) + const taskPulling = ref(false) + const pullHandler = ref(null) + const taskFilter = ref('all') + const currentList = ref([]) + + // 用户信息 + const isLogin = ref(false) + const userPower = ref(100) + + // 视频预览 + const showDialog = ref(false) + const currentVideoUrl = ref('') + + // 功能分类配置 + const categories = [ + { key: 'image_generation', name: '图片生成' }, + { key: 'image_editing', name: 'AI修图' }, + { key: 'image_effects', name: '图像特效' }, + { key: 'video_generation', name: '视频生成' }, + ] + + // 功能配置 + const functions = [ + { key: 'text_to_image', name: '文生图', category: 'image_generation', needsPrompt: true, needsImage: false, power: 20 }, + { key: 'image_to_image_portrait', name: '图生图', category: 'image_generation', needsPrompt: true, needsImage: true, power: 30 }, + { key: 'image_edit', name: '图像编辑', category: 'image_editing', needsPrompt: true, needsImage: true, multiple: true, power: 25 }, + { key: 'image_effects', name: '图像特效', category: 'image_effects', needsPrompt: false, needsImage: true, power: 15 }, + { key: 'text_to_video', name: '文生视频', category: 'video_generation', needsPrompt: true, needsImage: false, power: 100 }, + { key: 'image_to_video', name: '图生视频', category: 'video_generation', needsPrompt: true, needsImage: true, multiple: true, power: 120 }, + ] + + // 各功能的参数 + const textToImageParams = reactive({ + prompt: '', + size: '1328x1328', + scale: 2.5, + seed: -1, + use_pre_llm: false, + }) + + const imageToImageParams = reactive({ + image_input: '', + prompt: '演唱会现场的合照,闪光灯拍摄', + size: '1328x1328', + gpen: 0.4, + skin: 0.3, + skin_unifi: 0, + gen_mode: 'creative', + seed: -1, + }) + + const imageEditParams = reactive({ + image_urls: [], + prompt: '', + scale: 0.5, + seed: -1, + }) + + const imageEffectsParams = reactive({ + image_input1: '', + template_id: '', + size: '1328x1328', + }) + + const textToVideoParams = reactive({ + prompt: '', + aspect_ratio: '16:9', + seed: -1, + }) + + const imageToVideoParams = reactive({ + image_urls: [], + prompt: '', + aspect_ratio: '16:9', + seed: -1, + }) + + // 计算属性 + const currentFunction = computed(() => { + return functions.find(f => f.key === activeFunction.value) || functions[0] + }) + + const currentFunctions = computed(() => { + return functions.filter(f => f.category === activeCategory.value) + }) + + const needsPrompt = computed(() => currentFunction.value.needsPrompt) + const needsImage = computed(() => currentFunction.value.needsImage) + const needsMultipleImages = computed(() => currentFunction.value.multiple) + const currentPowerCost = computed(() => currentFunction.value.power) + + // 初始化方法 + const init = async () => { + try { + const user = await checkSession() + isLogin.value = true + userPower.value = user.power + + // 获取任务列表 + await fetchData(1) + + // 检查是否需要开始轮询 + const pendingCount = await getPendingCount() + if (pendingCount > 0) { + startPolling() + } + } catch (error) { + console.error('初始化失败:', error) + } + } + + // 切换功能分类 + const switchCategory = (category) => { + activeCategory.value = category + const categoryFunctions = functions.filter(f => f.category === category) + if (categoryFunctions.length > 0) { + if (category === 'image_generation') { + activeFunction.value = useImageInput.value ? 'image_to_image_portrait' : 'text_to_image' + } else if (category === 'video_generation') { + activeFunction.value = useImageInput.value ? 'image_to_video' : 'text_to_video' + } else { + activeFunction.value = categoryFunctions[0].key + } + } + } + + // 切换输入模式 + const switchInputMode = () => { + if (activeCategory.value === 'image_generation') { + activeFunction.value = useImageInput.value ? 'image_to_image_portrait' : 'text_to_image' + } else if (activeCategory.value === 'video_generation') { + activeFunction.value = useImageInput.value ? 'image_to_video' : 'text_to_video' + } + } + + // 切换功能 + const switchFunction = (functionKey) => { + activeFunction.value = functionKey + } + + // 获取当前算力消耗 + const getCurrentPowerCost = () => { + return currentFunction.value.power + } + + // 获取功能名称 + const getFunctionName = (type) => { + const func = functions.find(f => f.key === type) + return func ? func.name : type + } + + // 获取任务状态文本 + const getTaskStatusText = (status) => { + const statusMap = { + 'pending': '等待中', + 'processing': '处理中', + 'completed': '已完成', + 'failed': '失败' + } + return statusMap[status] || status + } + + // 获取状态类型 + const getStatusType = (status) => { + const typeMap = { + 'pending': 'info', + 'processing': 'warning', + 'completed': 'success', + 'failed': 'danger' + } + return typeMap[status] || 'info' + } + + // 切换任务筛选 + const switchTaskFilter = (filter) => { + taskFilter.value = filter + updateCurrentList() + } + + // 更新当前列表 + const updateCurrentList = () => { + if (taskFilter.value === 'all') { + currentList.value = list.value + } else if (taskFilter.value === 'image') { + currentList.value = list.value.filter(item => + ['text_to_image', 'image_to_image_portrait', 'image_edit', 'image_effects'].includes(item.type) + ) + } else if (taskFilter.value === 'video') { + currentList.value = list.value.filter(item => + ['text_to_video', 'image_to_video'].includes(item.type) + ) + } + } + + // 获取任务列表 + const fetchData = async (pageNum = 1) => { + try { + loading.value = true + page.value = pageNum + + const response = await httpGet('/api/jimeng/jobs', { + page: pageNum, + page_size: pageSize.value + }) + + if (response.data) { + list.value = response.data.jobs || [] + total.value = response.data.total || 0 + noData.value = list.value.length === 0 + updateCurrentList() + } + } catch (error) { + console.error('获取任务列表失败:', error) + showMessageError('获取任务列表失败') + } finally { + loading.value = false + } + } + + // 提交任务 + const submitTask = async () => { + if (!isLogin.value) { + showMessageError('请先登录') + return + } + + if (userPower.value < currentPowerCost.value) { + showMessageError('算力不足') + return + } + + try { + submitting.value = true + let apiUrl = '' + let requestData = {} + + switch (activeFunction.value) { + case 'text_to_image': + apiUrl = '/api/jimeng/text-to-image' + requestData = { + prompt: textToImageParams.prompt, + width: parseInt(textToImageParams.size.split('x')[0]), + height: parseInt(textToImageParams.size.split('x')[1]), + scale: textToImageParams.scale, + seed: textToImageParams.seed, + use_pre_llm: textToImageParams.use_pre_llm, + } + break + + case 'image_to_image_portrait': + apiUrl = '/api/jimeng/image-to-image-portrait' + requestData = { + image_input: imageToImageParams.image_input, + prompt: imageToImageParams.prompt, + width: parseInt(imageToImageParams.size.split('x')[0]), + height: parseInt(imageToImageParams.size.split('x')[1]), + gpen: imageToImageParams.gpen, + skin: imageToImageParams.skin, + skin_unifi: imageToImageParams.skin_unifi, + gen_mode: imageToImageParams.gen_mode, + seed: imageToImageParams.seed, + } + break + + case 'image_edit': + apiUrl = '/api/jimeng/image-edit' + requestData = { + image_urls: imageEditParams.image_urls, + prompt: imageEditParams.prompt, + scale: imageEditParams.scale, + seed: imageEditParams.seed, + } + break + + case 'image_effects': + apiUrl = '/api/jimeng/image-effects' + requestData = { + image_input1: imageEffectsParams.image_input1, + template_id: imageEffectsParams.template_id, + width: parseInt(imageEffectsParams.size.split('x')[0]), + height: parseInt(imageEffectsParams.size.split('x')[1]), + } + break + + case 'text_to_video': + apiUrl = '/api/jimeng/text-to-video' + requestData = { + prompt: textToVideoParams.prompt, + aspect_ratio: textToVideoParams.aspect_ratio, + seed: textToVideoParams.seed, + } + break + + case 'image_to_video': + apiUrl = '/api/jimeng/image-to-video' + requestData = { + image_urls: imageToVideoParams.image_urls, + prompt: imageToVideoParams.prompt, + aspect_ratio: imageToVideoParams.aspect_ratio, + seed: imageToVideoParams.seed, + } + break + } + + const response = await httpPost(apiUrl, requestData) + + if (response.data) { + showMessageOK('任务提交成功') + // 重新获取任务列表 + await fetchData(1) + // 开始轮询 + startPolling() + } + } catch (error) { + console.error('提交任务失败:', error) + showMessageError(error.message || '提交任务失败') + } finally { + submitting.value = false + } + } + + // 获取待处理任务数量 + const getPendingCount = async () => { + try { + const response = await httpGet('/api/jimeng/pending-count') + return response.data?.count || 0 + } catch (error) { + console.error('获取待处理任务数量失败:', error) + return 0 + } + } + + // 开始轮询 + const startPolling = () => { + if (taskPulling.value) return + + taskPulling.value = true + pullHandler.value = setInterval(async () => { + const pendingCount = await getPendingCount() + if (pendingCount > 0) { + await fetchData(page.value) + } else { + stopPolling() + } + }, 3000) + } + + // 停止轮询 + const stopPolling = () => { + if (pullHandler.value) { + clearInterval(pullHandler.value) + pullHandler.value = null + } + taskPulling.value = false + } + + // 重试任务 + const retryTask = async (taskId) => { + try { + const response = await httpPost(`/api/jimeng/retry/${taskId}`) + if (response.data) { + showMessageOK('重试任务已提交') + await fetchData(page.value) + startPolling() + } + } catch (error) { + console.error('重试任务失败:', error) + showMessageError(error.message || '重试任务失败') + } + } + + // 删除任务 + const removeJob = async (item) => { + try { + await ElMessageBox.confirm('确定要删除这个任务吗?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning', + }) + + const response = await httpGet('/api/jimeng/remove', { id: item.id }) + if (response.data) { + showMessageOK('删除成功') + await fetchData(page.value) + } + } catch (error) { + if (error !== 'cancel') { + console.error('删除任务失败:', error) + showMessageError(error.message || '删除任务失败') + } + } + } + + // 播放视频 + const playVideo = (item) => { + currentVideoUrl.value = item.video_url + showDialog.value = true + } + + // 下载文件 + const downloadFile = (item) => { + const url = item.video_url || item.img_url + if (url) { + const link = document.createElement('a') + link.href = url + link.download = `jimeng_${item.id}.${item.video_url ? 'mp4' : 'jpg'}` + link.click() + } + } + + // 清理 + const cleanup = () => { + stopPolling() + } + + // 返回所有状态和方法 + return { + // 状态 + activeCategory, + activeFunction, + useImageInput, + loading, + submitting, + list, + noData, + page, + pageSize, + total, + taskFilter, + currentList, + isLogin, + userPower, + showDialog, + currentVideoUrl, + nodata, + + // 配置 + categories, + functions, + currentFunctions, + + // 参数 + textToImageParams, + imageToImageParams, + imageEditParams, + imageEffectsParams, + textToVideoParams, + imageToVideoParams, + + // 计算属性 + currentFunction, + needsPrompt, + needsImage, + needsMultipleImages, + currentPowerCost, + + // 方法 + init, + switchCategory, + switchFunction, + switchInputMode, + getCurrentPowerCost, + getFunctionName, + getTaskStatusText, + getStatusType, + switchTaskFilter, + updateCurrentList, + fetchData, + submitTask, + getPendingCount, + startPolling, + stopPolling, + retryTask, + removeJob, + playVideo, + downloadFile, + cleanup, + + // 工具函数 + substr, + replaceImg, + } +}) \ No newline at end of file diff --git a/web/src/utils/libs.js b/web/src/utils/libs.js index cdb8695a..1c151507 100644 --- a/web/src/utils/libs.js +++ b/web/src/utils/libs.js @@ -255,3 +255,8 @@ export function isChrome() { const userAgent = navigator.userAgent.toLowerCase() return /chrome/.test(userAgent) && !/edg/.test(userAgent) } + +// 格式化日期时间 +export function formatDateTime(timestamp, format = 'yyyy-MM-dd HH:mm:ss') { + return dateFormat(timestamp, format) +} diff --git a/web/src/views/Home.vue b/web/src/views/Home.vue index 35e575b2..0ad5812e 100644 --- a/web/src/views/Home.vue +++ b/web/src/views/Home.vue @@ -69,7 +69,7 @@ +
+ + 登录 + +
-
+
@@ -281,7 +286,9 @@ const logout = function () { httpGet('/api/user/logout') .then(() => { removeUserToken() - router.push('/login') + // 刷新组件 + routerViewKey.value += 1 + loginUser.value = {} }) .catch(() => { ElMessage.error('注销失败!') diff --git a/web/src/views/Index.vue b/web/src/views/Index.vue index 9023e06f..61354a0e 100644 --- a/web/src/views/Index.vue +++ b/web/src/views/Index.vue @@ -69,7 +69,7 @@ class="nav-item-box" @click="router.push(item.url)" > - +
{{ item.name }}
@@ -107,20 +107,6 @@ const githubURL = ref(import.meta.env.VITE_GITHUB_URL) const giteeURL = ref(import.meta.env.VITE_GITEE_URL) const navs = ref([]) -const iconMap = ref({ - '/chat': 'icon-chat', - '/mj': 'icon-mj', - '/sd': 'icon-sd', - '/dalle': 'icon-dalle', - '/images-wall': 'icon-image', - '/suno': 'icon-suno', - '/xmind': 'icon-xmind', - '/apps': 'icon-app', - '/member': 'icon-vip-user', - '/invite': 'icon-share', - '/luma': 'icon-luma', -}) - const displayedChars = ref([]) const initAnimation = ref('') let timer = null // 定时器句柄 diff --git a/web/src/views/Jimeng.vue b/web/src/views/Jimeng.vue new file mode 100644 index 00000000..76baacb4 --- /dev/null +++ b/web/src/views/Jimeng.vue @@ -0,0 +1,799 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/Video.vue b/web/src/views/Video.vue index c8281602..bb01df83 100644 --- a/web/src/views/Video.vue +++ b/web/src/views/Video.vue @@ -115,12 +115,12 @@
-
+
循环参考图
-
+
提示词优化
diff --git a/web/src/views/admin/JimengJobs.vue b/web/src/views/admin/JimengJobs.vue new file mode 100644 index 00000000..cf87ceec --- /dev/null +++ b/web/src/views/admin/JimengJobs.vue @@ -0,0 +1,543 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/admin/SysConfig.vue b/web/src/views/admin/SysConfig.vue index cc3b8103..ce28b400 100644 --- a/web/src/views/admin/SysConfig.vue +++ b/web/src/views/admin/SysConfig.vue @@ -169,10 +169,10 @@ Date: Fri, 18 Jul 2025 20:38:39 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E6=95=B4=E7=90=86=E5=8D=B3=E6=A2=A6?= =?UTF-8?q?=E7=94=9F=E8=A7=86=E9=A2=91=E7=9A=84API=20=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/core/app_server.go | 1 + api/go.mod | 6 +- api/go.sum | 15 +- api/handler/jimeng_handler.go | 36 +-- api/main.go | 2 +- api/service/jimeng/consumer.go | 6 +- api/service/jimeng/service.go | 37 ++- api/service/jimeng/types.go | 148 ++++----- api/store/model/jimeng_job.go | 70 ++--- web/src/assets/css/jimeng.styl | 311 ++++++++++++++++++ web/src/assets/css/member.styl | 88 ++++-- web/src/assets/css/theme-dark.styl | 3 + web/src/assets/css/theme-light.styl | 2 + web/src/views/Jimeng.vue | 468 +++++----------------------- web/src/views/Member.vue | 52 ++-- 15 files changed, 647 insertions(+), 598 deletions(-) create mode 100644 web/src/assets/css/jimeng.styl diff --git a/api/core/app_server.go b/api/core/app_server.go index 2e81c245..4207808e 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -98,6 +98,7 @@ func (s *AppServer) Run(db *gorm.DB) error { &model.MidJourneyJob{}, &model.UserLoginLog{}, &model.DallJob{}, + &model.JimengJob{}, ) // 手动删除字段 if db.Migrator().HasColumn(&model.Order{}, "deleted_at") { diff --git a/api/go.mod b/api/go.mod index 1a1ce72a..21adc772 100644 --- a/api/go.mod +++ b/api/go.mod @@ -45,7 +45,7 @@ require ( github.com/go-pay/util v0.0.2 // indirect github.com/go-pay/xlog v0.0.2 // indirect github.com/go-pay/xtime v0.0.2 // indirect - github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect @@ -78,7 +78,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/minio/md5-simd v1.1.2 // indirect @@ -120,7 +120,7 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/fx v1.19.3 - go.uber.org/multierr v1.6.0 // indirect + go.uber.org/multierr v1.7.0 // indirect golang.org/x/crypto v0.23.0 golang.org/x/sys v0.20.0 // indirect gorm.io/gorm v1.25.1 diff --git a/api/go.sum b/api/go.sum index 77b64d48..d04c1fd5 100644 --- a/api/go.sum +++ b/api/go.sum @@ -87,8 +87,9 @@ github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-tika v0.3.1 h1:l+jr10hDhZjcgxFRfcQChRLo1bPXQeLFluMyvDhXTTA= @@ -115,8 +116,11 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -260,8 +264,8 @@ go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -360,6 +364,9 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/api/handler/jimeng_handler.go b/api/handler/jimeng_handler.go index ba832830..b104e1bb 100644 --- a/api/handler/jimeng_handler.go +++ b/api/handler/jimeng_handler.go @@ -2,7 +2,6 @@ package handler import ( "fmt" - "strconv" "time" "geekai/core" @@ -83,10 +82,10 @@ func (h *JimengHandler) TextToImage(c *gin.Context) { // 创建任务 taskReq := &jimeng.CreateTaskRequest{ - Type: model.JimengJobTypeTextToImage, + Type: model.JMTaskTypeTextToImage, Prompt: req.Prompt, Params: params, - ReqKey: model.ReqKeyTextToImage, + ReqKey: jimeng.ReqKeyTextToImage, Power: 20, } @@ -180,10 +179,10 @@ func (h *JimengHandler) ImageToImagePortrait(c *gin.Context) { // 创建任务 taskReq := &jimeng.CreateTaskRequest{ - Type: model.JimengJobTypeImageToImagePortrait, + Type: model.JMTaskTypeImageToImage, Prompt: req.Prompt, Params: params, - ReqKey: model.ReqKeyImageToImagePortrait, + ReqKey: jimeng.ReqKeyImageToImagePortrait, Power: 30, } @@ -259,10 +258,10 @@ func (h *JimengHandler) ImageEdit(c *gin.Context) { // 创建任务 taskReq := &jimeng.CreateTaskRequest{ - Type: model.JimengJobTypeImageEdit, + Type: model.JMTaskTypeImageEdit, Prompt: req.Prompt, Params: params, - ReqKey: model.ReqKeyImageEdit, + ReqKey: jimeng.ReqKeyImageEdit, Power: 25, } @@ -328,10 +327,10 @@ func (h *JimengHandler) ImageEffects(c *gin.Context) { // 创建任务 taskReq := &jimeng.CreateTaskRequest{ - Type: model.JimengJobTypeImageEffects, + Type: model.JMTaskTypeImageEffects, Prompt: "", Params: params, - ReqKey: model.ReqKeyImageEffects, + ReqKey: jimeng.ReqKeyImageEffects, Power: 15, } @@ -394,10 +393,10 @@ func (h *JimengHandler) TextToVideo(c *gin.Context) { // 创建任务 taskReq := &jimeng.CreateTaskRequest{ - Type: model.JimengJobTypeTextToVideo, + Type: model.JMTaskTypeTextToVideo, Prompt: req.Prompt, Params: params, - ReqKey: model.ReqKeyTextToVideo, + ReqKey: jimeng.ReqKeyTextToVideo, Power: 100, } @@ -470,10 +469,10 @@ func (h *JimengHandler) ImageToVideo(c *gin.Context) { // 创建任务 taskReq := &jimeng.CreateTaskRequest{ - Type: model.JimengJobTypeImageToVideo, + Type: model.JMTaskTypeImageToVideo, Prompt: req.Prompt, Params: params, - ReqKey: model.ReqKeyImageToVideo, + ReqKey: jimeng.ReqKeyImageToVideo, Power: 120, } @@ -569,9 +568,8 @@ func (h *JimengHandler) Retry(c *gin.Context) { return } - jobIdStr := c.Param("id") - jobId, err := strconv.ParseUint(jobIdStr, 10, 32) - if err != nil { + jobId := h.GetInt(c, "id", 0) + if jobId == 0 { resp.ERROR(c, "参数错误") return } @@ -582,20 +580,20 @@ func (h *JimengHandler) Retry(c *gin.Context) { resp.ERROR(c, "任务不存在") return } - + if job.UserId != user.Id { resp.ERROR(c, "无权限操作") return } // 只有失败的任务才能重试 - if job.Status != model.JimengJobStatusFailed { + if job.Status != model.JMTaskStatusFailed { resp.ERROR(c, "只有失败的任务才能重试") return } // 重置任务状态 - if err := h.jimengService.UpdateJobStatus(uint(jobId), model.JimengJobStatusPending, ""); err != nil { + if err := h.jimengService.UpdateJobStatus(uint(jobId), model.JMTaskStatusInQueue, ""); err != nil { logger.Errorf("reset job status failed: %v", err) resp.ERROR(c, "重置任务状态失败") return diff --git a/api/main.go b/api/main.go index f92a1d74..670636a1 100644 --- a/api/main.go +++ b/api/main.go @@ -525,7 +525,7 @@ func main() { group.GET("jobs", h.Jobs) group.GET("pending-count", h.PendingCount) group.GET("remove", h.Remove) - group.POST("retry/:id", h.Retry) + group.GET("retry", h.Retry) }), fx.Invoke(func(s *core.AppServer, h *admin.AdminJimengHandler) { group := s.Engine.Group("/api/admin/jimeng") diff --git a/api/service/jimeng/consumer.go b/api/service/jimeng/consumer.go index 00f20061..72b2ae4c 100644 --- a/api/service/jimeng/consumer.go +++ b/api/service/jimeng/consumer.go @@ -81,9 +81,9 @@ func (c *Consumer) processTask() { // 处理任务 if err := c.service.ProcessTask(jobId); err != nil { jimengLogger.Errorf("process jimeng task failed: job_id=%d, error=%v", jobId, err) - + // 任务失败,直接标记为失败状态,不进行重试 - c.service.UpdateJobStatus(jobId, model.JimengJobStatusFailed, err.Error()) + c.service.UpdateJobStatus(jobId, model.JMTaskStatusFailed, err.Error()) } else { jimengLogger.Infof("Jimeng task processed successfully: job_id=%d", jobId) } @@ -174,4 +174,4 @@ func (c *Consumer) GetTaskStats() (map[string]interface{}, error) { } return result, nil -} \ No newline at end of file +} diff --git a/api/service/jimeng/service.go b/api/service/jimeng/service.go index b011f55f..017e1d88 100644 --- a/api/service/jimeng/service.go +++ b/api/service/jimeng/service.go @@ -56,7 +56,7 @@ func (s *Service) CreateTask(userId uint, req *CreateTaskRequest) (*model.Jimeng ReqKey: req.ReqKey, Prompt: req.Prompt, TaskParams: string(paramsJson), - Status: model.JimengJobStatusPending, + Status: model.JMTaskStatusInQueue, Power: req.Power, CreatedAt: time.Now(), UpdatedAt: time.Now(), @@ -88,23 +88,23 @@ func (s *Service) ProcessTask(jobId uint) error { } // 更新任务状态为处理中 - if err := s.UpdateJobStatus(job.Id, model.JimengJobStatusProcessing, ""); err != nil { + if err := s.UpdateJobStatus(job.Id, model.JMTaskStatusGenerating, ""); err != nil { return fmt.Errorf("update job status failed: %w", err) } // 根据任务类型处理 switch job.Type { - case model.JimengJobTypeTextToImage: + case model.JMTaskTypeTextToImage: return s.processTextToImage(&job) - case model.JimengJobTypeImageToImagePortrait: + case model.JMTaskTypeImageToImage: return s.processImageToImagePortrait(&job) - case model.JimengJobTypeImageEdit: + case model.JMTaskTypeImageEdit: return s.processImageEdit(&job) - case model.JimengJobTypeImageEffects: + case model.JMTaskTypeImageEffects: return s.processImageEffects(&job) - case model.JimengJobTypeTextToVideo: + case model.JMTaskTypeTextToVideo: return s.processTextToVideo(&job) - case model.JimengJobTypeImageToVideo: + case model.JMTaskTypeImageToVideo: return s.processImageToVideo(&job) default: return fmt.Errorf("unsupported task type: %s", job.Type) @@ -517,10 +517,15 @@ func (s *Service) pollTaskStatus(jobId uint, taskId, reqKey string) error { } switch resp.Data.Status { - case TaskStatusDone: + case model.JMTaskStatusDone: + // 判断任务是否成功 + if resp.Message != "Success" { + return s.handleTaskError(jobId, fmt.Sprintf("task failed: %s", resp.Data.AlgorithmBaseResp.StatusMessage)) + } + // 任务完成,更新结果 updates := map[string]any{ - "status": model.JimengJobStatusCompleted, + "status": model.JMTaskStatusSuccess, "progress": 100, "updated_at": time.Now(), } @@ -535,15 +540,15 @@ func (s *Service) pollTaskStatus(jobId uint, taskId, reqKey string) error { return s.db.Model(&model.JimengJob{}).Where("id = ?", jobId).Updates(updates).Error - case TaskStatusInQueue: + case model.JMTaskStatusInQueue: // 任务在队列中 s.UpdateJobProgress(jobId, 10) - case TaskStatusGenerating: + case model.JMTaskStatusGenerating: // 任务处理中 s.UpdateJobProgress(jobId, 50) - case TaskStatusNotFound, TaskStatusExpired: + case model.JMTaskStatusNotFound: // 任务未找到或已过期 return s.handleTaskError(jobId, fmt.Sprintf("task not found or expired: %s", resp.Data.Status)) @@ -559,7 +564,7 @@ func (s *Service) pollTaskStatus(jobId uint, taskId, reqKey string) error { } // UpdateJobStatus 更新任务状态 -func (s *Service) UpdateJobStatus(jobId uint, status, errMsg string) error { +func (s *Service) UpdateJobStatus(jobId uint, status model.JMTaskStatus, errMsg string) error { updates := map[string]any{ "status": status, "updated_at": time.Now(), @@ -581,7 +586,7 @@ func (s *Service) UpdateJobProgress(jobId uint, progress int) error { // handleTaskError 处理任务错误 func (s *Service) handleTaskError(jobId uint, errMsg string) error { serviceLogger.Errorf("Jimeng task error (job_id: %d): %s", jobId, errMsg) - return s.UpdateJobStatus(jobId, model.JimengJobStatusFailed, errMsg) + return s.UpdateJobStatus(jobId, model.JMTaskStatusFailed, errMsg) } // GetJob 获取任务 @@ -618,7 +623,7 @@ func (s *Service) GetUserJobs(userId uint, page, pageSize int) ([]*model.JimengJ func (s *Service) GetPendingTaskCount(userId uint) (int64, error) { var count int64 err := s.db.Model(&model.JimengJob{}).Where("user_id = ? AND status IN (?)", userId, - []string{model.JimengJobStatusPending, model.JimengJobStatusProcessing}).Count(&count).Error + []model.JMTaskStatus{model.JMTaskStatusInQueue, model.JMTaskStatusGenerating}).Count(&count).Error return count, err } diff --git a/api/service/jimeng/types.go b/api/service/jimeng/types.go index 029e0f91..443ef4f1 100644 --- a/api/service/jimeng/types.go +++ b/api/service/jimeng/types.go @@ -1,25 +1,35 @@ package jimeng -import "time" +import "geekai/store/model" + +// ReqKey 常量定义 +const ( + ReqKeyTextToImage = "high_aes_general_v30l_zt2i" // 文生图 + ReqKeyImageToImagePortrait = "i2i_portrait_photo" // 图生图人像写真 + ReqKeyImageEdit = "seededit_v3.0" // 图像编辑 + ReqKeyImageEffects = "i2i_multi_style_zx2x" // 图像特效 + ReqKeyTextToVideo = "jimeng_vgfm_t2v_l20" // 文生视频 + ReqKeyImageToVideo = "jimeng_vgfm_i2v_l20" // 图生视频 +) // SubmitTaskRequest 提交任务请求 type SubmitTaskRequest struct { ReqKey string `json:"req_key"` // 文生图参数 - Prompt string `json:"prompt,omitempty"` - Seed int64 `json:"seed,omitempty"` - Scale float64 `json:"scale,omitempty"` - Width int `json:"width,omitempty"` - Height int `json:"height,omitempty"` - UsePreLLM bool `json:"use_pre_llm,omitempty"` + Prompt string `json:"prompt,omitempty"` + Seed int64 `json:"seed,omitempty"` + Scale float64 `json:"scale,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + UsePreLLM bool `json:"use_pre_llm,omitempty"` // 图生图参数 - ImageInput string `json:"image_input,omitempty"` - ImageUrls []string `json:"image_urls,omitempty"` - BinaryDataBase64 []string `json:"binary_data_base64,omitempty"` - Gpen float64 `json:"gpen,omitempty"` - Skin float64 `json:"skin,omitempty"` - SkinUnifi float64 `json:"skin_unifi,omitempty"` - GenMode string `json:"gen_mode,omitempty"` + ImageInput string `json:"image_input,omitempty"` + ImageUrls []string `json:"image_urls,omitempty"` + BinaryDataBase64 []string `json:"binary_data_base64,omitempty"` + Gpen float64 `json:"gpen,omitempty"` + Skin float64 `json:"skin,omitempty"` + SkinUnifi float64 `json:"skin_unifi,omitempty"` + GenMode string `json:"gen_mode,omitempty"` // 图像编辑参数 // 图像特效参数 ImageInput1 string `json:"image_input1,omitempty"` @@ -59,56 +69,28 @@ type QueryTaskResponse struct { StatusCode int `json:"status_code"` StatusMessage string `json:"status_message"` } `json:"algorithm_base_resp"` - BinaryDataBase64 []string `json:"binary_data_base64"` - ImageUrls []string `json:"image_urls"` - VideoUrl string `json:"video_url"` - RespData string `json:"resp_data"` - Status string `json:"status"` - LlmResult string `json:"llm_result"` - PeResult string `json:"pe_result"` - PredictTagsResult string `json:"predict_tags_result"` - RephraserResult string `json:"rephraser_result"` - VlmResult string `json:"vlm_result"` - InferCtx interface{} `json:"infer_ctx"` + BinaryDataBase64 []string `json:"binary_data_base64"` + ImageUrls []string `json:"image_urls"` + VideoUrl string `json:"video_url"` + RespData string `json:"resp_data"` + Status model.JMTaskStatus `json:"status"` + LlmResult string `json:"llm_result"` + PeResult string `json:"pe_result"` + PredictTagsResult string `json:"predict_tags_result"` + RephraserResult string `json:"rephraser_result"` + VlmResult string `json:"vlm_result"` + InferCtx any `json:"infer_ctx"` } `json:"data"` } -// TaskStatus 任务状态 -const ( - TaskStatusInQueue = "in_queue" // 任务已提交 - TaskStatusGenerating = "generating" // 任务处理中 - TaskStatusDone = "done" // 处理完成 - TaskStatusNotFound = "not_found" // 任务未找到 - TaskStatusExpired = "expired" // 任务已过期 -) - // CreateTaskRequest 创建任务请求 type CreateTaskRequest struct { - Type string `json:"type"` - Prompt string `json:"prompt"` - Params map[string]interface{} `json:"params"` - ReqKey string `json:"req_key"` - ImageUrls []string `json:"image_urls,omitempty"` - Power int `json:"power,omitempty"` -} - -// TaskInfo 任务信息 -type TaskInfo struct { - Id uint `json:"id"` - UserId uint `json:"user_id"` - TaskId string `json:"task_id"` - Type string `json:"type"` - ReqKey string `json:"req_key"` - Prompt string `json:"prompt"` - TaskParams string `json:"task_params"` - ImgURL string `json:"img_url"` - VideoURL string `json:"video_url"` - Progress int `json:"progress"` - Status string `json:"status"` - ErrMsg string `json:"err_msg"` - Power int `json:"power"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + Type model.JMTaskType `json:"type"` + Prompt string `json:"prompt"` + Params map[string]any `json:"params"` + ReqKey string `json:"req_key"` + ImageUrls []string `json:"image_urls,omitempty"` + Power int `json:"power,omitempty"` } // LogoInfo 水印信息 @@ -128,36 +110,36 @@ type ReqJsonConfig struct { // ImageEffectTemplate 图像特效模板 const ( - TemplateIdFelt3DPolaroid = "felt_3d_polaroid" // 毛毡3d拍立得风格 - TemplateIdMyWorld = "my_world" // 像素世界风 - TemplateIdMyWorldUniversal = "my_world_universal" // 像素世界-万物通用版 - TemplateIdPlasticBubbleFigure = "plastic_bubble_figure" // 盲盒玩偶风 - TemplateIdPlasticBubbleFigureCartoon = "plastic_bubble_figure_cartoon_text" // 塑料泡罩人偶-文字卡头版 - TemplateIdFurryDreamDoll = "furry_dream_doll" // 毛绒玩偶风 - TemplateIdMicroLandscapeMiniWorld = "micro_landscape_mini_world" // 迷你世界玩偶风 + TemplateIdFelt3DPolaroid = "felt_3d_polaroid" // 毛毡3d拍立得风格 + TemplateIdMyWorld = "my_world" // 像素世界风 + TemplateIdMyWorldUniversal = "my_world_universal" // 像素世界-万物通用版 + TemplateIdPlasticBubbleFigure = "plastic_bubble_figure" // 盲盒玩偶风 + TemplateIdPlasticBubbleFigureCartoon = "plastic_bubble_figure_cartoon_text" // 塑料泡罩人偶-文字卡头版 + TemplateIdFurryDreamDoll = "furry_dream_doll" // 毛绒玩偶风 + TemplateIdMicroLandscapeMiniWorld = "micro_landscape_mini_world" // 迷你世界玩偶风 TemplateIdMicroLandscapeProfessional = "micro_landscape_mini_world_professional" // 微型景观小世界-职业版 - TemplateIdAcrylicOrnaments = "acrylic_ornaments" // 亚克力挂饰 - TemplateIdFeltKeychain = "felt_keychain" // 毛毡钥匙扣 - TemplateIdLofiPixelCharacter = "lofi_pixel_character_mini_card" // Lofi像素人物小卡 - TemplateIdAngelFigurine = "angel_figurine" // 天使形象手办 - TemplateIdLyingInFluffyBelly = "lying_in_fluffy_belly" // 躺在毛茸茸肚皮里 - TemplateIdGlassBall = "glass_ball" // 玻璃球 + TemplateIdAcrylicOrnaments = "acrylic_ornaments" // 亚克力挂饰 + TemplateIdFeltKeychain = "felt_keychain" // 毛毡钥匙扣 + TemplateIdLofiPixelCharacter = "lofi_pixel_character_mini_card" // Lofi像素人物小卡 + TemplateIdAngelFigurine = "angel_figurine" // 天使形象手办 + TemplateIdLyingInFluffyBelly = "lying_in_fluffy_belly" // 躺在毛茸茸肚皮里 + TemplateIdGlassBall = "glass_ball" // 玻璃球 ) // AspectRatio 视频宽高比 const ( - AspectRatio16_9 = "16:9" // 1280×720 - AspectRatio9_16 = "9:16" // 720×1280 - AspectRatio1_1 = "1:1" // 960×960 - AspectRatio4_3 = "4:3" // 960×720 - AspectRatio3_4 = "3:4" // 720×960 - AspectRatio21_9 = "21:9" // 1680×720 - AspectRatio9_21 = "9:21" // 720×1680 + AspectRatio16_9 = "16:9" // 1280×720 + AspectRatio9_16 = "9:16" // 720×1280 + AspectRatio1_1 = "1:1" // 960×960 + AspectRatio4_3 = "4:3" // 960×720 + AspectRatio3_4 = "3:4" // 720×960 + AspectRatio21_9 = "21:9" // 1680×720 + AspectRatio9_21 = "9:21" // 720×1680 ) // GenMode 生成模式 const ( - GenModeCreative = "creative" // 提示词模式 - GenModeReference = "reference" // 全参考模式 - GenModeReferenceChar = "reference_char" // 人物参考模式 -) \ No newline at end of file + GenModeCreative = "creative" // 提示词模式 + GenModeReference = "reference" // 全参考模式 + GenModeReferenceChar = "reference_char" // 人物参考模式 +) diff --git a/api/store/model/jimeng_job.go b/api/store/model/jimeng_job.go index 5a16b027..ffc5fafa 100644 --- a/api/store/model/jimeng_job.go +++ b/api/store/model/jimeng_job.go @@ -6,50 +6,46 @@ import ( // JimengJob 即梦AI任务模型 type JimengJob struct { - Id uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` - UserId uint `gorm:"column:user_id;type:int;not null;index;comment:用户ID" json:"user_id"` - TaskId string `gorm:"column:task_id;type:varchar(100);not null;index;comment:任务ID" json:"task_id"` - Type string `gorm:"column:type;type:varchar(50);not null;comment:任务类型" json:"type"` - ReqKey string `gorm:"column:req_key;type:varchar(100);comment:请求Key" json:"req_key"` - Prompt string `gorm:"column:prompt;type:text;comment:提示词" json:"prompt"` - TaskParams string `gorm:"column:task_params;type:text;comment:任务参数JSON" json:"task_params"` - ImgURL string `gorm:"column:img_url;type:varchar(1024);comment:图片或封面URL" json:"img_url"` - VideoURL string `gorm:"column:video_url;type:varchar(1024);comment:视频URL" json:"video_url"` - RawData string `gorm:"column:raw_data;type:text;comment:原始API响应" json:"raw_data"` - Progress int `gorm:"column:progress;type:int;default:0;comment:进度百分比" json:"progress"` - Status string `gorm:"column:status;type:varchar(20);default:'pending';comment:任务状态" json:"status"` - ErrMsg string `gorm:"column:err_msg;type:varchar(1024);comment:错误信息" json:"err_msg"` - Power int `gorm:"column:power;type:int;default:0;comment:消耗算力" json:"power"` - CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null;comment:创建时间" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at;type:datetime;not null;comment:更新时间" json:"updated_at"` + Id uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + UserId uint `gorm:"column:user_id;type:int;not null;index;comment:用户ID" json:"user_id"` + TaskId string `gorm:"column:task_id;type:varchar(100);not null;index;comment:任务ID" json:"task_id"` + Type JMTaskType `gorm:"column:type;type:varchar(50);not null;comment:任务类型" json:"type"` + ReqKey string `gorm:"column:req_key;type:varchar(100);comment:请求Key" json:"req_key"` + Prompt string `gorm:"column:prompt;type:text;comment:提示词" json:"prompt"` + TaskParams string `gorm:"column:task_params;type:text;comment:任务参数JSON" json:"task_params"` + ImgURL string `gorm:"column:img_url;type:varchar(1024);comment:图片或封面URL" json:"img_url"` + VideoURL string `gorm:"column:video_url;type:varchar(1024);comment:视频URL" json:"video_url"` + RawData string `gorm:"column:raw_data;type:text;comment:原始API响应" json:"raw_data"` + Progress int `gorm:"column:progress;type:int;default:0;comment:进度百分比" json:"progress"` + Status JMTaskStatus `gorm:"column:status;type:varchar(20);default:'pending';comment:任务状态" json:"status"` + ErrMsg string `gorm:"column:err_msg;type:varchar(1024);comment:错误信息" json:"err_msg"` + Power int `gorm:"column:power;type:int(11);default:0;comment:消耗算力" json:"power"` + CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null;comment:创建时间" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;type:datetime;not null;comment:更新时间" json:"updated_at"` } -// JimengJobStatus 即梦任务状态常量 +// JMTaskStatus 任务状态 +type JMTaskStatus string + const ( - JimengJobStatusPending = "pending" - JimengJobStatusProcessing = "processing" - JimengJobStatusCompleted = "completed" - JimengJobStatusFailed = "failed" + JMTaskStatusInQueue = JMTaskStatus("in_queue") // 任务已提交 + JMTaskStatusGenerating = JMTaskStatus("generating") // 任务处理中 + JMTaskStatusDone = JMTaskStatus("done") // 处理完成 + JMTaskStatusNotFound = JMTaskStatus("not_found") // 任务未找到 + JMTaskStatusSuccess = JMTaskStatus("success") // 任务成功 + JMTaskStatusFailed = JMTaskStatus("failed") // 任务失败 ) -// JimengJobType 即梦任务类型常量 -const ( - JimengJobTypeTextToImage = "text_to_image" // 文生图 - JimengJobTypeImageToImagePortrait = "image_to_image_portrait" // 图生图人像写真 - JimengJobTypeImageEdit = "image_edit" // 图像编辑 - JimengJobTypeImageEffects = "image_effects" // 图像特效 - JimengJobTypeTextToVideo = "text_to_video" // 文生视频 - JimengJobTypeImageToVideo = "image_to_video" // 图生视频 -) +// JMTaskType 任务类型 +type JMTaskType string -// ReqKey 常量定义 const ( - ReqKeyTextToImage = "high_aes_general_v30l_zt2i" // 文生图 - ReqKeyImageToImagePortrait = "i2i_portrait_photo" // 图生图人像写真 - ReqKeyImageEdit = "seededit_v3.0" // 图像编辑 - ReqKeyImageEffects = "i2i_multi_style_zx2x" // 图像特效 - ReqKeyTextToVideo = "jimeng_vgfm_t2v_l20" // 文生视频 - ReqKeyImageToVideo = "jimeng_vgfm_i2v_l20" // 图生视频 + JMTaskTypeTextToImage = JMTaskType("text_to_image") // 文生图 + JMTaskTypeImageToImage = JMTaskType("image_to_image") // 图生图 + JMTaskTypeImageEdit = JMTaskType("image_edit") // 图像编辑 + JMTaskTypeImageEffects = JMTaskType("image_effects") // 图像特效 + JMTaskTypeTextToVideo = JMTaskType("text_to_video") // 文生视频 + JMTaskTypeImageToVideo = JMTaskType("image_to_video") // 图生视频 ) // TableName 返回数据表名称 diff --git a/web/src/assets/css/jimeng.styl b/web/src/assets/css/jimeng.styl new file mode 100644 index 00000000..fb69e673 --- /dev/null +++ b/web/src/assets/css/jimeng.styl @@ -0,0 +1,311 @@ +.page-jimeng { + display: flex; + min-height: 100vh; + background: var(--chat-bg); + + // 左侧参数面板 + .params-panel { + min-width: 380px; + max-width: 380px; + margin: 10px; + padding: 20px; + border-radius: 12px; + background: #ffffff; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + color: #333; + font-size: 14px; + overflow: auto; + + h2 { + font-weight: bold; + font-size: 20px; + text-align: center; + color: #333; + margin-bottom: 30px; + } + + // 功能分类按钮组 + .category-buttons { + margin-bottom: 25px; + + .category-label { + display: flex; + align-items: center; + margin-bottom: 15px; + font-size: 16px; + font-weight: 600; + color: #333; + + .el-icon { + margin-right: 8px; + color: #5865f2; + } + } + + .category-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; + + .category-btn { + display: flex; + flex-direction: column; + align-items: center; + padding: 15px 10px; + border: 2px solid #f0f0f0; + border-radius: 12px; + cursor: pointer; + transition: all 0.3s ease; + background: #fafafa; + + &:hover { + border-color: #5865f2; + background: #f8f9ff; + transform: translateY(-2px); + } + + &.active { + border-color: #5865f2; + background: linear-gradient(135deg, #5865f2 0%, #7289da 100%); + color: white; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(88, 101, 242, 0.3); + } + + .category-icon { + font-size: 20px; + margin-bottom: 8px; + } + + .category-name { + font-size: 12px; + font-weight: 500; + } + } + } + } + + // 功能开关 + .function-switch { + margin-bottom: 25px; + + .switch-label { + display: flex; + align-items: center; + margin-bottom: 15px; + font-size: 16px; + font-weight: 600; + color: #333; + + .el-icon { + margin-right: 8px; + color: #5865f2; + } + } + + .switch-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: 15px; + border: 1px solid #e0e0e0; + border-radius: 10px; + background: #f9f9f9; + + .switch-info { + flex: 1; + + .switch-title { + font-size: 14px; + font-weight: 600; + color: #333; + margin-bottom: 4px; + } + + .switch-desc { + font-size: 12px; + color: #666; + } + } + } + } + + // 参数容器 + .params-container { + .function-panel { + .param-line { + margin-bottom: 15px; + + &.pt { + margin-top: 20px; + } + + .label { + display: flex; + align-items: center; + margin-bottom: 8px; + font-weight: 600; + color: #333; + } + } + + .item-group { + display: flex; + align-items: center; + margin-bottom: 15px; + + .label { + margin-right: 15px; + font-weight: 600; + color: #333; + min-width: 80px; + } + } + + .text-info { + margin: 20px 0; + padding: 15px; + background: #f0f8ff; + border-radius: 8px; + border-left: 4px solid #5865f2; + } + + .submit-btn { + margin-top: 30px; + + .el-button { + width: 100%; + height: 50px; + font-size: 16px; + font-weight: 600; + } + } + } + } + } + + // 右侧主要内容区域 + .main-content { + flex: 1; + padding: 20px; + background: var(--chat-bg); + color: var(--text-theme-color); + + .works-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + + .h-title { + font-size: 24px; + font-weight: 600; + color: var(--text-theme-color); + margin: 0; + } + } + + .task-list { + .list-box { + .task-item { + display: flex; + align-items: center; + padding: 20px; + margin-bottom: 15px; + background: var(--card-bg); + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + .task-left { + margin-right: 20px; + + .task-preview { + width: 120px; + height: 90px; + border-radius: 8px; + overflow: hidden; + background: #f0f0f0; + display: flex; + align-items: center; + justify-content: center; + + .preview-image, .preview-video { + width: 100%; + height: 100%; + object-fit: cover; + } + + .preview-placeholder { + display: flex; + flex-direction: column; + align-items: center; + color: #999; + font-size: 12px; + + .el-icon { + font-size: 24px; + margin-bottom: 5px; + } + } + } + } + + .task-center { + flex: 1; + + .task-info { + display: flex; + gap: 8px; + margin-bottom: 10px; + } + + .task-prompt { + font-size: 14px; + color: var(--text-theme-color); + margin-bottom: 8px; + line-height: 1.4; + } + + .task-meta { + display: flex; + gap: 15px; + font-size: 12px; + color: #999; + } + } + + .task-right { + .task-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + } + } + } + + .pagination { + margin-top: 30px; + display: flex; + justify-content: center; + } + } + } +} + +// 响应式设计 +@media (max-width: 768px) { + .page-jimeng { + flex-direction: column; + + .params-panel { + min-width: 100%; + max-width: 100%; + margin: 10px 0; + } + + .main-content { + padding: 15px; + } + } +} \ No newline at end of file diff --git a/web/src/assets/css/member.styl b/web/src/assets/css/member.styl index 0aeb4838..ec584a86 100644 --- a/web/src/assets/css/member.styl +++ b/web/src/assets/css/member.styl @@ -1,5 +1,4 @@ .member { - // background-color: #282c34; height 100% .title { @@ -13,36 +12,79 @@ .inner { color var(--text-theme-color) - padding 15px 0 15px 15px; + padding 15px 0 15px 15px overflow-x hidden overflow-y visible display flex flex-flow row - .user-profile { - padding 10px 20px 20px 20px - width 300px - background-color var(--chat-bg) - color var(--text-theme-color) - border-radius 10px - //height 100vh - - .el-form-item__label { - color var(--text-theme-color) - justify-content start + .profile-card { + max-width 300px + border-radius 18px + box-shadow 0 4px 8px rgba(0,0,0,0.08) + padding 24px 16px + background var(--panel-bg) + position relative + z-index 1 + margin-bottom 24px + } + .profile-title { + font-size 18px + font-weight bold + margin-bottom 18px + color #2d8cf0 + letter-spacing 2px + text-align center + } + .profile-btn { + width 100% + margin-bottom 12px + font-size 16px + font-weight 500 + display flex + align-items center + justify-content center + border none + border-radius 8px + background linear-gradient(90deg, #6dd5ed 0%, #2193b0 100%) + color #fff + transition all 0.3s + i { + margin-right 8px + font-size 20px } - - .user-opt { - .el-col { - padding 10px - - .el-button { - width 100% - } - } + &:hover { + box-shadow 0 2px 12px #2193b0aa + transform translateY(-2px) scale(1.03) + background linear-gradient(90deg, #2193b0 0%, #6dd5ed 100%) } } - + .profile-btn.email { + background linear-gradient(90deg, #f7971e 0%, #ffd200 100%) + } + .profile-btn.mobile { + background linear-gradient(90deg, #43cea2 0%, #185a9d 100%) + } + .profile-btn.third { + background linear-gradient(90deg, #ff512f 0%, #dd2476 100%) + } + .profile-btn.password { + background linear-gradient(90deg, #1d4350 0%, #a43931 100%) + } + .profile-btn.redeem { + background linear-gradient(90deg, #00c6ff 0%, #0072ff 100%) + } + .profile-bg { + position absolute + left 0 + top 0 + width 100% + height 100% + z-index 0 + background url('data:image/svg+xml;utf8,') no-repeat center/cover + opacity 0.08 + pointer-events none + } .product-box { padding 0 20px diff --git a/web/src/assets/css/theme-dark.styl b/web/src/assets/css/theme-dark.styl index 4676dd6b..b8a14600 100644 --- a/web/src/assets/css/theme-dark.styl +++ b/web/src/assets/css/theme-dark.styl @@ -96,4 +96,7 @@ // el-dialog 阴影 --el-box-shadow: 0 0 15px rgba(107, 80, 225, 0.8); + + // 面板背景 + --panel-bg: linear-gradient(135deg, #252d58 0%, #1f243f 100%); } diff --git a/web/src/assets/css/theme-light.styl b/web/src/assets/css/theme-light.styl index 69405a1a..2ec52562 100644 --- a/web/src/assets/css/theme-light.styl +++ b/web/src/assets/css/theme-light.styl @@ -57,6 +57,8 @@ // 引用快样式 --quote-bg-color: #e0dfff; --quote-text-color: #333; + // 面板背景 + --panel-bg: linear-gradient(135deg, #f5eafe 0%, #e9e6fc 100%); } diff --git a/web/src/views/Jimeng.vue b/web/src/views/Jimeng.vue index 76baacb4..28a13a15 100644 --- a/web/src/views/Jimeng.vue +++ b/web/src/views/Jimeng.vue @@ -2,16 +2,10 @@
-

即梦AI

-
-
- - 功能分类 -
-
-
+
生成模式 @@ -34,17 +33,21 @@
- {{ store.useImageInput ? (store.activeCategory === 'image_generation' ? '图生图' : '图生视频') : (store.activeCategory === 'image_generation' ? '文生图' : '文生视频') }} + {{ + store.useImageInput + ? store.activeCategory === 'image_generation' + ? '图生图' + : '图生视频' + : store.activeCategory === 'image_generation' + ? '文生图' + : '文生视频' + }}
{{ store.useImageInput ? '使用图片作为输入' : '使用文字作为输入' }}
- +
@@ -68,7 +71,7 @@ show-word-limit />
- +
图片尺寸:
@@ -80,17 +83,22 @@
- +
创意度:
- +
种子值: - +
- +
智能优化提示词 @@ -105,7 +113,7 @@
- +
提示词:
@@ -119,7 +127,7 @@ show-word-limit />
- +
图片尺寸:
@@ -131,20 +139,25 @@
- +
GPEN强度:
- +
肌肤质感:
- +
种子值: - +
@@ -156,7 +169,7 @@
- +
编辑提示词:
@@ -170,15 +183,20 @@ show-word-limit />
- +
编辑强度:
- +
种子值: - +
@@ -190,7 +208,7 @@
- +
特效模板:
@@ -201,7 +219,7 @@
- +
输出尺寸:
@@ -230,7 +248,7 @@ show-word-limit />
- +
视频比例:
@@ -241,10 +259,15 @@
- +
种子值: - +
@@ -256,7 +279,7 @@
- +
提示词:
@@ -270,7 +293,7 @@ show-word-limit /> - +
视频比例:
@@ -281,10 +304,15 @@ - +
种子值: - +
@@ -296,9 +324,9 @@
-
- +
@@ -379,7 +407,7 @@ {{ item.power }}算力
- +
播放 - + 删除
@@ -418,11 +442,7 @@
- +