Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface MessageEditorProps {
onAttachFiles?: (files: File[]) => void;
autoFocus?: boolean;
currentMode?: ExecutionMode;
onModeChange?: (mode: ExecutionMode) => void;
onModeChange?: () => void;
}

export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
Expand Down Expand Up @@ -208,10 +208,7 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
</Flex>
{onModeChange && currentMode && (
<Flex align="center" gap="2">
<ModeIndicatorInput
mode={currentMode}
onModeChange={onModeChange}
/>
<ModeIndicatorInput mode={currentMode} />
<DiffStatsIndicator repoPath={repoPath} />
</Flex>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
import { useSettingsStore } from "@features/settings/stores/settingsStore";
import { useCwd } from "@features/sidebar/hooks/useCwd";
import {
Circle,
LockOpen,
Pause,
Pencil,
ShieldCheck,
} from "@phosphor-icons/react";
import { Flex, Select, Text } from "@radix-ui/themes";
import { trpcVanilla } from "@renderer/trpc";
import { EXECUTION_MODES, type ExecutionMode } from "@shared/types";
import { useQuery } from "@tanstack/react-query";
import { LockOpen, Pause, Pencil, ShieldCheck } from "@phosphor-icons/react";
import { Flex, Text } from "@radix-ui/themes";
import type { ExecutionMode } from "@shared/types";

interface ModeIndicatorInputProps {
mode: ExecutionMode;
taskId?: string;
onModeChange: (mode: ExecutionMode) => void;
}

const modeConfig: Record<
Expand All @@ -28,146 +16,55 @@ const modeConfig: Record<
> = {
plan: {
label: "plan mode on",
icon: <Pause size={12} weight="bold" color="var(--amber-11)" />,
icon: <Pause size={12} weight="bold" />,
colorVar: "var(--amber-11)",
},
default: {
label: "default mode",
icon: <Pencil size={12} color="var(--gray-11)" />,
icon: <Pencil size={12} />,
colorVar: "var(--gray-11)",
},
acceptEdits: {
label: "auto-accept edits",
icon: <ShieldCheck size={12} weight="fill" color="var(--green-11)" />,
icon: <ShieldCheck size={12} weight="fill" />,
colorVar: "var(--green-11)",
},
bypassPermissions: {
label: "bypass permissions",
icon: <LockOpen size={12} weight="bold" color="var(--red-11)" />,
icon: <LockOpen size={12} weight="bold" />,
colorVar: "var(--red-11)",
},
};

export function ModeIndicatorInput({
mode,
onModeChange,
taskId,
}: ModeIndicatorInputProps) {
export function ModeIndicatorInput({ mode }: ModeIndicatorInputProps) {
const config = modeConfig[mode];
const repoPath = useCwd(taskId ?? "");
const allowBypassPermissions = useSettingsStore(
(s) => s.allowBypassPermissions,
);

const availableModes = allowBypassPermissions
? EXECUTION_MODES
: EXECUTION_MODES.filter((m) => m !== "bypassPermissions");

const { data: diffStats } = useQuery({
queryKey: ["diff-stats", repoPath],
queryFn: () =>
trpcVanilla.git.getDiffStats.query({
directoryPath: repoPath as string,
}),
enabled: !!repoPath && !!taskId,
staleTime: 5000,
refetchInterval: 5000,
placeholderData: (prev) => prev,
});

const hasDiffStats = diffStats && diffStats.filesChanged > 0;

return (
<Select.Root value={mode} onValueChange={onModeChange} size="1">
<Select.Trigger
className="w-fit"
onClick={(e) => {
e.stopPropagation();
}}
>
<Flex align="center" gap="1">
<Flex align="center" justify="between" py="1">
<Flex align="center" gap="1">
<Text
size="1"
style={{
color: config.colorVar,
fontFamily: "monospace",
display: "flex",
alignItems: "center",
gap: "4px",
}}
>
{config.icon}
<Text
size="1"
style={{
color: config.colorVar,
fontFamily: "monospace",
}}
>
{config.label}
</Text>
<Text
size="1"
style={{
color: "var(--gray-9)",
fontFamily: "monospace",
}}
>
(shift+tab to cycle)
</Text>
{hasDiffStats && (
<Text
size="1"
style={{
color: "var(--gray-9)",
fontFamily: "monospace",
display: "flex",
alignItems: "center",
gap: "6px",
}}
>
<Circle size={4} weight="fill" style={{ margin: "0 4px" }} />
<span style={{ color: "var(--gray-11)" }}>
{diffStats.filesChanged}{" "}
{diffStats.filesChanged === 1 ? "file" : "files"}
</span>
<span style={{ color: "var(--green-9)" }}>
+{diffStats.linesAdded}
</span>
<span style={{ color: "var(--red-9)" }}>
-{diffStats.linesRemoved}
</span>
</Text>
)}
</Flex>
</Select.Trigger>
<Select.Content>
{availableModes.map((modeOption) => {
const optionConfig = modeConfig[modeOption];
const hoverBgClass =
modeOption === "plan"
? "hover:!bg-[var(--amber-11)]"
: modeOption === "default"
? "hover:!bg-[var(--gray-11)]"
: modeOption === "acceptEdits"
? "hover:!bg-[var(--green-11)]"
: "hover:!bg-[var(--red-11)]";
return (
<Select.Item
key={modeOption}
value={modeOption}
className={`group transition-colors ${hoverBgClass}`}
>
<Flex
align="center"
gap="1"
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]"
style={{
color: optionConfig.colorVar,
fontFamily: "monospace",
}}
>
<span className="group-hover:[&_svg]:!text-[black] group-hover:[&_svg]:!fill-[black] group-hover:[&_svg_path]:!fill-[black] group-hover:[&_svg_path]:!stroke-[black]">
{optionConfig.icon}
</span>
<Text size="1" className="group-hover:!text-[black]">
{optionConfig.label}
</Text>
</Flex>
</Select.Item>
);
})}
</Select.Content>
</Select.Root>
{config.label}
</Text>
<Text
size="1"
style={{
color: "var(--gray-9)",
fontFamily: "monospace",
}}
>
(shift+tab to cycle)
</Text>
</Flex>
</Flex>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,11 @@ export function SessionView({
}
}, [allowBypassPermissions, currentMode, taskId, isCloud, setSessionMode]);

const handleModeChange = useCallback(
(mode: ExecutionMode) => {
if (!taskId || isCloud) return;
setSessionMode(taskId, mode);
},
[taskId, isCloud, setSessionMode],
);
const handleModeChange = useCallback(() => {
if (!taskId || isCloud) return;
const nextMode = cycleExecutionMode(currentMode, allowBypassPermissions);
setSessionMode(taskId, nextMode);
}, [taskId, isCloud, currentMode, allowBypassPermissions, setSessionMode]);

const sessionId = taskId ?? "default";
const setContext = useDraftStore((s) => s.actions.setContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { TorchGlow } from "@components/TorchGlow";
import { FolderPicker } from "@features/folder-picker/components/FolderPicker";
import type { MessageEditorHandle } from "@features/message-editor/components/MessageEditor";
import type { ExecutionMode } from "@features/sessions/stores/sessionStore";
import {
cycleExecutionMode,
type ExecutionMode,
} from "@features/sessions/stores/sessionStore";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
import { useRepositoryIntegration } from "@hooks/useIntegrations";
import { Flex } from "@radix-ui/themes";
Expand Down Expand Up @@ -43,9 +46,11 @@ export function TaskInput() {
}
}, [allowBypassPermissions, executionMode]);

const handleModeChange = useCallback((mode: ExecutionMode) => {
setExecutionMode(mode);
}, []);
const handleModeChange = useCallback(() => {
setExecutionMode((current) =>
cycleExecutionMode(current, allowBypassPermissions),
);
}, [allowBypassPermissions]);

const { githubIntegration } = useRepositoryIntegration();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ import { EditorToolbar } from "@features/message-editor/components/EditorToolbar
import type { MessageEditorHandle } from "@features/message-editor/components/MessageEditor";
import { ModeIndicatorInput } from "@features/message-editor/components/ModeIndicatorInput";
import { useTiptapEditor } from "@features/message-editor/tiptap/useTiptapEditor";
import {
cycleExecutionMode,
type ExecutionMode,
} from "@features/sessions/stores/sessionStore";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
import type { ExecutionMode } from "@features/sessions/stores/sessionStore";
import { useConnectivity } from "@hooks/useConnectivity";
import { ArrowUp } from "@phosphor-icons/react";
import { Box, Flex, IconButton, Text, Tooltip } from "@radix-ui/themes";
Expand All @@ -27,7 +23,7 @@ interface TaskInputEditorProps {
hasDirectory: boolean;
onEmptyChange?: (isEmpty: boolean) => void;
executionMode: ExecutionMode;
onModeChange: (mode: ExecutionMode) => void;
onModeChange: () => void;
}

export const TaskInputEditor = forwardRef<
Expand All @@ -52,26 +48,19 @@ export const TaskInputEditor = forwardRef<
const isCloudMode = runMode === "cloud";
const { isOnline } = useConnectivity();
const isDisabled = isCreatingTask || !isOnline;
const { allowBypassPermissions } = useSettingsStore();

useHotkeys(
"shift+tab",
(e) => {
e.preventDefault();
onModeChange(cycleExecutionMode(executionMode, allowBypassPermissions));
onModeChange();
},
{
enableOnFormTags: true,
enableOnContentEditable: true,
enabled: !isCreatingTask && !isCloudMode,
},
[
onModeChange,
executionMode,
allowBypassPermissions,
isCreatingTask,
isCloudMode,
],
[onModeChange, isCreatingTask, isCloudMode],
);

const {
Expand Down Expand Up @@ -245,12 +234,7 @@ export const TaskInputEditor = forwardRef<
</Flex>
</Flex>
</Flex>
{!isCloudMode && (
<ModeIndicatorInput
mode={executionMode}
onModeChange={onModeChange}
/>
)}
{!isCloudMode && <ModeIndicatorInput mode={executionMode} />}
</>
);
},
Expand Down
Loading