export function getH3Base() { if (typeof window !== 'undefined' && window.__H3_BASE__ !== undefined) { return window.__H3_BASE__; } return ''; } export function getToken() { if (typeof window === 'undefined') return ''; return localStorage.getItem('h3_console_token') || ''; } export function setToken(token) { if (typeof window === 'undefined') return; if (!token) { localStorage.removeItem('h3_console_token'); } else { localStorage.setItem('h3_console_token', token.trim()); } } export function formatTimeAgo(ts) { if (!ts) return '—'; const now = Date.now() / 1000; const diff = Math.max(0, Math.round(now - ts)); if (diff < 60) return `${diff}秒前`; if (diff < 3600) return `${Math.round(diff / 60)}分钟前`; if (diff < 86400) return `${Math.round(diff / 3600)}小时前`; return `${Math.round(diff / 86400)}天前`; } export function formatDateTime(ts) { if (!ts) return '—'; return new Date(ts * 1000).toLocaleString('zh-CN', { hour12: false, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', }); } export function formatBytes(bytes) { if (!bytes || bytes <= 0) return '0 B'; const units = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`; } export function filenameFromUrl(u) { if (!u) return ''; try { const base = decodeURIComponent(u.split('/').pop().split('?')[0]); const m = base.match(/^[0-9a-f]{16}__(.+)$/); return m ? m[1] : base; } catch (e) { return u; } } // 上传返回的 URL 是写死内网 IP 的绝对地址(H3_CONSOLE_PUBLIC_BASE,raylight 要拉取,存库的值不能改), // 别的网络打开页面时浏览器连不上。展示时把本机上传的文件({16位hex}__ 前缀)改成当前页面同源路径; // 外部图床 URL 原样返回。绝不改 req 里存的 URL。 export function localAsset(u) { const m = u && u.match(/\/assets\/([0-9a-f]{16}__[^/?#]*)$/); return m ? getH3Base() + '/assets/' + m[1] : u; } export function thumbUrl(u) { if (!u) return u; u = localAsset(u); const i = u.indexOf('/assets/'); if (i === -1) return u; return u.slice(0, i) + '/thumbs/' + u.slice(i + '/assets/'.length); } export function h3FileUrl(path) { if (!path) return ''; const token = getToken(); return token ? `${path}${path.includes('?') ? '&' : '?'}token=${encodeURIComponent(token)}` : path; } export function getJobKind(req = {}) { if (req.first_frame_url || req.last_frame_url) return { key: 'fl2va', label: '首尾帧' }; if ((req.reference_image_urls || []).length || (req.reference_video_urls || []).length || (req.reference_audio_urls || []).length) { return { key: 'ref2va', label: '参考图' }; } return { key: 'text', label: '文生' }; }