feat: add git integration with file browsing and diff viewing

Add comprehensive git support across the stack:
- CLI: register git RPC handlers (status, diff numstat, diff file)
- Server: create git routes with proper session path resolution
- Web: add file browser, diff viewer, and git status visualization
- Add FileIcon component and git parser utilities
- Add TanStack Router routes for /files and /file pages
- Add git-themed CSS variables for light and dark modes
This commit is contained in:
weishu
2025-12-20 23:00:26 +08:00
parent 17eeba10d6
commit 8ebd4f6ab9
18 changed files with 1667 additions and 3 deletions
+40
View File
@@ -1,5 +1,8 @@
import type {
AuthResponse,
FileReadResponse,
FileSearchResponse,
GitCommandResponse,
MachinesResponse,
MessagesResponse,
SpawnResponse,
@@ -71,6 +74,43 @@ export class ApiClient {
return await this.request<MessagesResponse>(url)
}
async getGitStatus(sessionId: string): Promise<GitCommandResponse> {
return await this.request<GitCommandResponse>(`/api/sessions/${encodeURIComponent(sessionId)}/git-status`)
}
async getGitDiffNumstat(sessionId: string, staged: boolean): Promise<GitCommandResponse> {
const params = new URLSearchParams()
params.set('staged', staged ? 'true' : 'false')
return await this.request<GitCommandResponse>(`/api/sessions/${encodeURIComponent(sessionId)}/git-diff-numstat?${params.toString()}`)
}
async getGitDiffFile(sessionId: string, path: string, staged?: boolean): Promise<GitCommandResponse> {
const params = new URLSearchParams()
params.set('path', path)
if (staged !== undefined) {
params.set('staged', staged ? 'true' : 'false')
}
return await this.request<GitCommandResponse>(`/api/sessions/${encodeURIComponent(sessionId)}/git-diff-file?${params.toString()}`)
}
async searchSessionFiles(sessionId: string, query: string, limit?: number): Promise<FileSearchResponse> {
const params = new URLSearchParams()
if (query) {
params.set('query', query)
}
if (limit !== undefined) {
params.set('limit', `${limit}`)
}
const qs = params.toString()
return await this.request<FileSearchResponse>(`/api/sessions/${encodeURIComponent(sessionId)}/files${qs ? `?${qs}` : ''}`)
}
async readSessionFile(sessionId: string, path: string): Promise<FileReadResponse> {
const params = new URLSearchParams()
params.set('path', path)
return await this.request<FileReadResponse>(`/api/sessions/${encodeURIComponent(sessionId)}/file?${params.toString()}`)
}
async sendMessage(sessionId: string, text: string, localId?: string | null): Promise<void> {
await this.request(`/api/sessions/${encodeURIComponent(sessionId)}/messages`, {
method: 'POST',