diff --git a/app/api/v1/public_api/video.py b/app/api/v1/public_api/video.py index 2fc3d05..0a3976a 100644 --- a/app/api/v1/public_api/video.py +++ b/app/api/v1/public_api/video.py @@ -14,6 +14,7 @@ from app.core.logger import logger from app.services.grok.services.video import VideoService from app.services.grok.services.model import ModelService from app.api.v1.public_api import imagine as imagine_public_api +from app.services.grok.utils.cache import CacheService router = APIRouter() @@ -389,4 +390,13 @@ async def public_video_stop(data: VideoStopRequest): return {"status": "success", "removed": removed} +@router.get("/video/cache/list", dependencies=[Depends(verify_public_key)]) +async def public_video_cache_list(page: int = 1, page_size: int = 100): + page = max(1, int(page or 1)) + page_size = max(1, min(200, int(page_size or 100))) + cache_service = CacheService() + result = cache_service.list_files("video", page=page, page_size=page_size) + return {"status": "success", **result} + + __all__ = ["router"] diff --git a/app/static/public/css/video.css b/app/static/public/css/video.css index a8c252c..12c8e13 100644 --- a/app/static/public/css/video.css +++ b/app/static/public/css/video.css @@ -579,6 +579,44 @@ body { outline-offset: -1px; } +.cache-video-list { + max-height: 360px; + overflow: auto; + border: 1px solid var(--video-outline); + border-radius: 10px; + background: #fafafa; +} + +.cache-video-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 10px 12px; + border-bottom: 1px solid var(--video-outline); +} + +.cache-video-item:last-child { + border-bottom: none; +} + +.cache-video-meta { + min-width: 0; +} + +.cache-video-name { + font-size: 12px; + color: var(--accents-7); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.cache-video-sub { + font-size: 11px; + color: var(--accents-4); +} + @media (max-width: 900px) { .video-actions, .preview-actions, diff --git a/app/static/public/js/video.js b/app/static/public/js/video.js index cd60732..670c0f8 100644 --- a/app/static/public/js/video.js +++ b/app/static/public/js/video.js @@ -2,6 +2,10 @@ const startBtn = document.getElementById('startBtn'); const stopBtn = document.getElementById('stopBtn'); const clearBtn = document.getElementById('clearBtn'); + const pickCachedVideoBtn = document.getElementById('pickCachedVideoBtn'); + const cacheVideoModal = document.getElementById('cacheVideoModal'); + const closeCacheVideoModalBtn = document.getElementById('closeCacheVideoModalBtn'); + const cacheVideoList = document.getElementById('cacheVideoList'); const enterEditBtn = document.getElementById('enterEditBtn'); const editPanel = document.getElementById('editPanel'); const editHint = document.getElementById('editHint'); @@ -904,8 +908,10 @@ function openEditPanel() { if (!editPanel || !editHint || !editBody) return; const item = getSelectedVideoItem(); - const url = item ? String(item.dataset.url || '').trim() : ''; - if (!item || !url) { + const url = item + ? String(item.dataset.url || '').trim() + : String(selectedVideoUrl || '').trim(); + if (!url) { editPanel.classList.remove('hidden'); editHint.classList.remove('hidden'); editBody.classList.add('hidden'); @@ -925,6 +931,89 @@ editPanel.classList.add('hidden'); } + function openCacheVideoModal() { + if (!cacheVideoModal) return; + cacheVideoModal.classList.remove('hidden'); + cacheVideoModal.classList.add('is-open'); + } + + function closeCacheVideoModal() { + if (!cacheVideoModal) return; + cacheVideoModal.classList.remove('is-open'); + cacheVideoModal.classList.add('hidden'); + } + + function formatBytes(bytes) { + const n = Number(bytes || 0); + if (n <= 0) return '0 B'; + const units = ['B', 'KB', 'MB', 'GB']; + const idx = Math.min(units.length - 1, Math.floor(Math.log(n) / Math.log(1024))); + const val = n / Math.pow(1024, idx); + return `${val.toFixed(idx === 0 ? 0 : 1)} ${units[idx]}`; + } + + function formatMtime(ms) { + const d = new Date(Number(ms || 0)); + if (!Number.isFinite(d.getTime())) return '-'; + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + const hh = String(d.getHours()).padStart(2, '0'); + const mm = String(d.getMinutes()).padStart(2, '0'); + return `${y}-${m}-${day} ${hh}:${mm}`; + } + + async function loadCachedVideos() { + const authHeader = await ensurePublicKey(); + if (authHeader === null) { + toast('请先配置 Public Key', 'error'); + window.location.href = '/login'; + return []; + } + const res = await fetch('/v1/public/video/cache/list?page=1&page_size=100', { + headers: buildAuthHeaders(authHeader), + }); + if (!res.ok) { + throw new Error(`load_cache_failed_${res.status}`); + } + const data = await res.json(); + return Array.isArray(data.items) ? data.items : []; + } + + function renderCachedVideoList(items) { + if (!cacheVideoList) return; + if (!items.length) { + cacheVideoList.innerHTML = '