diff --git a/web/src/assets/css/mobile/jimeng.scss b/web/src/assets/css/mobile/jimeng.scss new file mode 100644 index 00000000..d1b4169d --- /dev/null +++ b/web/src/assets/css/mobile/jimeng.scss @@ -0,0 +1,787 @@ +/* JimengCreate Mobile Styles */ + +/* 自定义动画 */ +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fade-out { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(-10px); + } +} + +@keyframes scale-up { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.animate-fade-in { + animation: fade-in 0.3s ease-out; +} + +.animate-fade-out { + animation: fade-out 0.3s ease-out; +} + +.animate-scale-up { + animation: scale-up 0.3s ease-out; +} + +/* 文本截断 */ +.line-clamp-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* 深色模式适配 */ +@media (prefers-color-scheme: dark) { + .bg-gray-50 { + background-color: #1f2937; + } + + .bg-white { + background-color: #374151; + } + + .text-gray-900 { + color: #f9fafb; + } + + .text-gray-700 { + color: #d1d5db; + } + + .text-gray-600 { + color: #9ca3af; + } + + .text-gray-500 { + color: #6b7280; + } + + .border-gray-200 { + border-color: #4b5563; + } + + .bg-gray-100:hover { + background-color: #4b5563; + } +} + +/* 即梦创作页面专用样式 */ +.jimeng-create { + min-height: 100vh; + background-color: #f9fafb; + + /* 页面头部样式 */ + &__header { + position: sticky; + top: 0; + z-index: 40; + background-color: white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + + &-content { + display: flex; + align-items: center; + padding: 0 1rem; + height: 3.5rem; + } + + &-back-btn { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: 50%; + transition: background-color 0.2s; + + &:hover { + background-color: #f3f4f6; + } + + .iconfont { + color: #6b7280; + } + } + + &-title { + flex: 1; + text-align: center; + font-size: 1.125rem; + font-weight: 600; + color: #111827; + } + + &-spacer { + width: 2rem; + } + } + + /* 主要内容区域 */ + &__content { + padding: 1rem; + + .space-y-6 > * + * { + margin-top: 1.5rem; + } + } + + /* 功能分类选择 */ + &__category-section { + background-color: white; + border-radius: 0.75rem; + padding: 1rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + + &-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.75rem; + } + + &-button { + display: flex; + flex-direction: column; + align-items: center; + padding: 0.75rem; + border-radius: 0.5rem; + border: 2px solid; + transition: all 0.2s; + + &--active { + border-color: #3b82f6; + background-color: #eff6ff; + color: #1d4ed8; + } + + &--inactive { + border-color: #e5e7eb; + background-color: #f9fafb; + color: #6b7280; + + &:hover { + border-color: #d1d5db; + background-color: #f3f4f6; + } + } + + .iconfont { + font-size: 1.5rem; + margin-bottom: 0.5rem; + } + + span { + font-size: 0.875rem; + font-weight: 500; + } + } + } + + /* 生成模式切换 */ + &__mode-section { + background-color: white; + border-radius: 0.75rem; + padding: 1rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + + &-content { + display: flex; + align-items: center; + justify-content: space-between; + } + + &-title { + color: #111827; + font-weight: 500; + } + + &-description { + font-size: 0.875rem; + color: #6b7280; + margin-top: 0.25rem; + } + } + + /* 表单组件样式 */ + &__form-section { + background-color: white; + border-radius: 0.75rem; + padding: 1rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + + &-label { + display: block; + color: #374151; + font-weight: 500; + margin-bottom: 0.75rem; + } + + &-textarea { + width: 100%; + padding: 0.75rem 1rem; + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + resize: none; + transition: all 0.2s; + + &:focus { + outline: none; + ring: 2px solid #3b82f6; + border-color: transparent; + } + } + + &-counter { + text-align: right; + margin-top: 0.5rem; + font-size: 0.875rem; + color: #6b7280; + } + } + + /* 图片上传样式 */ + &__upload { + border: 2px dashed #d1d5db; + border-radius: 0.5rem; + padding: 1.5rem; + text-align: center; + cursor: pointer; + transition: all 0.2s; + + &:hover { + border-color: #60a5fa; + background-color: #eff6ff; + } + + &-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + } + + &-icon { + color: #3b82f6; + font-size: 1.5rem; + } + + &-text { + color: #374151; + font-weight: 500; + } + + &-preview { + position: relative; + + .el-image { + width: 8rem; + height: 8rem; + border-radius: 0.5rem; + } + } + + &-remove-btn { + position: absolute; + top: -0.5rem; + right: -0.5rem; + width: 1.5rem; + height: 1.5rem; + background-color: #ef4444; + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.75rem; + cursor: pointer; + transition: background-color 0.2s; + + &:hover { + background-color: #dc2626; + } + } + + /* 多图片上传样式 */ + &-multiple { + display: flex; + gap: 0.75rem; + + &-item { + position: relative; + + .el-image { + width: 6rem; + height: 6rem; + border-radius: 0.5rem; + } + } + + &-add { + width: 6rem; + height: 6rem; + border: 2px dashed #d1d5db; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: border-color 0.2s; + + &:hover { + border-color: #60a5fa; + } + + .iconfont { + color: #9ca3af; + font-size: 1.25rem; + } + } + } + } + + /* 滑块样式 */ + &__slider-section { + .space-y-4 > * + * { + margin-top: 1rem; + } + + &-header { + display: flex; + align-items: center; + gap: 0.5rem; + + label { + display: block; + color: #374151; + font-weight: 500; + } + + .el-tooltip { + .iconfont { + color: #9ca3af; + cursor: pointer; + } + } + } + } + + /* 开关样式 */ + &__switch-section { + display: flex; + align-items: center; + justify-content: space-between; + + span { + color: #111827; + font-weight: 500; + } + } + + /* 生成按钮 */ + &__submit-btn { + position: sticky; + bottom: 1rem; + background-color: white; + border-radius: 0.75rem; + padding: 1rem; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); + + button { + width: 100%; + padding: 1rem; + background: linear-gradient(to right, #3b82f6, #8b5cf6); + color: white; + font-weight: 600; + border-radius: 0.75rem; + border: none; + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + + &:hover:not(:disabled) { + background: linear-gradient(to right, #2563eb, #7c3aed); + } + + &:disabled { + background: linear-gradient(to right, #9ca3af, #9ca3af); + cursor: not-allowed; + } + + .iconfont { + &.animate-spin { + animation: spin 1s linear infinite; + } + } + } + } + + /* 作品列表样式 */ + &__works { + padding: 1rem; + + &-title { + font-size: 1.125rem; + font-weight: 600; + color: #111827; + margin-bottom: 1rem; + } + + &-list { + .space-y-4 > * + * { + margin-top: 1rem; + } + } + + &-item { + background-color: white; + border-radius: 0.75rem; + padding: 1rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + + &-content { + display: flex; + gap: 1rem; + } + + &-thumb { + flex-shrink: 0; + + &-container { + position: relative; + width: 4rem; + height: 4rem; + border-radius: 0.5rem; + overflow: hidden; + background-color: #f3f4f6; + } + + .el-image { + width: 100%; + height: 100%; + } + + &-placeholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: #f3f4f6; + + .iconfont { + color: #9ca3af; + font-size: 1.25rem; + } + } + + &-overlay { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 0, 0, 0.5); + opacity: 0; + transition: opacity 0.2s; + + &:hover { + opacity: 1; + } + + .iconfont { + color: white; + font-size: 1.25rem; + } + } + + &-status { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + + &--loading { + background-color: rgba(59, 130, 246, 0.2); + + .iconfont { + color: #3b82f6; + font-size: 1.25rem; + animation: spin 1s linear infinite; + } + } + + &--failed { + background-color: rgba(239, 68, 68, 0.2); + + .iconfont { + color: #ef4444; + font-size: 1.25rem; + } + } + } + } + + &-info { + flex: 1; + min-width: 0; + + &-header { + display: flex; + align-items: start; + justify-content: space-between; + } + + &-title { + color: #111827; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &-prompt { + color: #6b7280; + font-size: 0.875rem; + margin-top: 0.25rem; + } + + &-status { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + + &--failed { + color: #dc2626; + } + + &--processing { + color: #2563eb; + + .loading-spinner { + width: 0.75rem; + height: 0.75rem; + border: 1px solid #2563eb; + border-top-color: transparent; + border-radius: 50%; + animation: spin 1s linear infinite; + } + } + } + + &-tags { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0.5rem; + + &-item { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + border-radius: 9999px; + + &--primary { + background-color: #dbeafe; + color: #2563eb; + } + + &--warning { + background-color: #fef3c7; + color: #d97706; + } + + &--power { + background-color: #fed7aa; + color: #ea580c; + } + } + } + } + + &-actions { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 1rem; + + &-left { + display: flex; + gap: 0.5rem; + } + + &-btn { + padding: 0.375rem 0.75rem; + font-size: 0.875rem; + border-radius: 0.5rem; + border: none; + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + gap: 0.25rem; + + .iconfont { + font-size: 0.75rem !important; + } + + &--primary { + background-color: #2563eb; + color: white; + + &:hover { + background-color: #1d4ed8; + } + } + + &--success { + background-color: #16a34a; + color: white; + + &:hover { + background-color: #15803d; + } + } + + &--warning { + background-color: #ea580c; + color: white; + + &:hover { + background-color: #dc2626; + } + } + + &--danger { + background-color: #fef2f2; + color: #dc2626; + + &:hover { + background-color: #fecaca; + } + } + + &:disabled { + background-color: #9ca3af; + cursor: not-allowed; + } + } + } + } + + &-loading { + display: flex; + justify-content: center; + padding: 1rem; + + .iconfont { + color: #3b82f6; + font-size: 1.25rem; + animation: spin 1s linear infinite; + } + } + + &-finished { + text-align: center; + padding: 1rem; + color: #6b7280; + } + } + + /* 媒体预览弹窗 */ + &__media-dialog { + position: fixed; + inset: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 0, 0, 0.5); + + &-content { + background-color: white; + border-radius: 1rem; + width: 100%; + max-width: 56rem; + max-height: 80vh; + } + + &-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem; + border-bottom: 1px solid #e5e7eb; + + h3 { + font-size: 1.125rem; + font-weight: 600; + color: #111827; + } + + button { + padding: 0.5rem; + border: none; + background: none; + border-radius: 50%; + cursor: pointer; + transition: background-color 0.2s; + + &:hover { + background-color: #f3f4f6; + } + + .iconfont { + color: #6b7280; + } + } + } + + &-body { + padding: 1.5rem; + + img, video { + width: 100%; + max-height: 60vh; + object-fit: contain; + border-radius: 0.5rem; + } + } + } +} + +/* 旋转动画 */ +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/web/src/views/mobile/components/AppCard.vue b/web/src/components/mobile/AppCard.vue similarity index 100% rename from web/src/views/mobile/components/AppCard.vue rename to web/src/components/mobile/AppCard.vue diff --git a/web/src/views/mobile/components/CustomSelect.vue b/web/src/components/mobile/CustomSelect.vue similarity index 100% rename from web/src/views/mobile/components/CustomSelect.vue rename to web/src/components/mobile/CustomSelect.vue diff --git a/web/src/views/mobile/components/CustomSelectOption.vue b/web/src/components/mobile/CustomSelectOption.vue similarity index 100% rename from web/src/views/mobile/components/CustomSelectOption.vue rename to web/src/components/mobile/CustomSelectOption.vue diff --git a/web/src/views/mobile/components/EmptyState.vue b/web/src/components/mobile/EmptyState.vue similarity index 100% rename from web/src/views/mobile/components/EmptyState.vue rename to web/src/components/mobile/EmptyState.vue diff --git a/web/src/components/ui/BlackDialog.vue b/web/src/components/ui/BlackDialog.vue deleted file mode 100644 index 7615f983..00000000 --- a/web/src/components/ui/BlackDialog.vue +++ /dev/null @@ -1,103 +0,0 @@ - - - - - diff --git a/web/src/components/ui/BlackSelect.vue b/web/src/components/ui/BlackSelect.vue deleted file mode 100644 index 1d68b9d9..00000000 --- a/web/src/components/ui/BlackSelect.vue +++ /dev/null @@ -1,42 +0,0 @@ - - - diff --git a/web/src/components/ui/BlackSwitch.vue b/web/src/components/ui/BlackSwitch.vue deleted file mode 100644 index b956676d..00000000 --- a/web/src/components/ui/BlackSwitch.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/web/src/router.js b/web/src/router.js index 61245693..0b5132f7 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -361,19 +361,19 @@ const routes = [ meta: { title: 'Suno音乐创作' }, path: '/mobile/suno', name: 'mobile-suno', - component: () => import('@/views/mobile/pages/SunoCreate.vue'), + component: () => import('@/views/mobile/SunoCreate.vue'), }, { meta: { title: '视频生成' }, path: '/mobile/video', name: 'mobile-video', - component: () => import('@/views/mobile/pages/VideoCreate.vue'), + component: () => import('@/views/mobile/VideoCreate.vue'), }, { meta: { title: '即梦AI' }, path: '/mobile/jimeng', name: 'mobile-jimeng', - component: () => import('@/views/mobile/pages/JimengCreate.vue'), + component: () => import('@/views/mobile/JimengCreate.vue'), }, ], }, diff --git a/web/src/store/mobile/jimeng.js b/web/src/store/mobile/jimeng.js new file mode 100644 index 00000000..d7bdcf25 --- /dev/null +++ b/web/src/store/mobile/jimeng.js @@ -0,0 +1,410 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { httpGet, httpPost } from '@/utils/http' +import { showMessageError, showMessageOK, showLoading, closeLoading } from '@/utils/dialog' +import { showConfirmDialog } from 'vant' + +export const useJimengStore = defineStore('mobile-jimeng', () => { + // 响应式数据 + const activeCategory = ref('image_generation') + const useImageInput = ref(false) + const submitting = ref(false) + const listLoading = ref(false) + const listFinished = ref(false) + const currentList = ref([]) + const showMediaDialog = ref(false) + const currentMediaUrl = ref('') + const currentPrompt = ref('') + const page = ref(1) + const pageSize = ref(10) + const total = ref(0) + const currentPowerCost = ref(0) + const taskPulling = ref(true) + const tastPullHandler = ref(null) + + // 功能分类 + const categories = ref([ + { key: 'image_generation', name: '图像生成' }, + { key: 'image_editing', name: '图像编辑' }, + { key: 'image_effects', name: '图像特效' }, + { key: 'video_generation', name: '视频生成' }, + ]) + + // 选项数据 + const imageSizeOptions = [ + { label: '512x512', value: '512x512' }, + { label: '768x768', value: '768x768' }, + { label: '1024x1024', value: '1024x1024' }, + { label: '1024x1536', value: '1024x1536' }, + { label: '1536x1024', value: '1536x1024' }, + ] + + const videoAspectRatioOptions = [ + { label: '16:9', value: '16:9' }, + { label: '9:16', value: '9:16' }, + { label: '1:1', value: '1:1' }, + { label: '4:3', value: '4:3' }, + ] + + const imageEffectsTemplateOptions = [ + { label: '亚克力装饰', value: 'acrylic_ornaments' }, + { label: '天使小雕像', value: 'angel_figurine' }, + { label: '毛毫3D拍立得', value: 'felt_3d_polaroid' }, + { label: '水彩插图', value: 'watercolor_illustration' }, + ] + + // 功能参数 + const textToImageParams = ref({ + size: '1024x1024', + scale: 7.5, + use_pre_llm: false, + }) + + const imageToImageParams = ref({ + image_input: [], + size: '1024x1024', + }) + + const imageEditParams = ref({ + image_urls: [], + scale: 0.5, + }) + + const imageEffectsParams = ref({ + image_input1: [], + template_id: '', + size: '1024x1024', + }) + + const textToVideoParams = ref({ + aspect_ratio: '16:9', + }) + + const imageToVideoParams = ref({ + image_urls: [], + aspect_ratio: '16:9', + }) + + // 计算属性 + const activeFunction = computed(() => { + if (activeCategory.value === 'image_generation') { + return useImageInput.value ? 'image_to_image' : 'text_to_image' + } else if (activeCategory.value === 'image_editing') { + return 'image_edit' + } else if (activeCategory.value === 'video_generation') { + return useImageInput.value ? 'image_to_video' : 'text_to_video' + } + return 'text_to_image' + }) + + // Actions + const getCategoryIcon = (category) => { + const iconMap = { + image_generation: 'iconfont icon-image', + image_editing: 'iconfont icon-edit', + image_effects: 'iconfont icon-chuangzuo', + video_generation: 'iconfont icon-video', + } + return iconMap[category] || 'iconfont icon-image' + } + + const switchCategory = (key) => { + activeCategory.value = key + useImageInput.value = false + } + + const switchInputMode = () => { + currentPrompt.value = '' + } + + const handleMultipleImageUpload = (event) => { + const files = Array.from(event.target.files) + files.forEach((file) => { + if (imageToVideoParams.value.image_urls.length < 2) { + onImageUpload({ file, name: file.name }) + } + }) + } + + const removeImage = (index) => { + imageToVideoParams.value.image_urls.splice(index, 1) + } + + const onImageUpload = (file) => { + const formData = new FormData() + formData.append('file', file.file, file.name) + showLoading('正在上传图片...') + + return httpPost('/api/upload', formData) + .then((res) => { + showMessageOK('图片上传成功') + const imageData = { url: res.data.url, content: res.data.url } + + // 根据当前活动功能添加到相应的参数中 + if (activeFunction.value === 'image_to_image') { + imageToImageParams.value.image_input = [imageData] + } else if (activeFunction.value === 'image_edit') { + imageEditParams.value.image_urls = [imageData] + } else if (activeFunction.value === 'image_effects') { + imageEffectsParams.value.image_input1 = [imageData] + } else if (activeFunction.value === 'image_to_video') { + imageToVideoParams.value.image_urls.push(imageData) + } + + return res.data.url + }) + .catch((e) => { + showMessageError('图片上传失败:' + e.message) + }) + .finally(() => { + closeLoading() + }) + } + + const submitTask = () => { + if (!currentPrompt.value.trim()) { + showMessageError('请输入提示词') + return + } + + submitting.value = true + const params = { + type: activeFunction.value, + prompt: currentPrompt.value, + } + + // 根据功能类型添加相应参数 + if (activeFunction.value === 'text_to_image') { + Object.assign(params, textToImageParams.value) + } else if (activeFunction.value === 'image_to_image') { + Object.assign(params, imageToImageParams.value) + } else if (activeFunction.value === 'image_edit') { + Object.assign(params, imageEditParams.value) + } else if (activeFunction.value === 'image_effects') { + Object.assign(params, imageEffectsParams.value) + } else if (activeFunction.value === 'text_to_video') { + Object.assign(params, textToVideoParams.value) + } else if (activeFunction.value === 'image_to_video') { + Object.assign(params, imageToVideoParams.value) + } + + return httpPost('/api/jimeng/create', params) + .then(() => { + fetchData(1) + taskPulling.value = true + showMessageOK('创建任务成功') + currentPrompt.value = '' + }) + .catch((e) => { + showMessageError('创建任务失败:' + e.message) + }) + .finally(() => { + submitting.value = false + }) + } + + const fetchData = (_page) => { + if (_page) { + page.value = _page + } + listLoading.value = true + + return httpGet('/api/jimeng/list', { page: page.value, page_size: pageSize.value }) + .then((res) => { + total.value = res.data.total + let needPull = false + const items = [] + for (let v of res.data.items) { + if (v.status === 'in_queue' || v.status === 'generating') { + needPull = true + } + items.push(v) + } + listLoading.value = false + taskPulling.value = needPull + + if (page.value === 1) { + currentList.value = items + } else { + currentList.value.push(...items) + } + + if (items.length < pageSize.value) { + listFinished.value = true + } + }) + .catch((e) => { + listLoading.value = false + showMessageError('获取作品列表失败:' + e.message) + }) + } + + const loadMore = () => { + page.value++ + fetchData() + } + + const playMedia = (item) => { + currentMediaUrl.value = item.img_url || item.video_url + showMediaDialog.value = true + } + + const downloadFile = (item) => { + item.downloading = true + const link = document.createElement('a') + link.href = item.img_url || item.video_url + link.download = item.title || 'file' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + item.downloading = false + showMessageSuccess('开始下载') + } + + const retryTask = (id) => { + return httpPost('/api/jimeng/retry', { id }) + .then(() => { + showMessageOK('重试任务成功') + fetchData(1) + }) + .catch((e) => { + showMessageError('重试任务失败:' + e.message) + }) + } + + const removeJob = (item) => { + return showConfirmDialog({ + title: '确认删除', + message: '此操作将会删除任务相关文件,继续操作吗?', + confirmButtonText: '确认删除', + cancelButtonText: '取消', + }) + .then(() => { + return httpGet('/api/jimeng/remove', { id: item.id }) + .then(() => { + showMessageOK('任务删除成功') + fetchData(1) + }) + .catch((e) => { + showMessageError('任务删除失败:' + e.message) + }) + }) + .catch(() => {}) + } + + const getFunctionName = (type) => { + const nameMap = { + text_to_image: '文生图', + image_to_image: '图生图', + image_edit: '图像编辑', + image_effects: '图像特效', + text_to_video: '文生视频', + image_to_video: '图生视频', + } + return nameMap[type] || type + } + + const getTaskType = (type) => { + return type.includes('video') ? 'warning' : 'primary' + } + + const startTaskPolling = () => { + tastPullHandler.value = setInterval(() => { + if (taskPulling.value) { + fetchData(1) + } + }, 5000) + } + + const stopTaskPolling = () => { + if (tastPullHandler.value) { + clearInterval(tastPullHandler.value) + } + } + + const resetParams = () => { + textToImageParams.value = { + size: '1024x1024', + scale: 7.5, + use_pre_llm: false, + } + imageToImageParams.value = { + image_input: [], + size: '1024x1024', + } + imageEditParams.value = { + image_urls: [], + scale: 0.5, + } + imageEffectsParams.value = { + image_input1: [], + template_id: '', + size: '1024x1024', + } + textToVideoParams.value = { + aspect_ratio: '16:9', + } + imageToVideoParams.value = { + image_urls: [], + aspect_ratio: '16:9', + } + } + + const closeMediaDialog = () => { + showMediaDialog.value = false + currentMediaUrl.value = '' + } + + return { + // State + activeCategory, + useImageInput, + submitting, + listLoading, + listFinished, + currentList, + showMediaDialog, + currentMediaUrl, + currentPrompt, + page, + pageSize, + total, + currentPowerCost, + taskPulling, + tastPullHandler, + categories, + imageSizeOptions, + videoAspectRatioOptions, + imageEffectsTemplateOptions, + textToImageParams, + imageToImageParams, + imageEditParams, + imageEffectsParams, + textToVideoParams, + imageToVideoParams, + + // Computed + activeFunction, + + // Actions + getCategoryIcon, + switchCategory, + switchInputMode, + handleMultipleImageUpload, + removeImage, + onImageUpload, + submitTask, + fetchData, + loadMore, + playMedia, + downloadFile, + retryTask, + removeJob, + getFunctionName, + getTaskType, + startTaskPolling, + stopTaskPolling, + resetParams, + closeMediaDialog, + } +}) diff --git a/web/src/views/mobile/JimengCreate.vue b/web/src/views/mobile/JimengCreate.vue new file mode 100644 index 00000000..8ab37e84 --- /dev/null +++ b/web/src/views/mobile/JimengCreate.vue @@ -0,0 +1,706 @@ +