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
|
asChild
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
>
|
>
|
||||||
<Link to="/history">
|
<Link to="/history" className="inline-flex items-center gap-2">
|
||||||
<Clock className="h-4 w-4" />
|
<Clock className="h-4 w-4" />
|
||||||
<span className="hidden sm:inline">{formatMessage({ id: 'navigation.main.history' })}</span>
|
<span className="hidden sm:inline">{formatMessage({ id: 'navigation.main.history' })}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -114,6 +114,22 @@
|
|||||||
"on": "On",
|
"on": "On",
|
||||||
"off": "Off"
|
"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": {
|
"about": {
|
||||||
"title": "About",
|
"title": "About",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
|
|||||||
@@ -114,6 +114,22 @@
|
|||||||
"on": "开启",
|
"on": "开启",
|
||||||
"off": "关闭"
|
"off": "关闭"
|
||||||
},
|
},
|
||||||
|
"versionCheck": {
|
||||||
|
"title": "版本更新",
|
||||||
|
"currentVersion": "当前版本",
|
||||||
|
"latestVersion": "最新版本",
|
||||||
|
"checkNow": "立即检查",
|
||||||
|
"checking": "检查中...",
|
||||||
|
"autoCheck": "自动检查更新",
|
||||||
|
"autoCheckDesc": "每小时自动检查是否有新版本",
|
||||||
|
"upToDate": "已是最新版本",
|
||||||
|
"updateAvailable": "有新版本可用",
|
||||||
|
"updateCommand": "更新命令",
|
||||||
|
"viewRelease": "查看更新",
|
||||||
|
"lastChecked": "上次检查",
|
||||||
|
"checkFailed": "检查失败",
|
||||||
|
"never": "从未"
|
||||||
|
},
|
||||||
"about": {
|
"about": {
|
||||||
"title": "关于",
|
"title": "关于",
|
||||||
"version": "版本",
|
"version": "版本",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// ========================================
|
// ========================================
|
||||||
// Application settings and configuration with CLI tools management
|
// 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 { useIntl } from 'react-intl';
|
||||||
import {
|
import {
|
||||||
Settings,
|
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 ==========
|
// ========== System Status Section ==========
|
||||||
|
|
||||||
function SystemStatusSection() {
|
function SystemStatusSection() {
|
||||||
@@ -1118,6 +1293,9 @@ export function SettingsPage() {
|
|||||||
{/* System Status */}
|
{/* System Status */}
|
||||||
<SystemStatusSection />
|
<SystemStatusSection />
|
||||||
|
|
||||||
|
{/* Version Check */}
|
||||||
|
<VersionCheckSection />
|
||||||
|
|
||||||
{/* CLI Tools Configuration */}
|
{/* CLI Tools Configuration */}
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h2 className="text-lg font-semibold text-foreground flex items-center gap-2 mb-4">
|
<h2 className="text-lg font-semibold text-foreground flex items-center gap-2 mb-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user