'use client'; import { useState, useEffect } from 'react'; import { useI18n } from '../lib/useI18n'; export default function UpdateBanner() { const { t } = useI18n(); const [updateInfo, setUpdateInfo] = useState(null); const [dismissed, setDismissed] = useState(false); const [updating, setUpdating] = useState(false); const [updateResult, setUpdateResult] = useState(null); // { success, message, logs } const [downloadProgress, setDownloadProgress] = useState(null); // { progress, downloaded, total } const [sourceProgress, setSourceProgress] = useState(null); // { step, total, label, status } const isElectron = typeof window !== 'undefined' && window.electronAPI?.isElectron; useEffect(() => { const checkUpdate = async () => { try { const res = await fetch('/api/check-update', { cache: 'no-store' }); if (!res.ok) return; const data = await res.json(); if (data.hasUpdate && data.latest) { const dismissedVersion = sessionStorage.getItem('author-update-dismissed'); if (dismissedVersion === data.latest) return; setUpdateInfo(data); } } catch { // 网络失败静默跳过 } }; const timer = setTimeout(checkUpdate, 3000); return () => clearTimeout(timer); }, []); // 监听 Electron 下载进度 useEffect(() => { if (isElectron && window.electronAPI?.onUpdateProgress) { window.electronAPI.onUpdateProgress((data) => { setDownloadProgress(data); }); } }, [isElectron]); const handleDismiss = () => { setDismissed(true); if (updateInfo?.latest) { sessionStorage.setItem('author-update-dismissed', updateInfo.latest); } }; // Electron 客户端:自动下载安装 const handleElectronUpdate = async () => { setUpdating(true); setUpdateResult(null); setDownloadProgress({ progress: 0, downloaded: 0, total: 0 }); try { const result = await window.electronAPI.downloadAndInstallUpdate(); if (!result.success) { setUpdateResult({ success: false, message: t('update.updateFailed') + ': ' + (result.error || '') }); setDownloadProgress(null); } // 成功时 app 会自动退出,不需要处理 } catch (err) { setUpdateResult({ success: false, message: t('update.updateFailed') + ': ' + err.message }); setDownloadProgress(null); } finally { setUpdating(false); } }; // 源码部署:SSE 流式更新 const handleSourceUpdate = async () => { setUpdating(true); setUpdateResult(null); setSourceProgress(null); try { const res = await fetch('/api/update-source-stream', { method: 'POST' }); const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n\n'); buffer = lines.pop() || ''; for (const block of lines) { const dataLine = block.split('\n').find(l => l.startsWith('data: ')); if (!dataLine) continue; const data = JSON.parse(dataLine.slice(6)); if (data.done) { if (data.success) { if (data.alreadyUpToDate) { setUpdateResult({ success: true, message: t('update.alreadyLatest') }); } else { setUpdateResult({ success: true, message: t('update.updateSuccess') }); } } else { setUpdateResult({ success: false, message: t('update.updateFailed') + ': ' + (data.error || '') }); } setSourceProgress(null); } else { setSourceProgress(data); } } } } catch (err) { setUpdateResult({ success: false, message: t('update.updateFailed') + ': ' + err.message }); setSourceProgress(null); } finally { setUpdating(false); } }; const handleUpdate = () => { if (isElectron) { handleElectronUpdate(); } else if (updateInfo?.isSourceDeploy) { handleSourceUpdate(); } }; if (!updateInfo || dismissed) return null; const versionText = t('update.newVersion').replace('{version}', `v${updateInfo.latest}`); const canAutoUpdate = isElectron || updateInfo.isSourceDeploy; return (
🔔 {versionText} {/* 一键更新(Electron 或 源码部署) */} {canAutoUpdate && !updateResult && ( )} {/* 下载进度条(Electron) */} {updating && downloadProgress && downloadProgress.total > 0 && (
)} {/* 源码更新进度条 */} {updating && sourceProgress && (
)} {/* 更新结果提示 */} {updateResult && ( {updateResult.message} {updateResult.success && !updateResult.message.includes(t('update.alreadyLatest')) && ( )} )} {/* 不支持自动更新时:显示下载链接 */} {!canAutoUpdate && ( <> {t('update.viewSource')} {t('update.downloadClient')} )}
{/* 更新日志(源码部署) */} {updateResult?.logs && updateResult.logs.length > 0 && (
{updateResult.logs.map((l, i) => (
{l.msg}
))}
)}
); }