mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat: 添加版本检查功能及 Header 布局修复
- SettingsPage 新增 VersionCheckSection(自动/手动检查更新) - 添加版本检查相关中英文 i18n 翻译 - 修复 Header 历史链接的 flex 布局对齐
This commit is contained in:
@@ -84,7 +84,7 @@ export function Header({
|
||||
asChild
|
||||
className="gap-2"
|
||||
>
|
||||
<Link to="/history">
|
||||
<Link to="/history" className="inline-flex items-center gap-2">
|
||||
<Clock className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">{formatMessage({ id: 'navigation.main.history' })}</span>
|
||||
</Link>
|
||||
|
||||
@@ -114,6 +114,22 @@
|
||||
"on": "On",
|
||||
"off": "Off"
|
||||
},
|
||||
"versionCheck": {
|
||||
"title": "Version Update",
|
||||
"currentVersion": "Current Version",
|
||||
"latestVersion": "Latest Version",
|
||||
"checkNow": "Check Now",
|
||||
"checking": "Checking...",
|
||||
"autoCheck": "Auto-check for updates",
|
||||
"autoCheckDesc": "Automatically check for new versions every hour",
|
||||
"upToDate": "Up to date",
|
||||
"updateAvailable": "Update available",
|
||||
"updateCommand": "Update command",
|
||||
"viewRelease": "View Release",
|
||||
"lastChecked": "Last checked",
|
||||
"checkFailed": "Check failed",
|
||||
"never": "Never"
|
||||
},
|
||||
"about": {
|
||||
"title": "About",
|
||||
"version": "Version",
|
||||
|
||||
@@ -114,6 +114,22 @@
|
||||
"on": "开启",
|
||||
"off": "关闭"
|
||||
},
|
||||
"versionCheck": {
|
||||
"title": "版本更新",
|
||||
"currentVersion": "当前版本",
|
||||
"latestVersion": "最新版本",
|
||||
"checkNow": "立即检查",
|
||||
"checking": "检查中...",
|
||||
"autoCheck": "自动检查更新",
|
||||
"autoCheckDesc": "每小时自动检查是否有新版本",
|
||||
"upToDate": "已是最新版本",
|
||||
"updateAvailable": "有新版本可用",
|
||||
"updateCommand": "更新命令",
|
||||
"viewRelease": "查看更新",
|
||||
"lastChecked": "上次检查",
|
||||
"checkFailed": "检查失败",
|
||||
"never": "从未"
|
||||
},
|
||||
"about": {
|
||||
"title": "关于",
|
||||
"version": "版本",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// ========================================
|
||||
// Application settings and configuration with CLI tools management
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Settings,
|
||||
@@ -727,6 +727,181 @@ function ResponseLanguageSection() {
|
||||
);
|
||||
}
|
||||
|
||||
// ========== Version Check Section ==========
|
||||
|
||||
interface VersionData {
|
||||
currentVersion: string;
|
||||
latestVersion: string;
|
||||
hasUpdate: boolean;
|
||||
packageName: string;
|
||||
updateCommand: string;
|
||||
checkedAt: string;
|
||||
}
|
||||
|
||||
function VersionCheckSection() {
|
||||
const { formatMessage } = useIntl();
|
||||
const [versionData, setVersionData] = useState<VersionData | null>(null);
|
||||
const [checking, setChecking] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [lastChecked, setLastChecked] = useState<Date | null>(null);
|
||||
const [autoCheck, setAutoCheck] = useState(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem('ccw.autoUpdate');
|
||||
return saved === null ? true : JSON.parse(saved);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
const checkVersion = async (silent = false) => {
|
||||
if (!silent) setChecking(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch('/api/version-check');
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
|
||||
const data: VersionData = await response.json();
|
||||
if (!data.currentVersion) throw new Error('Invalid response');
|
||||
|
||||
setVersionData(data);
|
||||
setLastChecked(new Date());
|
||||
} catch (err) {
|
||||
if (!silent) setError(err instanceof Error ? err.message : 'Unknown error');
|
||||
} finally {
|
||||
setChecking(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Initial check
|
||||
checkVersion(true);
|
||||
|
||||
if (!autoCheck) return;
|
||||
const interval = setInterval(() => checkVersion(true), 60 * 60 * 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [autoCheck]);
|
||||
|
||||
const toggleAutoCheck = (enabled: boolean) => {
|
||||
setAutoCheck(enabled);
|
||||
localStorage.setItem('ccw.autoUpdate', JSON.stringify(enabled));
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-foreground flex items-center gap-2">
|
||||
<ArrowUpCircle className="w-5 h-5" />
|
||||
{formatMessage({ id: 'settings.versionCheck.title' })}
|
||||
</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={checking}
|
||||
onClick={() => checkVersion()}
|
||||
>
|
||||
<RefreshCw className={cn('w-3.5 h-3.5 mr-1.5', checking && 'animate-spin')} />
|
||||
{checking
|
||||
? formatMessage({ id: 'settings.versionCheck.checking' })
|
||||
: formatMessage({ id: 'settings.versionCheck.checkNow' })}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Version info */}
|
||||
<div className="rounded-lg border border-border p-4 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{formatMessage({ id: 'settings.versionCheck.currentVersion' })}
|
||||
</span>
|
||||
<Badge variant="secondary" className="font-mono text-xs">
|
||||
{versionData?.currentVersion ?? '...'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{formatMessage({ id: 'settings.versionCheck.latestVersion' })}
|
||||
</span>
|
||||
<Badge
|
||||
variant={versionData?.updateAvailable ? 'default' : 'secondary'}
|
||||
className="font-mono text-xs"
|
||||
>
|
||||
{versionData?.latestVersion ?? '...'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
{versionData && (
|
||||
<div className="flex items-center justify-between pt-2 border-t border-border">
|
||||
<span className="text-sm font-medium">
|
||||
{versionData.hasUpdate
|
||||
? formatMessage({ id: 'settings.versionCheck.updateAvailable' })
|
||||
: formatMessage({ id: 'settings.versionCheck.upToDate' })}
|
||||
</span>
|
||||
<span className={cn(
|
||||
'inline-block w-2.5 h-2.5 rounded-full',
|
||||
versionData.hasUpdate ? 'bg-orange-500' : 'bg-green-500'
|
||||
)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-border">
|
||||
<AlertTriangle className="w-4 h-4 text-destructive flex-shrink-0" />
|
||||
<span className="text-sm text-destructive">
|
||||
{formatMessage({ id: 'settings.versionCheck.checkFailed' })}: {error}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Update action */}
|
||||
{versionData?.hasUpdate && (
|
||||
<div className="rounded-lg border border-orange-500/30 bg-orange-500/5 p-4 space-y-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground mb-1">
|
||||
{formatMessage({ id: 'settings.versionCheck.updateCommand' })}
|
||||
</p>
|
||||
<code className="text-xs font-mono bg-muted px-3 py-1.5 rounded block">
|
||||
{versionData.updateCommand}
|
||||
</code>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a
|
||||
href="https://github.com/dyw0830/ccw/releases"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1.5"
|
||||
>
|
||||
{formatMessage({ id: 'settings.versionCheck.viewRelease' })}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Auto check toggle + last checked */}
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={autoCheck}
|
||||
onChange={(e) => toggleAutoCheck(e.target.checked)}
|
||||
className="rounded border-input"
|
||||
/>
|
||||
<div>
|
||||
<span className="text-sm font-medium">{formatMessage({ id: 'settings.versionCheck.autoCheck' })}</span>
|
||||
<p className="text-xs text-muted-foreground">{formatMessage({ id: 'settings.versionCheck.autoCheckDesc' })}</p>
|
||||
</div>
|
||||
</label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{formatMessage({ id: 'settings.versionCheck.lastChecked' })}:{' '}
|
||||
{lastChecked ? lastChecked.toLocaleTimeString() : formatMessage({ id: 'settings.versionCheck.never' })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ========== System Status Section ==========
|
||||
|
||||
function SystemStatusSection() {
|
||||
@@ -1118,6 +1293,9 @@ export function SettingsPage() {
|
||||
{/* System Status */}
|
||||
<SystemStatusSection />
|
||||
|
||||
{/* Version Check */}
|
||||
<VersionCheckSection />
|
||||
|
||||
{/* CLI Tools Configuration */}
|
||||
<Card className="p-6">
|
||||
<h2 className="text-lg font-semibold text-foreground flex items-center gap-2 mb-4">
|
||||
|
||||
Reference in New Issue
Block a user