Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions apps/web/src/components/app/agents-view-feedback-detail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { type FeedbackDetailState } from "@/components/app/feedback-utils";
import {
FeedbackDetailPanel,
MobileFeedbackSheet,
MobileReviewSummarySheet,
ReviewSummaryPanel,
} from "@/components/app/feedback-panel";
import { type Agent } from "@/components/app/types";

type FeedbackDetailProps = {
detail: NonNullable<FeedbackDetailState>;
agents: Agent[];
connectedAgentId: string | null;
sendTerminalInput: (data: string) => void;
onClose: () => void;
onNavigateItem: (parentAgentId: string, nextItemId: number) => void;
};

export function DesktopFeedbackDetail({
detail,
agents,
connectedAgentId,
sendTerminalInput,
onClose,
onNavigateItem,
}: FeedbackDetailProps): JSX.Element | null {
if ("summaryAgentId" in detail) {
const summaryAgent = agents.find((a) => a.id === detail.summaryAgentId);
return summaryAgent ? (
<ReviewSummaryPanel
key={`summary-${detail.summaryAgentId}`}
parentAgentId={detail.parentAgentId}
agent={summaryAgent}
onClose={onClose}
/>
) : null;
}

return (
<FeedbackDetailPanel
key={detail.parentAgentId}
parentAgentId={detail.parentAgentId}
itemId={detail.itemId}
isConnected={connectedAgentId === detail.parentAgentId}
sendTerminalInput={sendTerminalInput}
onClose={onClose}
onNavigate={(nextItemId) =>
onNavigateItem(detail.parentAgentId, nextItemId)
}
/>
);
}

export function MobileFeedbackDetail({
detail,
agents,
connectedAgentId,
sendTerminalInput,
onClose,
onNavigateItem,
}: FeedbackDetailProps): JSX.Element | null {
if ("summaryAgentId" in detail) {
const summaryAgent = agents.find((a) => a.id === detail.summaryAgentId);
return summaryAgent ? (
<MobileReviewSummarySheet
parentAgentId={detail.parentAgentId}
agent={summaryAgent}
onClose={onClose}
/>
) : null;
}

return (
<MobileFeedbackSheet
parentAgentId={detail.parentAgentId}
itemId={detail.itemId}
isConnected={connectedAgentId === detail.parentAgentId}
sendTerminalInput={sendTerminalInput}
onClose={onClose}
onNavigate={(nextItemId) =>
onNavigateItem(detail.parentAgentId, nextItemId)
}
/>
);
}
210 changes: 38 additions & 172 deletions apps/web/src/components/app/agents-view.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
Routes,
Route,
useMatch,
useNavigate,
useParams,
} from "react-router-dom";
import { Routes, Route, useParams } from "react-router-dom";
import { PanelLeftOpen, PanelRightOpen } from "lucide-react";

import { ChangesTab } from "@/components/app/changes-tab";
Expand All @@ -23,12 +17,9 @@ import {
import { CreateAgentDialog } from "@/components/app/create-agent-dialog";
import { DeleteAgentDialog } from "@/components/app/delete-agent-dialog";
import {
type FeedbackDetailState,
FeedbackDetailPanel,
MobileFeedbackSheet,
MobileReviewSummarySheet,
ReviewSummaryPanel,
} from "@/components/app/feedback-panel";
DesktopFeedbackDetail,
MobileFeedbackDetail,
} from "@/components/app/agents-view-feedback-detail";
import { MediaLightbox } from "@/components/app/media-lightbox";
import {
MediaSidebar,
Expand All @@ -52,19 +43,14 @@ import { GlassSidebar } from "@/components/ui/glass-sidebar";
import { uploadAgentMedia } from "@/lib/media-upload";
import { type AgentType, isCliAgentType } from "@/lib/agent-types";
import { type IdeType } from "@/lib/ide-types";
import {
agentChangesRoute,
agentFeedbackRoute,
agentReviewRoute,
agentRoute,
} from "@/lib/agent-routes";
import { cn } from "@/lib/utils";
import { useAgentActions } from "@/hooks/use-agent-actions";
import { useAgents } from "@/hooks/use-agents";
import { useMedia } from "@/hooks/use-media";
import { useMediaSidebarState } from "@/hooks/use-media-sidebar-state";
import { useTerminal } from "@/hooks/use-terminal";
import { useAgentFocus } from "@/hooks/use-agent-focus";
import { useAgentsViewRouting } from "@/hooks/use-agents-view-routing";
import { LaunchTemplateDialog } from "@/components/app/automations-launch-dialog";
import { CommandPalette } from "@/components/app/command-palette";
import { useAgentHotkeys } from "@/hooks/use-agent-hotkeys";
Expand Down Expand Up @@ -102,13 +88,7 @@ export function AgentsView({
triggerNavAnimation,
onNavigateSection,
}: AgentsViewProps): JSX.Element {
const navigate = useNavigate();
const { agentId: routeAgentId } = useParams();
const feedbackMatch = useMatch("/agents/:agentId/feedback/:itemId");
const reviewMatch = useMatch("/agents/:agentId/review/:summaryAgentId");
const changesMatch = useMatch("/agents/:agentId/changes");
const itemId = feedbackMatch?.params.itemId;
const summaryAgentId = reviewMatch?.params.summaryAgentId;

const [sharedConnectedAgentId, setSharedConnectedAgentId] = useState<
string | null
Expand All @@ -133,6 +113,22 @@ export function AgentsView({
routeAgentId ?? null
);

const {
changesMatch,
feedbackDetail,
feedbackDetailRendered,
handleFeedbackTransitionEnd,
closeFeedbackDetail,
openFeedbackDetail,
navigateFeedbackItem,
onTabChange,
} = useAgentsViewRouting({
routeAgentId,
agents,
agentsLoaded,
validatedSelectedAgentId,
});

const [createOpen, setCreateOpen] = useState(false);
const [requestedCreateType, setRequestedCreateType] =
useState<AgentType | null>(null);
Expand All @@ -147,22 +143,6 @@ export function AgentsView({
readExpandedAgentId()
);

const feedbackItemId =
itemId !== undefined && Number.isInteger(Number(itemId))
? Number(itemId)
: null;
const feedbackDetail = routeAgentId
? summaryAgentId
? { parentAgentId: routeAgentId, summaryAgentId }
: feedbackItemId !== null
? { parentAgentId: routeAgentId, itemId: feedbackItemId }
: null
: null;
const feedbackDetailStaleRef =
useRef<NonNullable<FeedbackDetailState> | null>(null);
if (feedbackDetail) feedbackDetailStaleRef.current = feedbackDetail;
const feedbackDetailRendered =
feedbackDetail ?? feedbackDetailStaleRef.current;
const pendingAutoAttachAgentIdRef = useRef<string | null>(null);
const sidebarAgentId = sharedConnectedAgentId ?? validatedSelectedAgentId;
const agentIds = useMemo(() => agents.map((a) => a.id), [agents]);
Expand Down Expand Up @@ -264,18 +244,6 @@ export function AgentsView({

useAgentFocus(focusedAgentId, "authenticated");

const onTabChange = useCallback(
(tab: "terminal" | "changes") => {
if (!routeAgentId) return;
navigate(
tab === "changes"
? agentChangesRoute(routeAgentId)
: agentRoute(routeAgentId),
{ replace: true }
);
},
[navigate, routeAgentId]
);
const { diffStats: focusedDiffStats } = useAgentDiffStats(
focusedAgentId ?? "",
!!focusedAgentId
Expand Down Expand Up @@ -318,13 +286,6 @@ export function AgentsView({
}, [agents, validatedSelectedAgentId]);
const prevSelectedExpansionTargetRef = useRef<string | null>(null);

useEffect(() => {
if (!routeAgentId) return;
if (!agentsLoaded) return;
if (validatedSelectedAgentId) return;
navigate("/agents", { replace: true });
}, [agentsLoaded, navigate, routeAgentId, validatedSelectedAgentId]);

useEffect(() => {
if (!selectedExpansionTarget) {
prevSelectedExpansionTargetRef.current = null;
Expand All @@ -339,25 +300,6 @@ export function AgentsView({
);
}, [selectedExpansionTarget]);

useEffect(() => {
if (!routeAgentId) return;
if (!agentsLoaded) return;
if (itemId !== undefined && feedbackItemId === null) {
navigate(agentRoute(routeAgentId), { replace: true });
}
}, [agentsLoaded, feedbackItemId, itemId, navigate, routeAgentId]);

useEffect(() => {
if (!routeAgentId || !summaryAgentId) return;
if (!agentsLoaded) return;
const summaryAgentExists = agents.some(
(agent) => agent.id === summaryAgentId
);
if (!summaryAgentExists) {
navigate(agentRoute(routeAgentId), { replace: true });
}
}, [agents, agentsLoaded, navigate, routeAgentId, summaryAgentId]);

useEffect(() => {
if (!validatedSelectedAgentId) return;
if (connectedAgentId === validatedSelectedAgentId) {
Expand Down Expand Up @@ -430,36 +372,6 @@ export function AgentsView({
openCreateDialog,
});

const closeFeedbackDetail = useCallback(() => {
if (validatedSelectedAgentId) {
navigate(agentRoute(validatedSelectedAgentId), { replace: true });
return;
}
navigate("/agents", { replace: true });
}, [navigate, validatedSelectedAgentId]);

const openFeedbackDetail = useCallback(
(state: FeedbackDetailState) => {
if (!state) {
closeFeedbackDetail();
return;
}
if ("summaryAgentId" in state) {
navigate(agentReviewRoute(state.parentAgentId, state.summaryAgentId));
return;
}
navigate(agentFeedbackRoute(state.parentAgentId, state.itemId));
},
[closeFeedbackDetail, navigate]
);

const navigateFeedbackItem = useCallback(
(parentAgentId: string, nextItemId: number) => {
navigate(agentFeedbackRoute(parentAgentId, nextItemId));
},
[navigate]
);

const toggleAgentDetails = useCallback((agentId: string) => {
setExpandedAgentId((current) => (current === agentId ? null : agentId));
}, []);
Expand Down Expand Up @@ -573,11 +485,7 @@ export function AgentsView({
? "grid-rows-[minmax(0,1fr)_minmax(0,1fr)]"
: "grid-rows-[minmax(0,1fr)_0fr]"
)}
onTransitionEnd={(e) => {
if (e.propertyName === "grid-template-rows" && !feedbackDetail) {
feedbackDetailStaleRef.current = null;
}
}}
onTransitionEnd={handleFeedbackTransitionEnd}
>
<div className="relative h-full min-h-0 min-w-0">
<div className="pointer-events-none absolute left-3 top-3 z-10 flex items-center gap-1">
Expand Down Expand Up @@ -699,39 +607,14 @@ export function AgentsView({
)}
>
{feedbackDetailRendered ? (
"summaryAgentId" in feedbackDetailRendered ? (
(() => {
const summaryAgent = agents.find(
(a) => a.id === feedbackDetailRendered.summaryAgentId
);
return summaryAgent ? (
<ReviewSummaryPanel
key={`summary-${feedbackDetailRendered.summaryAgentId}`}
parentAgentId={feedbackDetailRendered.parentAgentId}
agent={summaryAgent}
onClose={closeFeedbackDetail}
/>
) : null;
})()
) : (
<FeedbackDetailPanel
key={feedbackDetailRendered.parentAgentId}
parentAgentId={feedbackDetailRendered.parentAgentId}
itemId={feedbackDetailRendered.itemId}
isConnected={
connectedAgentId ===
feedbackDetailRendered.parentAgentId
}
sendTerminalInput={sendTerminalInput}
onClose={closeFeedbackDetail}
onNavigate={(nextItemId) =>
navigateFeedbackItem(
feedbackDetailRendered.parentAgentId,
nextItemId
)
}
/>
)
<DesktopFeedbackDetail
detail={feedbackDetailRendered}
agents={agents}
connectedAgentId={connectedAgentId}
sendTerminalInput={sendTerminalInput}
onClose={closeFeedbackDetail}
onNavigateItem={navigateFeedbackItem}
/>
) : null}
</div>
) : null}
Expand Down Expand Up @@ -783,31 +666,14 @@ export function AgentsView({
</div>

{isMobile && feedbackDetail ? (
"summaryAgentId" in feedbackDetail ? (
(() => {
const summaryAgent = agents.find(
(a) => a.id === feedbackDetail.summaryAgentId
);
return summaryAgent ? (
<MobileReviewSummarySheet
parentAgentId={feedbackDetail.parentAgentId}
agent={summaryAgent}
onClose={closeFeedbackDetail}
/>
) : null;
})()
) : (
<MobileFeedbackSheet
parentAgentId={feedbackDetail.parentAgentId}
itemId={feedbackDetail.itemId}
isConnected={connectedAgentId === feedbackDetail.parentAgentId}
sendTerminalInput={sendTerminalInput}
onClose={closeFeedbackDetail}
onNavigate={(nextItemId) =>
navigateFeedbackItem(feedbackDetail.parentAgentId, nextItemId)
}
/>
)
<MobileFeedbackDetail
detail={feedbackDetail}
agents={agents}
connectedAgentId={connectedAgentId}
sendTerminalInput={sendTerminalInput}
onClose={closeFeedbackDetail}
onNavigateItem={navigateFeedbackItem}
/>
) : null}

{isMobile ? (
Expand Down
Loading