Update files
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
|||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Platform,
|
Platform,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
|
import type { StyleProp, ViewStyle, TextProps } from "react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import {
|
import {
|
||||||
BottomSheetModal,
|
BottomSheetModal,
|
||||||
@@ -16,7 +17,7 @@ import {
|
|||||||
BottomSheetBackgroundProps,
|
BottomSheetBackgroundProps,
|
||||||
} from "@gorhom/bottom-sheet";
|
} from "@gorhom/bottom-sheet";
|
||||||
import Animated from "react-native-reanimated";
|
import Animated from "react-native-reanimated";
|
||||||
import { ChevronDown, ChevronRight, Pencil, Check, X } from "lucide-react-native";
|
import { ChevronDown, ChevronRight, Pencil, Check, X, Bot, Brain, Shield } from "lucide-react-native";
|
||||||
import { theme as defaultTheme } from "@/styles/theme";
|
import { theme as defaultTheme } from "@/styles/theme";
|
||||||
import type {
|
import type {
|
||||||
AgentMode,
|
AgentMode,
|
||||||
@@ -312,6 +313,8 @@ interface ComboSelectProps {
|
|||||||
allowCustomValue?: boolean;
|
allowCustomValue?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
onSelect: (id: string) => void;
|
onSelect: (id: string) => void;
|
||||||
|
icon?: ReactElement;
|
||||||
|
showLabel?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ComboSelect({
|
export function ComboSelect({
|
||||||
@@ -324,6 +327,8 @@ export function ComboSelect({
|
|||||||
allowCustomValue = false,
|
allowCustomValue = false,
|
||||||
isLoading,
|
isLoading,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
icon,
|
||||||
|
showLabel = true,
|
||||||
}: ComboSelectProps): ReactElement {
|
}: ComboSelectProps): ReactElement {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const anchorRef = useRef<View>(null);
|
const anchorRef = useRef<View>(null);
|
||||||
@@ -336,7 +341,7 @@ export function ComboSelect({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CompactSelectField
|
<FormSelectTrigger
|
||||||
label={label}
|
label={label}
|
||||||
value={displayValue}
|
value={displayValue}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
@@ -344,6 +349,8 @@ export function ComboSelect({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
controlRef={anchorRef}
|
controlRef={anchorRef}
|
||||||
|
icon={icon}
|
||||||
|
showLabel={showLabel}
|
||||||
/>
|
/>
|
||||||
<Combobox
|
<Combobox
|
||||||
options={options}
|
options={options}
|
||||||
@@ -368,9 +375,14 @@ interface CompactSelectFieldProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
controlRef?: React.RefObject<View | null>;
|
controlRef?: React.RefObject<View | null>;
|
||||||
|
icon?: ReactElement;
|
||||||
|
showLabel?: boolean;
|
||||||
|
containerStyle?: StyleProp<ViewStyle>;
|
||||||
|
valueEllipsizeMode?: TextProps["ellipsizeMode"];
|
||||||
|
testID?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CompactSelectField({
|
export function FormSelectTrigger({
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
@@ -378,6 +390,11 @@ function CompactSelectField({
|
|||||||
disabled,
|
disabled,
|
||||||
isLoading,
|
isLoading,
|
||||||
controlRef,
|
controlRef,
|
||||||
|
icon,
|
||||||
|
showLabel = true,
|
||||||
|
containerStyle,
|
||||||
|
valueEllipsizeMode,
|
||||||
|
testID,
|
||||||
}: CompactSelectFieldProps): ReactElement {
|
}: CompactSelectFieldProps): ReactElement {
|
||||||
const getWebKey = useCallback((event: unknown): string | null => {
|
const getWebKey = useCallback((event: unknown): string | null => {
|
||||||
if (!event || typeof event !== "object") return null;
|
if (!event || typeof event !== "object") return null;
|
||||||
@@ -411,25 +428,38 @@ function CompactSelectField({
|
|||||||
<Pressable
|
<Pressable
|
||||||
ref={controlRef}
|
ref={controlRef}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
testID={testID}
|
||||||
// @ts-ignore - tabIndex is web-only
|
// @ts-ignore - tabIndex is web-only
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
// @ts-ignore - onKeyDown is web-only
|
// @ts-ignore - onKeyDown is web-only
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={[styles.compactSelectControl, disabled && styles.compactSelectControlDisabled]}
|
style={[
|
||||||
|
styles.compactSelectControl,
|
||||||
|
!showLabel && styles.compactSelectControlInline,
|
||||||
|
containerStyle,
|
||||||
|
disabled && styles.compactSelectControlDisabled,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.compactSelectLabel}>{label}</Text>
|
{icon ? <View style={styles.compactSelectLeading}>{icon}</View> : null}
|
||||||
{isLoading ? (
|
<View style={styles.compactSelectValueContainer}>
|
||||||
<ActivityIndicator size="small" color={defaultTheme.colors.foregroundMuted} />
|
{showLabel ? (
|
||||||
) : (
|
<Text style={styles.compactSelectLabel}>{label}</Text>
|
||||||
<Text
|
) : null}
|
||||||
style={value ? styles.compactSelectValue : styles.compactSelectPlaceholder}
|
{isLoading ? (
|
||||||
numberOfLines={1}
|
<ActivityIndicator size="small" color={defaultTheme.colors.foregroundMuted} />
|
||||||
>
|
) : (
|
||||||
{value || placeholder || "Select..."}
|
<Text
|
||||||
</Text>
|
style={value ? styles.compactSelectValue : styles.compactSelectPlaceholder}
|
||||||
)}
|
numberOfLines={1}
|
||||||
|
ellipsizeMode={valueEllipsizeMode}
|
||||||
|
>
|
||||||
|
{value || placeholder || "Select..."}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<ChevronDown size={16} color={defaultTheme.colors.foregroundMuted} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -466,31 +496,28 @@ export function AgentConfigRow({
|
|||||||
providerDefinitions.map((def) => ({
|
providerDefinitions.map((def) => ({
|
||||||
id: def.id,
|
id: def.id,
|
||||||
label: def.label,
|
label: def.label,
|
||||||
description: def.description,
|
|
||||||
})),
|
})),
|
||||||
[providerDefinitions]
|
[providerDefinitions]
|
||||||
);
|
);
|
||||||
|
|
||||||
const modeSelectOptions: ComboSelectOption[] = useMemo(() => {
|
const modeSelectOptions: ComboSelectOption[] = useMemo(() => {
|
||||||
if (modeOptions.length === 0) {
|
if (modeOptions.length === 0) {
|
||||||
return [{ id: "", label: "Default", description: "Provider default mode" }];
|
return [{ id: "", label: "Default" }];
|
||||||
}
|
}
|
||||||
return modeOptions.map((mode) => ({
|
return modeOptions.map((mode) => ({
|
||||||
id: mode.id,
|
id: mode.id,
|
||||||
label: mode.label,
|
label: mode.label,
|
||||||
description: mode.description,
|
|
||||||
}));
|
}));
|
||||||
}, [modeOptions]);
|
}, [modeOptions]);
|
||||||
|
|
||||||
const modelSelectOptions: ComboSelectOption[] = useMemo(() => {
|
const modelSelectOptions: ComboSelectOption[] = useMemo(() => {
|
||||||
const opts: ComboSelectOption[] = [
|
const opts: ComboSelectOption[] = [
|
||||||
{ id: "", label: "Auto", description: "Provider default model" },
|
{ id: "", label: "Auto" },
|
||||||
];
|
];
|
||||||
for (const model of models) {
|
for (const model of models) {
|
||||||
opts.push({
|
opts.push({
|
||||||
id: model.id,
|
id: model.id,
|
||||||
label: model.label,
|
label: model.label,
|
||||||
description: model.description,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return opts;
|
return opts;
|
||||||
@@ -502,18 +529,20 @@ export function AgentConfigRow({
|
|||||||
<View style={styles.agentConfigRow}>
|
<View style={styles.agentConfigRow}>
|
||||||
<View style={styles.agentConfigColumn}>
|
<View style={styles.agentConfigColumn}>
|
||||||
<ComboSelect
|
<ComboSelect
|
||||||
label="PROVIDER"
|
label="Provider"
|
||||||
title="Select provider"
|
title="Select provider"
|
||||||
value={selectedProvider}
|
value={selectedProvider}
|
||||||
options={providerOptions}
|
options={providerOptions}
|
||||||
placeholder="Select..."
|
placeholder="Select..."
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onSelect={onSelectProvider}
|
onSelect={onSelectProvider}
|
||||||
|
icon={<Bot size={16} color={defaultTheme.colors.foregroundMuted} />}
|
||||||
|
showLabel={false}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.agentConfigColumn}>
|
<View style={styles.agentConfigColumn}>
|
||||||
<ComboSelect
|
<ComboSelect
|
||||||
label="MODEL"
|
label="Model"
|
||||||
title="Select model"
|
title="Select model"
|
||||||
value={selectedModel}
|
value={selectedModel}
|
||||||
options={modelSelectOptions}
|
options={modelSelectOptions}
|
||||||
@@ -521,17 +550,21 @@ export function AgentConfigRow({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
isLoading={isModelLoading}
|
isLoading={isModelLoading}
|
||||||
onSelect={onSelectModel}
|
onSelect={onSelectModel}
|
||||||
|
icon={<Brain size={16} color={defaultTheme.colors.foregroundMuted} />}
|
||||||
|
showLabel={false}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.agentConfigColumn}>
|
<View style={styles.agentConfigColumn}>
|
||||||
<ComboSelect
|
<ComboSelect
|
||||||
label="MODE"
|
label="Mode"
|
||||||
title="Select mode"
|
title="Select mode"
|
||||||
value={effectiveSelectedMode}
|
value={effectiveSelectedMode}
|
||||||
options={modeSelectOptions}
|
options={modeSelectOptions}
|
||||||
placeholder="Default"
|
placeholder="Default"
|
||||||
disabled={disabled || modeOptions.length === 0}
|
disabled={disabled || modeOptions.length === 0}
|
||||||
onSelect={onSelectMode}
|
onSelect={onSelectMode}
|
||||||
|
icon={<Shield size={16} color={defaultTheme.colors.foregroundMuted} />}
|
||||||
|
showLabel={false}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -563,7 +596,6 @@ export function AssistantDropdown({
|
|||||||
providerDefinitions.map((def) => ({
|
providerDefinitions.map((def) => ({
|
||||||
id: def.id,
|
id: def.id,
|
||||||
label: def.label,
|
label: def.label,
|
||||||
description: def.description,
|
|
||||||
})),
|
})),
|
||||||
[providerDefinitions]
|
[providerDefinitions]
|
||||||
);
|
);
|
||||||
@@ -1405,26 +1437,46 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
borderWidth: theme.borderWidth[1],
|
borderWidth: theme.borderWidth[1],
|
||||||
borderColor: theme.colors.border,
|
borderColor: theme.colors.border,
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[1],
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
},
|
},
|
||||||
|
compactSelectControlInline: {
|
||||||
|
minHeight: 40,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: theme.spacing[1],
|
||||||
|
},
|
||||||
compactSelectControlDisabled: {
|
compactSelectControlDisabled: {
|
||||||
opacity: theme.opacity[50],
|
opacity: theme.opacity[50],
|
||||||
},
|
},
|
||||||
|
compactSelectTopRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[1],
|
||||||
|
},
|
||||||
|
compactSelectLeading: {
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
marginRight: theme.spacing[1],
|
||||||
|
},
|
||||||
|
compactSelectValueContainer: {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
gap: theme.spacing[1],
|
||||||
|
},
|
||||||
compactSelectLabel: {
|
compactSelectLabel: {
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.sm,
|
||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.medium,
|
||||||
textTransform: "uppercase",
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
},
|
},
|
||||||
compactSelectValue: {
|
compactSelectValue: {
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.base,
|
||||||
},
|
},
|
||||||
compactSelectPlaceholder: {
|
compactSelectPlaceholder: {
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.base,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
||||||
@@ -67,7 +66,9 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
return models.find((m) => m.id === agent.model) ?? null;
|
return models.find((m) => m.id === agent.model) ?? null;
|
||||||
}, [models, agent.model]);
|
}, [models, agent.model]);
|
||||||
|
|
||||||
const displayModel = selectedModel?.label ?? agent.model ?? "default";
|
const displayModel = selectedModel
|
||||||
|
? selectedModel.label
|
||||||
|
: agent.model ?? "default";
|
||||||
|
|
||||||
const thinkingOptions = selectedModel?.thinkingOptions ?? null;
|
const thinkingOptions = selectedModel?.thinkingOptions ?? null;
|
||||||
const selectedThinkingId =
|
const selectedThinkingId =
|
||||||
@@ -106,14 +107,12 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
maxWidth={dropdownMaxWidth}
|
maxWidth={dropdownMaxWidth}
|
||||||
testID="agent-mode-menu"
|
testID="agent-mode-menu"
|
||||||
>
|
>
|
||||||
<DropdownMenuLabel>Mode</DropdownMenuLabel>
|
|
||||||
{agent.availableModes.map((mode) => {
|
{agent.availableModes.map((mode) => {
|
||||||
const isActive = mode.id === agent.currentModeId;
|
const isActive = mode.id === agent.currentModeId;
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={mode.id}
|
key={mode.id}
|
||||||
selected={isActive}
|
selected={isActive}
|
||||||
description={mode.description}
|
|
||||||
onSelect={() => handleModeChange(mode.id)}
|
onSelect={() => handleModeChange(mode.id)}
|
||||||
>
|
>
|
||||||
{mode.label}
|
{mode.label}
|
||||||
@@ -147,14 +146,12 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
maxWidth={dropdownMaxWidth}
|
maxWidth={dropdownMaxWidth}
|
||||||
testID="agent-model-menu"
|
testID="agent-model-menu"
|
||||||
>
|
>
|
||||||
<DropdownMenuLabel>Model</DropdownMenuLabel>
|
|
||||||
{models?.map((model) => {
|
{models?.map((model) => {
|
||||||
const isActive = model.id === agent.model;
|
const isActive = model.id === agent.model;
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={model.id}
|
key={model.id}
|
||||||
selected={isActive}
|
selected={isActive}
|
||||||
description={model.description}
|
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return;
|
return;
|
||||||
@@ -193,14 +190,12 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
maxWidth={dropdownMaxWidth}
|
maxWidth={dropdownMaxWidth}
|
||||||
testID="agent-thinking-menu"
|
testID="agent-thinking-menu"
|
||||||
>
|
>
|
||||||
<DropdownMenuLabel>Thinking</DropdownMenuLabel>
|
|
||||||
{thinkingOptions.map((opt) => {
|
{thinkingOptions.map((opt) => {
|
||||||
const isActive = opt.id === selectedThinkingId;
|
const isActive = opt.id === selectedThinkingId;
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={opt.id}
|
key={opt.id}
|
||||||
selected={isActive}
|
selected={isActive}
|
||||||
description={opt.description}
|
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return;
|
return;
|
||||||
@@ -245,7 +240,6 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
testID="agent-preferences-sheet"
|
testID="agent-preferences-sheet"
|
||||||
>
|
>
|
||||||
<View style={styles.sheetSection}>
|
<View style={styles.sheetSection}>
|
||||||
<Text style={styles.sheetLabel}>Model</Text>
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
style={({ pressed }) => [
|
style={({ pressed }) => [
|
||||||
@@ -260,14 +254,12 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
<ChevronDown size={16} color={theme.colors.foregroundMuted} />
|
<ChevronDown size={16} color={theme.colors.foregroundMuted} />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent side="top" align="start">
|
<DropdownMenuContent side="top" align="start">
|
||||||
<DropdownMenuLabel>Model</DropdownMenuLabel>
|
|
||||||
{models?.map((model) => {
|
{models?.map((model) => {
|
||||||
const isActive = model.id === agent.model;
|
const isActive = model.id === agent.model;
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={model.id}
|
key={model.id}
|
||||||
selected={isActive}
|
selected={isActive}
|
||||||
description={model.description}
|
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return;
|
return;
|
||||||
@@ -287,7 +279,6 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
|
|
||||||
{thinkingOptions && thinkingOptions.length > 1 && (
|
{thinkingOptions && thinkingOptions.length > 1 && (
|
||||||
<View style={styles.sheetSection}>
|
<View style={styles.sheetSection}>
|
||||||
<Text style={styles.sheetLabel}>Thinking</Text>
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
style={({ pressed }) => [
|
style={({ pressed }) => [
|
||||||
@@ -298,21 +289,19 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
accessibilityLabel="Select thinking option"
|
accessibilityLabel="Select thinking option"
|
||||||
testID="agent-preferences-thinking"
|
testID="agent-preferences-thinking"
|
||||||
>
|
>
|
||||||
<Text style={styles.sheetSelectText}>{displayThinking}</Text>
|
<Text style={styles.sheetSelectText}>{displayThinking}</Text>
|
||||||
<ChevronDown size={16} color={theme.colors.foregroundMuted} />
|
<ChevronDown size={16} color={theme.colors.foregroundMuted} />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent side="top" align="start">
|
<DropdownMenuContent side="top" align="start">
|
||||||
<DropdownMenuLabel>Thinking</DropdownMenuLabel>
|
{thinkingOptions.map((opt) => {
|
||||||
{thinkingOptions.map((opt) => {
|
const isActive = opt.id === selectedThinkingId;
|
||||||
const isActive = opt.id === selectedThinkingId;
|
return (
|
||||||
return (
|
<DropdownMenuItem
|
||||||
<DropdownMenuItem
|
key={opt.id}
|
||||||
key={opt.id}
|
selected={isActive}
|
||||||
selected={isActive}
|
onSelect={() => {
|
||||||
description={opt.description}
|
if (!client) {
|
||||||
onSelect={() => {
|
return;
|
||||||
if (!client) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
void client
|
void client
|
||||||
.setAgentThinkingOption(agentId, opt.id === "default" ? null : opt.id)
|
.setAgentThinkingOption(agentId, opt.id === "default" ? null : opt.id)
|
||||||
@@ -361,7 +350,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
modeBadgeText: {
|
modeBadgeText: {
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.sm,
|
||||||
fontWeight: theme.fontWeight.normal,
|
fontWeight: theme.fontWeight.normal,
|
||||||
},
|
},
|
||||||
prefsButton: {
|
prefsButton: {
|
||||||
@@ -378,11 +367,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
sheetSection: {
|
sheetSection: {
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
sheetLabel: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
fontWeight: theme.fontWeight.semibold,
|
|
||||||
},
|
|
||||||
sheetSelect: {
|
sheetSelect: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -401,7 +385,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
sheetSelectText: {
|
sheetSelectText: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.base,
|
||||||
fontWeight: theme.fontWeight.semibold,
|
fontWeight: theme.fontWeight.semibold,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1050,14 +1050,24 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={gitActions.primary.label}
|
accessibilityLabel={gitActions.primary.label}
|
||||||
>
|
>
|
||||||
{gitActions.primary.status === "pending" ? (
|
<View style={styles.splitButtonPrimaryInner}>
|
||||||
<ActivityIndicator size="small" color={theme.colors.foreground} style={styles.splitButtonSpinner} />
|
<View
|
||||||
) : (
|
style={[
|
||||||
<View style={styles.splitButtonContent}>
|
styles.splitButtonContent,
|
||||||
|
gitActions.primary.status === "pending" && styles.splitButtonContentHidden,
|
||||||
|
]}
|
||||||
|
>
|
||||||
{gitActions.primary.icon}
|
{gitActions.primary.icon}
|
||||||
<Text style={styles.splitButtonText}>{getActionDisplayLabel(gitActions.primary)}</Text>
|
<Text style={styles.splitButtonText}>{getActionDisplayLabel(gitActions.primary)}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
{gitActions.primary.status === "pending" ? (
|
||||||
|
<ActivityIndicator
|
||||||
|
size="small"
|
||||||
|
color={theme.colors.foreground}
|
||||||
|
style={styles.splitButtonSpinnerOverlay}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
{gitActions.secondary.length > 0 ? (
|
{gitActions.secondary.length > 0 ? (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -1292,15 +1302,21 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
splitButtonPrimary: {
|
splitButtonPrimary: {
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[1],
|
||||||
|
justifyContent: "center",
|
||||||
|
position: "relative",
|
||||||
|
},
|
||||||
|
splitButtonPrimaryInner: {
|
||||||
|
position: "relative",
|
||||||
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
splitButtonPrimaryDisabled: {
|
splitButtonPrimaryDisabled: {
|
||||||
opacity: 0.6,
|
opacity: 0.6,
|
||||||
},
|
},
|
||||||
splitButtonText: {
|
splitButtonText: {
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.sm,
|
||||||
lineHeight: theme.fontSize.xs * 1.5,
|
lineHeight: theme.fontSize.sm * 1.5,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.medium,
|
||||||
},
|
},
|
||||||
@@ -1310,9 +1326,12 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
splitButtonSpinner: {
|
splitButtonContentHidden: {
|
||||||
height: theme.fontSize.xs * 1.5,
|
opacity: 0,
|
||||||
width: theme.fontSize.xs * 1.5,
|
},
|
||||||
|
splitButtonSpinnerOverlay: {
|
||||||
|
position: "absolute",
|
||||||
|
transform: [{ scale: 0.8 }],
|
||||||
},
|
},
|
||||||
splitButtonCaret: {
|
splitButtonCaret: {
|
||||||
width: 36,
|
width: 36,
|
||||||
|
|||||||
@@ -20,13 +20,7 @@ import {
|
|||||||
} from "@gorhom/bottom-sheet";
|
} from "@gorhom/bottom-sheet";
|
||||||
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
|
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
|
||||||
import { Check, Search } from "lucide-react-native";
|
import { Check, Search } from "lucide-react-native";
|
||||||
import {
|
import { flip, offset as floatingOffset, shift, size as floatingSize, useFloating } from "@floating-ui/react-native";
|
||||||
flip,
|
|
||||||
offset as floatingOffset,
|
|
||||||
shift,
|
|
||||||
size as floatingSize,
|
|
||||||
useFloating,
|
|
||||||
} from "@floating-ui/react-native";
|
|
||||||
import { getNextActiveIndex } from "./combobox-keyboard";
|
import { getNextActiveIndex } from "./combobox-keyboard";
|
||||||
|
|
||||||
const IS_WEB = Platform.OS === "web";
|
const IS_WEB = Platform.OS === "web";
|
||||||
@@ -132,21 +126,24 @@ export function ComboboxItem({
|
|||||||
<Pressable
|
<Pressable
|
||||||
testID={testID}
|
testID={testID}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
style={({ pressed }) => [
|
style={({ pressed, hovered = false }) => [
|
||||||
styles.comboboxItem,
|
styles.comboboxItem,
|
||||||
|
hovered && styles.comboboxItemHovered,
|
||||||
pressed && styles.comboboxItemPressed,
|
pressed && styles.comboboxItemPressed,
|
||||||
active && styles.comboboxItemActive,
|
active && styles.comboboxItemActive,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View style={styles.comboboxItemCheckSlot}>
|
|
||||||
{selected ? <Check size={16} color={theme.colors.foreground} /> : null}
|
|
||||||
</View>
|
|
||||||
<View style={styles.comboboxItemContent}>
|
<View style={styles.comboboxItemContent}>
|
||||||
<Text numberOfLines={1} style={styles.comboboxItemLabel}>{label}</Text>
|
<Text numberOfLines={1} style={styles.comboboxItemLabel}>{label}</Text>
|
||||||
{description ? (
|
{description ? (
|
||||||
<Text numberOfLines={2} style={styles.comboboxItemDescription}>{description}</Text>
|
<Text numberOfLines={2} style={styles.comboboxItemDescription}>{description}</Text>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
{selected ? (
|
||||||
|
<View style={styles.comboboxItemTrailingSlot}>
|
||||||
|
<Check size={16} color={theme.colors.foregroundMuted} />
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -214,8 +211,8 @@ export function Combobox({
|
|||||||
|
|
||||||
const middleware = useMemo(
|
const middleware = useMemo(
|
||||||
() => [
|
() => [
|
||||||
floatingOffset({ mainAxis: 4 }),
|
floatingOffset(Platform.OS === "web" ? 0 : 4),
|
||||||
flip({ padding: collisionPadding }),
|
...(Platform.OS === "web" ? [] : [flip({ padding: collisionPadding })]),
|
||||||
shift({ padding: collisionPadding }),
|
shift({ padding: collisionPadding }),
|
||||||
floatingSize({
|
floatingSize({
|
||||||
padding: collisionPadding,
|
padding: collisionPadding,
|
||||||
@@ -238,7 +235,7 @@ export function Combobox({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { refs, floatingStyles, update } = useFloating({
|
const { refs, floatingStyles, update } = useFloating({
|
||||||
placement: "bottom-start",
|
placement: Platform.OS === "web" ? "top-start" : "bottom-start",
|
||||||
middleware,
|
middleware,
|
||||||
sameScrollView: false,
|
sameScrollView: false,
|
||||||
elements: {
|
elements: {
|
||||||
@@ -551,10 +548,14 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
searchInputContainer: {
|
searchInputContainer: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
borderBottomWidth: 1,
|
borderWidth: 1,
|
||||||
borderBottomColor: theme.colors.border,
|
borderColor: theme.colors.border,
|
||||||
|
backgroundColor: theme.colors.surface1,
|
||||||
|
borderRadius: theme.borderRadius.lg,
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
marginBottom: theme.spacing[1],
|
marginHorizontal: theme.spacing[2],
|
||||||
|
marginBottom: theme.spacing[2],
|
||||||
|
marginTop: theme.spacing[1],
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
searchInput: {
|
searchInput: {
|
||||||
@@ -571,17 +572,27 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
|
...(IS_WEB
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
marginHorizontal: theme.spacing[1],
|
||||||
|
marginBottom: theme.spacing[1],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
comboboxItemHovered: {
|
||||||
|
backgroundColor: theme.colors.surface1,
|
||||||
},
|
},
|
||||||
comboboxItemPressed: {
|
comboboxItemPressed: {
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface1,
|
||||||
},
|
},
|
||||||
comboboxItemActive: {
|
comboboxItemActive: {
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface1,
|
||||||
},
|
},
|
||||||
comboboxItemCheckSlot: {
|
comboboxItemTrailingSlot: {
|
||||||
width: 16,
|
width: 16,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
marginLeft: "auto",
|
||||||
},
|
},
|
||||||
comboboxItemContent: {
|
comboboxItemContent: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -603,7 +614,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
},
|
},
|
||||||
bottomSheetBackground: {
|
bottomSheetBackground: {
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface0,
|
||||||
borderTopLeftRadius: theme.borderRadius["2xl"],
|
borderTopLeftRadius: theme.borderRadius["2xl"],
|
||||||
borderTopRightRadius: theme.borderRadius["2xl"],
|
borderTopRightRadius: theme.borderRadius["2xl"],
|
||||||
},
|
},
|
||||||
@@ -616,13 +627,14 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
comboboxTitle: {
|
comboboxTitle: {
|
||||||
fontSize: theme.fontSize.lg,
|
fontSize: theme.fontSize.lg,
|
||||||
fontWeight: theme.fontWeight.semibold,
|
fontWeight: theme.fontWeight.medium,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
textAlign: "center",
|
textAlign: "left",
|
||||||
},
|
},
|
||||||
comboboxScrollContent: {
|
comboboxScrollContent: {
|
||||||
paddingBottom: theme.spacing[8],
|
paddingBottom: theme.spacing[8],
|
||||||
paddingHorizontal: theme.spacing[1],
|
paddingHorizontal: theme.spacing[2],
|
||||||
|
paddingTop: theme.spacing[1],
|
||||||
},
|
},
|
||||||
desktopOverlay: {
|
desktopOverlay: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -638,7 +650,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
backgroundColor: theme.colors.surface0,
|
backgroundColor: theme.colors.surface0,
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: theme.colors.borderAccent,
|
borderColor: theme.colors.border,
|
||||||
shadowColor: "#000",
|
shadowColor: "#000",
|
||||||
shadowOffset: { width: 0, height: 4 },
|
shadowOffset: { width: 0, height: 4 },
|
||||||
shadowOpacity: 0.2,
|
shadowOpacity: 0.2,
|
||||||
|
|||||||
@@ -458,6 +458,9 @@ export function DropdownMenuItem({
|
|||||||
label = successLabel;
|
label = successLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const trailingContent =
|
||||||
|
trailing ?? (!showSelectedCheck && selected ? <Check size={16} color={theme.colors.foregroundMuted} /> : null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
testID={testID}
|
testID={testID}
|
||||||
@@ -473,8 +476,12 @@ export function DropdownMenuItem({
|
|||||||
style={({ pressed, hovered }) => [
|
style={({ pressed, hovered }) => [
|
||||||
styles.item,
|
styles.item,
|
||||||
selected ? (selectedVariant === "accent" ? styles.itemSelectedAccent : styles.itemSelected) : null,
|
selected ? (selectedVariant === "accent" ? styles.itemSelectedAccent : styles.itemSelected) : null,
|
||||||
|
selected && (hovered || pressed) && selectedVariant !== "accent"
|
||||||
|
? styles.itemSelectedInteractive
|
||||||
|
: null,
|
||||||
isDisabled ? styles.itemDisabled : null,
|
isDisabled ? styles.itemDisabled : null,
|
||||||
(hovered || pressed) && !isDisabled ? styles.itemHovered : null,
|
hovered && !pressed && !isDisabled ? styles.itemHovered : null,
|
||||||
|
pressed && !isDisabled ? styles.itemPressed : null,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{showSelectedCheck ? (
|
{showSelectedCheck ? (
|
||||||
@@ -507,7 +514,7 @@ export function DropdownMenuItem({
|
|||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
{trailing ? <View style={styles.trailingSlot}>{trailing}</View> : null}
|
{trailingContent ? <View style={styles.trailingSlot}>{trailingContent}</View> : null}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -565,12 +572,20 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
|
borderWidth: theme.borderWidth[1],
|
||||||
|
borderColor: "transparent",
|
||||||
},
|
},
|
||||||
itemHovered: {
|
itemHovered: {
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface1,
|
||||||
|
},
|
||||||
|
itemPressed: {
|
||||||
|
backgroundColor: theme.colors.surface1,
|
||||||
},
|
},
|
||||||
itemSelected: {
|
itemSelected: {
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface1,
|
||||||
|
},
|
||||||
|
itemSelectedInteractive: {
|
||||||
|
backgroundColor: theme.colors.surface1,
|
||||||
},
|
},
|
||||||
itemSelectedAccent: {
|
itemSelectedAccent: {
|
||||||
backgroundColor: theme.colors.accent,
|
backgroundColor: theme.colors.accent,
|
||||||
|
|||||||
@@ -10,20 +10,16 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||||
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";
|
||||||
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
||||||
import { Monitor } from "lucide-react-native";
|
import { Folder, GitBranch, Menu, Monitor, PanelLeft } from "lucide-react-native";
|
||||||
import { MenuHeader } from "@/components/headers/menu-header";
|
import { HeaderToggleButton } from "@/components/headers/header-toggle-button";
|
||||||
import { AgentInputArea } from "@/components/agent-input-area";
|
import { AgentInputArea } from "@/components/agent-input-area";
|
||||||
import { AgentStreamView } from "@/components/agent-stream-view";
|
import { AgentStreamView } from "@/components/agent-stream-view";
|
||||||
import {
|
import { AgentConfigRow, FormSelectTrigger } from "@/components/agent-form/agent-form-dropdowns";
|
||||||
DropdownSheet,
|
import { Combobox } from "@/components/ui/combobox";
|
||||||
GitOptionsSection,
|
|
||||||
WorkingDirectoryDropdown,
|
|
||||||
AgentConfigRow,
|
|
||||||
} from "@/components/agent-form/agent-form-dropdowns";
|
|
||||||
import { FileDropZone } from "@/components/file-drop-zone";
|
import { FileDropZone } from "@/components/file-drop-zone";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useAgentFormState, type CreateAgentInitialValues } from "@/hooks/use-agent-form-state";
|
import { useAgentFormState, type CreateAgentInitialValues } from "@/hooks/use-agent-form-state";
|
||||||
@@ -34,6 +30,7 @@ import {
|
|||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
||||||
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import { formatConnectionStatus } from "@/utils/daemons";
|
import { formatConnectionStatus } from "@/utils/daemons";
|
||||||
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
||||||
import { MAX_CONTENT_WIDTH } from "@/constants/layout";
|
import { MAX_CONTENT_WIDTH } from "@/constants/layout";
|
||||||
@@ -123,6 +120,9 @@ export function DraftAgentScreen({
|
|||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { connectionStates } = useDaemonConnections();
|
||||||
const { daemons } = useDaemonRegistry();
|
const { daemons } = useDaemonRegistry();
|
||||||
|
const mobileView = usePanelStore((state) => state.mobileView);
|
||||||
|
const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
|
||||||
|
const toggleAgentList = usePanelStore((state) => state.toggleAgentList);
|
||||||
const params = useLocalSearchParams<DraftAgentParams>();
|
const params = useLocalSearchParams<DraftAgentParams>();
|
||||||
|
|
||||||
const { height: keyboardHeight } = useReanimatedKeyboardAnimation();
|
const { height: keyboardHeight } = useReanimatedKeyboardAnimation();
|
||||||
@@ -204,15 +204,26 @@ export function DraftAgentScreen({
|
|||||||
: undefined;
|
: undefined;
|
||||||
const hostLabel =
|
const hostLabel =
|
||||||
hostEntry?.daemon.label ?? selectedServerId ?? "Select host";
|
hostEntry?.daemon.label ?? selectedServerId ?? "Select host";
|
||||||
const hostStatus = hostEntry?.status
|
const isMobile =
|
||||||
? formatConnectionStatus(hostEntry.status)
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
: undefined;
|
const isSidebarOpen = isMobile ? mobileView === "agent-list" : desktopAgentListOpen;
|
||||||
|
const SidebarIcon = isMobile ? Menu : PanelLeft;
|
||||||
|
const sidebarIconColor = !isMobile && isSidebarOpen
|
||||||
|
? theme.colors.foreground
|
||||||
|
: theme.colors.foregroundMuted;
|
||||||
|
|
||||||
const [openDropdown, setOpenDropdown] = useState<"host" | null>(null);
|
const [isHostOpen, setIsHostOpen] = useState(false);
|
||||||
const [worktreeMode, setWorktreeMode] = useState<"none" | "create" | "attach">("none");
|
const [worktreeMode, setWorktreeMode] = useState<"none" | "create" | "attach">("none");
|
||||||
const [baseBranch, setBaseBranch] = useState("");
|
const [baseBranch, setBaseBranch] = useState("");
|
||||||
const [worktreeSlug, setWorktreeSlug] = useState("");
|
const [worktreeSlug, setWorktreeSlug] = useState("");
|
||||||
const [selectedWorktreePath, setSelectedWorktreePath] = useState("");
|
const [selectedWorktreePath, setSelectedWorktreePath] = useState("");
|
||||||
|
const [isWorkingDirOpen, setIsWorkingDirOpen] = useState(false);
|
||||||
|
const [isWorktreePickerOpen, setIsWorktreePickerOpen] = useState(false);
|
||||||
|
const [isBranchOpen, setIsBranchOpen] = useState(false);
|
||||||
|
const hostAnchorRef = useRef<View>(null);
|
||||||
|
const workingDirAnchorRef = useRef<View>(null);
|
||||||
|
const worktreeAnchorRef = useRef<View>(null);
|
||||||
|
const branchAnchorRef = useRef<View>(null);
|
||||||
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
|
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
|
||||||
const setPendingCreateAttempt = useCreateFlowStore((state) => state.setPending);
|
const setPendingCreateAttempt = useCreateFlowStore((state) => state.setPending);
|
||||||
const updatePendingAgentId = useCreateFlowStore((state) => state.updateAgentId);
|
const updatePendingAgentId = useCreateFlowStore((state) => state.updateAgentId);
|
||||||
@@ -276,15 +287,26 @@ export function DraftAgentScreen({
|
|||||||
const handleAddImagesCallback = useCallback((addImages: (images: ImageAttachment[]) => void) => {
|
const handleAddImagesCallback = useCallback((addImages: (images: ImageAttachment[]) => void) => {
|
||||||
addImagesRef.current = addImages;
|
addImagesRef.current = addImages;
|
||||||
}, []);
|
}, []);
|
||||||
const openDropdownSheet = useCallback((key: "host") => {
|
|
||||||
setOpenDropdown(key);
|
|
||||||
}, []);
|
|
||||||
const closeDropdown = useCallback(() => {
|
|
||||||
setOpenDropdown(null);
|
|
||||||
}, []);
|
|
||||||
const sessionAgents = useSessionStore((state) =>
|
const sessionAgents = useSessionStore((state) =>
|
||||||
selectedServerId ? state.sessions[selectedServerId]?.agents : undefined
|
selectedServerId ? state.sessions[selectedServerId]?.agents : undefined
|
||||||
);
|
);
|
||||||
|
const worktreePathLastCreatedAt = useMemo(() => {
|
||||||
|
const map = new Map<string, number>();
|
||||||
|
if (!sessionAgents) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
sessionAgents.forEach((agent) => {
|
||||||
|
if (!agent.cwd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ts = agent.createdAt.getTime();
|
||||||
|
const prev = map.get(agent.cwd);
|
||||||
|
if (!prev || ts > prev) {
|
||||||
|
map.set(agent.cwd, ts);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [sessionAgents]);
|
||||||
const agentWorkingDirSuggestions = useMemo(() => {
|
const agentWorkingDirSuggestions = useMemo(() => {
|
||||||
if (!selectedServerId || !sessionAgents) {
|
if (!selectedServerId || !sessionAgents) {
|
||||||
return [];
|
return [];
|
||||||
@@ -346,7 +368,6 @@ export function DraftAgentScreen({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const checkout = checkoutStatusQuery.data ?? null;
|
const checkout = checkoutStatusQuery.data ?? null;
|
||||||
const refetchCheckoutStatus = checkoutStatusQuery.refetch;
|
|
||||||
const checkoutQueryError =
|
const checkoutQueryError =
|
||||||
checkoutStatusQuery.error instanceof Error ? checkoutStatusQuery.error.message : null;
|
checkoutStatusQuery.error instanceof Error ? checkoutStatusQuery.error.message : null;
|
||||||
const checkoutPayloadError = checkout?.error ? checkout.error.message : null;
|
const checkoutPayloadError = checkout?.error ? checkout.error.message : null;
|
||||||
@@ -398,7 +419,6 @@ export function DraftAgentScreen({
|
|||||||
return payload.worktrees ?? [];
|
return payload.worktrees ?? [];
|
||||||
},
|
},
|
||||||
enabled:
|
enabled:
|
||||||
isAttachWorktree &&
|
|
||||||
Boolean(worktreeListRoot || trimmedWorkingDir) &&
|
Boolean(worktreeListRoot || trimmedWorkingDir) &&
|
||||||
!repoAvailabilityError &&
|
!repoAvailabilityError &&
|
||||||
Boolean(sessionClient) &&
|
Boolean(sessionClient) &&
|
||||||
@@ -409,17 +429,23 @@ export function DraftAgentScreen({
|
|||||||
refetchOnMount: "always",
|
refetchOnMount: "always",
|
||||||
});
|
});
|
||||||
const worktreeOptions = useMemo(() => {
|
const worktreeOptions = useMemo(() => {
|
||||||
return (worktreeListQuery.data ?? []).map((worktree) => ({
|
const options = (worktreeListQuery.data ?? []).map((worktree) => ({
|
||||||
path: worktree.worktreePath,
|
path: worktree.worktreePath,
|
||||||
label: worktree.branchName ?? worktree.head ?? "Unknown branch",
|
label: worktree.branchName ?? worktree.head ?? "Unknown branch",
|
||||||
}));
|
}));
|
||||||
}, [worktreeListQuery.data]);
|
return options.sort((a, b) => {
|
||||||
|
const aTs = worktreePathLastCreatedAt.get(a.path) ?? 0;
|
||||||
|
const bTs = worktreePathLastCreatedAt.get(b.path) ?? 0;
|
||||||
|
if (aTs !== bTs) {
|
||||||
|
return bTs - aTs;
|
||||||
|
}
|
||||||
|
return a.label.localeCompare(b.label);
|
||||||
|
});
|
||||||
|
}, [worktreeListQuery.data, worktreePathLastCreatedAt]);
|
||||||
const worktreeOptionsError =
|
const worktreeOptionsError =
|
||||||
worktreeListQuery.error instanceof Error ? worktreeListQuery.error.message : null;
|
worktreeListQuery.error instanceof Error ? worktreeListQuery.error.message : null;
|
||||||
const worktreeOptionsStatus: "idle" | "loading" | "ready" | "error" =
|
const worktreeOptionsStatus: "idle" | "loading" | "ready" | "error" =
|
||||||
!isAttachWorktree
|
worktreeListQuery.isPending || worktreeListQuery.isFetching
|
||||||
? "idle"
|
|
||||||
: worktreeListQuery.isPending || worktreeListQuery.isFetching
|
|
||||||
? "loading"
|
? "loading"
|
||||||
: worktreeListQuery.isError
|
: worktreeListQuery.isError
|
||||||
? "error"
|
? "error"
|
||||||
@@ -432,22 +458,6 @@ export function DraftAgentScreen({
|
|||||||
? "Select a worktree to attach"
|
? "Select a worktree to attach"
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const handleWorktreeModeChange = useCallback(
|
|
||||||
(mode: "none" | "create" | "attach") => {
|
|
||||||
setWorktreeMode(mode);
|
|
||||||
if (mode === "create" && !worktreeSlug) {
|
|
||||||
setWorktreeSlug(createNameId());
|
|
||||||
}
|
|
||||||
if (mode !== "attach") {
|
|
||||||
setSelectedWorktreePath("");
|
|
||||||
}
|
|
||||||
if (mode !== "none") {
|
|
||||||
refetchCheckoutStatus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[worktreeSlug, refetchCheckoutStatus]
|
|
||||||
);
|
|
||||||
|
|
||||||
const validateWorktreeName = useCallback(
|
const validateWorktreeName = useCallback(
|
||||||
(name: string): { valid: boolean; error?: string } => {
|
(name: string): { valid: boolean; error?: string } => {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@@ -579,6 +589,59 @@ export function DraftAgentScreen({
|
|||||||
}
|
}
|
||||||
}, [isNonGitDirectory, worktreeMode]);
|
}, [isNonGitDirectory, worktreeMode]);
|
||||||
|
|
||||||
|
const selectedWorktreeLabel =
|
||||||
|
worktreeOptions.find((option) => option.path === selectedWorktreePath)?.label ?? "";
|
||||||
|
const worktreeTriggerValue =
|
||||||
|
worktreeMode === "create"
|
||||||
|
? "Create new worktree"
|
||||||
|
: selectedWorktreeLabel || "Select worktree";
|
||||||
|
const hostOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
Array.from(connectionStates.values()).map(({ daemon, status }) => ({
|
||||||
|
id: daemon.serverId,
|
||||||
|
label: daemon.label?.trim() ? daemon.label : daemon.serverId,
|
||||||
|
description: formatConnectionStatus(status),
|
||||||
|
})),
|
||||||
|
[connectionStates]
|
||||||
|
);
|
||||||
|
const worktreeComboOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
id: "__create_new__",
|
||||||
|
label: "Create new worktree",
|
||||||
|
description: "Create a new isolated worktree",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "__none__",
|
||||||
|
label: "None",
|
||||||
|
description: "Do not use a worktree",
|
||||||
|
},
|
||||||
|
...worktreeOptions.map((option) => ({
|
||||||
|
id: option.path,
|
||||||
|
label: option.label,
|
||||||
|
description: option.path,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
[worktreeOptions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const branchComboOptions = useMemo(() => {
|
||||||
|
const branchSet = new Set<string>();
|
||||||
|
const currentBranch = checkout?.isGit ? checkout.currentBranch?.trim() : null;
|
||||||
|
if (currentBranch && currentBranch !== "HEAD") {
|
||||||
|
branchSet.add(currentBranch);
|
||||||
|
}
|
||||||
|
if (baseBranch.trim()) {
|
||||||
|
branchSet.add(baseBranch.trim());
|
||||||
|
}
|
||||||
|
for (const option of worktreeOptions) {
|
||||||
|
if (option.label) {
|
||||||
|
branchSet.add(option.label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(branchSet).map((name) => ({ id: name, label: name }));
|
||||||
|
}, [baseBranch, checkout, worktreeOptions]);
|
||||||
|
|
||||||
const createAgentClient = useSessionStore((state) =>
|
const createAgentClient = useSessionStore((state) =>
|
||||||
selectedServerId ? state.sessions[selectedServerId]?.client ?? null : null
|
selectedServerId ? state.sessions[selectedServerId]?.client ?? null : null
|
||||||
);
|
);
|
||||||
@@ -823,27 +886,27 @@ export function DraftAgentScreen({
|
|||||||
<FileDropZone onFilesDropped={handleFilesDropped}>
|
<FileDropZone onFilesDropped={handleFilesDropped}>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.agentPanel}>
|
<View style={styles.agentPanel}>
|
||||||
<MenuHeader
|
<View
|
||||||
title="New agent"
|
style={[
|
||||||
rightContent={
|
styles.menuToggleContainer,
|
||||||
<Pressable
|
isMobile ? { paddingTop: insets.top + theme.spacing[2] } : null,
|
||||||
style={styles.hostBadge}
|
]}
|
||||||
onPress={() => openDropdownSheet("host")}
|
>
|
||||||
>
|
<HeaderToggleButton
|
||||||
<Monitor size={14} color={theme.colors.foregroundMuted} />
|
onPress={toggleAgentList}
|
||||||
<Text style={styles.hostBadgeLabel}>{hostLabel}</Text>
|
tooltipLabel="Toggle sidebar"
|
||||||
<View
|
tooltipKeys={["mod", "B"]}
|
||||||
style={[
|
tooltipSide="right"
|
||||||
styles.hostStatusDot,
|
testID="menu-button"
|
||||||
hostEntry?.status === "online" && styles.hostStatusDotOnline,
|
nativeID="menu-button"
|
||||||
]}
|
accessible
|
||||||
/>
|
accessibilityRole="button"
|
||||||
{hostStatus ? (
|
accessibilityLabel={isSidebarOpen ? "Close menu" : "Open menu"}
|
||||||
<Text style={styles.hostBadgeStatus}>{hostStatus}</Text>
|
accessibilityState={{ expanded: isSidebarOpen }}
|
||||||
) : null}
|
>
|
||||||
</Pressable>
|
<SidebarIcon size={isMobile ? 20 : 16} color={sidebarIconColor} />
|
||||||
}
|
</HeaderToggleButton>
|
||||||
/>
|
</View>
|
||||||
|
|
||||||
<Animated.View style={[styles.contentContainer, animatedKeyboardStyle]}>
|
<Animated.View style={[styles.contentContainer, animatedKeyboardStyle]}>
|
||||||
{machine.tag === "creating" && draftAgent && selectedServerId ? (
|
{machine.tag === "creating" && draftAgent && selectedServerId ? (
|
||||||
@@ -859,13 +922,29 @@ export function DraftAgentScreen({
|
|||||||
) : (
|
) : (
|
||||||
<ScrollView style={styles.scrollView} contentContainerStyle={styles.configScrollContent}>
|
<ScrollView style={styles.scrollView} contentContainerStyle={styles.configScrollContent}>
|
||||||
<View style={styles.configSection}>
|
<View style={styles.configSection}>
|
||||||
<WorkingDirectoryDropdown
|
<View style={styles.topSelectorRow}>
|
||||||
workingDir={workingDir}
|
<FormSelectTrigger
|
||||||
errorMessage=""
|
controlRef={workingDirAnchorRef}
|
||||||
disabled={false}
|
containerStyle={styles.topSelectorPrimary}
|
||||||
suggestedPaths={agentWorkingDirSuggestions}
|
label="Working directory"
|
||||||
onSelectPath={setWorkingDirFromUser}
|
value={workingDir}
|
||||||
/>
|
placeholder="/path/to/project"
|
||||||
|
onPress={() => setIsWorkingDirOpen(true)}
|
||||||
|
icon={<Folder size={16} color={theme.colors.foregroundMuted} />}
|
||||||
|
showLabel={false}
|
||||||
|
valueEllipsizeMode="middle"
|
||||||
|
/>
|
||||||
|
<FormSelectTrigger
|
||||||
|
controlRef={hostAnchorRef}
|
||||||
|
containerStyle={styles.topSelectorSecondary}
|
||||||
|
label="Host"
|
||||||
|
value={hostLabel}
|
||||||
|
placeholder="Select host"
|
||||||
|
onPress={() => setIsHostOpen(true)}
|
||||||
|
icon={<Monitor size={16} color={theme.colors.foregroundMuted} />}
|
||||||
|
showLabel={false}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
{isDirectoryNotExists && (
|
{isDirectoryNotExists && (
|
||||||
<View style={styles.warningContainer}>
|
<View style={styles.warningContainer}>
|
||||||
<Text style={styles.warningText}>
|
<Text style={styles.warningText}>
|
||||||
@@ -873,6 +952,36 @@ export function DraftAgentScreen({
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
{trimmedWorkingDir.length > 0 && !isNonGitDirectory ? (
|
||||||
|
<View style={styles.topSelectorRow}>
|
||||||
|
<FormSelectTrigger
|
||||||
|
controlRef={worktreeAnchorRef}
|
||||||
|
containerStyle={
|
||||||
|
worktreeMode === "create" ? styles.halfSelector : styles.topSelectorPrimary
|
||||||
|
}
|
||||||
|
label="Worktree"
|
||||||
|
value={worktreeTriggerValue}
|
||||||
|
placeholder="Select worktree"
|
||||||
|
onPress={() => setIsWorktreePickerOpen(true)}
|
||||||
|
icon={<GitBranch size={16} color={theme.colors.foregroundMuted} />}
|
||||||
|
showLabel={false}
|
||||||
|
valueEllipsizeMode="middle"
|
||||||
|
/>
|
||||||
|
{worktreeMode === "create" ? (
|
||||||
|
<FormSelectTrigger
|
||||||
|
controlRef={branchAnchorRef}
|
||||||
|
containerStyle={styles.halfSelector}
|
||||||
|
label="Base branch"
|
||||||
|
value={baseBranch}
|
||||||
|
placeholder="From branch"
|
||||||
|
onPress={() => setIsBranchOpen(true)}
|
||||||
|
disabled={repoInfoStatus === "loading"}
|
||||||
|
icon={<GitBranch size={16} color={theme.colors.foregroundMuted} />}
|
||||||
|
showLabel={false}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
<AgentConfigRow
|
<AgentConfigRow
|
||||||
providerDefinitions={providerDefinitions}
|
providerDefinitions={providerDefinitions}
|
||||||
selectedProvider={selectedProvider}
|
selectedProvider={selectedProvider}
|
||||||
@@ -885,65 +994,89 @@ export function DraftAgentScreen({
|
|||||||
isModelLoading={isModelLoading}
|
isModelLoading={isModelLoading}
|
||||||
onSelectModel={setModelFromUser}
|
onSelectModel={setModelFromUser}
|
||||||
/>
|
/>
|
||||||
{trimmedWorkingDir.length > 0 && !isNonGitDirectory ? (
|
{baseBranchError ? <Text style={styles.errorInlineText}>{baseBranchError}</Text> : null}
|
||||||
<GitOptionsSection
|
{repoInfoError ? <Text style={styles.errorInlineText}>{repoInfoError}</Text> : null}
|
||||||
worktreeMode={worktreeMode}
|
{gitBlockingError ? <Text style={styles.errorInlineText}>{gitBlockingError}</Text> : null}
|
||||||
onWorktreeModeChange={handleWorktreeModeChange}
|
{attachWorktreeError ? <Text style={styles.errorInlineText}>{attachWorktreeError}</Text> : null}
|
||||||
worktreeSlug={worktreeSlug}
|
{worktreeOptionsError ? <Text style={styles.errorInlineText}>{worktreeOptionsError}</Text> : null}
|
||||||
currentBranch={checkout?.isGit ? checkout.currentBranch ?? null : null}
|
|
||||||
baseBranch={baseBranch}
|
|
||||||
onBaseBranchChange={handleBaseBranchChange}
|
|
||||||
status={repoInfoStatus}
|
|
||||||
repoError={repoInfoError}
|
|
||||||
gitValidationError={gitBlockingError}
|
|
||||||
baseBranchError={baseBranchError}
|
|
||||||
worktreeOptions={worktreeOptions}
|
|
||||||
selectedWorktreePath={selectedWorktreePath}
|
|
||||||
worktreeOptionsStatus={worktreeOptionsStatus}
|
|
||||||
worktreeOptionsError={worktreeOptionsError}
|
|
||||||
attachWorktreeError={attachWorktreeError}
|
|
||||||
onSelectWorktreePath={handleSelectWorktreePath}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</View>
|
</View>
|
||||||
<DropdownSheet
|
<Combobox
|
||||||
|
options={hostOptions}
|
||||||
|
value={selectedServerId ?? ""}
|
||||||
|
onSelect={(serverId) => setSelectedServerIdFromUser(serverId)}
|
||||||
title="Host"
|
title="Host"
|
||||||
visible={openDropdown === "host"}
|
searchPlaceholder="Search hosts..."
|
||||||
onClose={closeDropdown}
|
open={isHostOpen}
|
||||||
>
|
onOpenChange={setIsHostOpen}
|
||||||
{connectionStates.size === 0 ? (
|
anchorRef={hostAnchorRef}
|
||||||
<Text style={styles.dropdownHelper}>
|
/>
|
||||||
No hosts available yet.
|
|
||||||
</Text>
|
<Combobox
|
||||||
) : (
|
options={worktreeComboOptions}
|
||||||
<View style={styles.dropdownSheetList}>
|
value={
|
||||||
{Array.from(connectionStates.values()).map(({ daemon, status }) => {
|
worktreeMode === "create"
|
||||||
const isSelected = daemon.serverId === selectedServerId;
|
? "__create_new__"
|
||||||
const label = daemon.label?.trim() ? daemon.label : daemon.serverId;
|
: worktreeMode === "attach"
|
||||||
return (
|
? selectedWorktreePath
|
||||||
<Pressable
|
: "__none__"
|
||||||
key={daemon.serverId}
|
}
|
||||||
style={[
|
onSelect={(id) => {
|
||||||
styles.dropdownSheetOption,
|
if (id === "__create_new__") {
|
||||||
isSelected && styles.dropdownSheetOptionSelected,
|
setWorktreeMode("create");
|
||||||
]}
|
if (!worktreeSlug) {
|
||||||
onPress={() => {
|
setWorktreeSlug(createNameId());
|
||||||
setSelectedServerIdFromUser(daemon.serverId);
|
}
|
||||||
closeDropdown();
|
setSelectedWorktreePath("");
|
||||||
}}
|
return;
|
||||||
>
|
}
|
||||||
<Text style={styles.dropdownSheetOptionLabel}>
|
if (id === "__none__") {
|
||||||
{label}
|
setWorktreeMode("none");
|
||||||
</Text>
|
setSelectedWorktreePath("");
|
||||||
<Text style={styles.dropdownSheetOptionDescription}>
|
return;
|
||||||
{formatConnectionStatus(status)}
|
}
|
||||||
</Text>
|
handleSelectWorktreePath(id);
|
||||||
</Pressable>
|
setWorktreeMode("attach");
|
||||||
);
|
}}
|
||||||
})}
|
title="Select worktree"
|
||||||
</View>
|
searchPlaceholder="Search worktrees..."
|
||||||
)}
|
open={isWorktreePickerOpen}
|
||||||
</DropdownSheet>
|
onOpenChange={setIsWorktreePickerOpen}
|
||||||
|
emptyText="No worktrees found"
|
||||||
|
anchorRef={worktreeAnchorRef}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Combobox
|
||||||
|
options={agentWorkingDirSuggestions.map((path) => ({ id: path, label: path }))}
|
||||||
|
value={workingDir}
|
||||||
|
onSelect={setWorkingDirFromUser}
|
||||||
|
searchPlaceholder="/path/to/project"
|
||||||
|
emptyText={
|
||||||
|
agentWorkingDirSuggestions.length > 0
|
||||||
|
? "No agent directories match your search."
|
||||||
|
: "We'll suggest directories from agents on this host once they exist."
|
||||||
|
}
|
||||||
|
allowCustomValue
|
||||||
|
customValuePrefix="Use"
|
||||||
|
customValueDescription="Launch the agent in this directory"
|
||||||
|
title="Working directory"
|
||||||
|
open={isWorkingDirOpen}
|
||||||
|
onOpenChange={setIsWorkingDirOpen}
|
||||||
|
anchorRef={workingDirAnchorRef}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Combobox
|
||||||
|
options={branchComboOptions}
|
||||||
|
value={baseBranch}
|
||||||
|
onSelect={handleBaseBranchChange}
|
||||||
|
searchPlaceholder="Choose a base branch..."
|
||||||
|
allowCustomValue
|
||||||
|
customValuePrefix="Use"
|
||||||
|
customValueDescription="Use this branch name"
|
||||||
|
title="Select base branch"
|
||||||
|
open={isBranchOpen}
|
||||||
|
onOpenChange={setIsBranchOpen}
|
||||||
|
anchorRef={branchAnchorRef}
|
||||||
|
/>
|
||||||
|
|
||||||
{formErrorMessage ? (
|
{formErrorMessage ? (
|
||||||
<View style={styles.errorContainer}>
|
<View style={styles.errorContainer}>
|
||||||
@@ -980,6 +1113,11 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
agentPanel: {
|
agentPanel: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
menuToggleContainer: {
|
||||||
|
paddingHorizontal: theme.spacing[2],
|
||||||
|
paddingTop: theme.spacing[2],
|
||||||
|
alignItems: "flex-start",
|
||||||
|
},
|
||||||
contentContainer: {
|
contentContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
@@ -998,7 +1136,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
},
|
},
|
||||||
configSection: {
|
configSection: {
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[0],
|
||||||
paddingTop: theme.spacing[3],
|
paddingTop: theme.spacing[3],
|
||||||
paddingBottom: theme.spacing[4],
|
paddingBottom: theme.spacing[4],
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
@@ -1006,46 +1144,31 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
dropdownHelper: {
|
topSelectorRow: {
|
||||||
fontSize: theme.fontSize.sm,
|
flexDirection: "row",
|
||||||
color: theme.colors.foregroundMuted,
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
dropdownSheetList: {
|
topSelectorPrimary: {
|
||||||
marginTop: theme.spacing[3],
|
flex: 7,
|
||||||
},
|
},
|
||||||
dropdownSheetOption: {
|
topSelectorSecondary: {
|
||||||
paddingVertical: theme.spacing[3],
|
flex: 3,
|
||||||
paddingHorizontal: theme.spacing[4],
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
backgroundColor: theme.colors.surface0,
|
|
||||||
marginBottom: theme.spacing[2],
|
|
||||||
},
|
},
|
||||||
dropdownSheetOptionSelected: {
|
halfSelector: {
|
||||||
borderColor: theme.colors.palette.blue[400],
|
flex: 1,
|
||||||
backgroundColor: "rgba(59, 130, 246, 0.18)",
|
|
||||||
},
|
|
||||||
dropdownSheetOptionLabel: {
|
|
||||||
color: theme.colors.foreground,
|
|
||||||
fontWeight: theme.fontWeight.semibold,
|
|
||||||
},
|
|
||||||
dropdownSheetOptionDescription: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
marginTop: theme.spacing[1],
|
|
||||||
},
|
},
|
||||||
errorContainer: {
|
errorContainer: {
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[4],
|
||||||
paddingVertical: theme.spacing[3],
|
paddingVertical: theme.spacing[3],
|
||||||
marginHorizontal: theme.spacing[4],
|
marginHorizontal: theme.spacing[0],
|
||||||
marginBottom: theme.spacing[2],
|
marginBottom: theme.spacing[2],
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
backgroundColor: theme.colors.destructive,
|
backgroundColor: theme.colors.destructive,
|
||||||
},
|
},
|
||||||
errorText: {
|
errorText: {
|
||||||
color: theme.colors.destructiveForeground,
|
color: theme.colors.destructiveForeground,
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.base,
|
||||||
},
|
},
|
||||||
warningContainer: {
|
warningContainer: {
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
@@ -1055,35 +1178,11 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
warningText: {
|
warningText: {
|
||||||
color: "#000000",
|
color: "#000000",
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.base,
|
||||||
fontWeight: theme.fontWeight.semibold,
|
fontWeight: theme.fontWeight.semibold,
|
||||||
},
|
},
|
||||||
hostBadge: {
|
errorInlineText: {
|
||||||
flexDirection: "row",
|
color: theme.colors.palette.red[500],
|
||||||
alignItems: "center",
|
fontSize: theme.fontSize.base,
|
||||||
gap: theme.spacing[2],
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.full,
|
|
||||||
},
|
|
||||||
hostBadgeLabel: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
fontWeight: theme.fontWeight.semibold,
|
|
||||||
},
|
|
||||||
hostBadgeStatus: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
fontWeight: theme.fontWeight.medium,
|
|
||||||
},
|
|
||||||
hostStatusDot: {
|
|
||||||
width: 6,
|
|
||||||
height: 6,
|
|
||||||
borderRadius: theme.borderRadius.full,
|
|
||||||
backgroundColor: theme.colors.foregroundMuted,
|
|
||||||
},
|
|
||||||
hostStatusDotOnline: {
|
|
||||||
backgroundColor: theme.colors.palette.green[500],
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ const lightSemanticColors = {
|
|||||||
|
|
||||||
// Borders - shifted one step lighter
|
// Borders - shifted one step lighter
|
||||||
border: "#e4e4e7", // (was zinc-200, now zinc-200 - keep for contrast)
|
border: "#e4e4e7", // (was zinc-200, now zinc-200 - keep for contrast)
|
||||||
borderAccent: "#e4e4e7", // (was zinc-300, now zinc-200)
|
borderAccent: "#ececf1", // Softer accent border for low-emphasis outlines
|
||||||
|
|
||||||
// Brand
|
// Brand
|
||||||
accent: "#20744A",
|
accent: "#20744A",
|
||||||
@@ -141,7 +141,7 @@ const lightSemanticColors = {
|
|||||||
secondaryForeground: "#09090b",
|
secondaryForeground: "#09090b",
|
||||||
muted: "#f4f4f5",
|
muted: "#f4f4f5",
|
||||||
mutedForeground: "#71717a",
|
mutedForeground: "#71717a",
|
||||||
accentBorder: "#e4e4e7",
|
accentBorder: "#ececf1",
|
||||||
input: "#f4f4f5",
|
input: "#f4f4f5",
|
||||||
ring: "#18181b",
|
ring: "#18181b",
|
||||||
} as const;
|
} as const;
|
||||||
@@ -159,7 +159,7 @@ const darkSemanticColors = {
|
|||||||
|
|
||||||
// Borders
|
// Borders
|
||||||
border: "#27272a",
|
border: "#27272a",
|
||||||
borderAccent: "#3f3f46",
|
borderAccent: "#34343a",
|
||||||
|
|
||||||
// Brand
|
// Brand
|
||||||
accent: "#20744A",
|
accent: "#20744A",
|
||||||
@@ -183,7 +183,7 @@ const darkSemanticColors = {
|
|||||||
secondaryForeground: "#fafafa",
|
secondaryForeground: "#fafafa",
|
||||||
muted: "#27272a",
|
muted: "#27272a",
|
||||||
mutedForeground: "#a1a1aa",
|
mutedForeground: "#a1a1aa",
|
||||||
accentBorder: "#3f3f46",
|
accentBorder: "#34343a",
|
||||||
input: "#27272a",
|
input: "#27272a",
|
||||||
ring: "#d4d4d8",
|
ring: "#d4d4d8",
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -63,6 +63,21 @@ interface TurnContext {
|
|||||||
streamedReasoningThisTurn: boolean;
|
streamedReasoningThisTurn: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeClaudeModelLabel(model: ModelInfo): string {
|
||||||
|
const fallback = model.displayName?.trim() || model.value;
|
||||||
|
const prefix = model.description?.split(/[·•]/)[0]?.trim() || "";
|
||||||
|
if (!prefix) return fallback;
|
||||||
|
|
||||||
|
// Prefer concrete versioned labels from description (e.g. "Opus 4.6",
|
||||||
|
// "Sonnet 4.5"), especially when displayName is generic like
|
||||||
|
// "Default (recommended)".
|
||||||
|
if (/\d/.test(prefix)) {
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
const CLAUDE_CAPABILITIES: AgentCapabilityFlags = {
|
const CLAUDE_CAPABILITIES: AgentCapabilityFlags = {
|
||||||
supportsStreaming: true,
|
supportsStreaming: true,
|
||||||
supportsSessionPersistence: true,
|
supportsSessionPersistence: true,
|
||||||
@@ -371,7 +386,7 @@ export class ClaudeAgentClient implements AgentClient {
|
|||||||
return models.map((model) => ({
|
return models.map((model) => ({
|
||||||
provider: "claude" as const,
|
provider: "claude" as const,
|
||||||
id: model.value,
|
id: model.value,
|
||||||
label: model.displayName,
|
label: normalizeClaudeModelLabel(model),
|
||||||
description: model.description,
|
description: model.description,
|
||||||
thinkingOptions: [
|
thinkingOptions: [
|
||||||
{ id: "off", label: "Off", isDefault: true },
|
{ id: "off", label: "Off", isDefault: true },
|
||||||
|
|||||||
Reference in New Issue
Block a user