From a3ddc397710247513a28b9156a81430929296c75 Mon Sep 17 00:00:00 2001 From: kurihada Date: Wed, 17 Dec 2025 17:04:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=B7=BB=E5=8A=A0=E5=BA=95?= =?UTF-8?q?=E9=83=A8=E7=8A=B6=E6=80=81=E6=A0=8F=E5=92=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BC=96=E8=BE=91=E5=99=A8=E7=A9=BA=E7=8A=B6?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 StatusBar 组件,显示 Git 分支、诊断信息和连接状态 - 添加 Git API 端点 (GET /api/files/git) 获取分支和 dirty 状态 - 优化 CodeEditor 空状态,添加图标和引导提示 - 修复 Chat 页面高度问题 (h-screen -> h-full) --- packages/server/src/routes/files.ts | 35 ++++++ packages/ui/src/api/client.ts | 10 ++ packages/ui/src/components/CodeEditor.tsx | 23 +++- packages/ui/src/components/StatusBar.tsx | 140 ++++++++++++++++++++++ packages/ui/src/index.ts | 3 + packages/web/src/App.tsx | 8 +- packages/web/src/pages/Chat.tsx | 2 +- 7 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 packages/ui/src/components/StatusBar.tsx diff --git a/packages/server/src/routes/files.ts b/packages/server/src/routes/files.ts index 349605d..0309a08 100644 --- a/packages/server/src/routes/files.ts +++ b/packages/server/src/routes/files.ts @@ -7,8 +7,12 @@ import { Hono } from 'hono'; import { readdir, stat, readFile, writeFile, mkdir } from 'node:fs/promises'; import { join, resolve, basename, extname, dirname } from 'node:path'; +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; import { searchFiles as coreSearchFiles, type FileIndexEntry } from '@ai-assistant/core'; +const execAsync = promisify(exec); + const filesRouter = new Hono(); // 工作目录 (默认为当前目录) @@ -444,4 +448,35 @@ filesRouter.put('/write', async (c) => { } }); +// ============================================================================ +// GET /api/files/git - 获取 Git 信息 +// ============================================================================ +filesRouter.get('/git', async (c) => { + try { + // 获取当前分支 + const { stdout: branch } = await execAsync('git rev-parse --abbrev-ref HEAD', { + cwd: workingDirectory, + }); + + // 检查是否有未提交的更改 + const { stdout: status } = await execAsync('git status --porcelain', { + cwd: workingDirectory, + }); + + return c.json({ + success: true, + data: { + branch: branch.trim(), + dirty: status.trim().length > 0, + }, + }); + } catch { + // 可能不是 Git 仓库 + return c.json({ + success: true, + data: null, + }); + } +}); + export { filesRouter }; diff --git a/packages/ui/src/api/client.ts b/packages/ui/src/api/client.ts index 70d2e34..1317ba9 100644 --- a/packages/ui/src/api/client.ts +++ b/packages/ui/src/api/client.ts @@ -289,6 +289,16 @@ export async function getFileTree(path: string = '.', depth: number = 3): Promis return request('GET', `/files/tree?${params}`); } +// Git Info +export interface GitInfo { + branch: string; + dirty: boolean; +} + +export async function getGitInfo(): Promise<{ success: boolean; data: GitInfo | null }> { + return request('GET', '/files/git'); +} + // Config export async function getConfig(): Promise<{ success: boolean; data: ServerConfig }> { return request('GET', '/config'); diff --git a/packages/ui/src/components/CodeEditor.tsx b/packages/ui/src/components/CodeEditor.tsx index 52b8010..1002daf 100644 --- a/packages/ui/src/components/CodeEditor.tsx +++ b/packages/ui/src/components/CodeEditor.tsx @@ -13,7 +13,7 @@ import { html } from '@codemirror/lang-html'; import { css } from '@codemirror/lang-css'; import { markdown } from '@codemirror/lang-markdown'; import { oneDark } from '@codemirror/theme-one-dark'; -import { X, Save, Circle } from 'lucide-react'; +import { X, Save, Circle, FileCode, MousePointerClick } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { toast } from 'sonner'; import { cn } from '../utils/cn.js'; @@ -156,8 +156,25 @@ export function CodeEditor({ if (tabs.length === 0) { return ( -
-

No files open

+
+ +
+ +
+

No files open

+

+ Select a file from the explorer to view and edit its contents +

+
+ + Click a file to open +
+
); } diff --git a/packages/ui/src/components/StatusBar.tsx b/packages/ui/src/components/StatusBar.tsx new file mode 100644 index 0000000..b0107df --- /dev/null +++ b/packages/ui/src/components/StatusBar.tsx @@ -0,0 +1,140 @@ +/** + * StatusBar Component + * + * 底部状态栏,显示 Git 分支、诊断信息、连接状态等 + */ + +import { useState, useEffect, useCallback } from 'react'; +import { GitBranch, AlertTriangle, AlertCircle, Wifi, WifiOff, RefreshCw, CheckCircle } from 'lucide-react'; +import { cn } from '../utils/cn.js'; +import { getLSPDiagnostics, getGitInfo, type DiagnosticsSummary, type GitInfo } from '../api/client.js'; + +interface StatusBarProps { + className?: string; + /** 是否连接到服务器 */ + isConnected?: boolean; + /** 点击诊断信息回调 */ + onDiagnosticsClick?: () => void; + /** 刷新间隔 (ms) */ + refreshInterval?: number; +} + +export function StatusBar({ + className, + isConnected = true, + onDiagnosticsClick, + refreshInterval = 30000, +}: StatusBarProps) { + const [diagnostics, setDiagnostics] = useState(null); + const [gitInfo, setGitInfo] = useState(null); + const [loading, setLoading] = useState(false); + + // 加载诊断信息和 Git 信息 + const loadData = useCallback(async () => { + setLoading(true); + try { + const [diagResult, gitResult] = await Promise.all([ + getLSPDiagnostics(), + getGitInfo(), + ]); + + if (diagResult.success && diagResult.data.summary) { + setDiagnostics(diagResult.data.summary); + } + + if (gitResult.success && gitResult.data) { + setGitInfo(gitResult.data); + } + } catch { + // 忽略错误 + } finally { + setLoading(false); + } + }, []); + + // 初始加载和定时刷新 + useEffect(() => { + loadData(); + const interval = setInterval(loadData, refreshInterval); + return () => clearInterval(interval); + }, [loadData, refreshInterval]); + + const errorCount = diagnostics?.totalErrors ?? 0; + const warningCount = diagnostics?.totalWarnings ?? 0; + const hasIssues = errorCount > 0 || warningCount > 0; + + return ( +
+ {/* 左侧 */} +
+ {/* Git 分支 */} + {gitInfo && ( +
+ + {gitInfo.branch} + {gitInfo.dirty && *} +
+ )} + + {/* 刷新按钮 */} + +
+ + {/* 右侧 */} +
+ {/* 诊断信息 */} + + + {/* 连接状态 */} +
+ {isConnected ? : } +
+
+
+ ); +} diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 68e80b2..8a1cb75 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -20,6 +20,7 @@ export { readFile, writeFile, getFileTree, + getGitInfo, getConfig, updateConfig, // Commands API @@ -117,6 +118,7 @@ export type { FileWriteResponse, FileTreeNode, FileTreeResponse, + GitInfo, ServerConfig, // Command types CommandInfo, @@ -251,6 +253,7 @@ export { SessionPanel } from './components/SessionPanel.js'; export { FileExplorer } from './components/FileExplorer.js'; export { CodeEditor, getLanguageFromFilename, type EditorTab } from './components/CodeEditor.js'; export { IDE } from './components/IDE.js'; +export { StatusBar } from './components/StatusBar.js'; // Toast function (re-export from sonner) export { toast } from 'sonner'; diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 89d846e..0477173 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -16,6 +16,7 @@ import { LSPPanel, DiagnosticsPanel, SessionPanel, + StatusBar, Toaster, ThemeProvider, listSessions, @@ -111,9 +112,9 @@ export function App() { return ( -
+
{/* 主内容区域:左侧文件浏览器 + 右侧对话框 */} -
+
{/* 左侧:IDE(文件浏览器 + 代码编辑器) */}
@@ -147,6 +148,9 @@ export function App() {
+ {/* 底部状态栏 */} + setShowDiagnostics(true)} /> + {/* 命令面板 */} {showCommands && setShowCommands(false)} responsive />} diff --git a/packages/web/src/pages/Chat.tsx b/packages/web/src/pages/Chat.tsx index 7dcb4fa..db7a799 100644 --- a/packages/web/src/pages/Chat.tsx +++ b/packages/web/src/pages/Chat.tsx @@ -154,7 +154,7 @@ export function ChatPage({ ); return ( -
+
{/* Header */}

Chat