Files
geekai/web/src/store/jimeng.js
2025-09-12 18:58:52 +08:00

366 lines
9.1 KiB
JavaScript

// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * 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 { checkSession } from '@/store/cache'
import { JimengFunctions, JimengParams } from '@/store/data/jimeng_data'
import { useSharedStore } from '@/store/sharedata'
import { showMessageError, showMessageOK } from '@/utils/dialog'
import { httpDownload, httpGet, httpPost } from '@/utils/http'
import { replaceImg, substr } from '@/utils/libs'
import { ElMessageBox } from 'element-plus'
import { defineStore } from 'pinia'
import { reactive, ref } from 'vue'
export const useJimengStore = defineStore('jimeng', () => {
// 共同状态
const loading = ref(false)
const submitting = ref(false)
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const taskFilter = ref('all')
const currentList = ref([])
const isOver = ref(false)
// 用户信息
const isLogin = ref(false)
// 视频预览
const showDialog = ref(false)
const currentVideoUrl = ref('')
// 登录弹窗
const shareStore = useSharedStore()
// 积分消耗配置
const powerConfig = reactive({})
const currentPowerCost = ref('0积分')
// 功能配置
const functions = JimengFunctions
// 当前激活的功能
const activeFunction = ref('image')
// 参数配置
const functionParams = JimengParams
// 表单数据
const formData = ref({})
// 必填参数
const requiredKeys = ref({})
// 进度
const progress = ref({
image: 100,
video: 100,
virtualHuman: 38,
actionTransfer: 65,
})
// 切换功能
const switchFunction = (f) => {
activeFunction.value = f.key
formData.value = {}
setFunctionPowers()
}
// 获取功能名称
const getFunctionName = (type) => {
const func = functions.find((f) => f.key === type)
return func ? func.name : type
}
// 获取任务状态文本
const getTaskStatusText = (status) => {
const statusMap = {
in_queue: '任务排队中',
generating: '任务执行中',
success: '任务成功',
failed: '任务失败',
canceled: '任务已取消',
}
return statusMap[status] || status
}
// 获取状态类型
const getTaskType = (type) => {
const typeMap = {
text_to_image: 'primary',
image_to_image: 'primary',
image_edit: 'primary',
image_effects: 'primary',
text_to_video: 'success',
image_to_video: 'success',
}
return typeMap[type] || 'primary'
}
// 切换任务筛选
const switchTaskFilter = (filter) => {
taskFilter.value = filter
isOver.value = false
fetchData(1)
}
// 轮询定时器
let pollHandler = null
// 获取任务列表
const fetchData = async (pageNum = 1) => {
try {
loading.value = true
page.value = pageNum
const response = await httpPost('/api/jimeng/jobs', {
page: pageNum,
page_size: pageSize.value,
filter: taskFilter.value,
})
const data = response.data
if (!data.items || data.items.length === 0) {
isOver.value = true
if (pageNum === 1) {
currentList.value = []
}
return
}
total.value = data.total || 0
if (data.items.length < pageSize.value) {
isOver.value = true
}
if (pageNum === 1) {
currentList.value = data.items
} else {
currentList.value = currentList.value.concat(data.items)
}
} catch (error) {
showMessageError('获取任务列表失败:' + error.message)
} finally {
loading.value = false
}
}
// 简单轮询逻辑
const startPolling = () => {
if (pollHandler) {
clearInterval(pollHandler)
}
pollHandler = setInterval(async () => {
const response = await httpPost('/api/jimeng/jobs', {
page: 1,
page_size: 20,
})
const data = response.data
if (data.items.length === 0) {
stopPolling()
return
}
const todoList = data.items.filter(
(item) => item.status === 'in_queue' || item.status === 'generating'
)
// 更新当前列表
currentList.value.forEach((item) => {
const index = data.items.findIndex((i) => i.id === item.id)
if (index !== -1) {
Object.assign(item, data.items[index])
}
})
if (todoList.length === 0) {
stopPolling()
}
}, 3000)
}
const stopPolling = () => {
if (pollHandler) {
clearInterval(pollHandler)
pollHandler = null
}
}
// 提交任务
const submitTask = async () => {
if (!isLogin.value) {
shareStore.setShowLoginDialog(true)
return
}
console.log(formData.value)
for (const key in requiredKeys.value) {
if (!formData.value[key]) {
showMessageError('缺少参数:' + requiredKeys.value[key].label)
return
}
}
try {
submitting.value = true
formData.value.type = activeFunction.value
// 视频 duration 转成整数
if (formData.value.duration) {
formData.value.duration = parseInt(formData.value.duration)
}
if (formData.value.image_urls && !Array.isArray(formData.value.image_urls)) {
formData.value.image_urls = [formData.value.image_urls]
}
const response = await httpPost('/api/jimeng/task', formData.value)
showMessageOK('任务提交成功')
isOver.value = false
await fetchData(1)
startPolling()
} catch (error) {
console.error('提交任务失败:', error)
showMessageError(error.message || '提交任务失败')
} finally {
submitting.value = false
}
}
const downloadFile = async (item) => {
const url = replaceImg(item.video_url || item.img_url)
const downloadURL = `/api/download?url=${url}`
const urlObj = new URL(url)
const fileName = urlObj.pathname.split('/').pop()
item.downloading = true
try {
const response = await httpDownload(downloadURL)
const blob = new Blob([response.data])
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = fileName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(link.href)
item.downloading = false
} catch (error) {
showMessageError('下载失败')
item.downloading = false
}
}
// 重试任务
const retryTask = async (taskId) => {
try {
const response = await httpGet(`/api/jimeng/retry?id=${taskId}`)
if (response.data) {
showMessageOK('重试任务已提交')
isOver.value = false
await fetchData(1)
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(1)
}
} catch (error) {
if (error !== 'cancel') {
console.error('删除任务失败:', error)
showMessageError(error.message || '删除任务失败')
}
}
}
// 播放视频
const playVideo = (item) => {
currentVideoUrl.value = item.video_url
showDialog.value = true
}
const setFunctionPowers = () => {
if (activeFunction.value === 'image') {
currentPowerCost.value = `${powerConfig.image}积分/张`
} else {
currentPowerCost.value = `${powerConfig.video}积分/秒`
}
}
// 初始化方法
const init = async () => {
try {
// 获取积分消耗配置
const powerRes = await httpGet('/api/jimeng/power-config')
if (powerRes.data) {
Object.assign(powerConfig, powerRes.data)
setFunctionPowers()
}
const user = await checkSession()
isLogin.value = true
// 获取任务列表
await fetchData(1)
// 开始轮询
startPolling()
} catch (error) {
console.error('初始化失败:', error)
}
}
// 页面卸载时清理轮询
const cleanup = () => {
stopPolling()
}
// 返回所有状态和方法
return {
// 状态
activeFunction,
loading,
submitting,
page,
pageSize,
total,
taskFilter,
currentList,
isOver,
isLogin,
showDialog,
currentVideoUrl,
// 配置
functions,
activeFunction,
functionParams,
formData,
requiredKeys,
progress,
currentPowerCost,
// 方法
init,
switchFunction,
getFunctionName,
getTaskStatusText,
getTaskType,
switchTaskFilter,
fetchData,
submitTask,
downloadFile,
retryTask,
removeJob,
playVideo,
cleanup,
// 工具函数
substr,
replaceImg,
}
})