// 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 (
Choose how you want GitPilot to interact with your code. You can switch modes at any time.
{mode.description}