Update files
This commit is contained in:
@@ -1,187 +0,0 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
|
||||||
import { Pressable, Text, View } from "react-native";
|
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
||||||
import { Copy, Info } from "lucide-react-native";
|
|
||||||
import * as Clipboard from "expo-clipboard";
|
|
||||||
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
|
||||||
import { Fonts } from "@/constants/theme";
|
|
||||||
import { useToast } from "@/contexts/toast-context";
|
|
||||||
|
|
||||||
export type AgentDetailsSheetProps = {
|
|
||||||
visible: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
agentId: string;
|
|
||||||
persistenceSessionId: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function AgentDetailsSheet({
|
|
||||||
visible,
|
|
||||||
onClose,
|
|
||||||
agentId,
|
|
||||||
persistenceSessionId,
|
|
||||||
}: AgentDetailsSheetProps) {
|
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
const details = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
label: "Agent ID",
|
|
||||||
value: agentId,
|
|
||||||
testID: "agent-details-agent-id",
|
|
||||||
copyLabel: "agent id",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Persistence session ID",
|
|
||||||
value: persistenceSessionId,
|
|
||||||
testID: "agent-details-persistence-session-id",
|
|
||||||
copyLabel: "session id",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[agentId, persistenceSessionId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCopy = useCallback(
|
|
||||||
async (value: string | null, label: string) => {
|
|
||||||
if (!value) return;
|
|
||||||
try {
|
|
||||||
await Clipboard.setStringAsync(value);
|
|
||||||
toast.copied(label);
|
|
||||||
} catch {
|
|
||||||
toast.error("Copy failed");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[toast]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AdaptiveModalSheet
|
|
||||||
title="Agent details"
|
|
||||||
visible={visible}
|
|
||||||
onClose={onClose}
|
|
||||||
testID="agent-details-sheet"
|
|
||||||
snapPoints={["45%", "70%"]}
|
|
||||||
>
|
|
||||||
{details.map((row) => (
|
|
||||||
<DetailRow
|
|
||||||
key={row.label}
|
|
||||||
label={row.label}
|
|
||||||
value={row.value}
|
|
||||||
onCopy={() => void handleCopy(row.value, row.copyLabel)}
|
|
||||||
testID={row.testID}
|
|
||||||
disabled={!row.value}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</AdaptiveModalSheet>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function DetailRow({
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
onCopy,
|
|
||||||
disabled,
|
|
||||||
testID,
|
|
||||||
}: {
|
|
||||||
label: string;
|
|
||||||
value: string | null;
|
|
||||||
onCopy: () => void;
|
|
||||||
disabled?: boolean;
|
|
||||||
testID: string;
|
|
||||||
}) {
|
|
||||||
const { theme } = useUnistyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
testID={testID}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={`${label} copy`}
|
|
||||||
accessibilityHint={disabled ? "Not available" : "Copies to clipboard"}
|
|
||||||
disabled={disabled}
|
|
||||||
onPress={onCopy}
|
|
||||||
style={({ pressed }) => [
|
|
||||||
styles.row,
|
|
||||||
disabled ? styles.rowDisabled : null,
|
|
||||||
pressed && !disabled ? styles.rowPressed : null,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<View style={styles.rowTop}>
|
|
||||||
<View style={styles.labelRow}>
|
|
||||||
<Info size={16} color={theme.colors.foregroundMuted} />
|
|
||||||
<Text style={styles.label}>{label}</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.copyPill}>
|
|
||||||
<Copy size={14} color={theme.colors.foregroundMuted} />
|
|
||||||
<Text style={styles.copyText}>Copy</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
testID={`${testID}-value`}
|
|
||||||
style={[styles.value, !value ? styles.valueEmpty : null]}
|
|
||||||
numberOfLines={2}
|
|
||||||
ellipsizeMode="middle"
|
|
||||||
>
|
|
||||||
{value ?? "Not available"}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
|
||||||
row: {
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
borderWidth: theme.borderWidth[1],
|
|
||||||
borderColor: theme.colors.surface2,
|
|
||||||
backgroundColor: theme.colors.surface1,
|
|
||||||
paddingHorizontal: theme.spacing[4],
|
|
||||||
paddingVertical: theme.spacing[4],
|
|
||||||
gap: theme.spacing[3],
|
|
||||||
},
|
|
||||||
rowPressed: {
|
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
},
|
|
||||||
rowDisabled: {
|
|
||||||
opacity: 0.65,
|
|
||||||
},
|
|
||||||
rowTop: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
gap: theme.spacing[3],
|
|
||||||
},
|
|
||||||
labelRow: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
fontWeight: theme.fontWeight.semibold,
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
copyPill: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
borderRadius: theme.borderRadius.full,
|
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
},
|
|
||||||
copyText: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
fontWeight: theme.fontWeight.medium,
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
color: theme.colors.foreground,
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
fontFamily: Fonts.mono,
|
|
||||||
},
|
|
||||||
valueEmpty: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontFamily: Fonts.sans,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
@@ -424,12 +424,20 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
|
|||||||
<DropdownMenuContent align="end" width={220}>
|
<DropdownMenuContent align="end" width={220}>
|
||||||
<View style={styles.contextMetaBlock}>
|
<View style={styles.contextMetaBlock}>
|
||||||
<View style={styles.contextMetaRow}>
|
<View style={styles.contextMetaRow}>
|
||||||
<Text style={styles.contextMetaLabel}>Size</Text>
|
<Text style={styles.contextMetaLabel} numberOfLines={1}>
|
||||||
<Text style={styles.contextMetaValue}>{formatFileSize({ size: entry.size })}</Text>
|
Size
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.contextMetaValue} numberOfLines={1} ellipsizeMode="tail">
|
||||||
|
{formatFileSize({ size: entry.size })}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.contextMetaRow}>
|
<View style={styles.contextMetaRow}>
|
||||||
<Text style={styles.contextMetaLabel}>Modified</Text>
|
<Text style={styles.contextMetaLabel} numberOfLines={1}>
|
||||||
<Text style={styles.contextMetaValue}>{formatTimeAgo(new Date(entry.modifiedAt))}</Text>
|
Modified
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.contextMetaValue} numberOfLines={1} ellipsizeMode="tail">
|
||||||
|
{formatTimeAgo(new Date(entry.modifiedAt))}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
@@ -1132,11 +1140,15 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
contextMetaLabel: {
|
contextMetaLabel: {
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
contextMetaValue: {
|
contextMetaValue: {
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.medium,
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
textAlign: "right",
|
||||||
},
|
},
|
||||||
previewHeaderText: {
|
previewHeaderText: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@@ -15,7 +15,19 @@ import { ScrollView, type ScrollView as ScrollViewType } from "react-native-gest
|
|||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import * as Linking from "expo-linking";
|
import * as Linking from "expo-linking";
|
||||||
import { Archive, ChevronDown, ChevronRight, GitBranch, MoreVertical, ListChevronsDownUp, ListChevronsUpDown } from "lucide-react-native";
|
import {
|
||||||
|
Archive,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronRight,
|
||||||
|
GitBranch,
|
||||||
|
GitCommitHorizontal,
|
||||||
|
GitMerge,
|
||||||
|
ListChevronsDownUp,
|
||||||
|
ListChevronsUpDown,
|
||||||
|
MoreVertical,
|
||||||
|
RefreshCcw,
|
||||||
|
Upload,
|
||||||
|
} from "lucide-react-native";
|
||||||
import { useCheckoutGitActionsStore } from "@/stores/checkout-git-actions-store";
|
import { useCheckoutGitActionsStore } from "@/stores/checkout-git-actions-store";
|
||||||
import {
|
import {
|
||||||
useCheckoutDiffQuery,
|
useCheckoutDiffQuery,
|
||||||
@@ -37,6 +49,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
type ActionStatus,
|
type ActionStatus,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { GitHubIcon } from "@/components/icons/github-icon";
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Git Actions Data Structure
|
// Git Actions Data Structure
|
||||||
@@ -856,6 +869,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
successLabel: "Committed",
|
successLabel: "Committed",
|
||||||
disabled: commitDisabled,
|
disabled: commitDisabled,
|
||||||
status: commitStatus,
|
status: commitStatus,
|
||||||
|
icon: <GitCommitHorizontal size={16} color={theme.colors.foregroundMuted} />,
|
||||||
handler: handleCommit,
|
handler: handleCommit,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -869,6 +883,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
disabled: pushDisabled,
|
disabled: pushDisabled,
|
||||||
status: pushStatus,
|
status: pushStatus,
|
||||||
description: !hasRemote ? "No remote configured" : undefined,
|
description: !hasRemote ? "No remote configured" : undefined,
|
||||||
|
icon: <Upload size={16} color={theme.colors.foregroundMuted} />,
|
||||||
handler: handlePush,
|
handler: handlePush,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -883,6 +898,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
successLabel: "View PR",
|
successLabel: "View PR",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
status: "idle",
|
status: "idle",
|
||||||
|
icon: <GitHubIcon size={16} color={theme.colors.foregroundMuted} />,
|
||||||
handler: () => openURLInNewTab(prUrl),
|
handler: () => openURLInNewTab(prUrl),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -896,6 +912,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
successLabel: "PR Created",
|
successLabel: "PR Created",
|
||||||
disabled: prDisabled,
|
disabled: prDisabled,
|
||||||
status: prCreateStatus,
|
status: prCreateStatus,
|
||||||
|
icon: <GitHubIcon size={16} color={theme.colors.foregroundMuted} />,
|
||||||
handler: handleCreatePr,
|
handler: handleCreatePr,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -910,6 +927,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
disabled: mergeDisabled,
|
disabled: mergeDisabled,
|
||||||
status: mergeStatus,
|
status: mergeStatus,
|
||||||
description: hasUncommittedChanges ? "Requires clean working tree" : undefined,
|
description: hasUncommittedChanges ? "Requires clean working tree" : undefined,
|
||||||
|
icon: <GitMerge size={16} color={theme.colors.foregroundMuted} />,
|
||||||
handler: handleMergeBranch,
|
handler: handleMergeBranch,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -924,6 +942,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
disabled: mergeFromBaseDisabled,
|
disabled: mergeFromBaseDisabled,
|
||||||
status: mergeFromBaseStatus,
|
status: mergeFromBaseStatus,
|
||||||
description: hasUncommittedChanges ? "Requires clean working tree" : undefined,
|
description: hasUncommittedChanges ? "Requires clean working tree" : undefined,
|
||||||
|
icon: <RefreshCcw size={16} color={theme.colors.foregroundMuted} />,
|
||||||
handler: handleMergeFromBase,
|
handler: handleMergeFromBase,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -994,6 +1013,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
commitDisabled, pushDisabled, prDisabled, mergeDisabled, mergeFromBaseDisabled, archiveDisabled,
|
commitDisabled, pushDisabled, prDisabled, mergeDisabled, mergeFromBaseDisabled, archiveDisabled,
|
||||||
commitStatus, pushStatus, prCreateStatus, mergeStatus, mergeFromBaseStatus, archiveStatus,
|
commitStatus, pushStatus, prCreateStatus, mergeStatus, mergeFromBaseStatus, archiveStatus,
|
||||||
handleCommit, handlePush, handleCreatePr, handleMergeBranch, handleMergeFromBase, handleArchiveWorktree,
|
handleCommit, handlePush, handleCreatePr, handleMergeBranch, handleMergeFromBase, handleArchiveWorktree,
|
||||||
|
theme.colors.foregroundMuted,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Helper to get display label based on status
|
// Helper to get display label based on status
|
||||||
@@ -1033,7 +1053,10 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
{gitActions.primary.status === "pending" ? (
|
{gitActions.primary.status === "pending" ? (
|
||||||
<ActivityIndicator size="small" color={theme.colors.foreground} style={styles.splitButtonSpinner} />
|
<ActivityIndicator size="small" color={theme.colors.foreground} style={styles.splitButtonSpinner} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.splitButtonText}>{getActionDisplayLabel(gitActions.primary)}</Text>
|
<View style={styles.splitButtonContent}>
|
||||||
|
{gitActions.primary.icon}
|
||||||
|
<Text style={styles.splitButtonText}>{getActionDisplayLabel(gitActions.primary)}</Text>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
{gitActions.secondary.length > 0 ? (
|
{gitActions.secondary.length > 0 ? (
|
||||||
@@ -1054,6 +1077,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
{needsSeparator && index > 0 ? <DropdownMenuSeparator /> : null}
|
{needsSeparator && index > 0 ? <DropdownMenuSeparator /> : null}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
testID={`changes-menu-${action.id}`}
|
testID={`changes-menu-${action.id}`}
|
||||||
|
leading={action.icon}
|
||||||
disabled={action.disabled}
|
disabled={action.disabled}
|
||||||
status={action.status}
|
status={action.status}
|
||||||
pendingLabel={action.pendingLabel}
|
pendingLabel={action.pendingLabel}
|
||||||
@@ -1077,7 +1101,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
testID="changes-overflow-menu"
|
testID="changes-overflow-menu"
|
||||||
hitSlop={8}
|
hitSlop={8}
|
||||||
style={styles.iconButton}
|
style={[styles.iconButton, styles.overflowMenuButton]}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel="More actions"
|
accessibilityLabel="More actions"
|
||||||
>
|
>
|
||||||
@@ -1280,6 +1304,12 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.medium,
|
||||||
},
|
},
|
||||||
|
splitButtonContent: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
},
|
||||||
splitButtonSpinner: {
|
splitButtonSpinner: {
|
||||||
height: theme.fontSize.xs * 1.5,
|
height: theme.fontSize.xs * 1.5,
|
||||||
width: theme.fontSize.xs * 1.5,
|
width: theme.fontSize.xs * 1.5,
|
||||||
@@ -1298,6 +1328,9 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
},
|
},
|
||||||
|
overflowMenuButton: {
|
||||||
|
marginRight: -theme.spacing[2],
|
||||||
|
},
|
||||||
menuOverlay: {
|
menuOverlay: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import Svg, { Path } from "react-native-svg";
|
||||||
|
|
||||||
|
interface GitHubIconProps {
|
||||||
|
size?: number;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GitHubIcon({ size = 16, color = "currentColor" }: GitHubIconProps) {
|
||||||
|
return (
|
||||||
|
<Svg width={size} height={size} viewBox="0 -0.5 25 25" fill={color}>
|
||||||
|
<Path d="m12.301 0h.093c2.242 0 4.34.613 6.137 1.68l-.055-.031c1.871 1.094 3.386 2.609 4.449 4.422l.031.058c1.04 1.769 1.654 3.896 1.654 6.166 0 5.406-3.483 10-8.327 11.658l-.087.026c-.063.02-.135.031-.209.031-.162 0-.312-.054-.433-.144l.002.001c-.128-.115-.208-.281-.208-.466 0-.005 0-.01 0-.014v.001q0-.048.008-1.226t.008-2.154c.007-.075.011-.161.011-.249 0-.792-.323-1.508-.844-2.025.618-.061 1.176-.163 1.718-.305l-.076.017c.573-.16 1.073-.373 1.537-.642l-.031.017c.508-.28.938-.636 1.292-1.058l.006-.007c.372-.476.663-1.036.84-1.645l.009-.035c.209-.683.329-1.468.329-2.281 0-.045 0-.091-.001-.136v.007c0-.022.001-.047.001-.072 0-1.248-.482-2.383-1.269-3.23l.003.003c.168-.44.265-.948.265-1.479 0-.649-.145-1.263-.404-1.814l.011.026c-.115-.022-.246-.035-.381-.035-.334 0-.649.078-.929.216l.012-.005c-.568.21-1.054.448-1.512.726l.038-.022-.609.384c-.922-.264-1.981-.416-3.075-.416s-2.153.152-3.157.436l.081-.02q-.256-.176-.681-.433c-.373-.214-.814-.421-1.272-.595l-.066-.022c-.293-.154-.64-.244-1.009-.244-.124 0-.246.01-.364.03l.013-.002c-.248.524-.393 1.139-.393 1.788 0 .531.097 1.04.275 1.509l-.01-.029c-.785.844-1.266 1.979-1.266 3.227 0 .025 0 .051.001.076v-.004c-.001.039-.001.084-.001.13 0 .809.12 1.591.344 2.327l-.015-.057c.189.643.476 1.202.85 1.693l-.009-.013c.354.435.782.793 1.267 1.062l.022.011c.432.252.933.465 1.46.614l.046.011c.466.125 1.024.227 1.595.284l.046.004c-.431.428-.718 1-.784 1.638l-.001.012c-.207.101-.448.183-.699.236l-.021.004c-.256.051-.549.08-.85.08-.022 0-.044 0-.066 0h.003c-.394-.008-.756-.136-1.055-.348l.006.004c-.371-.259-.671-.595-.881-.986l-.007-.015c-.198-.336-.459-.614-.768-.827l-.009-.006c-.225-.169-.49-.301-.776-.38l-.016-.004-.32-.048c-.023-.002-.05-.003-.077-.003-.14 0-.273.028-.394.077l.007-.003q-.128.072-.08.184c.039.086.087.16.145.225l-.001-.001c.061.072.13.135.205.19l.003.002.112.08c.283.148.516.354.693.603l.004.006c.191.237.359.505.494.792l.01.024.16.368c.135.402.38.738.7.981l.005.004c.3.234.662.402 1.057.478l.016.002c.33.064.714.104 1.106.112h.007c.045.002.097.002.15.002.261 0 .517-.021.767-.062l-.027.004.368-.064q0 .609.008 1.418t.008.873v.014c0 .185-.08.351-.208.466h-.001c-.119.089-.268.143-.431.143-.075 0-.147-.011-.214-.032l.005.001c-4.929-1.689-8.409-6.283-8.409-11.69 0-2.268.612-4.393 1.681-6.219l-.032.058c1.094-1.871 2.609-3.386 4.422-4.449l.058-.031c1.739-1.034 3.835-1.645 6.073-1.645h.098-.005zm-7.64 17.666q.048-.112-.112-.192-.16-.048-.208.032-.048.112.112.192.144.096.208-.032zm.497.545q.112-.08-.032-.256-.16-.144-.256-.048-.112.08.032.256.159.157.256.047zm.48.72q.144-.112 0-.304-.128-.208-.272-.096-.144.08 0 .288t.272.112zm.672.673q.128-.128-.064-.304-.192-.192-.32-.048-.144.128.064.304.192.192.32.044zm.913.4q.048-.176-.208-.256-.24-.064-.304.112t.208.24q.24.097.304-.096zm1.009.08q0-.208-.272-.176-.256 0-.256.176 0 .208.272.176.256.001.256-.175zm.929-.16q-.032-.176-.288-.144-.256.048-.224.24t.288.128.225-.224z" />
|
||||||
|
</Svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -25,11 +25,11 @@ import { CheckCircle2, AlertTriangle } from "lucide-react-native";
|
|||||||
type ToastVariant = "default" | "success" | "error";
|
type ToastVariant = "default" | "success" | "error";
|
||||||
|
|
||||||
export type ToastShowOptions = {
|
export type ToastShowOptions = {
|
||||||
|
icon?: ReactNode;
|
||||||
variant?: ToastVariant;
|
variant?: ToastVariant;
|
||||||
durationMs?: number;
|
durationMs?: number;
|
||||||
/**
|
/**
|
||||||
* On Android we prefer the OS toast by default.
|
* Set to true to use OS toast on Android.
|
||||||
* Set to false to force the in-app toast.
|
|
||||||
*/
|
*/
|
||||||
nativeAndroid?: boolean;
|
nativeAndroid?: boolean;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
@@ -37,14 +37,16 @@ export type ToastShowOptions = {
|
|||||||
|
|
||||||
type ToastState = {
|
type ToastState = {
|
||||||
id: number;
|
id: number;
|
||||||
message: string;
|
content: ReactNode;
|
||||||
|
nativeMessage: string | null;
|
||||||
|
icon?: ReactNode;
|
||||||
variant: ToastVariant;
|
variant: ToastVariant;
|
||||||
durationMs: number;
|
durationMs: number;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ToastApi = {
|
export type ToastApi = {
|
||||||
show: (message: string, options?: ToastShowOptions) => void;
|
show: (content: ReactNode, options?: ToastShowOptions) => void;
|
||||||
copied: (label?: string) => void;
|
copied: (label?: string) => void;
|
||||||
error: (message: string) => void;
|
error: (message: string) => void;
|
||||||
};
|
};
|
||||||
@@ -66,23 +68,26 @@ export function ToastProvider({ children }: { children: ReactNode }) {
|
|||||||
const idRef = useRef(0);
|
const idRef = useRef(0);
|
||||||
|
|
||||||
const show = useCallback(
|
const show = useCallback(
|
||||||
(message: string, options?: ToastShowOptions) => {
|
(content: ReactNode, options?: ToastShowOptions) => {
|
||||||
const resolvedMessage = message.trim();
|
const nativeMessage =
|
||||||
if (!resolvedMessage) return;
|
typeof content === "string"
|
||||||
|
? content.trim()
|
||||||
|
: null;
|
||||||
|
if (!content || nativeMessage === "") return;
|
||||||
|
|
||||||
const variant = options?.variant ?? "default";
|
const variant = options?.variant ?? "default";
|
||||||
const durationMs = options?.durationMs ?? DEFAULT_DURATION_MS;
|
const durationMs = options?.durationMs ?? DEFAULT_DURATION_MS;
|
||||||
const nativeAndroid = options?.nativeAndroid ?? true;
|
const nativeAndroid = options?.nativeAndroid ?? false;
|
||||||
|
|
||||||
if (Platform.OS === "android" && nativeAndroid) {
|
if (Platform.OS === "android" && nativeAndroid && nativeMessage) {
|
||||||
const duration =
|
const duration =
|
||||||
durationMs <= 2500
|
durationMs <= 2500
|
||||||
? ToastAndroid.SHORT
|
? ToastAndroid.SHORT
|
||||||
: ToastAndroid.LONG;
|
: ToastAndroid.LONG;
|
||||||
ToastAndroid.showWithGravity(
|
ToastAndroid.showWithGravity(
|
||||||
resolvedMessage,
|
nativeMessage,
|
||||||
duration,
|
duration,
|
||||||
ToastAndroid.BOTTOM
|
ToastAndroid.TOP
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -90,7 +95,9 @@ export function ToastProvider({ children }: { children: ReactNode }) {
|
|||||||
idRef.current += 1;
|
idRef.current += 1;
|
||||||
setToast({
|
setToast({
|
||||||
id: idRef.current,
|
id: idRef.current,
|
||||||
message: resolvedMessage,
|
content,
|
||||||
|
nativeMessage,
|
||||||
|
icon: options?.icon,
|
||||||
variant,
|
variant,
|
||||||
durationMs,
|
durationMs,
|
||||||
testID: options?.testID,
|
testID: options?.testID,
|
||||||
@@ -103,7 +110,10 @@ export function ToastProvider({ children }: { children: ReactNode }) {
|
|||||||
() => ({
|
() => ({
|
||||||
show,
|
show,
|
||||||
copied: (label?: string) =>
|
copied: (label?: string) =>
|
||||||
show(label ? `Copied ${label}` : "Copied", { variant: "success" }),
|
show(label ? `Copied ${label}` : "Copied", {
|
||||||
|
variant: "success",
|
||||||
|
icon: <CheckCircle2 size={18} />,
|
||||||
|
}),
|
||||||
error: (message: string) => show(message, { variant: "error", durationMs: 3200 }),
|
error: (message: string) => show(message, { variant: "error", durationMs: 3200 }),
|
||||||
}),
|
}),
|
||||||
[show]
|
[show]
|
||||||
@@ -127,7 +137,7 @@ function ToastViewport({
|
|||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const opacity = useRef(new Animated.Value(0)).current;
|
const opacity = useRef(new Animated.Value(0)).current;
|
||||||
const translateY = useRef(new Animated.Value(8)).current;
|
const translateY = useRef(new Animated.Value(-8)).current;
|
||||||
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
const clearTimer = useCallback(() => {
|
const clearTimer = useCallback(() => {
|
||||||
@@ -147,7 +157,7 @@ function ToastViewport({
|
|||||||
useNativeDriver: true,
|
useNativeDriver: true,
|
||||||
}),
|
}),
|
||||||
Animated.timing(translateY, {
|
Animated.timing(translateY, {
|
||||||
toValue: 8,
|
toValue: -8,
|
||||||
duration: 140,
|
duration: 140,
|
||||||
easing: Easing.out(Easing.quad),
|
easing: Easing.out(Easing.quad),
|
||||||
useNativeDriver: true,
|
useNativeDriver: true,
|
||||||
@@ -163,13 +173,13 @@ function ToastViewport({
|
|||||||
if (!toast) {
|
if (!toast) {
|
||||||
clearTimer();
|
clearTimer();
|
||||||
opacity.setValue(0);
|
opacity.setValue(0);
|
||||||
translateY.setValue(8);
|
translateY.setValue(-8);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTimer();
|
clearTimer();
|
||||||
opacity.setValue(0);
|
opacity.setValue(0);
|
||||||
translateY.setValue(8);
|
translateY.setValue(-8);
|
||||||
|
|
||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
Animated.timing(opacity, {
|
Animated.timing(opacity, {
|
||||||
@@ -200,11 +210,13 @@ function ToastViewport({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const icon =
|
const icon =
|
||||||
toast.variant === "success" ? (
|
toast.icon ?? (
|
||||||
|
toast.variant === "success" ? (
|
||||||
<CheckCircle2 size={18} color={theme.colors.primary} />
|
<CheckCircle2 size={18} color={theme.colors.primary} />
|
||||||
) : toast.variant === "error" ? (
|
) : toast.variant === "error" ? (
|
||||||
<AlertTriangle size={18} color={theme.colors.destructive} />
|
<AlertTriangle size={18} color={theme.colors.destructive} />
|
||||||
) : null;
|
) : null
|
||||||
|
);
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<View style={styles.container} pointerEvents="box-none">
|
<View style={styles.container} pointerEvents="box-none">
|
||||||
@@ -212,8 +224,10 @@ function ToastViewport({
|
|||||||
testID={toast.testID ?? "app-toast"}
|
testID={toast.testID ?? "app-toast"}
|
||||||
style={[
|
style={[
|
||||||
styles.toast,
|
styles.toast,
|
||||||
|
toast.variant === "success" ? styles.toastSuccess : null,
|
||||||
|
toast.variant === "error" ? styles.toastError : null,
|
||||||
{
|
{
|
||||||
marginBottom: theme.spacing[4] + insets.bottom,
|
marginTop: theme.spacing[2] + insets.top,
|
||||||
opacity,
|
opacity,
|
||||||
transform: [{ translateY }],
|
transform: [{ translateY }],
|
||||||
},
|
},
|
||||||
@@ -221,16 +235,22 @@ function ToastViewport({
|
|||||||
accessibilityRole="alert"
|
accessibilityRole="alert"
|
||||||
>
|
>
|
||||||
{icon ? <View style={styles.iconSlot}>{icon}</View> : null}
|
{icon ? <View style={styles.iconSlot}>{icon}</View> : null}
|
||||||
<Text
|
{typeof toast.content === "string" ? (
|
||||||
testID="app-toast-message"
|
<Text
|
||||||
style={[
|
testID="app-toast-message"
|
||||||
styles.message,
|
style={[
|
||||||
toast.variant === "error" ? styles.messageError : null,
|
styles.message,
|
||||||
]}
|
toast.variant === "error" ? styles.messageError : null,
|
||||||
numberOfLines={2}
|
]}
|
||||||
>
|
numberOfLines={2}
|
||||||
{toast.message}
|
>
|
||||||
</Text>
|
{toast.content}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<View testID="app-toast-message" style={styles.contentSlot}>
|
||||||
|
{toast.content}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -248,18 +268,18 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: theme.spacing[4],
|
left: theme.spacing[4],
|
||||||
right: theme.spacing[4],
|
right: theme.spacing[4],
|
||||||
bottom: 0,
|
top: 0,
|
||||||
zIndex: OVERLAY_Z.toast,
|
zIndex: OVERLAY_Z.toast,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
toast: {
|
toast: {
|
||||||
width: "100%",
|
alignSelf: "center",
|
||||||
maxWidth: 520,
|
maxWidth: "92%",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[3],
|
gap: theme.spacing[3],
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface0,
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.full,
|
||||||
borderWidth: theme.borderWidth[1],
|
borderWidth: theme.borderWidth[1],
|
||||||
borderColor: theme.colors.border,
|
borderColor: theme.colors.border,
|
||||||
paddingVertical: theme.spacing[3],
|
paddingVertical: theme.spacing[3],
|
||||||
@@ -270,15 +290,25 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
shadowRadius: 8,
|
shadowRadius: 8,
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
},
|
},
|
||||||
|
toastSuccess: {
|
||||||
|
borderColor: theme.colors.border,
|
||||||
|
},
|
||||||
|
toastError: {
|
||||||
|
borderColor: theme.colors.destructive,
|
||||||
|
},
|
||||||
iconSlot: {
|
iconSlot: {
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
|
contentSlot: {
|
||||||
|
flexShrink: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
},
|
||||||
message: {
|
message: {
|
||||||
flex: 1,
|
flexShrink: 1,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.normal,
|
||||||
},
|
},
|
||||||
messageError: {
|
messageError: {
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
AppState,
|
AppState,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
|
import * as Clipboard from "expo-clipboard";
|
||||||
import { useFocusEffect } from "@react-navigation/native";
|
import { useFocusEffect } from "@react-navigation/native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
|
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
|
||||||
@@ -28,14 +29,13 @@ import {
|
|||||||
Folder,
|
Folder,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
PanelRight,
|
PanelRight,
|
||||||
Info,
|
CheckCircle2,
|
||||||
} from "lucide-react-native";
|
} from "lucide-react-native";
|
||||||
import { MenuHeader } from "@/components/headers/menu-header";
|
import { MenuHeader } from "@/components/headers/menu-header";
|
||||||
import { BackHeader } from "@/components/headers/back-header";
|
import { BackHeader } from "@/components/headers/back-header";
|
||||||
import { HeaderToggleButton } from "@/components/headers/header-toggle-button";
|
import { HeaderToggleButton } from "@/components/headers/header-toggle-button";
|
||||||
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 { AgentDetailsSheet } from "@/components/agent-details-sheet";
|
|
||||||
import { ExplorerSidebar } from "@/components/explorer-sidebar";
|
import { ExplorerSidebar } from "@/components/explorer-sidebar";
|
||||||
import { FileDropZone } from "@/components/file-drop-zone";
|
import { FileDropZone } from "@/components/file-drop-zone";
|
||||||
import type { ImageAttachment } from "@/components/message-input";
|
import type { ImageAttachment } from "@/components/message-input";
|
||||||
@@ -63,6 +63,7 @@ import { shortenPath } from "@/utils/shorten-path";
|
|||||||
import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info";
|
import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info";
|
||||||
import { useCheckoutStatusQuery } from "@/hooks/use-checkout-status-query";
|
import { useCheckoutStatusQuery } from "@/hooks/use-checkout-status-query";
|
||||||
import { useAgentInitialization } from "@/hooks/use-agent-initialization";
|
import { useAgentInitialization } from "@/hooks/use-agent-initialization";
|
||||||
|
import { useToast } from "@/contexts/toast-context";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -178,9 +179,9 @@ function AgentScreenContent({
|
|||||||
agentId,
|
agentId,
|
||||||
}: AgentScreenContentProps) {
|
}: AgentScreenContentProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
|
const toast = useToast();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
||||||
|
|
||||||
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
|
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
|
||||||
|
|
||||||
@@ -471,6 +472,11 @@ function AgentScreenContent({
|
|||||||
}, [resolvedAgentId, serverId, shouldUseOptimisticStream]);
|
}, [resolvedAgentId, serverId, shouldUseOptimisticStream]);
|
||||||
|
|
||||||
const effectiveAgent = agent ?? placeholderAgent;
|
const effectiveAgent = agent ?? placeholderAgent;
|
||||||
|
const providerLabel = (effectiveAgent?.provider ?? "Provider").replace(/^\w/, (m) =>
|
||||||
|
m.toUpperCase()
|
||||||
|
);
|
||||||
|
const providerSessionId =
|
||||||
|
effectiveAgent?.runtimeInfo?.sessionId ?? effectiveAgent?.persistence?.sessionId ?? null;
|
||||||
|
|
||||||
// Header subtitle: project path + branch (matching agent list row format)
|
// Header subtitle: project path + branch (matching agent list row format)
|
||||||
const headerProjectPath = effectiveAgent
|
const headerProjectPath = effectiveAgent
|
||||||
@@ -584,16 +590,6 @@ function AgentScreenContent({
|
|||||||
}
|
}
|
||||||
}, [resolvedAgentId, agent?.status, client]);
|
}, [resolvedAgentId, agent?.status, client]);
|
||||||
|
|
||||||
const handleViewChanges = useCallback(() => {
|
|
||||||
setExplorerTab("changes");
|
|
||||||
openFileExplorer();
|
|
||||||
}, [setExplorerTab, openFileExplorer]);
|
|
||||||
|
|
||||||
const handleBrowseFiles = useCallback(() => {
|
|
||||||
setExplorerTab("files");
|
|
||||||
openFileExplorer();
|
|
||||||
}, [setExplorerTab, openFileExplorer]);
|
|
||||||
|
|
||||||
const handleRefreshAgent = useCallback(() => {
|
const handleRefreshAgent = useCallback(() => {
|
||||||
if (!resolvedAgentId) {
|
if (!resolvedAgentId) {
|
||||||
return;
|
return;
|
||||||
@@ -603,6 +599,24 @@ function AgentScreenContent({
|
|||||||
});
|
});
|
||||||
}, [resolvedAgentId, refreshAgent]);
|
}, [resolvedAgentId, refreshAgent]);
|
||||||
|
|
||||||
|
const handleCopyMeta = useCallback(
|
||||||
|
async (label: string, value: string | null | undefined) => {
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await Clipboard.setStringAsync(value);
|
||||||
|
toast.show(`Copied ${label}`, {
|
||||||
|
variant: "success",
|
||||||
|
icon: <CheckCircle2 size={16} color={theme.colors.primary} />,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
toast.error("Copy failed");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[theme.colors.primary, toast]
|
||||||
|
);
|
||||||
|
|
||||||
if (!effectiveAgent) {
|
if (!effectiveAgent) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container} testID="agent-not-found">
|
<View style={styles.container} testID="agent-not-found">
|
||||||
@@ -677,19 +691,39 @@ function AgentScreenContent({
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" width={DROPDOWN_WIDTH} testID="agent-overflow-content">
|
<DropdownMenuContent align="end" width={DROPDOWN_WIDTH} testID="agent-overflow-content">
|
||||||
<View style={styles.menuMetaContainer}>
|
<View style={styles.menuMetaContainer}>
|
||||||
<View style={styles.menuMetaRow}>
|
<Pressable
|
||||||
<Text style={styles.menuMetaLabel}>Directory</Text>
|
style={({ hovered, pressed }) => [
|
||||||
|
styles.menuMetaRow,
|
||||||
|
(hovered || pressed) && styles.menuMetaRowActive,
|
||||||
|
]}
|
||||||
|
onPress={() => {
|
||||||
|
void handleCopyMeta("Directory", effectiveAgent.cwd);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.menuMetaLabel} numberOfLines={1}>
|
||||||
|
Directory
|
||||||
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
style={styles.menuMetaValue}
|
style={styles.menuMetaValue}
|
||||||
numberOfLines={2}
|
numberOfLines={1}
|
||||||
ellipsizeMode="middle"
|
ellipsizeMode="middle"
|
||||||
>
|
>
|
||||||
{shortenPath(effectiveAgent.cwd)}
|
{shortenPath(effectiveAgent.cwd)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</Pressable>
|
||||||
|
|
||||||
<View style={styles.menuMetaRow}>
|
<Pressable
|
||||||
<Text style={styles.menuMetaLabel}>Model</Text>
|
style={({ hovered, pressed }) => [
|
||||||
|
styles.menuMetaRow,
|
||||||
|
(hovered || pressed) && styles.menuMetaRowActive,
|
||||||
|
]}
|
||||||
|
onPress={() => {
|
||||||
|
void handleCopyMeta("Model", modelDisplayValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.menuMetaLabel} numberOfLines={1}>
|
||||||
|
Model
|
||||||
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
style={styles.menuMetaValue}
|
style={styles.menuMetaValue}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
@@ -697,48 +731,80 @@ function AgentScreenContent({
|
|||||||
>
|
>
|
||||||
{modelDisplayValue}
|
{modelDisplayValue}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</Pressable>
|
||||||
|
|
||||||
{checkout?.isGit && checkout.currentBranch && checkout.currentBranch !== "HEAD" ? (
|
{checkout?.isGit && checkout.currentBranch && checkout.currentBranch !== "HEAD" ? (
|
||||||
<View style={styles.menuMetaRow}>
|
<Pressable
|
||||||
<Text style={styles.menuMetaLabel}>Branch</Text>
|
style={({ hovered, pressed }) => [
|
||||||
<View style={styles.menuMetaValueRow}>
|
styles.menuMetaRow,
|
||||||
{checkoutStatusQuery.isFetching ? (
|
(hovered || pressed) && styles.menuMetaRowActive,
|
||||||
<>
|
]}
|
||||||
<ActivityIndicator
|
onPress={() => {
|
||||||
size="small"
|
if (checkoutStatusQuery.isFetching) {
|
||||||
color={theme.colors.foregroundMuted}
|
return;
|
||||||
/>
|
}
|
||||||
<Text style={styles.menuMetaPendingText}>Fetching…</Text>
|
void handleCopyMeta("Branch", checkout.currentBranch);
|
||||||
</>
|
}}
|
||||||
) : (
|
>
|
||||||
<Text
|
<Text style={styles.menuMetaLabel} numberOfLines={1}>
|
||||||
style={styles.menuMetaValue}
|
Branch
|
||||||
numberOfLines={1}
|
</Text>
|
||||||
ellipsizeMode="middle"
|
<Text
|
||||||
>
|
style={styles.menuMetaValue}
|
||||||
{checkout.currentBranch}
|
numberOfLines={1}
|
||||||
</Text>
|
ellipsizeMode="middle"
|
||||||
)}
|
>
|
||||||
</View>
|
{checkoutStatusQuery.isFetching ? "Fetching…" : checkout.currentBranch}
|
||||||
</View>
|
</Text>
|
||||||
|
</Pressable>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<Pressable
|
||||||
|
style={({ hovered, pressed }) => [
|
||||||
|
styles.menuMetaRow,
|
||||||
|
(hovered || pressed) && styles.menuMetaRowActive,
|
||||||
|
]}
|
||||||
|
onPress={() => {
|
||||||
|
void handleCopyMeta("Paseo ID", effectiveAgent.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.menuMetaLabel} numberOfLines={1}>
|
||||||
|
Paseo ID
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={styles.menuMetaValue}
|
||||||
|
numberOfLines={1}
|
||||||
|
ellipsizeMode="middle"
|
||||||
|
>
|
||||||
|
{effectiveAgent.id}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
|
||||||
|
<Pressable
|
||||||
|
style={({ hovered, pressed }) => [
|
||||||
|
styles.menuMetaRow,
|
||||||
|
providerSessionId && (hovered || pressed) && styles.menuMetaRowActive,
|
||||||
|
]}
|
||||||
|
disabled={!providerSessionId}
|
||||||
|
onPress={() => {
|
||||||
|
void handleCopyMeta(`${providerLabel} ID`, providerSessionId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.menuMetaLabel} numberOfLines={1}>
|
||||||
|
{providerLabel} ID
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={[styles.menuMetaValue, !providerSessionId && styles.menuMetaValueError]}
|
||||||
|
numberOfLines={1}
|
||||||
|
ellipsizeMode="middle"
|
||||||
|
>
|
||||||
|
{providerSessionId ?? "Not available"}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
<DropdownMenuItem
|
|
||||||
leading={<GitBranch size={16} color={theme.colors.foreground} />}
|
|
||||||
onSelect={handleViewChanges}
|
|
||||||
>
|
|
||||||
View changes
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
leading={<Folder size={16} color={theme.colors.foreground} />}
|
|
||||||
onSelect={handleBrowseFiles}
|
|
||||||
>
|
|
||||||
Browse files
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
leading={<RotateCcw size={16} color={theme.colors.foreground} />}
|
leading={<RotateCcw size={16} color={theme.colors.foreground} />}
|
||||||
disabled={isInitializing}
|
disabled={isInitializing}
|
||||||
@@ -755,16 +821,6 @@ function AgentScreenContent({
|
|||||||
>
|
>
|
||||||
{isInitializing ? "Refreshing..." : "Refresh"}
|
{isInitializing ? "Refreshing..." : "Refresh"}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
|
|
||||||
<DropdownMenuItem
|
|
||||||
testID="agent-menu-details"
|
|
||||||
leading={<Info size={16} color={theme.colors.foreground} />}
|
|
||||||
onSelect={() => setDetailsOpen(true)}
|
|
||||||
>
|
|
||||||
Details
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</View>
|
</View>
|
||||||
@@ -797,13 +853,6 @@ function AgentScreenContent({
|
|||||||
</View>
|
</View>
|
||||||
</FileDropZone>
|
</FileDropZone>
|
||||||
|
|
||||||
<AgentDetailsSheet
|
|
||||||
visible={detailsOpen}
|
|
||||||
onClose={() => setDetailsOpen(false)}
|
|
||||||
agentId={effectiveAgent.id}
|
|
||||||
persistenceSessionId={effectiveAgent.persistence?.sessionId ?? null}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Explorer Sidebar - Desktop: inline, Mobile: overlay */}
|
{/* Explorer Sidebar - Desktop: inline, Mobile: overlay */}
|
||||||
{!isMobile && isExplorerOpen && resolvedAgentId && (
|
{!isMobile && isExplorerOpen && resolvedAgentId && (
|
||||||
<ExplorerSidebar serverId={serverId} agentId={resolvedAgentId} cwd={effectiveAgent.cwd} />
|
<ExplorerSidebar serverId={serverId} agentId={resolvedAgentId} cwd={effectiveAgent.cwd} />
|
||||||
@@ -978,31 +1027,30 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
},
|
},
|
||||||
menuMetaContainer: {
|
menuMetaContainer: {
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingVertical: theme.spacing[1],
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
},
|
||||||
menuMetaRow: {
|
menuMetaRow: {
|
||||||
gap: theme.spacing[1],
|
minHeight: 32,
|
||||||
|
paddingHorizontal: theme.spacing[3],
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: theme.spacing[3],
|
||||||
|
},
|
||||||
|
menuMetaRowActive: {
|
||||||
|
backgroundColor: theme.colors.surface2,
|
||||||
},
|
},
|
||||||
menuMetaLabel: {
|
menuMetaLabel: {
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.sm,
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
letterSpacing: 0.5,
|
flexShrink: 0,
|
||||||
textTransform: "uppercase",
|
|
||||||
},
|
},
|
||||||
menuMetaValue: {
|
menuMetaValue: {
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
},
|
flex: 1,
|
||||||
menuMetaValueRow: {
|
minWidth: 0,
|
||||||
flexDirection: "row",
|
textAlign: "right",
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
|
||||||
menuMetaPendingText: {
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
},
|
},
|
||||||
menuMetaValueError: {
|
menuMetaValueError: {
|
||||||
color: theme.colors.destructive,
|
color: theme.colors.destructive,
|
||||||
|
|||||||
Reference in New Issue
Block a user