From d73a71d00f57d5bb2ffe4dacdd511eb22f21981c Mon Sep 17 00:00:00 2001 From: WangLeo <690854599@qq.com> Date: Fri, 3 Apr 2026 18:51:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=B2=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.production | 3 + Vidu Q3-T2V.json | 34 ++ components.d.ts | 7 +- index.html | 1 + src/apis/display/index.js | 5 + src/assets/dialog/videoPattern1.svg | 4 - src/assets/dialog/videoPattern4.svg | 4 +- .../{videoPattern3.svg => videoPattern5.svg} | 0 src/assets/dialog/videoPattern6.svg | 4 + src/components/Select/index.vue | 15 +- src/components/canvas/index.vue | 162 +++++---- src/components/dialogBox/Time/index.vue | 20 +- src/components/dialogBox/index.vue | 181 +++++++--- .../model/{index.vue => painting.vue} | 99 ++++-- src/components/dialogBox/model/video.vue | 149 ++++++++ src/components/dialogBox/pattern/index.vue | 13 +- .../proportion/{index.vue => painting.vue} | 87 +++-- src/components/dialogBox/proportion/video.vue | 241 +++++++++++++ .../dialogBox/videoImageUploader/index.vue | 330 ++++++++++++++++++ src/config/modelConfig/painting.json | 2 +- src/config/modelConfig/video.json | 26 +- src/config/runninghub/index.js | 67 ++-- src/stores/user.js | 18 +- src/utils/createTask.js | 11 +- src/utils/modelConfig.js | 95 +++++ src/utils/websocket.js | 14 +- src/views/home/display/components/set.vue | 13 +- src/views/home/display/index.vue | 4 +- src/views/home/index.vue | 2 +- 29 files changed, 1339 insertions(+), 272 deletions(-) create mode 100644 Vidu Q3-T2V.json delete mode 100644 src/assets/dialog/videoPattern1.svg rename src/assets/dialog/{videoPattern3.svg => videoPattern5.svg} (100%) create mode 100644 src/assets/dialog/videoPattern6.svg rename src/components/dialogBox/model/{index.vue => painting.vue} (50%) create mode 100644 src/components/dialogBox/model/video.vue rename src/components/dialogBox/proportion/{index.vue => painting.vue} (83%) create mode 100644 src/components/dialogBox/proportion/video.vue create mode 100644 src/components/dialogBox/videoImageUploader/index.vue create mode 100644 src/utils/modelConfig.js diff --git a/.env.production b/.env.production index 22963f5..ab104c6 100644 --- a/.env.production +++ b/.env.production @@ -17,6 +17,9 @@ VITE_API_PAY_TARGET = 'https://sxwz.xueai.art' # http://43.248.133.202 VITE_API_WORKFLOW_UPLOAD = 'https://designtools.xueai.art/workflow/file/upload' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow VITE_API_WORKFLOW_WS = 'wss://talkingdraw.xueai.art/testworkflow' +# 模型资源 +VITE_API_MODEL_RESOURCE = 'https://resources.xueai.art/AIGC' + # 是否开启KKFileView FILE_OPEN_PREVIEW = false # KKFileView服务器地址 diff --git a/Vidu Q3-T2V.json b/Vidu Q3-T2V.json new file mode 100644 index 0000000..625281a --- /dev/null +++ b/Vidu Q3-T2V.json @@ -0,0 +1,34 @@ +{ + "nodeInfoList": { + "prompt":{ "nodeId":"2", "fieldName":"prompt", "fieldValue":"" }, + "resolution":{ "nodeId":"2", "fieldName":"resolution", "fieldValue":"" }, + "proportion":{ "nodeId":"2", "fieldName":"aspect_ratio", "fieldValue":"" }, + "duration":{ "nodeId":"2", "fieldName":"duration", "fieldValue": 5}, + "audio":{ "nodeId":"2", "fieldName":"audio", "fieldValue": false} + }, + "workflowId": "2036349280088231938", + "display": { + "promptPlaceholder": {"default": "描述你想生成的画面和动作。"}, + "prompt": {"default": ""}, + "resolution": {"default": "1k","options":[ + { "value": "360", "label": "流畅 360P" }, + { "value": "540", "label": "标清 540P" }, + { "value": "720", "label": "高清 720P" }, + { "value": "1k", "label": "超清 1K" } + ]}, + "proportion": {"default": "16:9","options":[ + { "value": "21:9", "label": "21:9" }, + { "value": "16:9", "label": "16:9" }, + { "value": "4:3", "label": "4:3" }, + { "value": "1:1", "label": "1:1" }, + { "value": "3:4", "label": "3:4" }, + { "value": "9:16", "label": "9:16" } + ]}, + "duration": {"default": 5,"options":[ + { "value": 5, "label": "5秒" }, + { "value": 10, "label": "10秒" }, + { "value": 15, "label": "15秒" } + ]}, + "audio": {"default": false} + } +} \ No newline at end of file diff --git a/components.d.ts b/components.d.ts index 8124057..b124c6f 100644 --- a/components.d.ts +++ b/components.d.ts @@ -11,9 +11,7 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { - 2: typeof import('./src/components/virtual-scroller/VirtualScroller copy 2.vue')['default'] Canvas: typeof import('./src/components/canvas/index.vue')['default'] - copy: typeof import('./src/components/virtual-scroller/VirtualScroller copy.vue')['default'] DialogBox: typeof import('./src/components/dialogBox/index.vue')['default'] ElButton: typeof import('element-plus/es')['ElButton'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] @@ -26,15 +24,16 @@ declare module 'vue' { IEpStar: typeof import('~icons/ep/star')['default'] ImageUploader: typeof import('./src/components/dialogBox/imageUploader/index.vue')['default'] Img: typeof import('./src/components/Img/index.vue')['default'] - Model: typeof import('./src/components/dialogBox/model/index.vue')['default'] + Painting: typeof import('./src/components/dialogBox/model/painting.vue')['default'] Pattern: typeof import('./src/components/dialogBox/pattern/index.vue')['default'] Popover: typeof import('./src/components/Popover/index.vue')['default'] - Proportion: typeof import('./src/components/dialogBox/proportion/index.vue')['default'] Quantity: typeof import('./src/components/dialogBox/quantity/index.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] Select: typeof import('./src/components/Select/index.vue')['default'] Time: typeof import('./src/components/dialogBox/Time/index.vue')['default'] + Video: typeof import('./src/components/dialogBox/model/video.vue')['default'] + VideoImageUploader: typeof import('./src/components/dialogBox/videoImageUploader/index.vue')['default'] VirtualScroller: typeof import('./src/components/virtual-scroller/VirtualScroller.vue')['default'] 'VirtualScroller copy': typeof import('./src/components/virtual-scroller/VirtualScroller copy.vue')['default'] 'VirtualScroller copy 2': typeof import('./src/components/virtual-scroller/VirtualScroller copy 2.vue')['default'] diff --git a/index.html b/index.html index 660898f..6d755d8 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,7 @@ + AI Painting diff --git a/src/apis/display/index.js b/src/apis/display/index.js index f60a70d..da66eac 100644 --- a/src/apis/display/index.js +++ b/src/apis/display/index.js @@ -13,4 +13,9 @@ export function cancelOrCollect(query) { // 删除生成历史 export function deleteGenerateHistory(query) { return service.delete('/taskRecordHistory/delete', { params: query }) +} + +// 获取免费次数 +export function getFreeTimes(id) { + return service.get('/plantformBalance/userBalances', { params: { id } }) } \ No newline at end of file diff --git a/src/assets/dialog/videoPattern1.svg b/src/assets/dialog/videoPattern1.svg deleted file mode 100644 index 0541c40..0000000 --- a/src/assets/dialog/videoPattern1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/assets/dialog/videoPattern4.svg b/src/assets/dialog/videoPattern4.svg index 18619aa..0541c40 100644 --- a/src/assets/dialog/videoPattern4.svg +++ b/src/assets/dialog/videoPattern4.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/assets/dialog/videoPattern3.svg b/src/assets/dialog/videoPattern5.svg similarity index 100% rename from src/assets/dialog/videoPattern3.svg rename to src/assets/dialog/videoPattern5.svg diff --git a/src/assets/dialog/videoPattern6.svg b/src/assets/dialog/videoPattern6.svg new file mode 100644 index 0000000..18619aa --- /dev/null +++ b/src/assets/dialog/videoPattern6.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/Select/index.vue b/src/components/Select/index.vue index 84cf6a2..8039048 100644 --- a/src/components/Select/index.vue +++ b/src/components/Select/index.vue @@ -29,8 +29,8 @@ v-for="(option, index) in group.options" :key="option.value || index" class="dropdown-item" - :class="{ selected: option.value === selectedValue }" - @click.stop="selectOption(option)" + :class="{ selected: option.value === selectedValue, disabled: option.disabled }" + @click.stop="!option.disabled && selectOption(option)" >
@@ -46,8 +46,8 @@ v-for="(option, index) in options" :key="option.value || index" class="dropdown-item" - :class="{ selected: option.value === selectedValue }" - @click.stop="selectOption(option)" + :class="{ selected: option.value === selectedValue, disabled: option.disabled }" + @click.stop="!option.disabled && selectOption(option)" >
@@ -321,12 +321,17 @@ onBeforeUnmount(() => { line-height: normal; } -.dropdown-item:hover { +.dropdown-item:hover:not(.disabled) { color: #333333; background-color: #f5f6f7; border-radius: 10px; } +.dropdown-item.disabled { + cursor: not-allowed; + opacity: 0.5; +} + .dropdown-item.selected { color: #333; font-weight: 400; diff --git a/src/components/canvas/index.vue b/src/components/canvas/index.vue index c8e5732..cc353ab 100644 --- a/src/components/canvas/index.vue +++ b/src/components/canvas/index.vue @@ -89,7 +89,10 @@
- + @@ -158,7 +161,7 @@ diff --git a/src/components/dialogBox/pattern/index.vue b/src/components/dialogBox/pattern/index.vue index 0087bcd..2ad910d 100644 --- a/src/components/dialogBox/pattern/index.vue +++ b/src/components/dialogBox/pattern/index.vue @@ -19,10 +19,11 @@ + + diff --git a/src/components/dialogBox/videoImageUploader/index.vue b/src/components/dialogBox/videoImageUploader/index.vue new file mode 100644 index 0000000..4c7c708 --- /dev/null +++ b/src/components/dialogBox/videoImageUploader/index.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/src/config/modelConfig/painting.json b/src/config/modelConfig/painting.json index 17fe462..7e34bb7 100644 --- a/src/config/modelConfig/painting.json +++ b/src/config/modelConfig/painting.json @@ -12,6 +12,6 @@ { "value": "Jimeng_4.0", "label": "Jimeng.4.0" } ], "vision": [ - { "value": "Qwen3.5plus", "label": "Qwen3.5plus" } + { "value": "Qwen3.5plus", "label": "Qwen3.5plus", "disabled": true } ] } diff --git a/src/config/modelConfig/video.json b/src/config/modelConfig/video.json index 174eeb3..6171db5 100644 --- a/src/config/modelConfig/video.json +++ b/src/config/modelConfig/video.json @@ -1,8 +1,24 @@ { - "video": [ - { "value": "FlashHead", "label": "FlashHead" }, - { "value": "LTX2.3-T2V", "label": "LTX 2.3-T2V" }, - { "value": "Vidu Q3-I2V", "label": "Vidu Q3-I2V" }, - { "value": "Vidu Q3-T2V", "label": "Vidu Q3-T2V" } + "文生视频": [ + { "value": "LTX2.0", "label": "LTX2.0 T2V" }, + { "value": "viduQ3-T2V", "label": "viduQ3 T2V" } + ], + "首尾帧": [ + { "value": "Hailuo-02-fast", "label": "海螺 fast" }, + { "value": "LTX2.0-I2V", "label": "LTX2.0 I2V" }, + { "value": "LTX2.3-T2V", "label": "LTX2.3 T2V", "disabled": true }, + { "value": "ViduQ3-turbo", "label": "ViduQ3-turbo" } + ], + "数字人": [ + { "value": "FlashHead", "label": "FlashHead" } + ], + "全能参考": [ + { "value": "Seedance 2.0", "label": "Seedance 2.0", "disabled": true } + ], + "智能多帧": [ + { "value": "Seedance 2.0", "label": "Seedance 2.0", "disabled": true } + ], + "主体参考": [ + { "value": "Seedance 2.0", "label": "Seedance 2.0", "disabled": true } ] } diff --git a/src/config/runninghub/index.js b/src/config/runninghub/index.js index c081ae0..6eb61f1 100644 --- a/src/config/runninghub/index.js +++ b/src/config/runninghub/index.js @@ -1,12 +1,14 @@ -export async function Playload(data,modelType) { - // data = getWidthHeight(data) +import { fetchModelConfig } from '@/utils/modelConfig' + +export async function Playload(data) { try { - const response = await fetch(`https://resources.xueai.art/AIGC/static/public/Platform/Painting/workflows/${modelType}/${data.modelName}.json`) - const json = await response.json() + const json = await fetchModelConfig(data.type, data.modelName, data.modelType) const nodeInfoList = [] + const proportionParam = data.params.find(param => param.name === 'proportion') || {data: 0} + const resolutionParam = data.params.find(param => param.name === 'resolution') || {data: 0} - if (Array.isArray(data.imgs) && data.imgs.length > 0 && (modelType === 'image' || modelType === 'edit')) { + if (Array.isArray(data.imgs) && data.imgs.length > 0 && (data.modelType === 'image' || data.modelType === 'edit')) { for (const key of data.imgs) { if (json.nodeInfoList[key.name]) { console.log(key) @@ -27,9 +29,30 @@ export async function Playload(data,modelType) { } } + if ((json.nodeInfoList.width || json.nodeInfoList.height) && (proportionParam.data && resolutionParam.data)) { + const { width, height } = getWidthHeight({ + proportion: proportionParam.data, + resolution: resolutionParam.data + }) + json.nodeInfoList.width.fieldValue = width + json.nodeInfoList.height.fieldValue = height + nodeInfoList.push(json.nodeInfoList.width, json.nodeInfoList.height) + } + + if (Array.isArray(json.seed)) { + const min = Math.pow(10, 0) + const max = Math.pow(10, 9) - 1 + const randomNum = Math.floor(Math.random() * (max - min + 1)) + min + json.seed.map((seedItem) => { + seedItem.fieldValue = randomNum + nodeInfoList.push(seedItem) + }) + } + if (Array.isArray(json.must)) { nodeInfoList.push(...json.must) } + return { workflowId: json.workflowId, nodeInfoList @@ -47,21 +70,23 @@ export function result(result) { return { type: false, message: result.data.exception_message } } function getWidthHeight(data) { - // 去除分辨率字符串中的'p'并转换为数字 - // const resolution = 720 - const resolution = Number.parseInt(data.resolution.replace('p', '')) || Number.parseInt(data.resolution) - // 解析宽高比 - const aspectRatioParts = data.aspect_ratio.split(':') || data.aspect_ratio.split(':') - const widthRatio = Number.parseInt(aspectRatioParts[0]) - const heightRatio = Number.parseInt(aspectRatioParts[1]) - if (widthRatio > heightRatio) { - data.height = resolution - data.width = Math.round(resolution * widthRatio / heightRatio) - } else { - data.width = resolution - data.height = Math.round(resolution * heightRatio / widthRatio) - } - console.log(data.width, data.height) + let resolution = data.resolution * 1 - return data + let widthRatio = 16 + let heightRatio = 9 + if (data.proportion) { + const aspectRatioParts = data.proportion.split(':') || data.proportion.split(':') + widthRatio = Number.parseInt(aspectRatioParts[0]) || 16 + heightRatio = Number.parseInt(aspectRatioParts[1]) || 9 + } + let width, height + if (widthRatio > heightRatio) { + height = resolution + width = Math.round(resolution * widthRatio / heightRatio) + } else { + width = resolution + height = Math.round(resolution * heightRatio / widthRatio) + } + console.log(width, height) + return { width, height } } \ No newline at end of file diff --git a/src/stores/user.js b/src/stores/user.js index 19bad0b..8cf8182 100644 --- a/src/stores/user.js +++ b/src/stores/user.js @@ -4,6 +4,7 @@ import { getUserInfo as getUserInfoApi, logout as logoutApi } from '@/apis/auth' +import { getFreeTimes } from '@/apis/display' import { clearToken, getToken, setToken } from '@/utils/auth' const storeSetup = () => { @@ -33,6 +34,7 @@ const storeSetup = () => { const dept = ref({}) // 当前用户所在部门集合 const isLogin = ref(false) + const freeTimes = ref(0) // 免费次数 // 重置token const resetToken = () => { @@ -70,6 +72,18 @@ const storeSetup = () => { } } + // 获取免费次数 + const fetchFreeTimes = async (chargeType = 1) => { + if (userInfo.id) { + const res = await getFreeTimes(userInfo.id) + const balanceList = res.data || [] + const target = balanceList.find((item) => item.chargeType === chargeType) + freeTimes.value = target?.balance || 0 + return freeTimes.value + } + return 0 + } + // 登录 const accountLogin = async (req) => { const res = await accountLoginApi(req) @@ -120,12 +134,14 @@ const storeSetup = () => { dept, username, isLogin, + freeTimes, accountLogin, logout, logoutCallBack, getInfo, resetToken, - checkTokenValid + checkTokenValid, + fetchFreeTimes } } diff --git a/src/utils/createTask.js b/src/utils/createTask.js index af9aa0a..83205d8 100644 --- a/src/utils/createTask.js +++ b/src/utils/createTask.js @@ -1,19 +1,20 @@ import outPlatform from '@/config/index' // 处理音频生成任务的数据并返回 -export async function createTask(data, modelType, taskId, token) { - console.log(data, modelType) - const payload = await outPlatform[data.platform].Playload(data, modelType) +export async function createTask(data, taskId, token) { + console.log(data) + const payload = await outPlatform[data.platform].Playload(data) return { AIGC: data.AIGC, platform: data.platform, - prompt: data.prompt, - taskType: modelType === 'text' ? 1 : 2, + taskType: data.modelType === 'text' ? 1 : 2, modelName: data.modelName, payload, taskId, token, + quantity: data.quantity, + free: data.free, result: data.result } } diff --git a/src/utils/modelConfig.js b/src/utils/modelConfig.js new file mode 100644 index 0000000..67d717b --- /dev/null +++ b/src/utils/modelConfig.js @@ -0,0 +1,95 @@ +const STORAGE_PREFIX = 'model_config_' + +function getTodayDateString() { + const today = new Date() + return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}` +} + +function getStorageKey(modelName, modelType) { + return `${STORAGE_PREFIX}${modelType}_${modelName}` +} + +function getConfigFromStorage(modelName, modelType) { + try { + const key = getStorageKey(modelName, modelType) + const stored = localStorage.getItem(key) + + if (!stored) { + return null + } + + const data = JSON.parse(stored) + const todayStr = getTodayDateString() + + if (data.storageDate !== todayStr) { + localStorage.removeItem(key) + return null + } + + return data.config + } catch (error) { + console.error('从localStorage读取配置失败:', error) + return null + } +} + +function saveConfigToStorage(modelName, modelType, config) { + try { + const key = getStorageKey(modelName, modelType) + const data = { + config, + storageDate: getTodayDateString(), + timestamp: Date.now() + } + localStorage.setItem(key, JSON.stringify(data)) + } catch (error) { + console.error('保存配置到localStorage失败:', error) + } +} + +export async function fetchModelConfig(type, modelName, modelType) { + const cachedConfig = getConfigFromStorage(modelName, modelType) + + if (cachedConfig) { + console.log(`从缓存加载模型配置: ${modelName}`) + return cachedConfig + } + + try { + const url = `${import.meta.env.VITE_API_MODEL_RESOURCE}/static/public/Platform/${type}/workflows/${modelType}/${modelName}.json` + console.log(`从远程获取模型配置: ${url}`) + + const response = await fetch(url) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const config = await response.json() + + saveConfigToStorage(modelName, modelType, config) + + return config + } catch (error) { + console.error('获取模型配置失败:', error) + throw error + } +} + +export function clearModelConfigCache(modelName, modelType) { + const key = getStorageKey(modelName, modelType) + localStorage.removeItem(key) +} + +export function clearAllModelConfigCache() { + const keysToRemove = [] + + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i) + if (key && key.startsWith(STORAGE_PREFIX)) { + keysToRemove.push(key) + } + } + + keysToRemove.forEach(key => localStorage.removeItem(key)) +} diff --git a/src/utils/websocket.js b/src/utils/websocket.js index 097f30f..8cba46a 100644 --- a/src/utils/websocket.js +++ b/src/utils/websocket.js @@ -1,15 +1,15 @@ import { ElNotification } from 'element-plus' import { h, ref } from 'vue' -import { useDisplayStore } from '@/stores' +import { useDisplayStore, useUserStore } from '@/stores' import { getToken } from '@/utils/auth' import { createTask, getTask } from '@/utils/createTask' import { userError } from '@/utils/tokenError' export function getChargeType(chargeType) { switch (chargeType) { - case 'painting': + case 'Painting': return 1 - case 'video': + case 'Video': return 4 default: return 2 @@ -58,7 +58,7 @@ export function websocketSuccess() { }) } -export async function generate(modelType, data, generateData, type) { +export async function generate(data, generateData) { const progress_text = ref('') const message = ref('') const useDisplay = useDisplayStore() @@ -68,7 +68,7 @@ export async function generate(modelType, data, generateData, type) { useDisplay.isSubGerenate = true - const result = await createTask(data, modelType, taskId, token) + const result = await createTask(data, taskId, token) console.log(result) // const token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjY0NDEwODAyMjk1OTgzNzIzMCwicm5TdHIiOiJiWkVwS2JLWFJyZmRIaFFHWXZKTkdzOGdGM0JSRmxQOCJ9.5eQ2GtVdrDntQDe2tnF8vl_DhTfd2uW-KNqzvl1imc0' const wsURL = `${import.meta.env.VITE_API_WORKFLOW_WS}/?token=Bearer ${token}` @@ -102,7 +102,7 @@ export async function generate(modelType, data, generateData, type) { useDisplay.addGeneratingItem({ taskId: taskId, - type: type, + type: data.type, generateData: generateData }) setTimeout(() => { @@ -145,8 +145,8 @@ export async function generate(modelType, data, generateData, type) { } else if (event.code === 1000 && event.reason === 'success') { console.log('收到服务器消息:', res) const result = await getTask(res) + if(useUserStore().freeTimes) await useUserStore().fetchFreeTimes() if (result.type) { - if (currentTaskId) { useDisplay.updateItemToSuccess(currentTaskId, result.urls) } diff --git a/src/views/home/display/components/set.vue b/src/views/home/display/components/set.vue index 59e08da..6c91529 100644 --- a/src/views/home/display/components/set.vue +++ b/src/views/home/display/components/set.vue @@ -2,10 +2,10 @@
-
+
- {{ props.item.generateData.prompt || '生成图片' }} + {{ props.item.generateData.prompt || '生成图片' }}
{{ props.item.generateData.model }}
{{ props.item.generateData.proportion }}
@@ -45,7 +45,7 @@
-
+
index @@ -70,10 +70,10 @@
-
+
-