Add host selector to import agent modal
This commit is contained in:
@@ -35,7 +35,7 @@ import Animated, {
|
|||||||
runOnJS,
|
runOnJS,
|
||||||
} from "react-native-reanimated";
|
} from "react-native-reanimated";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
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 { theme as defaultTheme } from "@/styles/theme";
|
||||||
import { useRecentPaths } from "@/hooks/use-recent-paths";
|
import { useRecentPaths } from "@/hooks/use-recent-paths";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
@@ -169,7 +169,13 @@ type ImportCandidate = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ProviderFilter = "all" | AgentProvider;
|
type ProviderFilter = "all" | AgentProvider;
|
||||||
type DropdownKey = "assistant" | "permissions" | "model" | "workingDir" | "baseBranch";
|
type DropdownKey =
|
||||||
|
| "assistant"
|
||||||
|
| "permissions"
|
||||||
|
| "model"
|
||||||
|
| "workingDir"
|
||||||
|
| "baseBranch"
|
||||||
|
| "host";
|
||||||
|
|
||||||
type RepoInfoState = {
|
type RepoInfoState = {
|
||||||
cwd: string;
|
cwd: string;
|
||||||
@@ -426,6 +432,7 @@ function AgentFlowModal({
|
|||||||
"Selected host";
|
"Selected host";
|
||||||
const selectedDaemonStatusLabel = formatConnectionStatus(selectedDaemonStatus);
|
const selectedDaemonStatusLabel = formatConnectionStatus(selectedDaemonStatus);
|
||||||
const hasSelectedDaemon = Boolean(selectedServerId);
|
const hasSelectedDaemon = Boolean(selectedServerId);
|
||||||
|
const hostBadgeLabel = hasSelectedDaemon ? selectedDaemonLabel : "Select host";
|
||||||
const selectedDaemonIsOffline = selectedDaemonStatus !== "online";
|
const selectedDaemonIsOffline = selectedDaemonStatus !== "online";
|
||||||
const selectedDaemonLastError = selectedDaemonConnection?.lastError?.trim();
|
const selectedDaemonLastError = selectedDaemonConnection?.lastError?.trim();
|
||||||
const daemonAvailabilityError = !hasSelectedDaemon
|
const daemonAvailabilityError = !hasSelectedDaemon
|
||||||
@@ -1787,6 +1794,23 @@ function AgentFlowModal({
|
|||||||
paddingRight={horizontalPaddingRight}
|
paddingRight={horizontalPaddingRight}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
title={modalTitle}
|
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 ? (
|
{isCreateFlow ? (
|
||||||
<>
|
<>
|
||||||
@@ -2133,6 +2157,40 @@ function AgentFlowModal({
|
|||||||
)}
|
)}
|
||||||
</View>
|
</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>
|
</View>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
@@ -2175,6 +2233,7 @@ interface ModalHeaderProps {
|
|||||||
paddingRight: number;
|
paddingRight: number;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
title: string;
|
title: string;
|
||||||
|
rightContent?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModalHeader({
|
function ModalHeader({
|
||||||
@@ -2183,13 +2242,17 @@ function ModalHeader({
|
|||||||
paddingRight,
|
paddingRight,
|
||||||
onClose,
|
onClose,
|
||||||
title,
|
title,
|
||||||
|
rightContent,
|
||||||
}: ModalHeaderProps): ReactElement {
|
}: ModalHeaderProps): ReactElement {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.header, { paddingTop, paddingLeft, paddingRight }]}>
|
<View style={[styles.header, { paddingTop, paddingLeft, paddingRight }]}>
|
||||||
<Text style={styles.headerTitle}>{title}</Text>
|
<Text style={styles.headerTitle}>{title}</Text>
|
||||||
<Pressable onPress={onClose} style={styles.closeButton} hitSlop={8}>
|
<View style={styles.headerActions}>
|
||||||
<X size={20} color={defaultTheme.colors.mutedForeground} />
|
{rightContent}
|
||||||
</Pressable>
|
<Pressable onPress={onClose} style={styles.closeButton} hitSlop={8}>
|
||||||
|
<X size={20} color={defaultTheme.colors.mutedForeground} />
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2652,12 +2715,40 @@ const styles = StyleSheet.create(((theme: any) => ({
|
|||||||
fontSize: theme.fontSize["2xl"],
|
fontSize: theme.fontSize["2xl"],
|
||||||
fontWeight: theme.fontWeight.semibold,
|
fontWeight: theme.fontWeight.semibold,
|
||||||
},
|
},
|
||||||
|
headerActions: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[3],
|
||||||
|
},
|
||||||
closeButton: {
|
closeButton: {
|
||||||
padding: theme.spacing[2],
|
padding: theme.spacing[2],
|
||||||
borderRadius: theme.borderRadius.full,
|
borderRadius: theme.borderRadius.full,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "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: {
|
scroll: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -58,11 +58,12 @@ Improvements to the new agent screen in the app.
|
|||||||
- Add daemon-level E2E test to verify import works correctly.
|
- 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).
|
- **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.
|
- 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.
|
- Use the same UI pattern as the new agent screen: host selector in the header.
|
||||||
- Reference the new agent screen implementation for consistency.
|
- 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.
|
- [x] **Iteration 6**: Fix gallery 15s timeout causing full-screen error crash.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user