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 (
-