diff --git a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/page.tsx b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/page.tsx index 213a21489..05dd9cc4c 100644 --- a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/page.tsx +++ b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/page.tsx @@ -13,10 +13,16 @@ import { Download, SlidersHorizontal, ArrowLeft, + ChevronLeft, + ChevronRight, AlertTriangle, X, MoreVertical, + RefreshCw, + Edit, + CloudDownload, } from "lucide-react"; +import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; import { useRouter } from "next/navigation"; import { cn } from "@/lib/utils"; @@ -46,8 +52,15 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, + DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; import { Label } from "@/components/ui/label"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { Breadcrumbs } from "@/components/breadcrumbs"; import { SessionHeader } from "./session-header"; import { getPhaseColor } from "@/utils/session-helpers"; @@ -147,6 +160,18 @@ export default function ProjectSessionDetailPage({ const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [userHasInteracted, setUserHasInteracted] = useState(false); + // Left panel visibility and size state + const [leftPanelVisible, setLeftPanelVisible] = useState(() => { + if (typeof window === 'undefined') return true; + const saved = localStorage.getItem('session-left-panel-visible'); + return saved === null ? true : saved === 'true'; + }); + const [leftPanelSize, setLeftPanelSize] = useState(() => { + if (typeof window === 'undefined') return 30; + const saved = localStorage.getItem('session-left-panel-size'); + return saved ? parseFloat(saved) : 30; + }); + // Directory browser state (unified for artifacts, repos, and workflow) const [selectedDirectory, setSelectedDirectory] = useState({ type: "artifacts", @@ -172,6 +197,16 @@ export default function ProjectSessionDetailPage({ }); }, [params]); + // Persist left panel visibility + useEffect(() => { + localStorage.setItem('session-left-panel-visible', String(leftPanelVisible)); + }, [leftPanelVisible]); + + // Persist left panel size + useEffect(() => { + localStorage.setItem('session-left-panel-size', String(leftPanelSize)); + }, [leftPanelSize]); + // Session queue hook (localStorage-backed) const sessionQueue = useSessionQueue(projectName, sessionName); @@ -1452,122 +1487,650 @@ export default function ProjectSessionDetailPage({ {/* Main content area */}
-
+
{/* Mobile sidebar overlay */} {mobileMenuOpen && ( -
setMobileMenuOpen(false)} /> )} - {/* Left Column - Accordions - always show with state-based styling */} - {session && ( -
- {/* Backdrop blur layer for entire sidebar */} - {phase !== "Running" && ( -
- )} - - {/* State overlay for non-running sessions */} - {phase !== "Running" && ( -
-
- {/* Starting states */} - {["Creating", "Pending"].includes(phase) && ( - <> - -

Starting Session

-

- Setting up your workspace... -

- - )} - - {/* Stopping state */} - {phase === "Stopping" && ( - <> - -

Stopping Session

-

- Saving workspace state... -

- - )} - - {/* Hibernated states */} - {["Stopped", "Completed", "Failed"].includes(phase) && ( -
-

Session Hibernated

- - {/* Session details */} -
- {workflowManagement.activeWorkflow && ( -
-

Workflow

- - {workflowManagement.activeWorkflow} - -
+ {/* Floating show button when panel is hidden (desktop only) */} + {!leftPanelVisible && !mobileMenuOpen && ( + + )} + + {/* Desktop resizable panels */} + + {leftPanelVisible && ( + <> + setLeftPanelSize(size)} + className="relative" + > + {/* Left Column - Accordions */} +
+
+ + + + ({ + name: f.name, + path: f.path, + size: f.size, + }))} + onAddRepository={() => setContextModalOpen(true)} + onRemoveRepository={(repoName) => + removeRepoMutation.mutate(repoName) + } + onRemoveFile={(fileName) => + removeFileMutation.mutate(fileName) + } + /> + + + + + + {/* File Explorer */} + + +
+ + File Explorer + + EXPERIMENTAL + + {gitOps.gitStatus?.hasChanges && ( +
+ {(gitOps.gitStatus?.totalAdded ?? 0) > 0 && ( + + +{gitOps.gitStatus.totalAdded} + )} - - {session?.spec?.repos && session.spec.repos.length > 0 && ( -
-

- Repositories ({session.spec.repos.length}) -

-
- {session.spec.repos.slice(0, 3).map((repo, idx) => ( -
- • {repo.url?.split('/').pop()?.replace('.git', '')} -
- ))} - {session.spec.repos.length > 3 && ( -
- +{session.spec.repos.length - 3} more -
- )} -
-
+ {(gitOps.gitStatus?.totalRemoved ?? 0) > 0 && ( + + -{gitOps.gitStatus.totalRemoved} + )} - - {(!workflowManagement.activeWorkflow && (!session?.spec?.repos || session.spec.repos.length === 0)) && ( -
-

- No workflow or repositories configured -

+
+ )} +
+ + +
+

+ Browse, view, and manage files in your workspace + directories. Track changes and sync with Git for + version control. +

+ + {/* Directory Selector */} +
+ + +
+ + {/* File Browser */} +
+
+
+ {(fileOps.currentSubPath || + fileOps.viewingFile) && ( + + )} + + + + {selectedDirectory.path} + {fileOps.currentSubPath && + `/${fileOps.currentSubPath}`} + {fileOps.viewingFile && + `/${fileOps.viewingFile.path}`} + +
+ + {fileOps.viewingFile ? ( +
+ + + + + + + + Sync to Jira - Coming soon + + + Sync to GDrive - Coming soon + + + +
+ ) : ( +
+ +
)}
- - +
- )} + + {/* Remote Configuration */} + {!currentRemote ? ( +
+ + Set up Git remote for version control + + +
+ ) : ( +
+
+
+ + + {currentRemote?.url + ?.split("/") + .slice(-2) + .join("/") + .replace(".git", "") || ""} + /{currentRemote?.branch || "main"} + +
+ +
+ + {mergeStatus && !mergeStatus.canMergeClean ? ( +
+ + + conflict + +
+ ) : gitOps.gitStatus?.hasChanges || + mergeStatus?.remoteCommitsAhead ? ( +
+ {mergeStatus?.remoteCommitsAhead ? ( + + ↓{mergeStatus.remoteCommitsAhead} + + ) : null} + {gitOps.gitStatus?.hasChanges ? ( + + {gitOps.gitStatus?.uncommittedFiles ?? + 0}{" "} + uncommitted + + ) : null} +
+ ) : null} + + + + + + + +

+ {gitOps.gitStatus?.hasChanges + ? "Commit changes first" + : `Sync with origin/${currentRemote?.branch || "main"}`} +

+
+
+
+ + + + + + + setRemoteDialogOpen(true)} + > + + Manage Remote + + + setCommitModalOpen(true)} + disabled={!gitOps.gitStatus?.hasChanges} + > + + Commit Changes + + + gitOps.handleGitPull(refetchMergeStatus) + } + disabled={ + !mergeStatus?.canMergeClean || + gitOps.isPulling + } + > + + Pull + + + gitOps.handleGitPush(refetchMergeStatus) + } + disabled={ + !mergeStatus?.canMergeClean || + gitOps.isPushing || + gitOps.gitStatus?.hasChanges + } + > + + Push + + + { + const newRemotes = { + ...directoryRemotes, + }; + delete newRemotes[ + selectedDirectory.path + ]; + setDirectoryRemotes(newRemotes); + successToast("Git remote disconnected"); + }} + > + + Disconnect + + + +
+
+ )} +
+
+ + +
+ + {/* Hide panel button at bottom */} +
+ +
+
+ + + + )} + + {/* Right Column - Messages */} + +
+ + + {/* Repository change overlay */} + {repoChanging && ( +
+ + + Updating Repositories... + +
+

+ Please wait while repositories are being + updated. This may take 10-20 seconds... +

+
+
+
+
+ )} + +
+ + Promise.resolve(sendChat())} + onInterrupt={aguiInterrupt} + onEndSession={() => Promise.resolve(handleEndSession())} + onGoToResults={() => {}} + onContinue={handleContinue} + workflowMetadata={workflowMetadata} + onCommandClick={handleCommandClick} + isRunActive={isRunActive} + showWelcomeExperience={true} + activeWorkflow={workflowManagement.activeWorkflow} + userHasInteracted={userHasInteracted} + queuedMessages={sessionQueue.messages} + hasRealMessages={hasRealMessages} + welcomeExperienceComponent={ + setUserHasInteracted(true)} + userHasInteracted={userHasInteracted} + sessionPhase={session?.status?.phase} + hasRealMessages={hasRealMessages} + onLoadWorkflow={() => setCustomWorkflowDialogOpen(true)} + selectedWorkflow={workflowManagement.selectedWorkflow} + /> + } + /> + +
+
+
+
+
+ + + {/* Mobile view (unchanged) */} +
+ + + {repoChanging && ( +
+ + + Updating Repositories... + +
+

+ Please wait while repositories are being + updated. This may take 10-20 seconds... +

+
+
+
+ )} + +
+ + Promise.resolve(sendChat())} + onInterrupt={aguiInterrupt} + onEndSession={() => Promise.resolve(handleEndSession())} + onGoToResults={() => {}} + onContinue={handleContinue} + workflowMetadata={workflowMetadata} + onCommandClick={handleCommandClick} + isRunActive={isRunActive} + showWelcomeExperience={true} + activeWorkflow={workflowManagement.activeWorkflow} + userHasInteracted={userHasInteracted} + queuedMessages={sessionQueue.messages} + hasRealMessages={hasRealMessages} + welcomeExperienceComponent={ + setUserHasInteracted(true)} + userHasInteracted={userHasInteracted} + sessionPhase={session?.status?.phase} + hasRealMessages={hasRealMessages} + onLoadWorkflow={() => setCustomWorkflowDialogOpen(true)} + selectedWorkflow={workflowManagement.selectedWorkflow} + /> + } + /> +
- )} +
+
+
+ {/* Mobile left panel sidebar */} +
{/* Mobile close button */} -
+
- )} - - {/* Right Column - Messages */} -
- - - {/* Repository change overlay */} - {repoChanging && ( -
- - - Updating Repositories... - -
-

- Please wait while repositories are being - updated. This may take 10-20 seconds... -

-
-
-
-
- )} - -
- - Promise.resolve(sendChat())} - onInterrupt={aguiInterrupt} - onEndSession={() => Promise.resolve(handleEndSession())} - onGoToResults={() => {}} - onContinue={handleContinue} - workflowMetadata={workflowMetadata} - onCommandClick={handleCommandClick} - isRunActive={isRunActive} - showWelcomeExperience={!["Completed", "Failed", "Stopped", "Stopping"].includes(session?.status?.phase || "")} - activeWorkflow={workflowManagement.activeWorkflow} - userHasInteracted={userHasInteracted} - queuedMessages={sessionQueue.messages} - hasRealMessages={hasRealMessages} - welcomeExperienceComponent={ - setUserHasInteracted(true)} - userHasInteracted={userHasInteracted} - sessionPhase={session?.status?.phase} - hasRealMessages={hasRealMessages} - onLoadWorkflow={() => setCustomWorkflowDialogOpen(true)} - selectedWorkflow={workflowManagement.selectedWorkflow} - /> - } - /> - -
-
-
-