From e37ca87887dfad8bcf0ff685b5486f8aeb73e3be Mon Sep 17 00:00:00 2001 From: nepula_h_okuyama Date: Thu, 11 Sep 2025 22:48:01 +0900 Subject: [PATCH] #1 feat: add configurable Enter key behavior for message input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add enterKeyBehavior setting to config schema (shift-enter-send | enter-send) - Implement Enter key behavior toggle in SettingsControls component - Update ChatInput to respect user's Enter key preference - Support IME composition to prevent accidental sends during Japanese input - Add dynamic placeholder text based on selected behavior 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../components/chatForm/ChatInput.tsx | 18 +++++++-- .../components/newChat/NewChat.tsx | 12 +++++- .../components/resumeChat/ResumeChat.tsx | 12 +++++- src/components/SettingsControls.tsx | 38 +++++++++++++++++++ src/server/config/config.ts | 1 + 5 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/app/projects/[projectId]/components/chatForm/ChatInput.tsx b/src/app/projects/[projectId]/components/chatForm/ChatInput.tsx index e4fe8c9..7485dc4 100644 --- a/src/app/projects/[projectId]/components/chatForm/ChatInput.tsx +++ b/src/app/projects/[projectId]/components/chatForm/ChatInput.tsx @@ -2,6 +2,7 @@ import { AlertCircleIcon, LoaderIcon, SendIcon } from "lucide-react"; import { type FC, useCallback, useId, useRef, useState } from "react"; import { Button } from "../../../../../components/ui/button"; import { Textarea } from "../../../../../components/ui/textarea"; +import { useConfig } from "../../../../hooks/useConfig"; import type { CommandCompletionRef } from "./CommandCompletion"; import type { FileCompletionRef } from "./FileCompletion"; import { InlineCompletion } from "./InlineCompletion"; @@ -42,6 +43,7 @@ export const ChatInput: FC = ({ const commandCompletionRef = useRef(null); const fileCompletionRef = useRef(null); const helpId = useId(); + const { config } = useConfig(); const handleSubmit = async () => { if (!message.trim()) return; @@ -58,9 +60,19 @@ export const ChatInput: FC = ({ return; } - if (e.key === "Enter" && e.shiftKey) { - e.preventDefault(); - handleSubmit(); + // IMEで変換中の場合は送信しない + if (e.key === "Enter" && !e.nativeEvent.isComposing) { + const isEnterSend = config?.enterKeyBehavior === "enter-send"; + + if (isEnterSend && !e.shiftKey) { + // Enter: Send mode + e.preventDefault(); + handleSubmit(); + } else if (!isEnterSend && e.shiftKey) { + // Shift+Enter: Send mode (default) + e.preventDefault(); + handleSubmit(); + } } }; diff --git a/src/app/projects/[projectId]/components/newChat/NewChat.tsx b/src/app/projects/[projectId]/components/newChat/NewChat.tsx index f72939f..62f4e30 100644 --- a/src/app/projects/[projectId]/components/newChat/NewChat.tsx +++ b/src/app/projects/[projectId]/components/newChat/NewChat.tsx @@ -1,4 +1,5 @@ import type { FC } from "react"; +import { useConfig } from "../../../../hooks/useConfig"; import { ChatInput, useNewChatMutation } from "../chatForm"; export const NewChat: FC<{ @@ -6,18 +7,27 @@ export const NewChat: FC<{ onSuccess?: () => void; }> = ({ projectId, onSuccess }) => { const startNewChat = useNewChatMutation(projectId, onSuccess); + const { config } = useConfig(); const handleSubmit = async (message: string) => { await startNewChat.mutateAsync({ message }); }; + const getPlaceholder = () => { + const isEnterSend = config?.enterKeyBehavior === "enter-send"; + if (isEnterSend) { + return "Type your message here... (Start with / for commands, @ for files, Enter to send)"; + } + return "Type your message here... (Start with / for commands, @ for files, Shift+Enter to send)"; + }; + return ( = ({ projectId, sessionId, isPausedTask, isRunningTask }) => { const resumeChat = useResumeChatMutation(projectId, sessionId); + const { config } = useConfig(); const handleSubmit = async (message: string) => { await resumeChat.mutateAsync({ message }); @@ -23,6 +25,14 @@ export const ResumeChat: FC<{ return "Resume"; }; + const getPlaceholder = () => { + const isEnterSend = config?.enterKeyBehavior === "enter-send"; + if (isEnterSend) { + return "Type your message... (Start with / for commands, Enter to send)"; + } + return "Type your message... (Start with / for commands, Shift+Enter to send)"; + }; + return (
= ({ await onConfigChanged(); }; + const handleEnterKeyBehaviorChange = async (value: string) => { + const newConfig = { + ...config, + enterKeyBehavior: value as "shift-enter-send" | "enter-send", + }; + updateConfig(newConfig); + await onConfigChanged(); + }; + return (
@@ -97,6 +113,28 @@ export const SettingsControls: FC = ({ title

)} + +
+ {showLabels && ( + + )} + + {showDescriptions && ( +

+ Choose how the Enter key behaves in message input +

+ )} +
); }; diff --git a/src/server/config/config.ts b/src/server/config/config.ts index 26563ed..4845c82 100644 --- a/src/server/config/config.ts +++ b/src/server/config/config.ts @@ -3,6 +3,7 @@ import z from "zod"; export const configSchema = z.object({ hideNoUserMessageSession: z.boolean().optional().default(true), unifySameTitleSession: z.boolean().optional().default(true), + enterKeyBehavior: z.enum(["shift-enter-send", "enter-send"]).optional().default("shift-enter-send"), }); export type Config = z.infer;