import { useCallback, useEffect, useState } from "react"; import { Icon } from "../components/icon"; import { Link, useParams, useSearchParams } from "react-router-dom"; import { apiClient } from "../api/client"; import { getRepositoryStatus, listRepositories, type GitRepository, type GitStatus, } from "../api/git_repositories"; import { CommitPanel } from "../components/commit-panel"; import { FileEditor } from "../components/file-editor"; import { GitToolbar } from "../components/git-toolbar"; import { InstanceList } from "../components/instance-list"; import { WorkspaceHeader } from "../components/workspace-header"; import { listToolTypes } from "../api/tool_types"; import type { ToolType } from "../api/tool_types"; type WorkspaceStatus = "loading" | "ready" | "error" | "empty"; interface FileTreeEntry { name: string; type: "file" | "directory"; path: string; size?: number; mode?: string; last_commit?: { hash: string; message: string; author: string; date: string; } | null; } interface Project { id: string; name: string; description?: string | null; } export const RepoWorkspace = () => { const { projectId } = useParams<{ projectId: string }>(); const [searchParams, setSearchParams] = useSearchParams(); const [status, setStatus] = useState("loading"); const [project, setProject] = useState(null); const [repositories, setRepositories] = useState([]); const [selectedRepoId, setSelectedRepoId] = useState( searchParams.get("repo") ); const [branches, setBranches] = useState([]); const [currentBranch, setCurrentBranch] = useState("main"); const [gitStatus, setGitStatus] = useState(null); const [toolTypes, setToolTypes] = useState([]); const loadProject = useCallback(async () => { if (!projectId) return; try { const response = await apiClient.get(`/projects/${projectId}`); setProject(response.data); } catch { setProject(null); } }, [projectId]); const loadRepositories = useCallback(async () => { if (!projectId) return; setStatus("loading"); try { const data = await listRepositories(projectId); setRepositories(data); if (data.length === 0) { setStatus("empty"); } else { setStatus("ready"); // If no repo selected, select the first one if (!selectedRepoId) { setSelectedRepoId(data[0].id); const newParams = new URLSearchParams(searchParams); newParams.set("repo", data[0].id); setSearchParams(newParams, { replace: true }); } } } catch { setRepositories([]); setStatus("error"); } }, [projectId, selectedRepoId, searchParams, setSearchParams]); const loadBranches = useCallback(async () => { if (!projectId || !selectedRepoId) return; try { const response = await apiClient.get( `/projects/${projectId}/repositories/${selectedRepoId}/branches` ); const branchList = response.data.branches.map((b: { name: string }) => b.name); setBranches(branchList); const defaultBranch = response.data.default_branch; if (defaultBranch) { setCurrentBranch(defaultBranch); } } catch { setBranches([]); } }, [projectId, selectedRepoId]); const loadGitStatus = useCallback(async () => { if (!projectId || !selectedRepoId) return; try { const data = await getRepositoryStatus(projectId, selectedRepoId); setGitStatus(data); } catch { setGitStatus(null); } }, [projectId, selectedRepoId]); const loadToolTypes = useCallback(async () => { try { const data = await listToolTypes(); setToolTypes(data); } catch { setToolTypes([]); } }, []); useEffect(() => { void loadProject(); void loadRepositories(); void loadToolTypes(); }, [loadProject, loadRepositories, loadToolTypes]); useEffect(() => { void loadBranches(); void loadGitStatus(); }, [loadBranches, loadGitStatus]); const handleRepoChange = (repoId: string) => { setSelectedRepoId(repoId); const newParams = new URLSearchParams(searchParams); newParams.set("repo", repoId); newParams.delete("branch"); newParams.delete("path"); setSearchParams(newParams); }; const selectedRepo = repositories.find((r) => r.id === selectedRepoId); return (
{project && ( )} {status === "loading" && (

Loading repositories...

)} {status === "error" && (

Failed to load repositories

)} {status === "empty" && (

No repositories in this project yet.

Manage Repositories
)} {status === "ready" && repositories.length > 0 && ( <> {selectedRepoId && ( { setCurrentBranch(branch); const newParams = new URLSearchParams(searchParams); newParams.set("branch", branch); setSearchParams(newParams); }} onRefresh={() => { void loadBranches(); void loadGitStatus(); window.dispatchEvent(new CustomEvent("refresh-file-tree")); }} /> )}
{selectedRepoId && ( )}
)}
); }; // File Browser Component const FileBrowser = ({ projectId, repoId, gitStatus, }: { projectId: string; repoId: string; gitStatus: GitStatus | null; }) => { const [searchParams, setSearchParams] = useSearchParams(); const [entries, setEntries] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const branch = searchParams.get("branch") || "main"; const path = searchParams.get("path") || ""; const loadFiles = useCallback(async () => { setLoading(true); setError(null); try { const response = await apiClient.get( `/projects/${projectId}/repositories/${repoId}/files`, { params: { branch, path, }, } ); setEntries(response.data.entries || []); } catch { setError("Failed to load files"); } finally { setLoading(false); } }, [projectId, repoId, branch, path]); useEffect(() => { void loadFiles(); }, [loadFiles]); // Listen for refresh events useEffect(() => { const handleRefresh = () => void loadFiles(); window.addEventListener("refresh-file-tree", handleRefresh); return () => window.removeEventListener("refresh-file-tree", handleRefresh); }, [loadFiles]); const handleEntryClick = (entry: FileTreeEntry) => { if (entry.type === "directory") { const newParams = new URLSearchParams(searchParams); newParams.set("path", entry.path); setSearchParams(newParams); } else { const newParams = new URLSearchParams(searchParams); newParams.set("file", entry.path); setSearchParams(newParams); } }; const navigateUp = () => { if (!path) return; const parentPath = path.split("/").slice(0, -1).join("/"); const newParams = new URLSearchParams(searchParams); if (parentPath) { newParams.set("path", parentPath); } else { newParams.delete("path"); } setSearchParams(newParams); }; const getFileStatus = (filePath: string): string | null => { if (!gitStatus) return null; if (gitStatus.modified.includes(filePath)) return "modified"; if (gitStatus.added.includes(filePath)) return "added"; if (gitStatus.deleted.includes(filePath)) return "deleted"; if (gitStatus.untracked.includes(filePath)) return "untracked"; return null; }; if (loading) return

Loading files...

; if (error) return

{error}

; return (
{path && ( )} {entries.length === 0 && (

No files in this repository yet.

)} {entries.map((entry) => { const fileStatus = entry.type === "file" ? getFileStatus(entry.path) : null; return ( ); })}
); };