Skip to content

Commit fe8c122

Browse files
committed
feat: Make mode selection a select
1 parent bdbcef4 commit fe8c122

6 files changed

Lines changed: 117 additions & 47 deletions

File tree

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

Lines changed: 7 additions & 1 deletion
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,13 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
206206
</Flex>
207207
</Flex>
208208
{onModeChange && currentMode && (
209+
<<<<<<< HEAD
209210
<ModeIndicatorInput mode={currentMode} taskId={taskId} />
211+
||||||| parent of fe3d9050 (feat: Make mode selection a select)
212+
<ModeIndicatorInput mode={currentMode} />
213+
=======
214+
<ModeIndicatorInput mode={currentMode} onModeChange={onModeChange} />
215+
>>>>>>> fe3d9050 (feat: Make mode selection a select)
210216
)}
211217
</Flex>
212218
);

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

Lines changed: 93 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import type { ExecutionMode } from "@features/sessions/stores/sessionStore";
22
import { useCwd } from "@features/sidebar/hooks/useCwd";
33
import { LockOpen, Circle,Pause, Pencil, ShieldCheck } from "@phosphor-icons/react";
4-
import { Flex, Text } from "@radix-ui/themes";
54
import { trpcVanilla } from "@renderer/trpc";
65
import { useQuery } from "@tanstack/react-query";
6+
import { Flex, Select, Text } from "@radix-ui/themes";
7+
import { EXECUTION_MODES } from "@shared/constants";
78

89
interface ModeIndicatorInputProps {
910
mode: ExecutionMode;
1011
taskId?: string;
12+
onModeChange: (mode: ExecutionMode) => void;
1113
}
1214

1315
const modeConfig: Record<
@@ -20,27 +22,31 @@ const modeConfig: Record<
2022
> = {
2123
plan: {
2224
label: "plan mode on",
23-
icon: <Pause size={12} weight="bold" />,
25+
icon: <Pause size={12} weight="bold" color="var(--amber-11)" />,
2426
colorVar: "var(--amber-11)",
2527
},
2628
default: {
2729
label: "default mode",
28-
icon: <Pencil size={12} />,
30+
icon: <Pencil size={12} color="var(--gray-11)" />,
2931
colorVar: "var(--gray-11)",
3032
},
3133
acceptEdits: {
3234
label: "auto-accept edits",
33-
icon: <ShieldCheck size={12} weight="fill" />,
35+
icon: <ShieldCheck size={12} weight="fill" color="var(--green-11)" />,
3436
colorVar: "var(--green-11)",
3537
},
3638
bypassPermissions: {
3739
label: "bypass permissions",
38-
icon: <LockOpen size={12} weight="bold" />,
40+
icon: <LockOpen size={12} weight="bold" color="var(--red-11)" />,
3941
colorVar: "var(--red-11)",
4042
},
4143
};
4244

43-
export function ModeIndicatorInput({ mode, taskId }: ModeIndicatorInputProps) {
45+
export function ModeIndicatorInput({
46+
mode,
47+
onModeChange,
48+
taskId,
49+
}: ModeIndicatorInputProps) {
4450
const config = modeConfig[mode];
4551
const repoPath = useCwd(taskId ?? "");
4652

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

6167
return (
62-
<Flex align="center" justify="between" py="1">
63-
<Flex align="center" gap="1">
64-
<Text
65-
size="1"
66-
style={{
67-
color: config.colorVar,
68-
fontFamily: "monospace",
69-
display: "flex",
70-
alignItems: "center",
71-
gap: "4px",
72-
}}
73-
>
68+
<Select.Root
69+
value={mode}
70+
onValueChange={onModeChange}
71+
disabled={disabled}
72+
size="1"
73+
>
74+
<Select.Trigger
75+
className="w-fit"
76+
onClick={(e) => {
77+
e.stopPropagation();
78+
}}
79+
>
80+
<Flex align="center" gap="1">
7481
{config.icon}
75-
{config.label}
76-
</Text>
77-
<Text
78-
size="1"
79-
style={{
80-
color: "var(--gray-9)",
81-
fontFamily: "monospace",
82-
}}
83-
>
84-
(shift+tab to cycle)
85-
</Text>
82+
<Text
83+
size="1"
84+
style={{
85+
color: config.colorVar,
86+
fontFamily: "monospace",
87+
}}
88+
>
89+
{config.label}
90+
</Text>
91+
<Text
92+
size="1"
93+
style={{
94+
color: "var(--gray-9)",
95+
fontFamily: "monospace",
96+
}}
97+
>
98+
(shift+tab to cycle)
99+
</Text>
86100
{hasDiffStats && (
87101
<Text
88102
size="1"
@@ -107,7 +121,55 @@ export function ModeIndicatorInput({ mode, taskId }: ModeIndicatorInputProps) {
107121
</span>
108122
</Text>
109123
)}
110-
</Flex>
111-
</Flex>
124+
</Flex>
125+
</Select.Trigger>
126+
<Select.Content>
127+
{EXECUTION_MODES.map((modeOption) => {
128+
const optionConfig = modeConfig[modeOption];
129+
const hoverBgClass =
130+
modeOption === "plan"
131+
? "hover:!bg-[var(--amber-11)]"
132+
: modeOption === "default"
133+
? "hover:!bg-[var(--gray-11)]"
134+
: modeOption === "acceptEdits"
135+
? "hover:!bg-[var(--green-11)]"
136+
: "hover:!bg-[var(--red-11)]";
137+
return (
138+
<Select.Item
139+
key={modeOption}
140+
value={modeOption}
141+
className={`group transition-colors ${hoverBgClass}`}
142+
>
143+
<Flex
144+
align="center"
145+
gap="1"
146+
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]"
147+
style={{
148+
color: optionConfig.colorVar,
149+
fontFamily: "monospace",
150+
}}
151+
>
152+
<span className="group-hover:[&_svg]:!text-[black] group-hover:[&_svg]:!fill-[black] group-hover:[&_svg_path]:!fill-[black] group-hover:[&_svg_path]:!stroke-[black]">
153+
{optionConfig.icon}
154+
</span>
155+
<Text size="1" className="group-hover:!text-[black]">
156+
{optionConfig.label}
157+
</Text>
158+
</Flex>
159+
</Select.Item>
160+
);
161+
})}
162+
</Select.Content>
163+
<style>{`
164+
.group:hover svg {
165+
color: black !important;
166+
fill: black !important;
167+
}
168+
.group:hover svg path {
169+
fill: black !important;
170+
stroke: black !important;
171+
}
172+
`}</style>
173+
</Select.Root>
112174
);
113175
}

apps/twig/src/renderer/features/sessions/components/SessionView.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import type { Plan } from "@features/sessions/types";
1313
import { Warning } from "@phosphor-icons/react";
1414
import { Box, Button, ContextMenu, Flex, Text } from "@radix-ui/themes";
15+
import { EXECUTION_MODES } from "@shared/constants";
1516
import {
1617
type AcpMessage,
1718
isJsonRpcNotification,
@@ -29,13 +30,6 @@ import { InlinePermissionSelector } from "./InlinePermissionSelector";
2930
import { PlanStatusBar } from "./PlanStatusBar";
3031
import { RawLogsView } from "./raw-logs/RawLogsView";
3132

32-
const EXECUTION_MODES: ExecutionMode[] = [
33-
"plan",
34-
"default",
35-
"acceptEdits",
36-
"bypassPermissions",
37-
];
38-
3933
function cycleMode(current: ExecutionMode): ExecutionMode {
4034
const currentIndex = EXECUTION_MODES.indexOf(current);
4135
const nextIndex = (currentIndex + 1) % EXECUTION_MODES.length;

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useSettingsStore } from "@features/settings/stores/settingsStore";
66
import { useRepositoryIntegration } from "@hooks/useIntegrations";
77
import { Flex } from "@radix-ui/themes";
88
import { useRegisteredFoldersStore } from "@renderer/stores/registeredFoldersStore";
9+
import { EXECUTION_MODES } from "@shared/constants";
910
import { useNavigationStore } from "@stores/navigationStore";
1011
import { useTaskDirectoryStore } from "@stores/taskDirectoryStore";
1112
import { useCallback, useEffect, useRef, useState } from "react";
@@ -14,13 +15,6 @@ import { SuggestedTasks } from "./SuggestedTasks";
1415
import { TaskInputEditor } from "./TaskInputEditor";
1516
import { type WorkspaceMode, WorkspaceModeSelect } from "./WorkspaceModeSelect";
1617

17-
const EXECUTION_MODES: ExecutionMode[] = [
18-
"plan",
19-
"default",
20-
"acceptEdits",
21-
"bypassPermissions",
22-
];
23-
2418
function cycleMode(current: ExecutionMode): ExecutionMode {
2519
const currentIndex = EXECUTION_MODES.indexOf(current);
2620
const nextIndex = (currentIndex + 1) % EXECUTION_MODES.length;

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)