Add host selector to import agent modal

This commit is contained in:
Mohamed Boudra
2025-12-29 00:06:06 +07:00
parent 265caaf140
commit 5120f73f49
2 changed files with 98 additions and 6 deletions
@@ -35,7 +35,7 @@ import Animated, {
runOnJS,
} from "react-native-reanimated";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { Mic, Check, X, RefreshCcw } from "lucide-react-native";
import { Mic, Check, X, RefreshCcw, Monitor } from "lucide-react-native";
import { theme as defaultTheme } from "@/styles/theme";
import { useRecentPaths } from "@/hooks/use-recent-paths";
import { useRouter } from "expo-router";
@@ -169,7 +169,13 @@ type ImportCandidate = {
};
type ProviderFilter = "all" | AgentProvider;
type DropdownKey = "assistant" | "permissions" | "model" | "workingDir" | "baseBranch";
type DropdownKey =
| "assistant"
| "permissions"
| "model"
| "workingDir"
| "baseBranch"
| "host";
type RepoInfoState = {
cwd: string;
@@ -426,6 +432,7 @@ function AgentFlowModal({
"Selected host";
const selectedDaemonStatusLabel = formatConnectionStatus(selectedDaemonStatus);
const hasSelectedDaemon = Boolean(selectedServerId);
const hostBadgeLabel = hasSelectedDaemon ? selectedDaemonLabel : "Select host";
const selectedDaemonIsOffline = selectedDaemonStatus !== "online";
const selectedDaemonLastError = selectedDaemonConnection?.lastError?.trim();
const daemonAvailabilityError = !hasSelectedDaemon
@@ -1787,6 +1794,23 @@ function AgentFlowModal({
paddingRight={horizontalPaddingRight}
onClose={handleClose}
title={modalTitle}
rightContent={
isImportFlow ? (
<Pressable
style={styles.hostBadge}
onPress={() => openDropdownSheet("host")}
>
<Monitor size={14} color={defaultTheme.colors.mutedForeground} />
<Text style={styles.hostBadgeLabel}>{hostBadgeLabel}</Text>
<View
style={[
styles.hostStatusDot,
selectedDaemonStatus === "online" && styles.hostStatusDotOnline,
]}
/>
</Pressable>
) : undefined
}
/>
{isCreateFlow ? (
<>
@@ -2133,6 +2157,40 @@ function AgentFlowModal({
)}
</View>
)}
<DropdownSheet
title="Host"
visible={openDropdown === "host"}
onClose={closeDropdown}
>
{daemonEntries.length === 0 ? (
<Text style={styles.helperText}>No hosts available yet.</Text>
) : (
<View style={styles.dropdownSheetList}>
{daemonEntries.map(({ daemon, status }) => {
const isSelected = daemon.id === selectedServerId;
const label = daemon.label ?? daemon.wsUrl ?? daemon.id;
return (
<Pressable
key={daemon.id}
style={[
styles.dropdownSheetOption,
isSelected && styles.dropdownSheetOptionSelected,
]}
onPress={() => {
setSelectedServerIdFromUser(daemon.id);
closeDropdown();
}}
>
<Text style={styles.dropdownSheetOptionLabel}>{label}</Text>
<Text style={styles.dropdownSheetOptionDescription}>
{formatConnectionStatus(status)}
</Text>
</Pressable>
);
})}
</View>
)}
</DropdownSheet>
</View>
</Animated.View>
</View>
@@ -2175,6 +2233,7 @@ interface ModalHeaderProps {
paddingRight: number;
onClose: () => void;
title: string;
rightContent?: ReactNode;
}
function ModalHeader({
@@ -2183,13 +2242,17 @@ function ModalHeader({
paddingRight,
onClose,
title,
rightContent,
}: ModalHeaderProps): ReactElement {
return (
<View style={[styles.header, { paddingTop, paddingLeft, paddingRight }]}>
<Text style={styles.headerTitle}>{title}</Text>
<Pressable onPress={onClose} style={styles.closeButton} hitSlop={8}>
<X size={20} color={defaultTheme.colors.mutedForeground} />
</Pressable>
<View style={styles.headerActions}>
{rightContent}
<Pressable onPress={onClose} style={styles.closeButton} hitSlop={8}>
<X size={20} color={defaultTheme.colors.mutedForeground} />
</Pressable>
</View>
</View>
);
}
@@ -2652,12 +2715,40 @@ const styles = StyleSheet.create(((theme: any) => ({
fontSize: theme.fontSize["2xl"],
fontWeight: theme.fontWeight.semibold,
},
headerActions: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[3],
},
closeButton: {
padding: theme.spacing[2],
borderRadius: theme.borderRadius.full,
alignItems: "center",
justifyContent: "center",
},
hostBadge: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
paddingHorizontal: theme.spacing[3],
paddingVertical: theme.spacing[2],
backgroundColor: theme.colors.muted,
borderRadius: theme.borderRadius.full,
},
hostBadgeLabel: {
color: theme.colors.mutedForeground,
fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.semibold,
},
hostStatusDot: {
width: 6,
height: 6,
borderRadius: theme.borderRadius.full,
backgroundColor: theme.colors.mutedForeground,
},
hostStatusDotOnline: {
backgroundColor: theme.colors.palette.green[500],
},
scroll: {
flex: 1,
},
+2 -1
View File
@@ -58,11 +58,12 @@ Improvements to the new agent screen in the app.
- Add daemon-level E2E test to verify import works correctly.
- **Done (2025-12-28 23:58)**: WHAT: `packages/server/src/server/agent/providers/codex-mcp-agent.ts:4133-4476` adds Codex persisted session listing from rollout files plus JSON/JSONL timeline parsing and session metadata extraction; `packages/server/src/server/daemon.e2e.test.ts:2597-2662` adds Codex persisted agent import E2E coverage. RESULT: Codex import list now populates from on-disk rollouts with timeline previews and is covered by a daemon E2E. EVIDENCE: Not run (not requested).
- [ ] **Iteration 5**: Add host selector to import agent screen.
- [x] **Iteration 5**: Add host selector to import agent screen.
- When importing an agent, user should be able to choose which host to import to.
- Use the same UI pattern as the new agent screen: host selector in the header.
- Reference the new agent screen implementation for consistency.
- **Done (2025-12-29 00:05)**: WHAT: `packages/app/src/components/create-agent-modal.tsx:172-2193` adds a host dropdown key, renders the header host badge in the import flow, and adds a host selection sheet; `packages/app/src/components/create-agent-modal.tsx:2718-2751` adds host badge/header action styles. RESULT: import agent modal now lets users pick the target host from the header selector. EVIDENCE: Not run (not requested).
- [x] **Iteration 6**: Fix gallery 15s timeout causing full-screen error crash.