// frontend/components/AdminTabs/WorkspaceModesTab.jsx import React, { useState } from "react"; import { startSession } from "../../utils/api.js"; /** * Workspace Modes tab — allows the user to start a session in one of * three modes (folder, local_git, github). Calls POST /api/session/start. * * Best practices applied: * - Loading state while the request is in flight * - Per-mode error state (not a global error) * - Disabled card during submission to prevent double-click * - ARIA role="button" + aria-disabled for accessibility * - Toast notification on success * - Success callback so App.jsx can set activeSessionId and switch to workspace view */ const MODES = [ { id: "folder", title: "Folder Mode", description: "Work with any local folder. No Git required.", requires: "A local folder path", enables: "Chat, explain, review", promptKey: "folder_path", promptLabel: "Folder path (absolute)", promptPlaceholder: "/home/you/myproject", buildPayload: (value) => ({ mode: "folder", folder_path: value }), }, { id: "local_git", title: "Local Git Mode", description: "Full repo + branch context for AI assistance.", requires: "A local Git repository", enables: "All local features (branches, diff, commit)", promptKey: "repo_root", promptLabel: "Repository root (absolute path)", promptPlaceholder: "/home/you/my-git-repo", buildPayload: (value) => ({ mode: "local_git", repo_root: value }), }, { id: "github", title: "GitHub Mode", description: "PRs, issues, remote workflows via GitHub API.", requires: "GitHub token (already signed in)", enables: "Full platform features", promptKey: "repo_full_name", promptLabel: "Repository (owner/repo)", promptPlaceholder: "octocat/hello-world", buildPayload: (value) => ({ mode: "github", repo_full_name: value }), }, ]; export default function WorkspaceModesTab({ onSessionStarted, showToast }) { const [activeModeId, setActiveModeId] = useState(null); const [inputValue, setInputValue] = useState(""); const [submittingId, setSubmittingId] = useState(null); const [errorByMode, setErrorByMode] = useState({}); const handleCardClick = (mode) => { if (submittingId) return; setActiveModeId(mode.id); setInputValue(""); setErrorByMode((prev) => ({ ...prev, [mode.id]: null })); }; const handleStart = async (mode) => { const trimmed = inputValue.trim(); if (!trimmed) { setErrorByMode((prev) => ({ ...prev, [mode.id]: `${mode.promptLabel} is required`, })); return; } setSubmittingId(mode.id); setErrorByMode((prev) => ({ ...prev, [mode.id]: null })); try { const payload = mode.buildPayload(trimmed); const result = await startSession(payload); showToast?.( `${mode.title} started`, `Session ${result.session_id?.slice(0, 8) || ""} is now active.` ); onSessionStarted?.(result); setActiveModeId(null); setInputValue(""); } catch (err) { setErrorByMode((prev) => ({ ...prev, [mode.id]: err?.message || "Failed to start session", })); } finally { setSubmittingId(null); } }; const handleCancel = () => { if (submittingId) return; setActiveModeId(null); setInputValue(""); }; return (

Workspace Modes

Choose how you want GitPilot to interact with your code. You can switch modes at any time.

{MODES.map((mode) => { const isActive = activeModeId === mode.id; const isSubmitting = submittingId === mode.id; const error = errorByMode[mode.id]; return (
!isActive && handleCardClick(mode)} onKeyDown={(e) => { if ((e.key === "Enter" || e.key === " ") && !isActive) { e.preventDefault(); handleCardClick(mode); } }} style={{ background: isActive ? "#1e3a5f" : "#1a1b26", borderRadius: "8px", padding: "20px", border: isActive ? "1px solid #3B82F6" : "1px solid #2a2b36", cursor: submittingId && !isSubmitting ? "not-allowed" : "pointer", opacity: submittingId && !isSubmitting ? 0.5 : 1, transition: "all 150ms ease", }} >

{mode.title}

{mode.description}

Requires: {mode.requires}
Enables: {mode.enables}
{isActive && (
e.stopPropagation()} style={{ marginTop: "12px" }}> setInputValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); handleStart(mode); } else if (e.key === "Escape") { handleCancel(); } }} placeholder={mode.promptPlaceholder} disabled={isSubmitting} autoFocus style={{ width: "100%", padding: "6px 8px", background: "#0d0e15", border: "1px solid #2a2b36", borderRadius: "4px", color: "#fff", fontSize: "12px", fontFamily: "monospace", }} /> {error && (
{error}
)}
)}
); })}
); }