feat(ui): 优化 Header 和状态栏布局

- ContextUsage: 紧凑模式下始终显示压缩按钮
- StatusBar: 连接状态移至中间位置,显示绿点动画和文字
- StatusBar: 添加内部连接状态检测(通过 health API)
- Chat: 移除 Header 中的连接状态指示器
This commit is contained in:
2025-12-17 17:40:27 +08:00
parent 619cd2d2dd
commit babe65719b
3 changed files with 81 additions and 48 deletions
+21 -3
View File
@@ -152,9 +152,27 @@ export function ContextUsage({
</div> </div>
{/* 数值 */} {/* 数值 */}
<span className={cn('text-xs', textColor)}>{formatted}</span> <span className={cn('text-xs', textColor)}>{formatted}</span>
{/* 警告图标 */} {/* 压缩按钮 */}
{shouldCompress && ( {showCompressButton && (
<AlertTriangle size={12} className="text-amber-500" /> <Button
variant="ghost"
size="sm"
className={cn(
'h-5 px-1.5 text-xs',
shouldCompress
? 'text-amber-500 hover:text-amber-400 hover:bg-amber-500/10'
: 'text-fg-muted hover:text-fg-secondary hover:bg-surface-muted'
)}
onClick={handleCompress}
disabled={compressing}
title="压缩对话上下文"
>
{compressing ? (
<RefreshCw size={12} className="animate-spin" />
) : (
<Zap size={12} />
)}
</Button>
)} )}
</div> </div>
); );
+58 -16
View File
@@ -5,9 +5,9 @@
*/ */
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { GitBranch, AlertTriangle, AlertCircle, Wifi, WifiOff, RefreshCw, CheckCircle } from 'lucide-react'; import { GitBranch, AlertTriangle, AlertCircle, WifiOff, RefreshCw, CheckCircle } from 'lucide-react';
import { cn } from '../utils/cn.js'; import { cn } from '../utils/cn.js';
import { getLSPDiagnostics, getGitInfo, type DiagnosticsSummary, type GitInfo } from '../api/client.js'; import { getLSPDiagnostics, getGitInfo, getHealth, type DiagnosticsSummary, type GitInfo } from '../api/client.js';
interface StatusBarProps { interface StatusBarProps {
className?: string; className?: string;
@@ -21,13 +21,17 @@ interface StatusBarProps {
export function StatusBar({ export function StatusBar({
className, className,
isConnected = true, isConnected: isConnectedProp,
onDiagnosticsClick, onDiagnosticsClick,
refreshInterval = 30000, refreshInterval = 30000,
}: StatusBarProps) { }: StatusBarProps) {
const [diagnostics, setDiagnostics] = useState<DiagnosticsSummary | null>(null); const [diagnostics, setDiagnostics] = useState<DiagnosticsSummary | null>(null);
const [gitInfo, setGitInfo] = useState<GitInfo | null>(null); const [gitInfo, setGitInfo] = useState<GitInfo | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [connectionStatus, setConnectionStatus] = useState(true);
// 如果外部传入了 isConnected,使用外部值;否则使用内部检测
const isConnected = isConnectedProp ?? connectionStatus;
// 加载诊断信息和 Git 信息 // 加载诊断信息和 Git 信息
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
@@ -45,12 +49,37 @@ export function StatusBar({
if (gitResult.success && gitResult.data) { if (gitResult.success && gitResult.data) {
setGitInfo(gitResult.data); setGitInfo(gitResult.data);
} }
// 如果没有外部传入连接状态,内部检测
if (isConnectedProp === undefined) {
setConnectionStatus(true);
}
} catch { } catch {
// 忽略错误 // 请求失败说明可能断开连接
if (isConnectedProp === undefined) {
setConnectionStatus(false);
}
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, [isConnectedProp]);
// 检测连接状态(如果没有外部传入)
const checkConnection = useCallback(async () => {
if (isConnectedProp !== undefined) return;
try {
await getHealth();
setConnectionStatus(true);
} catch {
setConnectionStatus(false);
}
}, [isConnectedProp]);
// 初始连接检测
useEffect(() => {
checkConnection();
}, [checkConnection]);
// 初始加载和定时刷新 // 初始加载和定时刷新
useEffect(() => { useEffect(() => {
@@ -94,6 +123,30 @@ export function StatusBar({
</button> </button>
</div> </div>
{/* 中间 - 连接状态 */}
<div
className={cn(
'flex items-center gap-1.5',
isConnected ? 'text-green-400' : 'text-red-400'
)}
title={isConnected ? 'Connected to server' : 'Disconnected from server'}
>
{isConnected ? (
<>
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500" />
</span>
<span>Connected</span>
</>
) : (
<>
<WifiOff size={12} />
<span>Disconnected</span>
</>
)}
</div>
{/* 右侧 */} {/* 右侧 */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* 诊断信息 */} {/* 诊断信息 */}
@@ -123,17 +176,6 @@ export function StatusBar({
</span> </span>
)} )}
</button> </button>
{/* 连接状态 */}
<div
className={cn(
'flex items-center gap-1',
isConnected ? 'text-green-400' : 'text-red-400'
)}
title={isConnected ? 'Connected to server' : 'Disconnected from server'}
>
{isConnected ? <Wifi size={12} /> : <WifiOff size={12} />}
</div>
</div> </div>
</div> </div>
); );
+2 -29
View File
@@ -3,7 +3,7 @@
*/ */
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { WifiOff, MessageSquare, Terminal, Plug, Zap, Bot, History, Server, MessagesSquare } from 'lucide-react'; import { MessageSquare, Terminal, Plug, Zap, Bot, History, Server, MessagesSquare } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { import {
@@ -129,30 +129,6 @@ export function ChatPage({
</motion.div> </motion.div>
); );
// 连接状态指示器
const ConnectionStatus = () => (
<div className="flex items-center gap-1.5 text-sm">
{isConnected ? (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="flex items-center gap-1.5"
>
<span className="relative flex h-2.5 w-2.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-green-500" />
</span>
<span className="text-green-400 hidden sm:inline">Connected</span>
</motion.div>
) : (
<div className="flex items-center gap-1.5 text-red-400">
<WifiOff size={16} />
<span className="hidden sm:inline">Disconnected</span>
</div>
)}
</div>
);
return ( return (
<div className="flex-1 flex flex-col h-full"> <div className="flex-1 flex flex-col h-full">
{/* Header */} {/* Header */}
@@ -164,14 +140,11 @@ export function ChatPage({
<ContextUsage <ContextUsage
sessionId={sessionId} sessionId={sessionId}
compact compact
showCompressButton={false} showCompressButton
refreshInterval={30000} refreshInterval={30000}
/> />
)} )}
{/* 连接状态 */}
<ConnectionStatus />
{/* 工具栏按钮 */} {/* 工具栏按钮 */}
{(onOpenCommands || onOpenMCP || onOpenHooks || onOpenAgents || onOpenCheckpoints || onOpenProviders || onOpenLSP || onOpenDiagnostics || onOpenSessions) && ( {(onOpenCommands || onOpenMCP || onOpenHooks || onOpenAgents || onOpenCheckpoints || onOpenProviders || onOpenLSP || onOpenDiagnostics || onOpenSessions) && (
<div className="flex items-center gap-1.5 border-l border-line-muted pl-3"> <div className="flex items-center gap-1.5 border-l border-line-muted pl-3">