Add agent list sidebar on large screens
Show AgentList as a left sidebar (280px) when viewing an agent on screens >= 768px wide. The current agent is highlighted with a primary-colored border and accent background. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
7b00544caa
commit
fe5e86d514
@@ -19,6 +19,8 @@ import { MoreVertical, GitBranch, Folder, RotateCcw, PlusCircle, Download, Users
|
|||||||
import { BackHeader } from "@/components/headers/back-header";
|
import { BackHeader } from "@/components/headers/back-header";
|
||||||
import { AgentStreamView } from "@/components/agent-stream-view";
|
import { AgentStreamView } from "@/components/agent-stream-view";
|
||||||
import { AgentInputArea } from "@/components/agent-input-area";
|
import { AgentInputArea } from "@/components/agent-input-area";
|
||||||
|
import { AgentList } from "@/components/agent-list";
|
||||||
|
import { useAggregatedAgents } from "@/hooks/use-aggregated-agents";
|
||||||
import { CreateAgentModal, ImportAgentModal, type CreateAgentInitialValues } from "@/components/create-agent-modal";
|
import { CreateAgentModal, ImportAgentModal, type CreateAgentInitialValues } from "@/components/create-agent-modal";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
||||||
import type { ConnectionStatus } from "@/contexts/daemon-connections-context";
|
import type { ConnectionStatus } from "@/contexts/daemon-connections-context";
|
||||||
@@ -133,11 +135,17 @@ type AgentScreenContentProps = {
|
|||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SIDEBAR_WIDTH = 280;
|
||||||
|
const LARGE_SCREEN_BREAKPOINT = 768;
|
||||||
|
|
||||||
function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentProps) {
|
function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
||||||
|
const isLargeScreen = windowWidth >= LARGE_SCREEN_BREAKPOINT;
|
||||||
|
|
||||||
|
const { agents: aggregatedAgents, isRevalidating, refreshAll } = useAggregatedAgents();
|
||||||
const [menuVisible, setMenuVisible] = useState(false);
|
const [menuVisible, setMenuVisible] = useState(false);
|
||||||
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
||||||
const [menuContentHeight, setMenuContentHeight] = useState(0);
|
const [menuContentHeight, setMenuContentHeight] = useState(0);
|
||||||
@@ -531,43 +539,60 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{/* Header */}
|
<View style={[styles.mainLayout, isLargeScreen && styles.mainLayoutRow]}>
|
||||||
<BackHeader
|
{/* Sidebar - only on large screens */}
|
||||||
title={agent.title || "Agent"}
|
{isLargeScreen && (
|
||||||
onBack={handleBack}
|
<View style={[styles.sidebar, { width: SIDEBAR_WIDTH }]}>
|
||||||
rightContent={
|
<AgentList
|
||||||
<View ref={menuButtonRef} collapsable={false}>
|
agents={aggregatedAgents}
|
||||||
<Pressable onPress={handleOpenMenu} style={styles.menuButton}>
|
isRefreshing={isRevalidating}
|
||||||
<MoreVertical size={20} color={theme.colors.foreground} />
|
onRefresh={refreshAll}
|
||||||
</Pressable>
|
selectedAgentId={resolvedAgentId}
|
||||||
</View>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Content Area with Keyboard Animation */}
|
|
||||||
<View style={styles.contentContainer}>
|
|
||||||
<ReanimatedAnimated.View style={[styles.content, animatedKeyboardStyle]}>
|
|
||||||
{isInitializing ? (
|
|
||||||
<View style={styles.loadingContainer}>
|
|
||||||
<ActivityIndicator size="large" color={theme.colors.primary} />
|
|
||||||
<Text style={styles.loadingText}>Loading agent...</Text>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<AgentStreamView
|
|
||||||
agentId={agent.id}
|
|
||||||
serverId={serverId}
|
|
||||||
agent={agent}
|
|
||||||
streamItems={streamItems}
|
|
||||||
pendingPermissions={pendingPermissions}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</View>
|
||||||
</ReanimatedAnimated.View>
|
)}
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Agent Input Area */}
|
{/* Main agent panel */}
|
||||||
{!isInitializing && agent && resolvedAgentId && (
|
<View style={styles.agentPanel}>
|
||||||
<AgentInputArea agentId={resolvedAgentId} serverId={serverId} />
|
{/* Header */}
|
||||||
)}
|
<BackHeader
|
||||||
|
title={agent.title || "Agent"}
|
||||||
|
onBack={handleBack}
|
||||||
|
rightContent={
|
||||||
|
<View ref={menuButtonRef} collapsable={false}>
|
||||||
|
<Pressable onPress={handleOpenMenu} style={styles.menuButton}>
|
||||||
|
<MoreVertical size={20} color={theme.colors.foreground} />
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Content Area with Keyboard Animation */}
|
||||||
|
<View style={styles.contentContainer}>
|
||||||
|
<ReanimatedAnimated.View style={[styles.content, animatedKeyboardStyle]}>
|
||||||
|
{isInitializing ? (
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<ActivityIndicator size="large" color={theme.colors.primary} />
|
||||||
|
<Text style={styles.loadingText}>Loading agent...</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<AgentStreamView
|
||||||
|
agentId={agent.id}
|
||||||
|
serverId={serverId}
|
||||||
|
agent={agent}
|
||||||
|
streamItems={streamItems}
|
||||||
|
pendingPermissions={pendingPermissions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ReanimatedAnimated.View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Agent Input Area */}
|
||||||
|
{!isInitializing && agent && resolvedAgentId && (
|
||||||
|
<AgentInputArea agentId={resolvedAgentId} serverId={serverId} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Dropdown Menu */}
|
{/* Dropdown Menu */}
|
||||||
<Modal
|
<Modal
|
||||||
@@ -793,6 +818,19 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: theme.colors.background,
|
backgroundColor: theme.colors.background,
|
||||||
},
|
},
|
||||||
|
mainLayout: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
mainLayoutRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: theme.colors.border,
|
||||||
|
},
|
||||||
|
agentPanel: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
contentContainer: {
|
contentContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ interface AgentListProps {
|
|||||||
agents: AggregatedAgent[];
|
agents: AggregatedAgent[];
|
||||||
isRefreshing?: boolean;
|
isRefreshing?: boolean;
|
||||||
onRefresh?: () => void;
|
onRefresh?: () => void;
|
||||||
|
selectedAgentId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AgentList({ agents, isRefreshing = false, onRefresh }: AgentListProps) {
|
export function AgentList({ agents, isRefreshing = false, onRefresh, selectedAgentId }: AgentListProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const [actionAgent, setActionAgent] = useState<AggregatedAgent | null>(null);
|
const [actionAgent, setActionAgent] = useState<AggregatedAgent | null>(null);
|
||||||
|
|
||||||
@@ -87,11 +88,13 @@ export function AgentList({ agents, isRefreshing = false, onRefresh }: AgentList
|
|||||||
const timeAgo = formatTimeAgo(agent.lastActivityAt);
|
const timeAgo = formatTimeAgo(agent.lastActivityAt);
|
||||||
const providerLabel = getAgentProviderDefinition(agent.provider).label;
|
const providerLabel = getAgentProviderDefinition(agent.provider).label;
|
||||||
const isRunning = agent.status === "running";
|
const isRunning = agent.status === "running";
|
||||||
|
const isSelected = selectedAgentId === agent.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={({ pressed }) => [
|
style={({ pressed }) => [
|
||||||
styles.agentItem,
|
styles.agentItem,
|
||||||
|
isSelected && styles.agentItemSelected,
|
||||||
pressed && styles.agentItemPressed,
|
pressed && styles.agentItemPressed,
|
||||||
]}
|
]}
|
||||||
onPress={() => handleAgentPress(agent.serverId, agent.id)}
|
onPress={() => handleAgentPress(agent.serverId, agent.id)}
|
||||||
@@ -157,7 +160,7 @@ export function AgentList({ agents, isRefreshing = false, onRefresh }: AgentList
|
|||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[handleAgentLongPress, handleAgentPress, theme.colors.muted, theme.colors.mutedForeground, theme.colors.primary, theme.colors.primaryForeground]
|
[handleAgentLongPress, handleAgentPress, selectedAgentId, theme.colors.muted, theme.colors.mutedForeground, theme.colors.primary, theme.colors.primaryForeground]
|
||||||
);
|
);
|
||||||
|
|
||||||
const keyExtractor = useCallback(
|
const keyExtractor = useCallback(
|
||||||
@@ -247,6 +250,12 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
marginBottom: theme.spacing[2],
|
marginBottom: theme.spacing[2],
|
||||||
backgroundColor: theme.colors.muted,
|
backgroundColor: theme.colors.muted,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "transparent",
|
||||||
|
},
|
||||||
|
agentItemSelected: {
|
||||||
|
backgroundColor: theme.colors.accent,
|
||||||
|
borderColor: theme.colors.primary,
|
||||||
},
|
},
|
||||||
agentItemPressed: {
|
agentItemPressed: {
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
|
|||||||
Reference in New Issue
Block a user