import { useState, useEffect } from "react"; import { Icon } from "@/components/ui/Icon"; import { createInstance, startInstance, type ToolInstance } from "../api/sessions"; import type { Project } from "../types"; import { listRepositoryBranches, type GitRepository, type Branch } from "@/api/git-repositories"; import type { ToolType } from "@/api/tool-types"; import { listSSHKeys, type SSHKey } from "@/api/ssh-keys"; import { listConfigProfiles, type ConfigProfile } from "../api/config_profiles"; interface CreateSessionFormProps { projects: Project[]; repositories: GitRepository[]; toolTypes: ToolType[]; fixedProjectId?: string; fixedRepoId?: string; projectName?: string; repoName?: string; showCloneMode?: boolean; showFixedFields?: boolean; onProjectChange?: (projectId: string) => void; onSuccess?: (instance: ToolInstance) => void; onCancel?: () => void; submitLabel?: string; className?: string; } export const CreateSessionForm = ({ projects, repositories, toolTypes, fixedProjectId, fixedRepoId, projectName, repoName, showCloneMode = true, showFixedFields = true, onProjectChange, onSuccess, onCancel, submitLabel = "Create Session", className = "", }: CreateSessionFormProps) => { const [selectedProject, setSelectedProject] = useState(fixedProjectId || ""); const [selectedRepo, setSelectedRepo] = useState(fixedRepoId || ""); const [selectedToolType, setSelectedToolType] = useState(""); const [displayName, setDisplayName] = useState(""); const [cloneMode, setCloneMode] = useState<"mount" | "clone">("mount"); const [branch, setBranch] = useState("main"); const [sshKeys, setSshKeys] = useState([]); const [configProfiles, setConfigProfiles] = useState([]); const [selectedConfigProfile, setSelectedConfigProfile] = useState(""); const [selectedSshKeyIds, setSelectedSshKeyIds] = useState([]); const [branches, setBranches] = useState([]); const [isLoadingBranches, setIsLoadingBranches] = useState(false); const [isCreatingNewBranch, setIsCreatingNewBranch] = useState(false); const [newBranchName, setNewBranchName] = useState(""); const [baseBranch, setBaseBranch] = useState(""); const [status, setStatus] = useState<"idle" | "creating" | "error">("idle"); const [progress, setProgress] = useState(""); const [error, setError] = useState(null); // Load SSH keys useEffect(() => { const loadKeys = async () => { try { const keys = await listSSHKeys(); setSshKeys(keys); } catch { // ignore } }; void loadKeys(); }, []); // Load config profiles when tool type is selected useEffect(() => { const projectId = fixedProjectId || selectedProject; if (!selectedToolType || !projectId) { setConfigProfiles([]); setSelectedConfigProfile(""); return; } const loadProfiles = async () => { try { const profiles = await listConfigProfiles(projectId, selectedToolType); setConfigProfiles(profiles); // Auto-select default if available const defaultProfile = profiles.find((p) => p.is_default); if (defaultProfile) { setSelectedConfigProfile(defaultProfile.id); } } catch { // ignore } }; void loadProfiles(); }, [selectedToolType, selectedProject, fixedProjectId]); // Load branches when selected repo changes useEffect(() => { const projectId = fixedProjectId || selectedProject; if (!selectedRepo || !projectId || !showCloneMode) { setBranches([]); return; } const loadBranches = async () => { setIsLoadingBranches(true); try { const response = await listRepositoryBranches(projectId, selectedRepo); setBranches(response.branches); if (response.default_branch) { setBranch(response.default_branch); setBaseBranch(response.default_branch); } } catch { // ignore } finally { setIsLoadingBranches(false); } }; void loadBranches(); }, [selectedRepo, selectedProject, fixedProjectId, showCloneMode]); // Filter repositories by selected project const availableRepos = selectedProject ? repositories.filter((r) => r.project_id === selectedProject) : []; const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); setError(null); const projectId = fixedProjectId || selectedProject; const repoId = fixedRepoId || selectedRepo; if (!projectId || !repoId || !selectedToolType) { setError("Project, repository, and tool type are required"); return; } if (showCloneMode && cloneMode === "clone") { const repo = repositories.find((r) => r.id === repoId); if (!repo?.ssh_key_id) { setError("Repository must have an SSH key assigned for clone mode"); return; } } setStatus("creating"); setProgress("Creating instance..."); try { const instance = await createInstance( projectId, repoId, selectedToolType, displayName || undefined, showCloneMode ? cloneMode : undefined, showCloneMode && cloneMode === "clone" ? isCreatingNewBranch ? baseBranch : branch : undefined, showCloneMode && cloneMode === "clone" && isCreatingNewBranch ? newBranchName : undefined, selectedConfigProfile || undefined, selectedSshKeyIds.length > 0 ? selectedSshKeyIds : undefined ); setProgress("Starting container..."); await startInstance( projectId, repoId, instance.id, selectedConfigProfile || undefined, selectedSshKeyIds.length > 0 ? selectedSshKeyIds : undefined ); // Reset form if (!fixedProjectId) setSelectedProject(""); if (!fixedRepoId) setSelectedRepo(""); setSelectedToolType(""); setDisplayName(""); setCloneMode("mount"); setBranch("main"); setIsCreatingNewBranch(false); setNewBranchName(""); setBaseBranch(""); setBranches([]); setSelectedSshKeyIds([]); setStatus("idle"); onSuccess?.(instance); } catch { setStatus("error"); setError("Failed to create session"); setProgress(""); } }; const isSubmitting = status === "creating"; // Determine which steps are active/unlocked const hasProject = !!(fixedProjectId || selectedProject); const hasRepo = !!(fixedRepoId || selectedRepo); const hasToolType = !!selectedToolType; const renderStep = ( label: string, number: number, isActive: boolean, isComplete: boolean, children: React.ReactNode ) => { const stepClass = `workflow-step ${isActive ? "active" : ""} ${isComplete ? "complete" : ""}`; return (
{number} {label}
{children}
); }; return (
{isSubmitting && (

{progress || "Creating session..."}

)}
{/* Step 1: Project */} {renderStep("Select Project", 1, true, hasProject, fixedProjectId && showFixedFields ? ( ) : ( ) )} {/* Step 2: Repository */} {hasProject && renderStep("Select Repository", 2, true, hasRepo, fixedRepoId && showFixedFields ? ( ) : ( ) )} {/* Step 3: Tool Type */} {hasRepo && renderStep("Select Tool", 3, true, hasToolType, )} {/* Step 4: Config Profile */} {hasToolType && renderStep("Config Profile (optional)", 4, true, false, )} {/* Step 5: SSH Keys */} {hasToolType && renderStep("SSH Keys (optional)", 5, true, false,
{sshKeys.length === 0 && ( No SSH keys configured. )} {sshKeys.map((key) => ( ))}
Selected keys will be mounted into the container at ~/.ssh
)} {/* Step 6: Clone Mode & Branch */} {showCloneMode && hasToolType && renderStep("Repository Access", 6, true, false,
{cloneMode === "clone" && ( <> {isCreatingNewBranch && ( <> )} {selectedRepo && (
{(() => { const repo = repositories.find((r) => r.id === selectedRepo); if (!repo) return null; if (repo.ssh_key_id) { const key = sshKeys.find((k) => k.id === repo.ssh_key_id); return ( SSH key: {key?.name || "Assigned"} ); } return ( No SSH key assigned to this repository. Clone mode requires an SSH key. ); })()}
)} )}
)} {/* Step 7: Display Name */} {hasToolType && renderStep("Display Name (optional)", 7, true, !!displayName, )} {/* Error & Submit */} {error &&

{error}

} {hasToolType && (
{onCancel && ( )}
)}
); };