"vscode:/vscode.git/clone" did not exist on "fcfd094972ca77f016ee4c5ca074a40b213a04e4"
TaskDetails.vue 75.5 KB
Newer Older
LiangLiu's avatar
LiangLiu committed
1
<script setup>
LiangLiu's avatar
LiangLiu committed
2
import { ref, watch, onMounted, onUnmounted, computed, nextTick } from 'vue'
LiangLiu's avatar
LiangLiu committed
3
4
5
6
7
8
import { showTaskDetailModal,
        modalTask,
        closeTaskDetailModal,
        cancelTask,
        reuseTask,
        downloadFile,
LiangLiu's avatar
LiangLiu committed
9
        handleDownloadFile,
LiangLiu's avatar
LiangLiu committed
10
11
        deleteTask,
        getTaskTypeName,
LiangLiu's avatar
LiangLiu committed
12
        showFailureDetails,
LiangLiu's avatar
LiangLiu committed
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
        formatTime,
        getTaskStatusDisplay,
        getStatusTextClass,
        getProgressTitle,
        getProgressInfo,
        getOverallProgress,
        getSubtaskStatusText,
        getSubtaskProgress,
        formatEstimatedTime,
        generateShareUrl,
        copyShareLink,
        shareToSocial,
        copyPrompt,
        getTaskFileUrlSync,
        getTaskFileFromCache,
        getTaskFileUrl,
        showAlert,
        apiRequest,
        startPollingTask,
        resumeTask,
         } from '../utils/other'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
const { t, locale } = useI18n()
const route = useRoute()
const router = useRouter()

// 添加响应式变量
const showDetails = ref(false)
const loadingTaskFiles = ref(false)

LiangLiu's avatar
LiangLiu committed
44
45
46
47
48
49
50
51
// 音频播放器相关
const audioElement = ref(null)
const isPlaying = ref(false)
const audioDuration = ref(0)
const currentTime = ref(0)
const isDragging = ref(false)
const currentAudioUrl = ref('')

LiangLiu's avatar
LiangLiu committed
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 获取图片素材
const getImageMaterials = () => {
    if (!modalTask.value?.inputs?.input_image) return []
    return [['input_image', getTaskFileUrlSync(modalTask.value.task_id, 'input_image')]]
}

// 获取音频素材
const getAudioMaterials = () => {
    if (!modalTask.value?.inputs?.input_audio) return []
    return [['input_audio', getTaskFileUrlSync(modalTask.value.task_id, 'input_audio')]]
}

// 路由关闭功能
const closeWithRoute = () => {
    closeTaskDetailModal()
    modalTask.value = null
LiangLiu's avatar
LiangLiu committed
68
69
70
71
72
73
74
75
76
    // 只有当前路由是 /task/:id 时才进行路由跳转
    // 如果在其他页面(如 /generate)打开的弹窗,关闭时保持在原页面
    if (route.path.startsWith('/task/')) {
        // 从任务详情路由进入的,返回到上一页或首页
        if (window.history.length > 1) {
            router.go(-1)
        } else {
            router.push('/')
        }
LiangLiu's avatar
LiangLiu committed
77
    }
LiangLiu's avatar
LiangLiu committed
78
    // 如果不是任务详情路由,不做任何路由跳转,保持在当前页面
LiangLiu's avatar
LiangLiu committed
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
}

// 键盘事件处理
const handleKeydown = (event) => {
    if (event.key === 'Escape' && showTaskDetailModal.value) {
        closeWithRoute()
    }
}

// 获取文件扩展名
const getFileExtension = (fileKey) => {
    if (fileKey.includes('video')) return 'mp4'
    if (fileKey.includes('image')) return 'jpg'
    if (fileKey.includes('audio')) return 'mp3'
    return 'file'
}


const getTaskFailureInfo = (task) => {
                if (!task) return null;

                // 检查子任务的失败信息
                if (!task.fail_msg && task.subtasks && task.subtasks.length > 0) {
                    const failedSubtasks = task.subtasks.filter(subtask =>
                        (subtask.extra_info && subtask.extra_info.fail_msg) || subtask.fail_msg
                    );
                    if (failedSubtasks.length > 0) {
                        const msg = failedSubtasks.map(subtask =>
                            (subtask.extra_info && subtask.extra_info.fail_msg) || subtask.fail_msg
                        ).join('\n');
                        task.fail_msg = msg;
                    }
                }
                console.log('task.fail_msg', task.fail_msg);
                return task.fail_msg;
            };

const viewTaskDetail = async (task) => {
    try {
        const response = await apiRequest(`/api/v1/task/query?task_id=${task.task_id}`);
        console.log('viewTaskDetail: response=', response);
        if (response && response.ok) {
            modalTask.value = await response.json();
            console.log('updated task data:', modalTask.value);
        }
    } catch (error) {
        console.warn(`Failed to fetch updated task data: task_id=${task.task_id}`, error.message);
    }
    // 如果任务还在进行中,开始轮询状态
    if (['CREATED', 'PENDING', 'RUNNING'].includes(task.status)) {

        startPollingTask(task.task_id);
    }
    if (['FAILED'].includes(task.status)) {
        modalTask.value.fail_msg = getTaskFailureInfo(task);
    }
};

// 监听modalTask的第一次变化,确保任务详情正确加载
const hasLoadedTask = ref(false);
watch(modalTask, (newTask) => {
    if (newTask && !hasLoadedTask.value) {
        console.log('modalTask第一次变化,加载任务详情:', newTask);
        viewTaskDetail(newTask);
        hasLoadedTask.value = true;
    }
}, { immediate: true });

// 生命周期钩子
onMounted(async () => {
    document.addEventListener('keydown', handleKeydown)
    console.log('TaskDetails组件已挂载,当前modalTask:', modalTask.value);
})

onUnmounted(() => {
    document.removeEventListener('keydown', handleKeydown)
LiangLiu's avatar
LiangLiu committed
155
156
157
158
159
    // 清理音频资源
    const audio = getCurrentAudioElement()
    if (audio) {
        audio.pause()
    }
LiangLiu's avatar
LiangLiu committed
160
})
LiangLiu's avatar
LiangLiu committed
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248

// 格式化音频时间
const formatAudioTime = (seconds) => {
    if (!seconds || isNaN(seconds)) return '0:00'
    const mins = Math.floor(seconds / 60)
    const secs = Math.floor(seconds % 60)
    return `${mins}:${secs.toString().padStart(2, '0')}`
}

// 获取当前音频元素(处理可能是数组的情况)
const getCurrentAudioElement = () => {
    return Array.isArray(audioElement.value) ? audioElement.value[0] : audioElement.value
}

// 切换播放/暂停
const toggleAudioPlayback = () => {
    const audio = getCurrentAudioElement()
    if (!audio) return

    if (audio.paused) {
        audio.play().catch(error => {
            console.log('播放失败:', error)
        })
    } else {
        audio.pause()
    }
}

// 音频加载完成
const onAudioLoaded = () => {
    const audio = getCurrentAudioElement()
    if (audio) {
        audioDuration.value = audio.duration || 0
    }
}

// 时间更新
const onTimeUpdate = () => {
    const audio = getCurrentAudioElement()
    if (audio && !isDragging.value) {
        currentTime.value = audio.currentTime || 0
    }
}

// 进度条变化处理
const onProgressChange = (event) => {
    const audio = getCurrentAudioElement()
    if (audioDuration.value > 0 && audio && event.target) {
        const newTime = parseFloat(event.target.value)
        currentTime.value = newTime
        audio.currentTime = newTime
    }
}

// 进度条拖拽结束处理
const onProgressEnd = (event) => {
    const audio = getCurrentAudioElement()
    if (audio && audioDuration.value > 0 && event.target) {
        const newTime = parseFloat(event.target.value)
        audio.currentTime = newTime
        currentTime.value = newTime
    }
    isDragging.value = false
}

// 播放结束
const onAudioEnded = () => {
    isPlaying.value = false
    currentTime.value = 0
}

// 监听音频URL变化
watch(() => getAudioMaterials(), (newMaterials) => {
    if (newMaterials && newMaterials.length > 0) {
        currentAudioUrl.value = newMaterials[0][1]
        nextTick(() => {
            const audio = getCurrentAudioElement()
            if (audio) {
                audio.load()
            }
        })
    } else {
        currentAudioUrl.value = ''
        isPlaying.value = false
        currentTime.value = 0
        audioDuration.value = 0
    }
}, { immediate: true })
LiangLiu's avatar
LiangLiu committed
249
250
</script>
<template>
LiangLiu's avatar
LiangLiu committed
251
            <!-- 任务详情弹窗 - Apple 极简风格 -->
LiangLiu's avatar
LiangLiu committed
252
253
            <div v-cloak>
                <div v-if="showTaskDetailModal"
LiangLiu's avatar
LiangLiu committed
254
                    class="fixed inset-0 bg-black/50 dark:bg-black/60 backdrop-blur-sm z-[60] flex items-center justify-center p-2 sm:p-1"
LiangLiu's avatar
LiangLiu committed
255
                    @click="closeWithRoute">
LiangLiu's avatar
LiangLiu committed
256
                    <!-- 任务完成时的大弹窗 - Apple 风格 -->
LiangLiu's avatar
LiangLiu committed
257
                    <div v-if="modalTask?.status === 'SUCCEED'"
LiangLiu's avatar
LiangLiu committed
258
259
260
261
262
                        class="w-full h-full max-w-7xl max-h-[100vh] bg-white/95 dark:bg-[#1e1e1e]/95 backdrop-blur-[40px] backdrop-saturate-[180%] border border-black/10 dark:border-white/10 rounded-3xl shadow-[0_20px_60px_rgba(0,0,0,0.2)] dark:shadow-[0_20px_60px_rgba(0,0,0,0.6)] overflow-hidden flex flex-col" @click.stop>
                        <!-- 弹窗头部 - Apple 风格 -->
                        <div class="flex items-center justify-between px-8 py-5 border-b border-black/8 dark:border-white/8 bg-white/50 dark:bg-[#1e1e1e]/50 backdrop-blur-[20px]">
                            <h3 class="text-xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] flex items-center gap-3 tracking-tight">
                                <i class="fas fa-check-circle text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
LiangLiu's avatar
LiangLiu committed
263
                                {{ t('taskDetail') }}
LiangLiu's avatar
LiangLiu committed
264
                            </h3>
LiangLiu's avatar
LiangLiu committed
265
266
267
268
269
                            <div class="flex items-center gap-2">
                                <button @click="closeWithRoute"
                                        class="w-9 h-9 flex items-center justify-center bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:text-red-500 dark:hover:text-red-400 hover:bg-white dark:hover:bg-[#3a3a3c] rounded-full transition-all duration-200 hover:scale-110 active:scale-100"
                                        :title="t('close')">
                                    <i class="fas fa-times text-sm"></i>
LiangLiu's avatar
LiangLiu committed
270
271
272
273
                                </button>
                            </div>
                        </div>

LiangLiu's avatar
LiangLiu committed
274
275
276
                        <!-- 主要内容区域 - Apple 风格 -->
                        <div class="flex-1 overflow-y-auto main-scrollbar">
                            <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 p-8 lg:p-12">
LiangLiu's avatar
LiangLiu committed
277
                                <!-- 左侧视频区域 -->
LiangLiu's avatar
LiangLiu committed
278
279
                                <div class="flex items-center justify-center">
                                    <div class="w-full max-w-[400px] aspect-[9/16] bg-black dark:bg-[#000000] rounded-2xl overflow-hidden shadow-[0_8px_24px_rgba(0,0,0,0.15)] dark:shadow-[0_8px_24px_rgba(0,0,0,0.5)]">
LiangLiu's avatar
LiangLiu committed
280
281
282
283
284
                                        <!-- 视频播放器 -->
                                        <video
                                            v-if="modalTask?.outputs?.output_video"
                                            :src="getTaskFileUrlSync(modalTask.task_id, 'output_video')"
                                            :poster="getTaskFileUrlSync(modalTask.task_id, 'input_image')"
LiangLiu's avatar
LiangLiu committed
285
                                            class="w-full h-full object-contain"
LiangLiu's avatar
LiangLiu committed
286
287
288
289
290
291
292
293
                                            controls
                                            loop
                                            preload="metadata"
                                            @loadstart="onVideoLoadStart"
                                            @canplay="onVideoCanPlay"
                                            @error="onVideoError">
                                            {{ t('browserNotSupported') }}
                                        </video>
LiangLiu's avatar
LiangLiu committed
294
295
296
                                        <div v-else class="w-full h-full flex flex-col items-center justify-center bg-[#f5f5f7] dark:bg-[#1c1c1e]">
                                            <div class="w-16 h-16 rounded-full bg-black/5 dark:bg-white/5 flex items-center justify-center mb-4">
                                                <i class="fas fa-video text-3xl text-[#86868b] dark:text-[#98989d]"></i>
LiangLiu's avatar
LiangLiu committed
297
                                            </div>
LiangLiu's avatar
LiangLiu committed
298
                                            <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('videoNotAvailable') }}</p>
LiangLiu's avatar
LiangLiu committed
299
300
301
302
303
                                        </div>
                                    </div>
                                </div>

                                <!-- 右侧信息区域 -->
LiangLiu's avatar
LiangLiu committed
304
305
306
307
308
309
310
311
312
                                <div class="flex items-center justify-center">
                                    <div class="w-full max-w-[400px] aspect-[9/16] relative flex flex-col">
                                        <!-- 右上角操作按钮 - Apple 极简风格 -->
                                        <div class="absolute top-0 right-0 flex items-center gap-2 z-10">
                                            <!-- 分享按钮 -->
                                            <button @click="copyShareLink(modalTask.task_id, 'task')"
                                                    class="w-10 h-10 flex items-center justify-center bg-white/95 dark:bg-[#1e1e1e]/95 backdrop-blur-[20px] border border-black/10 dark:border-white/10 rounded-full shadow-[0_2px_8px_rgba(0,0,0,0.12)] dark:shadow-[0_2px_8px_rgba(0,0,0,0.4)] text-[#1d1d1f] dark:text-[#f5f5f7] hover:scale-110 hover:shadow-[0_4px_12px_rgba(0,0,0,0.15)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.5)] active:scale-100 transition-all duration-200"
                                                    :title="t('share')">
                                                <i class="fas fa-share-alt text-xs"></i>
LiangLiu's avatar
LiangLiu committed
313
                                            </button>
LiangLiu's avatar
LiangLiu committed
314
315
316
317
318
                                            <!-- 删除按钮 -->
                                            <button @click="deleteTask(modalTask.task_id, true)"
                                                    class="w-10 h-10 flex items-center justify-center bg-white/95 dark:bg-[#1e1e1e]/95 backdrop-blur-[20px] border border-black/10 dark:border-white/10 rounded-full shadow-[0_2px_8px_rgba(0,0,0,0.12)] dark:shadow-[0_2px_8px_rgba(0,0,0,0.4)] text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 hover:scale-110 hover:shadow-[0_4px_12px_rgba(239,68,68,0.2)] dark:hover:shadow-[0_4px_12px_rgba(248,113,113,0.3)] active:scale-100 transition-all duration-200"
                                                    :title="t('delete')">
                                                <i class="fas fa-trash text-xs"></i>
LiangLiu's avatar
LiangLiu committed
319
                                            </button>
LiangLiu's avatar
LiangLiu committed
320
                                        </div>
LiangLiu's avatar
LiangLiu committed
321

LiangLiu's avatar
LiangLiu committed
322
323
324
325
326
327
328
329
330
331
332
333
                                        <!-- 居中的内容区域 -->
                                        <div class="flex-1 flex items-center justify-center px-8 py-6">
                                            <div class="w-full">
                                                <!-- 标题 -->
                                                <div class="text-center mb-6">
                                                    <h1 class="text-3xl sm:text-4xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] mb-3 tracking-tight">
                                                        {{ t('taskCompleted') }}
                                                    </h1>
                                                    <p class="text-sm sm:text-base text-[#86868b] dark:text-[#98989d] tracking-tight">
                                                        {{ t('taskCompletedSuccessfully') }}
                                                    </p>
                                                </div>
LiangLiu's avatar
LiangLiu committed
334

LiangLiu's avatar
LiangLiu committed
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
                                                <!-- 特性列表 - Apple 风格 -->
                                                <div class="grid grid-cols-3 gap-2 mb-6">
                                                    <div class="flex flex-col items-center gap-1.5 p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl">
                                                        <i class="fas fa-toolbox text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                        <span class="text-[11px] text-[#1d1d1f] dark:text-[#f5f5f7] font-medium tracking-tight">{{ getTaskTypeName(modalTask) }}</span>
                                                    </div>
                                                    <div class="flex flex-col items-center gap-1.5 p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl">
                                                        <i class="fas fa-robot text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                        <span class="text-[11px] text-[#1d1d1f] dark:text-[#f5f5f7] font-medium tracking-tight truncate max-w-full">{{ modalTask.model_cls }}</span>
                                                    </div>
                                                    <div class="flex flex-col items-center gap-1.5 p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl">
                                                        <i class="fas fa-clock text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                        <span class="text-[11px] text-[#1d1d1f] dark:text-[#f5f5f7] font-medium tracking-tight">{{ Math.round(modalTask.extra_info?.active_elapse || 0) }}s</span>
                                                    </div>
                                                </div>
LiangLiu's avatar
LiangLiu committed
350

LiangLiu's avatar
LiangLiu committed
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
                                                <!-- 操作按钮 - Apple 风格 -->
                                                <div class="space-y-2.5">
                                                    <button v-if="modalTask?.outputs?.output_video"
                                                            @click="handleDownloadFile(modalTask.task_id, 'output_video', modalTask.outputs.output_video)"
                                                            class="w-full rounded-full bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] border-0 px-6 py-3 text-[15px] font-semibold text-white hover:scale-[1.02] hover:shadow-[0_8px_24px_rgba(var(--brand-primary-rgb),0.35)] dark:hover:shadow-[0_8px_24px_rgba(var(--brand-primary-light-rgb),0.4)] active:scale-100 transition-all duration-200 ease-out tracking-tight flex items-center justify-center gap-2">
                                                        <i class="fas fa-download text-sm"></i>
                                                        <span>{{ t('downloadVideo') }}</span>
                                                    </button>
                                                    <button @click="reuseTask(modalTask)"
                                                            class="w-full rounded-full bg-white dark:bg-[#3a3a3c] border border-black/8 dark:border-white/8 px-6 py-2.5 text-[15px] font-medium text-[#1d1d1f] dark:text-[#f5f5f7] hover:bg-white/80 dark:hover:bg-[#3a3a3c]/80 hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.3)] active:scale-[0.98] transition-all duration-200 tracking-tight flex items-center justify-center gap-2">
                                                        <i class="fas fa-magic text-sm"></i>
                                                        <span>{{ t('reuseTask') }}</span>
                                                    </button>
                                                    <button @click="showDetails = !showDetails"
                                                            class="w-full rounded-full bg-white dark:bg-[#3a3a3c] border border-black/8 dark:border-white/8 px-6 py-2.5 text-[15px] font-medium text-[#1d1d1f] dark:text-[#f5f5f7] hover:bg-white/80 dark:hover:bg-[#3a3a3c]/80 hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.3)] active:scale-[0.98] transition-all duration-200 tracking-tight flex items-center justify-center gap-2">
                                                        <i :class="showDetails ? 'fas fa-chevron-up' : 'fas fa-info-circle'" class="text-sm"></i>
                                                        <span>{{ showDetails ? t('hideDetails') : t('showDetails') }}</span>
                                                    </button>
                                                </div>
                                            </div>
LiangLiu's avatar
LiangLiu committed
371
372
373
374
375
                                        </div>
                                    </div>
                                </div>
                            </div>

LiangLiu's avatar
LiangLiu committed
376
377
378
379
380
381
382
                            <!-- 详细信息面板 - Apple 风格 (成功状态)-->
                            <div v-if="showDetails && modalTask" class="bg-[#f5f5f7] dark:bg-[#1c1c1e] border-t border-black/8 dark:border-white/8 py-12">
                                <div class="max-w-6xl mx-auto px-8">
                                    <!-- 输入素材标题 - Apple 风格 -->
                                    <h2 class="text-2xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] flex items-center justify-center gap-3 mb-8 tracking-tight">
                                        <i class="fas fa-upload text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                        <span>{{ t('inputMaterials') }}</span>
LiangLiu's avatar
LiangLiu committed
383
384
                                    </h2>

LiangLiu's avatar
LiangLiu committed
385
386
387
388
389
390
391
392
393
394
                                    <!-- 三个并列的分块卡片 - Apple 风格 -->
                                    <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                                        <!-- 图片卡片 - Apple 风格 -->
                                        <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]">
                                            <!-- 卡片头部 -->
                                            <div class="flex items-center justify-between px-5 py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8">
                                                <div class="flex items-center gap-3">
                                                    <i class="fas fa-image text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                    <h3 class="text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('image') }}</h3>
                                                </div>
LiangLiu's avatar
LiangLiu committed
395
396
                                                <button v-if="getImageMaterials().length > 0"
                                                        @click="handleDownloadFile(modalTask.task_id, 'input_image', modalTask.inputs.input_image)"
LiangLiu's avatar
LiangLiu committed
397
                                                        class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
LiangLiu's avatar
LiangLiu committed
398
                                                        :title="t('download')">
LiangLiu's avatar
LiangLiu committed
399
                                                    <i class="fas fa-download text-xs"></i>
LiangLiu's avatar
LiangLiu committed
400
401
                                                </button>
                                            </div>
LiangLiu's avatar
LiangLiu committed
402
403
404
405
406
                                            <!-- 卡片内容 -->
                                            <div class="p-6 min-h-[200px]">
                                                <div v-if="getImageMaterials().length > 0">
                                                    <div v-for="[inputName, url] in getImageMaterials()" :key="inputName" class="rounded-xl overflow-hidden border border-black/8 dark:border-white/8">
                                                        <img :src="url" :alt="inputName" class="w-full h-auto object-contain">
LiangLiu's avatar
LiangLiu committed
407
408
                                                    </div>
                                                </div>
LiangLiu's avatar
LiangLiu committed
409
410
411
412
                                                <div v-else class="flex flex-col items-center justify-center h-[150px]">
                                                    <i class="fas fa-image text-3xl text-[#86868b]/30 dark:text-[#98989d]/30 mb-3"></i>
                                                    <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('noImage') }}</p>
                                                </div>
LiangLiu's avatar
LiangLiu committed
413
414
415
                                            </div>
                                        </div>

LiangLiu's avatar
LiangLiu committed
416
417
418
419
420
421
422
423
                                        <!-- 音频卡片 - Apple 风格 -->
                                        <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]">
                                            <!-- 卡片头部 -->
                                            <div class="flex items-center justify-between px-5 py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8">
                                                <div class="flex items-center gap-3">
                                                    <i class="fas fa-music text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                    <h3 class="text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('audio') }}</h3>
                                                </div>
LiangLiu's avatar
LiangLiu committed
424
425
                                                <button v-if="getAudioMaterials().length > 0"
                                                        @click="handleDownloadFile(modalTask.task_id, 'input_audio', modalTask.inputs.input_audio)"
LiangLiu's avatar
LiangLiu committed
426
                                                        class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
LiangLiu's avatar
LiangLiu committed
427
                                                        :title="t('download')">
LiangLiu's avatar
LiangLiu committed
428
                                                    <i class="fas fa-download text-xs"></i>
LiangLiu's avatar
LiangLiu committed
429
430
                                                </button>
                                            </div>
LiangLiu's avatar
LiangLiu committed
431
432
433
434
                                            <!-- 卡片内容 -->
                                            <div class="p-6 min-h-[200px]">
                                                <div v-if="getAudioMaterials().length > 0" class="space-y-4">
                                                    <div v-for="[inputName, url] in getAudioMaterials()" :key="inputName">
LiangLiu's avatar
LiangLiu committed
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
                                                        <!-- 音频播放器卡片 - Apple 风格 -->
                                                        <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)] w-full p-4">
                                                            <div class="relative flex items-center mb-3">
                                                                <!-- 头像容器 -->
                                                                <div class="relative mr-3 flex-shrink-0">
                                                                    <!-- 透明白色头像 -->
                                                                    <div class="w-12 h-12 rounded-full bg-white/40 dark:bg-white/20 border border-white/30 dark:border-white/20 transition-all duration-200"></div>
                                                                    <!-- 播放/暂停按钮 -->
                                                                    <button
                                                                        @click="toggleAudioPlayback"
                                                                        class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 bg-[color:var(--brand-primary)]/90 dark:bg-[color:var(--brand-primary-light)]/90 rounded-full flex items-center justify-center text-white cursor-pointer hover:scale-110 transition-all duration-200 z-20 shadow-[0_2px_8px_rgba(var(--brand-primary-rgb),0.3)] dark:shadow-[0_2px_8px_rgba(var(--brand-primary-light-rgb),0.4)]"
                                                                    >
                                                                        <i :class="isPlaying ? 'fas fa-pause' : 'fas fa-play'" class="text-xs ml-0.5"></i>
                                                                    </button>
                                                                </div>

                                                                <!-- 音频信息 -->
                                                                <div class="flex-1 min-w-0">
                                                                    <div class="text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight truncate">
                                                                        {{ t('audio') }}
                                                                    </div>
                                                                </div>

                                                                <!-- 音频时长 -->
                                                                <div class="text-xs font-medium text-[#86868b] dark:text-[#98989d] tracking-tight flex-shrink-0">
                                                                    {{ formatAudioTime(currentTime) }} / {{ formatAudioTime(audioDuration) }}
                                                                </div>
                                                            </div>

                                                            <!-- 进度条 -->
                                                            <div class="flex items-center gap-2" v-if="audioDuration > 0">
                                                                <input
                                                                    type="range"
                                                                    :min="0"
                                                                    :max="audioDuration"
                                                                    :value="currentTime"
                                                                    @input="onProgressChange"
                                                                    @change="onProgressChange"
                                                                    @mousedown="isDragging = true"
                                                                    @mouseup="onProgressEnd"
                                                                    @touchstart="isDragging = true"
                                                                    @touchend="onProgressEnd"
                                                                    class="flex-1 h-1 bg-black/6 dark:bg-white/15 rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-[color:var(--brand-primary)] dark:[&::-webkit-slider-thumb]:bg-[color:var(--brand-primary-light)] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:cursor-pointer"
                                                                />
                                                            </div>
                                                        </div>

                                                        <!-- 隐藏的音频元素 -->
                                                        <audio
                                                            ref="audioElement"
                                                            :src="url"
                                                            @loadedmetadata="onAudioLoaded"
                                                            @timeupdate="onTimeUpdate"
                                                            @ended="onAudioEnded"
                                                            @play="isPlaying = true"
                                                            @pause="isPlaying = false"
                                                            class="hidden"
                                                        ></audio>
LiangLiu's avatar
LiangLiu committed
493
494
495
496
497
                                                    </div>
                                                </div>
                                                <div v-else class="flex flex-col items-center justify-center h-[150px]">
                                                    <i class="fas fa-music text-3xl text-[#86868b]/30 dark:text-[#98989d]/30 mb-3"></i>
                                                    <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('noAudio') }}</p>
LiangLiu's avatar
LiangLiu committed
498
499
500
501
                                                </div>
                                            </div>
                                        </div>

LiangLiu's avatar
LiangLiu committed
502
503
504
505
506
507
508
509
                                        <!-- 提示词卡片 - Apple 风格 -->
                                        <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]">
                                            <!-- 卡片头部 -->
                                            <div class="flex items-center justify-between px-5 py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8">
                                                <div class="flex items-center gap-3">
                                                    <i class="fas fa-file-alt text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                    <h3 class="text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('prompt') }}</h3>
                                                </div>
LiangLiu's avatar
LiangLiu committed
510
511
                                                <button v-if="modalTask?.params?.prompt"
                                                        @click="copyPrompt(modalTask?.params?.prompt)"
LiangLiu's avatar
LiangLiu committed
512
                                                        class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
LiangLiu's avatar
LiangLiu committed
513
                                                        :title="t('copy')">
LiangLiu's avatar
LiangLiu committed
514
                                                    <i class="fas fa-copy text-xs"></i>
LiangLiu's avatar
LiangLiu committed
515
516
                                                </button>
                                            </div>
LiangLiu's avatar
LiangLiu committed
517
518
519
520
521
522
523
524
525
                                            <!-- 卡片内容 -->
                                            <div class="p-6 min-h-[200px]">
                                                <div v-if="modalTask?.params?.prompt" class="bg-white/50 dark:bg-[#1e1e1e]/50 backdrop-blur-[10px] border border-black/6 dark:border-white/6 rounded-xl p-4">
                                                    <p class="text-sm text-[#1d1d1f] dark:text-[#f5f5f7] leading-relaxed tracking-tight break-words">{{ modalTask.params.prompt }}</p>
                                                </div>
                                                <div v-else class="flex flex-col items-center justify-center h-[150px]">
                                                    <i class="fas fa-file-alt text-3xl text-[#86868b]/30 dark:text-[#98989d]/30 mb-3"></i>
                                                    <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('noPrompt') }}</p>
                                                </div>
LiangLiu's avatar
LiangLiu committed
526
527
528
529
530
531
532
533
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

LiangLiu's avatar
LiangLiu committed
534
535
536
537
538
539
540
541
                    <!-- 其他状态的弹窗 - Apple 风格 -->
                    <div v-else class="w-full max-w-7xl max-h-[95vh] bg-white/95 dark:bg-[#1e1e1e]/95 backdrop-blur-[40px] backdrop-saturate-[180%] border border-black/10 dark:border-white/10 rounded-3xl shadow-[0_20px_60px_rgba(0,0,0,0.2)] dark:shadow-[0_20px_60px_rgba(0,0,0,0.6)] overflow-hidden flex flex-col" @click.stop>
                        <!-- 弹窗头部 - Apple 风格 -->
                        <div class="flex items-center justify-between px-8 py-5 border-b border-black/8 dark:border-white/8 bg-white/50 dark:bg-[#1e1e1e]/50 backdrop-blur-[20px]">
                            <h3 class="text-xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] flex items-center gap-3 tracking-tight">
                                <i v-if="modalTask?.status === 'FAILED'" class="fas fa-exclamation-triangle text-red-500 dark:text-red-400"></i>
                                <i v-else-if="modalTask?.status === 'CANCEL'" class="fas fa-ban text-[#86868b] dark:text-[#98989d]"></i>
                                <i v-else class="fas fa-spinner fa-spin text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
LiangLiu's avatar
LiangLiu committed
542
                                <span>{{ t('taskDetail') }}</span>
LiangLiu's avatar
LiangLiu committed
543
                            </h3>
LiangLiu's avatar
LiangLiu committed
544
545
546
547
548
                            <div class="flex items-center gap-2">
                                <button @click="closeWithRoute"
                                        class="w-9 h-9 flex items-center justify-center bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:text-red-500 dark:hover:text-red-400 hover:bg-white dark:hover:bg-[#3a3a3c] rounded-full transition-all duration-200 hover:scale-110 active:scale-100"
                                        :title="t('close')">
                                    <i class="fas fa-times text-sm"></i>
LiangLiu's avatar
LiangLiu committed
549
550
551
552
                                </button>
                            </div>
                        </div>

LiangLiu's avatar
LiangLiu committed
553
554
555
556
557
558
                        <!-- 主要内容区域 - Apple 风格 -->
                        <div class="flex-1 overflow-y-auto main-scrollbar">
                            <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 p-8 lg:p-12">
                                <!-- 左侧占位图区域 - Apple 风格 -->
                                <div class="flex items-center justify-center">
                                    <div class="w-full max-w-[400px] aspect-[9/16] bg-black dark:bg-[#000000] rounded-2xl overflow-hidden shadow-[0_8px_24px_rgba(0,0,0,0.15)] dark:shadow-[0_8px_24px_rgba(0,0,0,0.5)] relative">
LiangLiu's avatar
LiangLiu committed
559
                                        <!-- 根据状态显示不同的占位图 -->
LiangLiu's avatar
LiangLiu committed
560
561
562
563
564
                                        <!-- 进行中状态 -->
                                        <div v-if="['CREATED', 'PENDING', 'RUNNING'].includes(modalTask?.status)" class="w-full h-full flex flex-col items-center justify-center relative bg-[#f5f5f7] dark:bg-[#1c1c1e]">
                                            <!-- 如果有图像输入,显示为背景 -->
                                            <div v-if="getImageMaterials().length > 0" class="absolute top-0 left-0 w-full h-full z-[1]">
                                                <img :src="getImageMaterials()[0][1]" :alt="getImageMaterials()[0][0]" class="w-full h-full object-cover opacity-20 blur-sm">
LiangLiu's avatar
LiangLiu committed
565
                                            </div>
LiangLiu's avatar
LiangLiu committed
566
567
568
569
570
571
                                            <div class="absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-[2]">
                                                <div class="relative w-12 h-12 mb-6">
                                                    <div class="absolute inset-0 rounded-full border-2 border-black/8 dark:border-white/8"></div>
                                                    <div class="absolute inset-0 rounded-full border-2 border-transparent border-t-[color:var(--brand-primary)] dark:border-t-[color:var(--brand-primary-light)] animate-spin"></div>
                                                </div>
                                                <p class="text-sm font-medium text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('videoGenerating') }}...</p>
LiangLiu's avatar
LiangLiu committed
572
573
                                            </div>
                                        </div>
LiangLiu's avatar
LiangLiu committed
574
575
576
577
578
579
580
581
582
583
                                        <!-- 失败状态 -->
                                        <div v-else-if="modalTask?.status === 'FAILED'" class="w-full h-full flex flex-col items-center justify-center relative bg-[#fef2f2] dark:bg-[#2c1b1b]">
                                            <div v-if="getImageMaterials().length > 0" class="absolute top-0 left-0 w-full h-full z-[1]">
                                                <img :src="getImageMaterials()[0][1]" :alt="getImageMaterials()[0][0]" class="w-full h-full object-cover opacity-10 blur-sm">
                                            </div>
                                            <div class="absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-[2]">
                                                <div class="w-16 h-16 rounded-full bg-red-500/10 dark:bg-red-400/10 flex items-center justify-center mb-4">
                                                    <i class="fas fa-exclamation-triangle text-3xl text-red-500 dark:text-red-400"></i>
                                                </div>
                                                <p class="text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('videoGeneratingFailed') }}</p>
LiangLiu's avatar
LiangLiu committed
584
585
                                            </div>
                                        </div>
LiangLiu's avatar
LiangLiu committed
586
587
588
589
590
591
592
593
594
595
                                        <!-- 取消状态 -->
                                        <div v-else-if="modalTask?.status === 'CANCEL'" class="w-full h-full flex flex-col items-center justify-center relative bg-[#f5f5f7] dark:bg-[#1c1c1e]">
                                            <div v-if="getImageMaterials().length > 0" class="absolute top-0 left-0 w-full h-full z-[1]">
                                                <img :src="getImageMaterials()[0][1]" :alt="getImageMaterials()[0][0]" class="w-full h-full object-cover opacity-10 blur-sm">
                                            </div>
                                            <div class="absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-[2]">
                                                <div class="w-16 h-16 rounded-full bg-black/5 dark:bg-white/5 flex items-center justify-center mb-4">
                                                    <i class="fas fa-ban text-3xl text-[#86868b] dark:text-[#98989d]"></i>
                                                </div>
                                                <p class="text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('taskCancelled') }}</p>
LiangLiu's avatar
LiangLiu committed
596
597
598
599
600
                                            </div>
                                        </div>
                                    </div>
                                </div>

LiangLiu's avatar
LiangLiu committed
601
602
603
604
605
606
607
608
609
610
611
                                <!-- 右侧信息区域 - Apple 风格 -->
                                <div class="flex items-center justify-center">
                                    <div class="w-full max-w-[400px] aspect-[9/16] relative flex flex-col">
                                        <!-- 右上角删除按钮 - Apple 极简风格 -->
                                        <div class="absolute top-0 right-0 z-10">
                                            <button v-if="['FAILED', 'CANCEL'].includes(modalTask?.status)"
                                                    @click="deleteTask(modalTask.task_id, true)"
                                                    class="w-8 h-8 flex items-center justify-center bg-white/95 dark:bg-[#1e1e1e]/95 backdrop-blur-[20px] border border-black/10 dark:border-white/10 rounded-full shadow-[0_2px_8px_rgba(0,0,0,0.12)] dark:shadow-[0_2px_8px_rgba(0,0,0,0.4)] text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 hover:scale-110 hover:shadow-[0_4px_12px_rgba(239,68,68,0.2)] dark:hover:shadow-[0_4px_12px_rgba(248,113,113,0.3)] active:scale-100 transition-all duration-200"
                                                    :title="t('delete')">
                                                <i class="fas fa-trash text-xs"></i>
                                            </button>
LiangLiu's avatar
LiangLiu committed
612
613
                                        </div>

LiangLiu's avatar
LiangLiu committed
614
615
616
617
618
619
620
621
622
623
624
                                        <!-- 居中的内容区域 -->
                                        <div class="flex-1 flex items-center justify-center px-8 py-6">
                                            <div class="w-full">
                                                <!-- 标题和状态 - Apple 风格 -->
                                                <div class="text-center mb-6">
                                                    <h1 class="text-3xl sm:text-4xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] mb-3 tracking-tight">
                                                        <span v-if="modalTask?.status === 'SUCCEED'">{{ t('taskCompleted') }}</span>
                                                        <span v-else-if="modalTask?.status === 'FAILED'">{{ t('taskFailed') }}</span>
                                                        <span v-else-if="modalTask?.status === 'CANCEL'">{{ t('taskCancelled') }}</span>
                                                        <span v-else-if="modalTask?.status === 'RUNNING'">{{ t('taskRunning') }}</span>
                                                        <span v-else-if="modalTask?.status === 'PENDING'">{{ t('taskPending') }}</span>
LiangLiu's avatar
LiangLiu committed
625
                                                        <span v-else>{{ t('taskDetail') }}</span>
LiangLiu's avatar
LiangLiu committed
626
627
628
629
630
631
632
633
634
635
636
637
                                                    </h1>
                                                </div>

                                                <!-- 进度条 - Apple 风格 -->
                                                <div v-if="['CREATED', 'PENDING', 'RUNNING'].includes(modalTask?.status)" class="mb-6">
                                            <div v-for="(subtask, index) in (modalTask.subtasks || [])" :key="index">
                                                <!-- PENDING状态:Apple 风格排队显示 -->
                                                <div v-if="subtask.status === 'PENDING'" class="text-center">
                                                    <div v-if="subtask.estimated_pending_order !== null && subtask.estimated_pending_order !== undefined && subtask.estimated_pending_order >= 0" class="flex flex-col items-center gap-3">
                                                        <!-- 排队图标 -->
                                                        <div class="flex flex-wrap justify-center gap-1.5 mb-2">
                                                            <i v-for="n in Math.min(Math.max(subtask.estimated_pending_order, 0), 10)"
LiangLiu's avatar
LiangLiu committed
638
                                                               :key="n"
LiangLiu's avatar
LiangLiu committed
639
640
                                                               class="fas fa-circle text-[8px] text-[#86868b] dark:text-[#98989d] opacity-60"></i>
                                                            <span v-if="subtask.estimated_pending_order > 10" class="text-xs text-[#86868b] dark:text-[#98989d] font-medium ml-0.5">
LiangLiu's avatar
LiangLiu committed
641
642
643
                                                                +{{ subtask.estimated_pending_order - 10 }}
                                                            </span>
                                                        </div>
LiangLiu's avatar
LiangLiu committed
644
645
                                                        <!-- 排队文字 -->
                                                        <span class="text-sm text-[#1d1d1f] dark:text-[#f5f5f7] font-medium tracking-tight">
LiangLiu's avatar
LiangLiu committed
646
647
648
649
650
                                                            {{ t('queuePosition') }}: {{ subtask.estimated_pending_order }}
                                                        </span>
                                                    </div>
                                                </div>

LiangLiu's avatar
LiangLiu committed
651
652
653
654
655
656
                                                <!-- RUNNING状态:Apple 风格进度条 -->
                                                <div v-else-if="subtask.status === 'RUNNING'" class="w-full">
                                                    <!-- 进度条 -->
                                                    <div class="mb-4">
                                                        <div class="relative w-full h-1 bg-black/8 dark:bg-white/8 rounded-full overflow-hidden">
                                                            <div class="absolute top-0 left-0 h-full bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] rounded-full transition-all duration-500 ease-out" :style="{ width: getSubtaskProgress(subtask) + '%' }"></div>
LiangLiu's avatar
LiangLiu committed
657
658
                                                        </div>
                                                    </div>
LiangLiu's avatar
LiangLiu committed
659
660
661
                                                    <!-- 百分比显示 -->
                                                    <div class="flex justify-center items-center">
                                                        <span class="text-2xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">
LiangLiu's avatar
LiangLiu committed
662
663
664
665
666
667
                                                            {{ getSubtaskProgress(subtask) }}%
                                                        </span>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
LiangLiu's avatar
LiangLiu committed
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688

                                                <!-- 描述 - Apple 风格 -->
                                                <div class="text-sm sm:text-base text-[#86868b] dark:text-[#98989d] text-center mb-6 tracking-tight">
                                                    <p v-if="['RUNNING'].includes(modalTask?.status)" class="mb-0">
                                                        {{ t('aiIsGeneratingYourVideo') }}
                                                    </p>
                                                    <p v-else-if="['CREATED'].includes(modalTask?.status)" class="mb-0">
                                                        {{ t('taskSubmittedSuccessfully') }}
                                                    </p>
                                                    <p v-else-if="['PENDING'].includes(modalTask?.status)" class="mb-0">
                                                        {{ t('taskQueuePleaseWait') }}
                                                    </p>
                                                    <div v-else-if="modalTask?.status === 'FAILED'">
                                                        <p class="mb-4">{{ t('sorryYourVideoGenerationTaskFailed') }}</p>
                                                        <button v-if="modalTask?.fail_msg"
                                                                @click="showFailureDetails = !showFailureDetails"
                                                                class="text-sm text-[#86868b] dark:text-[#98989d] hover:text-red-500 dark:hover:text-red-400 transition-colors underline underline-offset-2">
                                                            {{ showFailureDetails ? t('hideDetails') : t('viewErrorDetails') }}
                                                        </button>
                                                        <div v-if="showFailureDetails && modalTask?.fail_msg" class="mt-4 p-4 bg-black/2 dark:bg-white/2 border border-black/6 dark:border-white/6 rounded-xl text-left">
                                                            <p class="text-xs text-[#86868b] dark:text-[#98989d] whitespace-pre-wrap leading-relaxed">{{ modalTask?.fail_msg }}</p>
LiangLiu's avatar
LiangLiu committed
689
690
                                                        </div>
                                                    </div>
LiangLiu's avatar
LiangLiu committed
691
692
693
                                                    <p v-else-if="modalTask?.status === 'CANCEL'" class="mb-0">
                                                        {{ t('thisTaskHasBeenCancelledYouCanRegenerateOrViewTheMaterialsYouUploadedBefore') }}
                                                    </p>
LiangLiu's avatar
LiangLiu committed
694
695
                                                </div>

LiangLiu's avatar
LiangLiu committed
696
697
698
699
700
701
702
703
704
705
706
                                                <!-- 特性列表 - Apple 风格 -->
                                                <div class="grid grid-cols-2 gap-2 mb-6">
                                                    <div class="flex flex-col items-center gap-1.5 p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl">
                                                        <i class="fas fa-toolbox text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                        <span class="text-[11px] text-[#1d1d1f] dark:text-[#f5f5f7] font-medium tracking-tight">{{ getTaskTypeName(modalTask) }}</span>
                                                    </div>
                                                    <div class="flex flex-col items-center gap-1.5 p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl">
                                                        <i class="fas fa-robot text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                        <span class="text-[11px] text-[#1d1d1f] dark:text-[#f5f5f7] font-medium tracking-tight truncate max-w-full">{{ modalTask.model_cls }}</span>
                                                    </div>
                                                </div>
LiangLiu's avatar
LiangLiu committed
707

LiangLiu's avatar
LiangLiu committed
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
                                                <!-- 操作按钮 - Apple 风格 -->
                                                <div class="space-y-2.5">
                                                    <!-- 进行中状态:取消按钮 -->
                                                    <button v-if="['CREATED', 'PENDING', 'RUNNING'].includes(modalTask?.status)"
                                                            @click="cancelTask(modalTask.task_id, true)"
                                                            class="w-full rounded-full bg-white dark:bg-[#3a3a3c] border border-black/8 dark:border-white/8 px-6 py-3 text-[15px] font-semibold text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 hover:border-red-500/30 dark:hover:border-red-400/30 hover:shadow-[0_8px_24px_rgba(239,68,68,0.2)] dark:hover:shadow-[0_8px_24px_rgba(248,113,113,0.3)] active:scale-[0.98] transition-all duration-200 tracking-tight flex items-center justify-center gap-2">
                                                        <i class="fas fa-times text-sm"></i>
                                                        <span>{{ t('cancelTask') }}</span>
                                                    </button>

                                                    <!-- 失败或取消状态:重试按钮 -->
                                                    <button v-if="modalTask?.status === 'FAILED' || modalTask?.status === 'CANCEL'"
                                                            @click="resumeTask(modalTask.task_id, true)"
                                                            class="w-full rounded-full bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] border-0 px-6 py-3 text-[15px] font-semibold text-white hover:scale-[1.02] hover:shadow-[0_8px_24px_rgba(var(--brand-primary-rgb),0.35)] dark:hover:shadow-[0_8px_24px_rgba(var(--brand-primary-light-rgb),0.4)] active:scale-100 transition-all duration-200 ease-out tracking-tight flex items-center justify-center gap-2">
                                                        <i class="fas fa-redo text-sm"></i>
                                                        <span>{{ modalTask?.status === 'CANCEL' ? t('regenerateTask') : t('retryTask') }}</span>
                                                    </button>

                                                    <!-- 通用按钮 -->
                                                    <button v-if="['SUCCEED', 'FAILED', 'CANCEL','CREATED', 'PENDING', 'RUNNING'].includes(modalTask?.status)"
                                                            @click="reuseTask(modalTask)"
                                                            class="w-full rounded-full bg-white dark:bg-[#3a3a3c] border border-black/8 dark:border-white/8 px-6 py-2.5 text-[15px] font-medium text-[#1d1d1f] dark:text-[#f5f5f7] hover:bg-white/80 dark:hover:bg-[#3a3a3c]/80 hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.3)] active:scale-[0.98] transition-all duration-200 tracking-tight flex items-center justify-center gap-2">
                                                        <i class="fas fa-copy text-sm"></i>
                                                        <span>{{ t('reuseTask') }}</span>
                                                    </button>

                                                    <button @click="showDetails = !showDetails"
                                                            class="w-full rounded-full bg-white dark:bg-[#3a3a3c] border border-black/8 dark:border-white/8 px-6 py-2.5 text-[15px] font-medium text-[#1d1d1f] dark:text-[#f5f5f7] hover:bg-white/80 dark:hover:bg-[#3a3a3c]/80 hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.3)] active:scale-[0.98] transition-all duration-200 tracking-tight flex items-center justify-center gap-2">
                                                        <i :class="showDetails ? 'fas fa-chevron-up' : 'fas fa-info-circle'" class="text-sm"></i>
                                                        <span>{{ showDetails ? t('hideDetails') : t('showDetails') }}</span>
                                                    </button>
                                                </div>
                                            </div>
LiangLiu's avatar
LiangLiu committed
741
742
743
744
745
                                        </div>
                                    </div>
                                </div>
                            </div>

LiangLiu's avatar
LiangLiu committed
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
                            <!-- 详细信息面板 - Apple 风格(其他状态)-->
                        <div v-if="showDetails && modalTask" class="bg-[#f5f5f7] dark:bg-[#1c1c1e] border-t border-black/8 dark:border-white/8 py-12">
                            <div class="max-w-6xl mx-auto px-8">
                                <!-- 输入素材标题 - Apple 风格 -->
                                <h2 class="text-2xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] flex items-center justify-center gap-3 mb-8 tracking-tight">
                                    <i class="fas fa-upload text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                    <span>{{ t('inputMaterials') }}</span>
                                </h2>

                                <!-- 三个并列的分块卡片 - Apple 风格 -->
                                <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                                    <!-- 图片卡片 - Apple 风格 -->
                                    <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]">
                                        <!-- 卡片头部 -->
                                        <div class="flex items-center justify-between px-5 py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8">
                                            <div class="flex items-center gap-3">
                                                <i class="fas fa-image text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                <h3 class="text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('image') }}</h3>
LiangLiu's avatar
LiangLiu committed
764
                                            </div>
LiangLiu's avatar
LiangLiu committed
765
766
767
768
769
770
                                            <button v-if="getImageMaterials().length > 0"
                                                    @click="handleDownloadFile(modalTask.task_id, 'input_image', modalTask.inputs.input_image)"
                                                    class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
                                                    :title="t('download')">
                                                <i class="fas fa-download text-xs"></i>
                                            </button>
LiangLiu's avatar
LiangLiu committed
771
                                        </div>
LiangLiu's avatar
LiangLiu committed
772
773
774
775
776
                                        <!-- 卡片内容 -->
                                        <div class="p-6 min-h-[200px]">
                                            <div v-if="getImageMaterials().length > 0">
                                                <div v-for="[inputName, url] in getImageMaterials()" :key="inputName" class="rounded-xl overflow-hidden border border-black/8 dark:border-white/8">
                                                    <img :src="url" :alt="inputName" class="w-full h-auto object-contain">
LiangLiu's avatar
LiangLiu committed
777
778
                                                </div>
                                            </div>
LiangLiu's avatar
LiangLiu committed
779
780
781
                                            <div v-else class="flex flex-col items-center justify-center h-[150px]">
                                                <i class="fas fa-image text-3xl text-[#86868b]/30 dark:text-[#98989d]/30 mb-3"></i>
                                                <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('noImage') }}</p>
LiangLiu's avatar
LiangLiu committed
782
783
784
785
                                            </div>
                                        </div>
                                    </div>

LiangLiu's avatar
LiangLiu committed
786
787
788
789
790
791
792
                                    <!-- 音频卡片 - Apple 风格 -->
                                    <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]">
                                        <!-- 卡片头部 -->
                                        <div class="flex items-center justify-between px-5 py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8">
                                            <div class="flex items-center gap-3">
                                                <i class="fas fa-music text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                <h3 class="text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('audio') }}</h3>
LiangLiu's avatar
LiangLiu committed
793
                                            </div>
LiangLiu's avatar
LiangLiu committed
794
795
796
797
798
799
                                            <button v-if="getAudioMaterials().length > 0"
                                                    @click="handleDownloadFile(modalTask.task_id, 'input_audio', modalTask.inputs.input_audio)"
                                                    class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
                                                    :title="t('download')">
                                                <i class="fas fa-download text-xs"></i>
                                            </button>
LiangLiu's avatar
LiangLiu committed
800
                                        </div>
LiangLiu's avatar
LiangLiu committed
801
802
803
804
                                        <!-- 卡片内容 -->
                                        <div class="p-6 min-h-[200px]">
                                            <div v-if="getAudioMaterials().length > 0" class="space-y-4">
                                                <div v-for="[inputName, url] in getAudioMaterials()" :key="inputName">
LiangLiu's avatar
LiangLiu committed
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
                                                    <!-- 音频播放器卡片 - Apple 风格 -->
                                                    <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)] w-full p-4">
                                                        <div class="relative flex items-center mb-3">
                                                            <!-- 头像容器 -->
                                                            <div class="relative mr-3 flex-shrink-0">
                                                                <!-- 透明白色头像 -->
                                                                <div class="w-12 h-12 rounded-full bg-white/40 dark:bg-white/20 border border-white/30 dark:border-white/20 transition-all duration-200"></div>
                                                                <!-- 播放/暂停按钮 -->
                                                                <button
                                                                    @click="toggleAudioPlayback"
                                                                    class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 bg-[color:var(--brand-primary)]/90 dark:bg-[color:var(--brand-primary-light)]/90 rounded-full flex items-center justify-center text-white cursor-pointer hover:scale-110 transition-all duration-200 z-20 shadow-[0_2px_8px_rgba(var(--brand-primary-rgb),0.3)] dark:shadow-[0_2px_8px_rgba(var(--brand-primary-light-rgb),0.4)]"
                                                                >
                                                                    <i :class="isPlaying ? 'fas fa-pause' : 'fas fa-play'" class="text-xs ml-0.5"></i>
                                                                </button>
                                                            </div>

                                                            <!-- 音频信息 -->
                                                            <div class="flex-1 min-w-0">
                                                                <div class="text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight truncate">
                                                                    {{ t('audio') }}
                                                                </div>
                                                            </div>

                                                            <!-- 音频时长 -->
                                                            <div class="text-xs font-medium text-[#86868b] dark:text-[#98989d] tracking-tight flex-shrink-0">
                                                                {{ formatAudioTime(currentTime) }} / {{ formatAudioTime(audioDuration) }}
                                                            </div>
                                                        </div>

                                                        <!-- 进度条 -->
                                                        <div class="flex items-center gap-2" v-if="audioDuration > 0">
                                                            <input
                                                                type="range"
                                                                :min="0"
                                                                :max="audioDuration"
                                                                :value="currentTime"
                                                                @input="onProgressChange"
                                                                @change="onProgressChange"
                                                                @mousedown="isDragging = true"
                                                                @mouseup="onProgressEnd"
                                                                @touchstart="isDragging = true"
                                                                @touchend="onProgressEnd"
                                                                class="flex-1 h-1 bg-black/6 dark:bg-white/15 rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-[color:var(--brand-primary)] dark:[&::-webkit-slider-thumb]:bg-[color:var(--brand-primary-light)] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:cursor-pointer"
                                                            />
                                                        </div>
                                                    </div>

                                                    <!-- 隐藏的音频元素 -->
                                                    <audio
                                                        ref="audioElement"
                                                        :src="url"
                                                        @loadedmetadata="onAudioLoaded"
                                                        @timeupdate="onTimeUpdate"
                                                        @ended="onAudioEnded"
                                                        @play="isPlaying = true"
                                                        @pause="isPlaying = false"
                                                        class="hidden"
                                                    ></audio>
LiangLiu's avatar
LiangLiu committed
863
864
                                                </div>
                                            </div>
LiangLiu's avatar
LiangLiu committed
865
866
867
                                            <div v-else class="flex flex-col items-center justify-center h-[150px]">
                                                <i class="fas fa-music text-3xl text-[#86868b]/30 dark:text-[#98989d]/30 mb-3"></i>
                                                <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('noAudio') }}</p>
LiangLiu's avatar
LiangLiu committed
868
869
870
871
                                            </div>
                                        </div>
                                    </div>

LiangLiu's avatar
LiangLiu committed
872
873
874
875
876
877
878
                                    <!-- 提示词卡片 - Apple 风格 -->
                                    <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]">
                                        <!-- 卡片头部 -->
                                        <div class="flex items-center justify-between px-5 py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8">
                                            <div class="flex items-center gap-3">
                                                <i class="fas fa-file-alt text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                <h3 class="text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('prompt') }}</h3>
LiangLiu's avatar
LiangLiu committed
879
                                            </div>
LiangLiu's avatar
LiangLiu committed
880
881
882
883
884
885
                                            <button v-if="modalTask?.params?.prompt"
                                                    @click="copyPrompt(modalTask?.params?.prompt)"
                                                    class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
                                                    :title="t('copy')">
                                                <i class="fas fa-copy text-xs"></i>
                                            </button>
LiangLiu's avatar
LiangLiu committed
886
                                        </div>
LiangLiu's avatar
LiangLiu committed
887
888
889
890
                                        <!-- 卡片内容 -->
                                        <div class="p-6 min-h-[200px]">
                                            <div v-if="modalTask?.params?.prompt" class="bg-white/50 dark:bg-[#1e1e1e]/50 backdrop-blur-[10px] border border-black/6 dark:border-white/6 rounded-xl p-4">
                                                <p class="text-sm text-[#1d1d1f] dark:text-[#f5f5f7] leading-relaxed tracking-tight break-words">{{ modalTask.params.prompt }}</p>
LiangLiu's avatar
LiangLiu committed
891
                                            </div>
LiangLiu's avatar
LiangLiu committed
892
893
894
                                            <div v-else class="flex flex-col items-center justify-center h-[150px]">
                                                <i class="fas fa-file-alt text-3xl text-[#86868b]/30 dark:text-[#98989d]/30 mb-3"></i>
                                                <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('noPrompt') }}</p>
LiangLiu's avatar
LiangLiu committed
895
896
897
898
899
900
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
LiangLiu's avatar
LiangLiu committed
901
                        </div>
LiangLiu's avatar
LiangLiu committed
902
                    </div>
LiangLiu's avatar
LiangLiu committed
903
904
                </div>
            </div>
LiangLiu's avatar
LiangLiu committed
905
906
907
</template>

<style scoped>
LiangLiu's avatar
LiangLiu committed
908
/* Apple 风格极简样式 - 所有样式已通过 Tailwind CSS 的 dark: 前缀在 template 中定义 */
LiangLiu's avatar
LiangLiu committed
909
</style>