feat: 支持 GitHub 主源与自定义策略更新

This commit is contained in:
ILoveBingLu
2026-04-01 23:41:45 +08:00
parent 87605b404a
commit a77e88907b
10 changed files with 614 additions and 64 deletions
+114
View File
@@ -88,6 +88,120 @@
}
}
.force-update-overlay {
position: fixed;
inset: 0;
z-index: 5000;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(6, 10, 16, 0.82);
backdrop-filter: blur(14px);
}
.force-update-card {
width: min(720px, 100%);
max-height: 85vh;
overflow: auto;
padding: 28px;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: var(--bg-secondary);
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
h2 {
margin: 16px 0 10px;
font-size: 28px;
font-weight: 700;
color: var(--text-primary);
}
.force-update-desc {
margin: 0 0 18px;
color: var(--text-secondary);
line-height: 1.7;
}
.force-update-meta {
display: grid;
gap: 8px;
margin-bottom: 18px;
padding: 14px 16px;
border-radius: 14px;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 14px;
}
.force-update-notes {
margin-bottom: 18px;
padding: 14px 16px;
border-radius: 14px;
background: var(--bg-primary);
.force-update-notes-title {
margin-bottom: 10px;
font-weight: 600;
color: var(--text-primary);
}
pre {
margin: 0;
white-space: pre-wrap;
word-break: break-word;
color: var(--text-secondary);
font-family: inherit;
line-height: 1.6;
}
}
.force-update-progress {
margin-bottom: 18px;
}
.force-update-progress-label {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
color: var(--text-primary);
font-size: 14px;
font-weight: 600;
}
.force-update-progress-bar {
height: 10px;
border-radius: 999px;
background: var(--bg-primary);
overflow: hidden;
}
.force-update-progress-fill {
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #ff7849 0%, #ff4747 100%);
}
.force-update-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
}
}
.force-update-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 92, 92, 0.12);
color: #ff5c5c;
font-size: 13px;
font-weight: 700;
}
// 独立聊天窗口容器
.chat-window-container {
height: 100vh;
+79 -2
View File
@@ -38,6 +38,21 @@ import { useAuthStore } from './stores/authStore'
import { X, Shield, Loader2 } from 'lucide-react'
import './App.scss'
type AppUpdateInfo = {
hasUpdate: boolean
forceUpdate: boolean
currentVersion: string
version?: string
releaseNotes?: string
title?: string
message?: string
minimumSupportedVersion?: string
reason?: 'minimum-version' | 'blocked-version'
checkedAt: number
updateSource: 'github' | 'custom' | 'none'
policySource: 'github' | 'custom' | 'none'
}
function App() {
const navigate = useNavigate()
const location = useLocation()
@@ -55,7 +70,7 @@ function App() {
const [showActivation, setShowActivation] = useState(false)
// 更新提示状态
const [updateInfo, setUpdateInfo] = useState<{ version: string; releaseNotes: string } | null>(null)
const [updateInfo, setUpdateInfo] = useState<AppUpdateInfo | null>(null)
const [downloadProgress, setDownloadProgress] = useState<number | null>(null)
// 加载主题配置
@@ -136,6 +151,15 @@ function App() {
// 监听启动时的更新通知
useEffect(() => {
let mounted = true
window.electronAPI.app.getUpdateState?.().then((info) => {
if (mounted && info?.hasUpdate) {
setUpdateInfo(info)
}
}).catch((error) => {
console.error('获取更新状态失败:', error)
})
const removeUpdateListener = window.electronAPI.app.onUpdateAvailable?.((info) => {
setUpdateInfo(info)
})
@@ -162,6 +186,7 @@ function App() {
})
return () => {
mounted = false
removeUpdateListener?.()
removeSessionsListener?.()
removeUpdateAvailableListener?.()
@@ -179,6 +204,7 @@ function App() {
}, [])
const dismissUpdate = () => {
if (updateInfo?.forceUpdate) return
setUpdateInfo(null)
}
@@ -466,12 +492,13 @@ function App() {
return (
<div className="app-container">
<TitleBar />
{updateInfo && (
{updateInfo && !updateInfo.forceUpdate && (
<div className="update-toast">
<div className="update-toast-icon">🎉</div>
<div className="update-toast-content">
<div className="update-toast-title"></div>
<div className="update-toast-version">v{updateInfo.version} </div>
<div className="update-toast-version">{updateInfo.updateSource === 'github' ? 'GitHub Release' : '未知'}</div>
</div>
<button className="update-toast-btn" onClick={() => {
window.electronAPI.app.downloadAndInstall()
@@ -484,6 +511,56 @@ function App() {
</button>
</div>
)}
{updateInfo?.forceUpdate && (
<div className="force-update-overlay">
<div className="force-update-card">
<div className="force-update-badge">
<Shield size={18} />
<span></span>
</div>
<h2>{updateInfo.title || '必须更新后才能继续使用'}</h2>
<p className="force-update-desc">
{updateInfo.message || '当前版本已被标记为需要立即升级,应用将限制继续使用,直到安装最新版本。'}
</p>
<div className="force-update-meta">
<div>v{updateInfo.currentVersion}</div>
{updateInfo.version && <div>v{updateInfo.version}</div>}
{updateInfo.minimumSupportedVersion && <div>v{updateInfo.minimumSupportedVersion}</div>}
<div>{updateInfo.updateSource === 'github' ? 'GitHub Release' : '未检测到普通更新源'}</div>
<div>{updateInfo.policySource === 'github' ? 'GitHub 策略源' : updateInfo.policySource === 'custom' ? '自定义策略源' : '无'}</div>
</div>
{updateInfo.releaseNotes && (
<div className="force-update-notes">
<div className="force-update-notes-title"></div>
<pre>{updateInfo.releaseNotes}</pre>
</div>
)}
{downloadProgress !== null && (
<div className="force-update-progress">
<div className="force-update-progress-label">
<Loader2 size={16} className="spin" />
<span>... {downloadProgress.toFixed(0)}%</span>
</div>
<div className="force-update-progress-bar">
<div className="force-update-progress-fill" style={{ width: `${downloadProgress}%` }} />
</div>
</div>
)}
<div className="force-update-actions">
<button className="btn btn-primary" onClick={() => window.electronAPI.app.downloadAndInstall()}>
</button>
<button className="btn btn-secondary" onClick={() => window.electronAPI.window.close()}>
退
</button>
</div>
</div>
</div>
)}
<Box
sx={{
+49 -3
View File
@@ -98,7 +98,30 @@ function SettingsPage() {
const [isDownloading, setIsDownloading] = useState(false)
const [downloadProgress, setDownloadProgress] = useState(0)
const [appVersion, setAppVersion] = useState('')
const [updateInfo, setUpdateInfo] = useState<{ hasUpdate: boolean; version?: string; releaseNotes?: string } | null>(null)
const [updateInfo, setUpdateInfo] = useState<{
hasUpdate: boolean
forceUpdate: boolean
currentVersion: string
version?: string
releaseNotes?: string
title?: string
message?: string
minimumSupportedVersion?: string
reason?: 'minimum-version' | 'blocked-version'
checkedAt: number
updateSource: 'github' | 'custom' | 'none'
policySource: 'github' | 'custom' | 'none'
} | null>(null)
const [updateSourceInfo, setUpdateSourceInfo] = useState<{
primaryUpdateSource: 'github'
githubRepository: {
owner: string
repo: string
}
policySources: Array<'github' | 'custom'>
policyPrecedence: 'github'
forceUpdatePolicyFallbackUrl: string
} | null>(null)
const [keyStatus, setKeyStatus] = useState('')
const [message, setMessage] = useState<{ text: string; success: boolean } | null>(null)
const [showDecryptKey, setShowDecryptKey] = useState(false)
@@ -466,7 +489,7 @@ function SettingsPage() {
const result = await window.electronAPI.app.checkForUpdates()
if (result.hasUpdate) {
setUpdateInfo(result)
showMessage(`发现新版本 ${result.version}`, true)
showMessage(result.forceUpdate ? `检测到强制更新 ${result.version}` : `发现新版本 ${result.version}`, true)
} else {
showMessage('当前已是最新版本', true)
}
@@ -2646,6 +2669,14 @@ function SettingsPage() {
}
}, [location.state])
useEffect(() => {
window.electronAPI.app.getUpdateSourceInfo?.().then((info) => {
setUpdateSourceInfo(info)
}).catch((error) => {
console.error('获取更新源信息失败:', error)
})
}, [])
const renderAboutTab = () => (
<div className="tab-content about-tab">
<div className="about-card">
@@ -2657,9 +2688,24 @@ function SettingsPage() {
<p className="about-version">v{appVersion || '...'}</p>
<div className="about-update">
{updateSourceInfo && (
<div className="update-hint" style={{ marginBottom: '10px' }}>
GitHub Release ({updateSourceInfo.githubRepository.owner}/{updateSourceInfo.githubRepository.repo})<br />
{updateSourceInfo.forceUpdatePolicyFallbackUrl}
</div>
)}
{updateInfo?.hasUpdate ? (
<>
<p className="update-hint"> v{updateInfo.version} </p>
<p className="update-hint">
{updateInfo.forceUpdate ? '检测到强制更新' : `新版本 v${updateInfo.version} 可用`}
</p>
<p className="update-hint">
{updateInfo.updateSource === 'github' ? 'GitHub Release' : '未知'} /
{updateInfo.policySource === 'github' ? 'GitHub' : updateInfo.policySource === 'custom' ? '自定义源' : '无'}
</p>
{updateInfo.forceUpdate && updateInfo.minimumSupportedVersion && (
<p className="update-hint">v{updateInfo.minimumSupportedVersion}</p>
)}
{isDownloading ? (
<div className="download-progress">
<div className="progress-bar">
+52 -2
View File
@@ -81,12 +81,62 @@ export interface ElectronAPI {
cwd: string
mode: 'dev' | 'packaged'
} | null>
checkForUpdates: () => Promise<{ hasUpdate: boolean; version?: string; releaseNotes?: string }>
getUpdateState: () => Promise<{
hasUpdate: boolean
forceUpdate: boolean
currentVersion: string
version?: string
releaseNotes?: string
title?: string
message?: string
minimumSupportedVersion?: string
reason?: 'minimum-version' | 'blocked-version'
checkedAt: number
updateSource: 'github' | 'custom' | 'none'
policySource: 'github' | 'custom' | 'none'
} | null>
getUpdateSourceInfo: () => Promise<{
primaryUpdateSource: 'github'
githubRepository: {
owner: string
repo: string
}
policySources: Array<'github' | 'custom'>
policyPrecedence: 'github'
forceUpdatePolicyFallbackUrl: string
}>
checkForUpdates: () => Promise<{
hasUpdate: boolean
forceUpdate: boolean
currentVersion: string
version?: string
releaseNotes?: string
title?: string
message?: string
minimumSupportedVersion?: string
reason?: 'minimum-version' | 'blocked-version'
checkedAt: number
updateSource: 'github' | 'custom' | 'none'
policySource: 'github' | 'custom' | 'none'
}>
downloadAndInstall: () => Promise<void>
getStartupDbConnected?: () => Promise<boolean>
setAppIcon: (iconName: string) => Promise<void>
onDownloadProgress: (callback: (progress: number) => void) => () => void
onUpdateAvailable: (callback: (info: { version: string; releaseNotes: string }) => void) => () => void
onUpdateAvailable: (callback: (info: {
hasUpdate: boolean
forceUpdate: boolean
currentVersion: string
version?: string
releaseNotes?: string
title?: string
message?: string
minimumSupportedVersion?: string
reason?: 'minimum-version' | 'blocked-version'
checkedAt: number
updateSource: 'github' | 'custom' | 'none'
policySource: 'github' | 'custom' | 'none'
}) => void) => () => void
}
httpApi: {
getStatus: () => Promise<{