Skip to content

Commit ee5bbf6

Browse files
Twixescharlesvien
authored andcommitted
feat: Make mode selection a select
1 parent 1b9c9c5 commit ee5bbf6

File tree

5 files changed

+111
-34
lines changed

5 files changed

+111
-34
lines changed

apps/twig/src/renderer/features/message-editor/components/MessageEditor.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ interface MessageEditorProps {
2626
onAttachFiles?: (files: File[]) => void;
2727
autoFocus?: boolean;
2828
currentMode?: ExecutionMode;
29-
onModeChange?: () => void;
29+
onModeChange?: (mode: ExecutionMode) => void;
3030
}
3131

3232
export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
@@ -206,7 +206,7 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
206206
</Flex>
207207
</Flex>
208208
{onModeChange && currentMode && (
209-
<ModeIndicatorInput mode={currentMode} taskId={taskId} />
209+
<ModeIndicatorInput mode={currentMode} onModeChange={onModeChange} taskId={taskId} />
210210
)}
211211
</Flex>
212212
);

apps/twig/src/renderer/features/message-editor/components/ModeIndicatorInput.tsx

Lines changed: 93 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import {
77
Pencil,
88
ShieldCheck,
99
} from "@phosphor-icons/react";
10-
import { Flex, Text } from "@radix-ui/themes";
1110
import { trpcVanilla } from "@renderer/trpc";
1211
import { useQuery } from "@tanstack/react-query";
12+
import { Flex, Select, Text } from "@radix-ui/themes";
13+
import { EXECUTION_MODES } from "@shared/constants";
1314

1415
interface ModeIndicatorInputProps {
1516
mode: ExecutionMode;
1617
taskId?: string;
18+
onModeChange: (mode: ExecutionMode) => void;
1719
}
1820

1921
const modeConfig: Record<
@@ -26,27 +28,31 @@ const modeConfig: Record<
2628
> = {
2729
plan: {
2830
label: "plan mode on",
29-
icon: <Pause size={12} weight="bold" />,
31+
icon: <Pause size={12} weight="bold" color="var(--amber-11)" />,
3032
colorVar: "var(--amber-11)",
3133
},
3234
default: {
3335
label: "default mode",
34-
icon: <Pencil size={12} />,
36+
icon: <Pencil size={12} color="var(--gray-11)" />,
3537
colorVar: "var(--gray-11)",
3638
},
3739
acceptEdits: {
3840
label: "auto-accept edits",
39-
icon: <ShieldCheck size={12} weight="fill" />,
41+
icon: <ShieldCheck size={12} weight="fill" color="var(--green-11)" />,
4042
colorVar: "var(--green-11)",
4143
},
4244
bypassPermissions: {
4345
label: "bypass permissions",
44-
icon: <LockOpen size={12} weight="bold" />,
46+
icon: <LockOpen size={12} weight="bold" color="var(--red-11)" />,
4547
colorVar: "var(--red-11)",
4648
},
4749
};
4850

49-
export function ModeIndicatorInput({ mode, taskId }: ModeIndicatorInputProps) {
51+
export function ModeIndicatorInput({
52+
mode,
53+
onModeChange,
54+
taskId,
55+
}: ModeIndicatorInputProps) {
5056
const config = modeConfig[mode];
5157
const repoPath = useCwd(taskId ?? "");
5258

@@ -65,30 +71,38 @@ export function ModeIndicatorInput({ mode, taskId }: ModeIndicatorInputProps) {
6571
const hasDiffStats = diffStats && diffStats.filesChanged > 0;
6672

6773
return (
68-
<Flex align="center" justify="between" py="1">
69-
<Flex align="center" gap="1">
70-
<Text
71-
size="1"
72-
style={{
73-
color: config.colorVar,
74-
fontFamily: "monospace",
75-
display: "flex",
76-
alignItems: "center",
77-
gap: "4px",
78-
}}
79-
>
74+
<Select.Root
75+
value={mode}
76+
onValueChange={onModeChange}
77+
disabled={disabled}
78+
size="1"
79+
>
80+
<Select.Trigger
81+
className="w-fit"
82+
onClick={(e) => {
83+
e.stopPropagation();
84+
}}
85+
>
86+
<Flex align="center" gap="1">
8087
{config.icon}
81-
{config.label}
82-
</Text>
83-
<Text
84-
size="1"
85-
style={{
86-
color: "var(--gray-9)",
87-
fontFamily: "monospace",
88-
}}
89-
>
90-
(shift+tab to cycle)
91-
</Text>
88+
<Text
89+
size="1"
90+
style={{
91+
color: config.colorVar,
92+
fontFamily: "monospace",
93+
}}
94+
>
95+
{config.label}
96+
</Text>
97+
<Text
98+
size="1"
99+
style={{
100+
color: "var(--gray-9)",
101+
fontFamily: "monospace",
102+
}}
103+
>
104+
(shift+tab to cycle)
105+
</Text>
92106
{hasDiffStats && (
93107
<Text
94108
size="1"
@@ -113,7 +127,55 @@ export function ModeIndicatorInput({ mode, taskId }: ModeIndicatorInputProps) {
113127
</span>
114128
</Text>
115129
)}
116-
</Flex>
117-
</Flex>
130+
</Flex>
131+
</Select.Trigger>
132+
<Select.Content>
133+
{EXECUTION_MODES.map((modeOption) => {
134+
const optionConfig = modeConfig[modeOption];
135+
const hoverBgClass =
136+
modeOption === "plan"
137+
? "hover:!bg-[var(--amber-11)]"
138+
: modeOption === "default"
139+
? "hover:!bg-[var(--gray-11)]"
140+
: modeOption === "acceptEdits"
141+
? "hover:!bg-[var(--green-11)]"
142+
: "hover:!bg-[var(--red-11)]";
143+
return (
144+
<Select.Item
145+
key={modeOption}
146+
value={modeOption}
147+
className={`group transition-colors ${hoverBgClass}`}
148+
>
149+
<Flex
150+
align="center"
151+
gap="1"
152+
className="group-hover:!text-[black] [&_svg]:group-hover:!text-[black] [&_svg]:group-hover:!fill-[black] [&_svg_path]:group-hover:!fill-[black] [&_svg_path]:group-hover:!stroke-[black]"
153+
style={{
154+
color: optionConfig.colorVar,
155+
fontFamily: "monospace",
156+
}}
157+
>
158+
<span className="group-hover:[&_svg]:!text-[black] group-hover:[&_svg]:!fill-[black] group-hover:[&_svg_path]:!fill-[black] group-hover:[&_svg_path]:!stroke-[black]">
159+
{optionConfig.icon}
160+
</span>
161+
<Text size="1" className="group-hover:!text-[black]">
162+
{optionConfig.label}
163+
</Text>
164+
</Flex>
165+
</Select.Item>
166+
);
167+
})}
168+
</Select.Content>
169+
<style>{`
170+
.group:hover svg {
171+
color: black !important;
172+
fill: black !important;
173+
}
174+
.group:hover svg path {
175+
fill: black !important;
176+
stroke: black !important;
177+
}
178+
`}</style>
179+
</Select.Root>
118180
);
119181
}

apps/twig/src/renderer/features/task-detail/components/TaskInput.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useSettingsStore } from "@features/settings/stores/settingsStore";
99
import { useRepositoryIntegration } from "@hooks/useIntegrations";
1010
import { Flex } from "@radix-ui/themes";
1111
import { useRegisteredFoldersStore } from "@renderer/stores/registeredFoldersStore";
12+
import { EXECUTION_MODES } from "@shared/constants";
1213
import { useNavigationStore } from "@stores/navigationStore";
1314
import { useTaskDirectoryStore } from "@stores/taskDirectoryStore";
1415
import { useCallback, useEffect, useRef, useState } from "react";

apps/twig/src/renderer/features/task-detail/components/TaskInputEditor.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,12 @@ export const TaskInputEditor = forwardRef<
234234
</Flex>
235235
</Flex>
236236
</Flex>
237-
{!isCloudMode && <ModeIndicatorInput mode={executionMode} />}
237+
{!isCloudMode && (
238+
<ModeIndicatorInput
239+
mode={executionMode}
240+
onModeChange={onModeChange}
241+
/>
242+
)}
238243
</>
239244
);
240245
},

apps/twig/src/shared/constants.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { ExecutionMode } from "@/main/services/agent/schemas";
2+
13
/**
24
* Branch naming conventions.
35
* - Reading: Accept all prefixes for backwards compatibility
@@ -21,3 +23,10 @@ export function isTwigBranch(branchName: string): boolean {
2123
export const DATA_DIR = ".twig";
2224
export const WORKSPACES_DIR = ".twig/workspaces";
2325
export const LEGACY_DATA_DIRS = [".array"];
26+
27+
export const EXECUTION_MODES: ExecutionMode[] = [
28+
"default",
29+
"acceptEdits",
30+
"plan",
31+
"bypassPermissions",
32+
];

0 commit comments

Comments
 (0)