// 全局变量 let currentTheme = 'light'; let isGenerating = false; let currentApiKey = null; // 初始化 document.addEventListener('DOMContentLoaded', function() { console.log('页面加载完成,开始初始化...'); try { initializeTheme(); initializeEventListeners(); initializeRangeSliders(); // 先加载本地缓存的 API 密钥,避免未传 key 导致 401 loadLocalApiKey(); // 再检查后端密钥状态(环境变量/服务端存储) checkApiKeyStatus(); loadVideoLibrary(); console.log('初始化完成'); } catch (error) { console.error('初始化失败:', error); } }); // 主题切换功能 function initializeTheme() { const savedTheme = localStorage.getItem('theme') || 'light'; setTheme(savedTheme); } function setTheme(theme) { currentTheme = theme; document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); const themeToggle = document.getElementById('themeToggle'); if (themeToggle) { const icon = themeToggle.querySelector('i'); if (icon) { if (theme === 'dark') { icon.className = 'fas fa-sun'; themeToggle.title = '切换到浅色模式'; } else { icon.className = 'fas fa-moon'; themeToggle.title = '切换到深色模式'; } } } } function toggleTheme() { const newTheme = currentTheme === 'light' ? 'dark' : 'light'; setTheme(newTheme); } // 从本地缓存读取 API 密钥并启用(不改后端) function loadLocalApiKey() { try { const cached = localStorage.getItem('fal_api_key'); if (cached && cached.length >= 10) { currentApiKey = cached; const apiKeyStatus = document.getElementById('apiKeyStatus'); const apiKeyButton = document.getElementById('apiKeyButton'); const modalApiKeyStatus = document.getElementById('modalApiKeyStatus'); if (apiKeyStatus) apiKeyStatus.textContent = '本地缓存'; if (apiKeyButton) apiKeyButton.classList.add('configured'); if (modalApiKeyStatus) modalApiKeyStatus.textContent = '已配置(来源:本地缓存)'; } } catch (e) { console.warn('读取本地缓存的 API key 失败:', e); } } // API Key 管理功能 async function checkApiKeyStatus() { try { const response = await fetch('/api/check-key'); const data = await response.json(); updateApiKeyStatus(data); } catch (error) { console.error('检查 API key 状态失败:', error); updateApiKeyStatus({ hasStoredKey: false, hasEnvKey: false, keySource: 'none' }); } } function updateApiKeyStatus(status) { const { hasStoredKey, hasEnvKey, keySource } = status; const apiKeyStatus = document.getElementById('apiKeyStatus'); const apiKeyButton = document.getElementById('apiKeyButton'); const modalApiKeyStatus = document.getElementById('modalApiKeyStatus'); if (apiKeyStatus) { if (keySource === 'environment') { apiKeyStatus.textContent = '环境变量'; if (apiKeyButton) apiKeyButton.classList.add('configured'); if (modalApiKeyStatus) modalApiKeyStatus.textContent = '已配置(来源:环境变量)'; } else if (keySource === 'stored') { apiKeyStatus.textContent = '已保存'; if (apiKeyButton) apiKeyButton.classList.add('configured'); if (modalApiKeyStatus) modalApiKeyStatus.textContent = '已配置(来源:本地存储)'; } else { apiKeyStatus.textContent = '未配置'; if (apiKeyButton) apiKeyButton.classList.remove('configured'); if (modalApiKeyStatus) modalApiKeyStatus.textContent = '未配置 - 请输入 API 密钥'; } } } // 事件监听器初始化 function initializeEventListeners() { console.log('初始化事件监听器...'); // 主题切换 const themeToggle = document.getElementById('themeToggle'); if (themeToggle) { themeToggle.addEventListener('click', toggleTheme); console.log('主题切换按钮已绑定'); } // API Key 管理 const apiKeyButton = document.getElementById('apiKeyButton'); const apiKeyModal = document.getElementById('apiKeyModal'); const closeModal = document.getElementById('closeModal'); if (apiKeyButton && apiKeyModal) { apiKeyButton.addEventListener('click', () => { apiKeyModal.style.display = 'flex'; checkApiKeyStatus(); }); console.log('API Key 按钮已绑定'); } if (closeModal && apiKeyModal) { closeModal.addEventListener('click', () => { apiKeyModal.style.display = 'none'; }); // 点击模态框背景关闭 apiKeyModal.addEventListener('click', (e) => { if (e.target === apiKeyModal) { apiKeyModal.style.display = 'none'; } }); console.log('模态框关闭事件已绑定'); } // API Key 相关按钮 const toggleApiKeyVisibility = document.getElementById('toggleApiKeyVisibility'); const saveApiKeyButton = document.getElementById('saveApiKeyButton'); const testApiKeyButton = document.getElementById('testApiKeyButton'); if (toggleApiKeyVisibility) { toggleApiKeyVisibility.addEventListener('click', toggleApiKeyVisibilityFunc); } if (saveApiKeyButton) { saveApiKeyButton.addEventListener('click', saveApiKey); } if (testApiKeyButton) { testApiKeyButton.addEventListener('click', testApiKey); } // ESC 键关闭模态框 document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && apiKeyModal && apiKeyModal.style.display === 'flex') { apiKeyModal.style.display = 'none'; } }); // 标签页切换 const tabButtons = document.querySelectorAll('.tab-button'); tabButtons.forEach(button => { button.addEventListener('click', () => switchTab(button.dataset.tab)); }); console.log(`${tabButtons.length} 个标签按钮已绑定`); // 图片上传相关 const imageUploadArea = document.getElementById('imageUploadArea'); const imageFile = document.getElementById('imageFile'); const removeImage = document.getElementById('removeImage'); if (imageUploadArea && imageFile) { imageUploadArea.addEventListener('click', () => imageFile.click()); imageUploadArea.addEventListener('dragover', handleDragOver); imageUploadArea.addEventListener('drop', handleDrop); imageUploadArea.addEventListener('dragleave', handleDragLeave); imageFile.addEventListener('change', handleImageSelect); console.log('图片上传事件已绑定'); } if (removeImage) { removeImage.addEventListener('click', clearImagePreview); } // 表单提交 const imageToVideoForm = document.getElementById('imageToVideoForm'); const textToVideoForm = document.getElementById('textToVideoForm'); if (imageToVideoForm) { imageToVideoForm.addEventListener('submit', handleImageToVideoSubmit); console.log('图片转视频表单已绑定'); } if (textToVideoForm) { textToVideoForm.addEventListener('submit', handleTextToVideoSubmit); console.log('文本转视频表单已绑定'); } // 结果清除 const clearResult = document.getElementById('clearResult'); if (clearResult) { clearResult.addEventListener('click', clearResults); } // 视频库相关 const refreshLibrary = document.getElementById('refreshLibrary'); if (refreshLibrary) { refreshLibrary.addEventListener('click', loadVideoLibrary); } // 模型选择(文本转视频) const t2vModelSelect = document.getElementById('t2vModel'); if (t2vModelSelect) { t2vModelSelect.addEventListener('change', updateModelSettingsVisibility); // 初始化时根据默认选择显示对应参数 updateModelSettingsVisibility(); console.log('模型选择切换事件已绑定'); } // 模型选择(图片转视频) const i2vModelSelect = document.getElementById('i2vModel'); if (i2vModelSelect) { i2vModelSelect.addEventListener('change', updateI2VModelSettingsVisibility); // 初始化时根据默认选择显示对应参数 updateI2VModelSettingsVisibility(); console.log('图片转视频模型选择切换事件已绑定'); } console.log('事件监听器初始化完成'); } // 范围滑块初始化 function initializeRangeSliders() { const ranges = document.querySelectorAll('.form-range'); ranges.forEach(range => { const valueSpan = document.getElementById(range.id + 'Value'); if (valueSpan) { valueSpan.textContent = range.value; range.addEventListener('input', () => { valueSpan.textContent = range.value; }); } }); console.log(`${ranges.length} 个滑块已初始化`); } // 根据模型选择显示/隐藏对应高级设置 function updateModelSettingsVisibility() { const model = document.getElementById('t2vModel')?.value || 'seedance-pro-fast'; const wanSettings = document.getElementById('wanSettings'); const seedSettings = document.getElementById('seedanceSettings'); if (wanSettings && seedSettings) { if (model === 'wan-v2.2-a14b') { wanSettings.style.display = 'block'; seedSettings.style.display = 'none'; } else { wanSettings.style.display = 'none'; seedSettings.style.display = 'block'; } } } // 根据图片转视频模型选择显示/隐藏对应高级设置 function updateI2VModelSettingsVisibility() { const model = document.getElementById('i2vModel')?.value || 'seedance-pro-fast'; const wanSettings = document.getElementById('i2vWanSettings'); const seedSettings = document.getElementById('i2vSeedanceSettings'); if (wanSettings && seedSettings) { if (model === 'wan-v2.2-a14b') { wanSettings.style.display = 'block'; seedSettings.style.display = 'none'; } else { wanSettings.style.display = 'none'; seedSettings.style.display = 'block'; } } } // API Key 相关函数 function toggleApiKeyVisibilityFunc() { const apiKeyInput = document.getElementById('apiKeyInput'); const toggleButton = document.getElementById('toggleApiKeyVisibility'); if (apiKeyInput && toggleButton) { const icon = toggleButton.querySelector('i'); if (apiKeyInput.type === 'password') { apiKeyInput.type = 'text'; if (icon) icon.className = 'fas fa-eye-slash'; } else { apiKeyInput.type = 'password'; if (icon) icon.className = 'fas fa-eye'; } } } async function saveApiKey() { const apiKeyInput = document.getElementById('apiKeyInput'); const saveApiKeyCheckbox = document.getElementById('saveApiKey'); const saveApiKeyButton = document.getElementById('saveApiKeyButton'); if (!apiKeyInput) return; const apiKey = apiKeyInput.value.trim(); const shouldSave = saveApiKeyCheckbox ? saveApiKeyCheckbox.checked : true; if (!apiKey) { showNotification('请输入 API 密钥', 'error'); return; } // 基本检查 if (apiKey.length < 10) { showNotification('API 密钥长度不足', 'error'); return; } // 显示保存状态 if (saveApiKeyButton) { saveApiKeyButton.disabled = true; saveApiKeyButton.innerHTML = ' 保存中...'; } if (shouldSave) { try { console.log('正在保存 API 密钥到服务器...'); const response = await fetch('/api/save-key', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey }) }); const result = await response.json(); console.log('保存结果:', result); if (result.success) { showNotification('API 密钥保存成功', 'success'); // 同步前端使用,避免未传 key 导致 401 currentApiKey = apiKey; try { localStorage.setItem('fal_api_key', apiKey); } catch (e) {} checkApiKeyStatus(); const apiKeyModal = document.getElementById('apiKeyModal'); if (apiKeyModal) apiKeyModal.style.display = 'none'; } else { showNotification(result.error || '保存失败', 'error'); console.error('保存失败:', result); } } catch (error) { console.error('保存 API key 失败:', error); showNotification('保存失败,请检查网络连接', 'error'); } } else { // 临时设置 currentApiKey = apiKey; showNotification('API 密钥已设置(临时)', 'success'); const apiKeyModal = document.getElementById('apiKeyModal'); if (apiKeyModal) apiKeyModal.style.display = 'none'; const apiKeyStatus = document.getElementById('apiKeyStatus'); const apiKeyButton = document.getElementById('apiKeyButton'); if (apiKeyStatus) apiKeyStatus.textContent = '临时设置'; if (apiKeyButton) apiKeyButton.classList.add('configured'); } // 恢复按钮状态 if (saveApiKeyButton) { saveApiKeyButton.disabled = false; saveApiKeyButton.innerHTML = ' 保存设置'; } } async function testApiKey() { const testApiKeyButton = document.getElementById('testApiKeyButton'); const apiKeyInput = document.getElementById('apiKeyInput'); if (!testApiKeyButton) return; const apiKey = apiKeyInput ? apiKeyInput.value.trim() : null; testApiKeyButton.disabled = true; testApiKeyButton.innerHTML = ' 连接中...'; try { const response = await fetch('/api/test-key', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey }) }); const result = await response.json(); if (result.success) { showNotification(`✅ ${result.message}`, 'success'); if (result.note) { setTimeout(() => { showNotification(result.note, 'info'); }, 1500); } } else { showNotification(result.error || 'API 密钥格式验证失败', 'error'); // 显示具体的格式问题 if (result.tips && result.tips.length > 0) { console.error('格式问题:', result.tips); setTimeout(() => { result.tips.forEach((tip, index) => { setTimeout(() => { showNotification(tip, 'warning'); }, index * 1000); }); }, 1000); } } } catch (error) { console.error('测试 API key 失败:', error); showNotification('网络连接失败,请重试', 'error'); } finally { testApiKeyButton.disabled = false; testApiKeyButton.innerHTML = ' 测试连接'; } } // 标签页切换 function switchTab(tabId) { const tabButtons = document.querySelectorAll('.tab-button'); const tabContents = document.querySelectorAll('.tab-content'); tabButtons.forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tabId); }); tabContents.forEach(content => { content.classList.toggle('active', content.id === tabId); }); clearResults(); } // 图片处理函数 function handleDragOver(e) { e.preventDefault(); const imageUploadArea = document.getElementById('imageUploadArea'); if (imageUploadArea) imageUploadArea.classList.add('dragover'); } function handleDragLeave(e) { e.preventDefault(); const imageUploadArea = document.getElementById('imageUploadArea'); if (imageUploadArea) imageUploadArea.classList.remove('dragover'); } function handleDrop(e) { e.preventDefault(); const imageUploadArea = document.getElementById('imageUploadArea'); if (imageUploadArea) imageUploadArea.classList.remove('dragover'); const files = e.dataTransfer.files; if (files.length > 0 && files[0].type.startsWith('image/')) { handleImageFile(files[0]); } } function handleImageSelect(e) { const file = e.target.files[0]; if (file && file.type.startsWith('image/')) { handleImageFile(file); } } function handleImageFile(file) { if (file.size > 50 * 1024 * 1024) { showNotification('图片文件大小不能超过 50MB', 'error'); return; } const reader = new FileReader(); reader.onload = function(e) { const previewImg = document.getElementById('previewImg'); const imagePreview = document.getElementById('imagePreview'); const uploadPlaceholder = document.querySelector('.upload-placeholder'); const imageUrl = document.getElementById('imageUrl'); if (previewImg) previewImg.src = e.target.result; if (imagePreview) imagePreview.style.display = 'block'; if (uploadPlaceholder) uploadPlaceholder.style.display = 'none'; if (imageUrl) imageUrl.value = ''; }; reader.readAsDataURL(file); } function clearImagePreview() { const imagePreview = document.getElementById('imagePreview'); const uploadPlaceholder = document.querySelector('.upload-placeholder'); const imageFile = document.getElementById('imageFile'); const imageUrl = document.getElementById('imageUrl'); if (imagePreview) imagePreview.style.display = 'none'; if (uploadPlaceholder) uploadPlaceholder.style.display = 'block'; if (imageFile) imageFile.value = ''; if (imageUrl) imageUrl.value = ''; } // 表单提交处理 async function handleImageToVideoSubmit(e) { e.preventDefault(); // 并发生成允许,不阻塞生成按钮 const prompt = document.getElementById('i2vPrompt')?.value.trim(); const imageUrl = document.getElementById('imageUrl')?.value.trim(); const imageFile = document.getElementById('imageFile'); const model = document.getElementById('i2vModel')?.value || 'seedance-pro-fast'; if (!prompt) { showNotification('请输入文本描述', 'error'); return; } if (!imageFile?.files[0] && !imageUrl) { showNotification('请选择图片或输入图片URL', 'error'); return; } const formData = new FormData(); if (imageFile?.files[0]) { formData.append('image', imageFile.files[0]); } else { formData.append('image_url', imageUrl); } const apiKey = currentApiKey; if (apiKey) { formData.append('userApiKey', apiKey); } formData.append('prompt', prompt); formData.append('model', model); if (model === 'seedance-pro-fast') { // Seedance 1.0 Pro Fast 所需参数 formData.append('aspect_ratio', document.getElementById('i2vSeedAspectRatio')?.value || 'auto'); formData.append('resolution', document.getElementById('i2vSeedResolution')?.value || '1080p'); formData.append('duration', document.getElementById('i2vSeedDuration')?.value || '5'); formData.append('camera_fixed', document.getElementById('i2vCameraFixed')?.checked ? 'true' : 'false'); formData.append('seed', (document.getElementById('i2vSeedValue')?.value ?? '-1').toString()); formData.append('enable_safety_checker', document.getElementById('i2vEnableSafety')?.checked ? 'true' : 'false'); } else { // WAN v2.2-a14b 参数(保持原逻辑) formData.append('negative_prompt', document.getElementById('i2vNegativePrompt')?.value || ''); formData.append('num_frames', document.getElementById('i2vFrames')?.value || '81'); formData.append('frames_per_second', document.getElementById('i2vFps')?.value || '16'); formData.append('resolution', document.getElementById('i2vResolution')?.value || '720p'); formData.append('aspect_ratio', document.getElementById('i2vAspectRatio')?.value || 'auto'); formData.append('video_quality', document.getElementById('i2vQuality')?.value || 'high'); formData.append('enable_safety_checker', document.getElementById('i2vDisableSafety')?.checked ? 'false' : 'true'); } await generateVideo('/api/image-to-video', formData); } async function handleTextToVideoSubmit(e) { e.preventDefault(); // 并发生成允许,不阻塞生成按钮 const prompt = document.getElementById('t2vPrompt')?.value.trim(); if (!prompt) { showNotification('请输入文本描述', 'error'); return; } const model = document.getElementById('t2vModel')?.value || 'seedance-pro-fast'; const apiKey = currentApiKey; if (model === 'seedance-pro-fast') { // Bytedance Seedance 1.0 Pro Fast 入参 const requestData = { prompt: prompt, aspect_ratio: document.getElementById('seedAspectRatio')?.value || '16:9', resolution: document.getElementById('seedResolution')?.value || '1080p', duration: document.getElementById('seedDuration')?.value || '5', camera_fixed: !!document.getElementById('t2vCameraFixed')?.checked, seed: parseInt(document.getElementById('t2vSeed')?.value ?? '-1', 10), enable_safety_checker: !!document.getElementById('t2vEnableSafety')?.checked, model: 'seedance-pro-fast' }; if (apiKey) { requestData.userApiKey = apiKey; } await generateVideo('/api/text-to-video', requestData, 'json'); return; } // WAN v2.2-a14b 入参(保持原逻辑) const requestData = { prompt: prompt, negative_prompt: document.getElementById('t2vNegativePrompt')?.value || '', num_frames: parseInt(document.getElementById('t2vFrames')?.value || '81', 10), frames_per_second: parseInt(document.getElementById('t2vFps')?.value || '16', 10), resolution: document.getElementById('t2vResolution')?.value || '720p', aspect_ratio: document.getElementById('t2vAspectRatio')?.value || '16:9', video_quality: document.getElementById('t2vQuality')?.value || 'high', enable_safety_checker: document.getElementById('t2vDisableSafety')?.checked ? false : true, model: 'wan-v2.2-a14b' }; if (apiKey) { requestData.userApiKey = apiKey; } await generateVideo('/api/text-to-video', requestData, 'json'); } // 视频生成 async function generateVideo(endpoint, data, contentType = 'form') { // 在生成栏中创建队列项并显示进度,允许并发生成 const queueList = document.getElementById('queueList'); const queueCount = document.getElementById('queueCount'); if (!queueList) { // 兼容旧页面:如果没有队列容器,直接执行旧逻辑的通知与结果展示 try { const options = { method: 'POST' }; if (contentType === 'json') { options.headers = { 'Content-Type': 'application/json' }; options.body = JSON.stringify(data); } else { options.body = data; } const response = await fetch(endpoint, options); const result = await response.json(); if (result.success && result.data && result.data.video) { showResult(result.data.video.url, result.data.prompt); showNotification('视频生成成功!', 'success'); } else { throw new Error(result.error || '视频生成失败'); } } catch (error) { console.error('生成错误:', error); showNotification(error.message || '生成失败,请重试', 'error'); } return; } // 若为空队列占位,先移除 if (queueList.classList.contains('empty')) { queueList.classList.remove('empty'); queueList.innerHTML = ''; } const taskId = `task-${Date.now()}-${Math.floor(Math.random() * 1000)}`; const title = endpoint.includes('image') ? '图片转视频' : '文本转视频'; const promptText = (() => { if (typeof data === 'object' && contentType === 'json') { return data.prompt ?? ''; } // FormData 场景:直接从页面读取 const domId = endpoint.includes('image') ? 'i2vPrompt' : 't2vPrompt'; return document.getElementById(domId)?.value?.trim() ?? ''; })(); const item = document.createElement('div'); item.className = 'queue-item'; item.id = taskId; item.innerHTML = `
${title}
${escapeHtml(promptText)}
${new Date().toLocaleString('zh-CN')}
运行中
`; queueList.appendChild(item); // 更新队列计数 if (queueCount) { const count = queueList.querySelectorAll('.queue-item').length; queueCount.textContent = `${count} 任务`; } // 每个任务独立的进度模拟(服务端暂不提供实时进度回传) let progress = 0; const progressEl = () => document.getElementById(`${taskId}-progress`); const statusEl = () => document.getElementById(`${taskId}-status`); const progressInterval = setInterval(() => { progress += Math.random() * 15; if (progress > 90) progress = 90; if (progressEl()) progressEl().style.width = progress + '%'; }, 900); try { const options = { method: 'POST' }; if (contentType === 'json') { options.headers = { 'Content-Type': 'application/json' }; options.body = JSON.stringify(data); } else { options.body = data; } const response = await fetch(endpoint, options); const result = await response.json(); if (result.success && result.data && result.data.video) { clearInterval(progressInterval); if (progressEl()) progressEl().style.width = '100%'; if (statusEl()) { statusEl().className = 'queue-status done'; statusEl().textContent = '完成'; } showResult(result.data.video.url, result.data.prompt); showNotification('视频生成成功!', 'success'); } else { throw new Error(result.error || '视频生成失败'); } } catch (error) { clearInterval(progressInterval); console.error('生成错误:', error); if (statusEl()) { statusEl().className = 'queue-status error'; statusEl().textContent = '错误'; } if (error.message.includes('API') || error.message.includes('密钥')) { showNotification('请先配置 API 密钥', 'error'); setTimeout(() => { const apiKeyModal = document.getElementById('apiKeyModal'); if (apiKeyModal) apiKeyModal.style.display = 'flex'; }, 1000); } else { showNotification(error.message || '生成失败,请重试', 'error'); } } function escapeHtml(str) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return String(str || '').replace(/[&<>"']/g, s => map[s]); } } // 加载状态 function showLoading() { // 不再显示全屏遮罩,也不禁用按钮,避免占用全屏和阻塞生成键 const loadingContainer = document.getElementById('loadingContainer'); if (loadingContainer) loadingContainer.style.display = 'none'; } function hideLoading() { // 保持按钮可用,不做处理 const loadingContainer = document.getElementById('loadingContainer'); if (loadingContainer) loadingContainer.style.display = 'none'; } function simulateProgress() { // 已改为每个任务独立的进度显示,此处不再使用全局模拟 return; } // 结果显示 function showResult(videoUrl, prompt) { const resultVideo = document.getElementById('resultVideo'); const downloadLink = document.getElementById('downloadLink'); const resultContainer = document.getElementById('resultContainer'); if (resultVideo) resultVideo.src = videoUrl; if (downloadLink) { downloadLink.href = videoUrl; downloadLink.download = `generated-video-${Date.now()}.mp4`; } if (resultContainer) { resultContainer.style.display = 'block'; resultContainer.scrollIntoView({ behavior: 'smooth' }); } } function clearResults() { const resultContainer = document.getElementById('resultContainer'); const resultVideo = document.getElementById('resultVideo'); const downloadLink = document.getElementById('downloadLink'); if (resultContainer) resultContainer.style.display = 'none'; if (resultVideo) resultVideo.src = ''; if (downloadLink) downloadLink.href = ''; } // 通知系统 function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `
${message}
`; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${getNotificationColor(type)}; color: white; padding: 1rem 1.5rem; border-radius: 0.5rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 1001; transform: translateX(100%); transition: transform 0.3s ease; max-width: 400px; `; document.body.appendChild(notification); setTimeout(() => { notification.style.transform = 'translateX(0)'; }, 100); setTimeout(() => { notification.style.transform = 'translateX(100%)'; setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 300); }, 4000); } function getNotificationIcon(type) { switch (type) { case 'success': return 'fa-check-circle'; case 'error': return 'fa-exclamation-circle'; case 'warning': return 'fa-exclamation-triangle'; default: return 'fa-info-circle'; } } function getNotificationColor(type) { switch (type) { case 'success': return '#3b82f6'; case 'error': return '#ef4444'; case 'warning': return '#f59e0b'; default: return '#3b82f6'; } } // 视频库功能 async function loadVideoLibrary() { const videoLibraryContent = document.getElementById('videoLibraryContent'); const videoCount = document.getElementById('videoCount'); if (!videoLibraryContent) return; // 显示加载状态 videoLibraryContent.innerHTML = `
正在加载视频库...
`; try { const response = await fetch('/api/videos'); const data = await response.json(); if (data.videos && data.videos.length > 0) { displayVideoLibrary(data.videos); if (videoCount) { videoCount.textContent = `共 ${data.videos.length} 个视频`; } } else { displayEmptyLibrary(); if (videoCount) { videoCount.textContent = '共 0 个视频'; } } } catch (error) { console.error('加载视频库失败:', error); videoLibraryContent.innerHTML = `

加载视频库失败

`; } } function displayVideoLibrary(videos) { const videoLibraryContent = document.getElementById('videoLibraryContent'); const videoGrid = document.createElement('div'); videoGrid.className = 'video-grid'; videos.forEach(video => { const videoItem = createVideoItem(video); videoGrid.appendChild(videoItem); }); videoLibraryContent.innerHTML = ''; videoLibraryContent.appendChild(videoGrid); } function createVideoItem(video) { const item = document.createElement('div'); item.className = 'video-item'; const fileSize = formatFileSize(video.size); const createdDate = new Date(video.created).toLocaleString('zh-CN'); item.innerHTML = `
${video.filename}
${createdDate} ${fileSize}
`; return item; } function displayEmptyLibrary() { const videoLibraryContent = document.getElementById('videoLibraryContent'); videoLibraryContent.innerHTML = `

暂无视频

生成的视频会自动保存到这里

`; } function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } function playVideoInModal(videoPath) { // 在结果容器中播放视频 const resultVideo = document.getElementById('resultVideo'); const resultContainer = document.getElementById('resultContainer'); const downloadLink = document.getElementById('downloadLink'); if (resultVideo && resultContainer && downloadLink) { resultVideo.src = videoPath; downloadLink.href = videoPath; downloadLink.download = videoPath.split('/').pop(); resultContainer.style.display = 'block'; resultContainer.scrollIntoView({ behavior: 'smooth' }); } } function downloadVideo(videoPath, filename) { const link = document.createElement('a'); link.href = videoPath; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); showNotification('开始下载视频', 'success'); } // 修改 showResult 函数,生成视频后自动刷新视频库 function showResult(videoUrl, prompt) { const resultVideo = document.getElementById('resultVideo'); const downloadLink = document.getElementById('downloadLink'); const resultContainer = document.getElementById('resultContainer'); if (resultVideo) resultVideo.src = videoUrl; if (downloadLink) { downloadLink.href = videoUrl; downloadLink.download = `generated-video-${Date.now()}.mp4`; } if (resultContainer) { resultContainer.style.display = 'block'; resultContainer.scrollIntoView({ behavior: 'smooth' }); } // 刷新视频库 setTimeout(() => { loadVideoLibrary(); }, 2000); } // 错误处理 window.addEventListener('error', function(e) { console.error('全局错误:', e.error); if (isGenerating) { hideLoading(); showNotification('发生未知错误,请重试', 'error'); isGenerating = false; } }); window.addEventListener('online', () => { showNotification('网络连接已恢复', 'success'); }); window.addEventListener('offline', () => { showNotification('网络连接已断开', 'warning'); });