#1 feat: add configurable Enter key behavior for message input

- 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 <noreply@anthropic.com>
This commit is contained in:
nepula_h_okuyama
2025-09-11 22:48:01 +09:00
parent 1e6030bd52
commit e37ca87887
5 changed files with 76 additions and 5 deletions

View File

@@ -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<ChatInputProps> = ({
const commandCompletionRef = useRef<CommandCompletionRef>(null);
const fileCompletionRef = useRef<FileCompletionRef>(null);
const helpId = useId();
const { config } = useConfig();
const handleSubmit = async () => {
if (!message.trim()) return;
@@ -58,9 +60,19 @@ export const ChatInput: FC<ChatInputProps> = ({
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();
}
}
};

View File

@@ -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 (
<ChatInput
projectId={projectId}
onSubmit={handleSubmit}
isPending={startNewChat.isPending}
error={startNewChat.error}
placeholder="Type your message here... (Start with / for commands, @ for files, Shift+Enter to send)"
placeholder={getPlaceholder()}
buttonText="Start Chat"
minHeight="min-h-[200px]"
containerClassName="space-y-4"

View File

@@ -1,4 +1,5 @@
import type { FC } from "react";
import { useConfig } from "../../../../../../hooks/useConfig";
import {
ChatInput,
useResumeChatMutation,
@@ -11,6 +12,7 @@ export const ResumeChat: FC<{
isRunningTask: boolean;
}> = ({ 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 (
<div className="border-t border-border/50 bg-muted/20 p-4 mt-6">
<ChatInput
@@ -30,7 +40,7 @@ export const ResumeChat: FC<{
onSubmit={handleSubmit}
isPending={resumeChat.isPending}
error={resumeChat.error}
placeholder="Type your message... (Start with / for commands, Shift+Enter to send)"
placeholder={getPlaceholder()}
buttonText={getButtonText()}
minHeight="min-h-[100px]"
containerClassName="space-y-2"

View File

@@ -4,6 +4,13 @@ import { useQueryClient } from "@tanstack/react-query";
import { type FC, useCallback, useId } from "react";
import { configQueryConfig, useConfig } from "@/app/hooks/useConfig";
import { Checkbox } from "@/components/ui/checkbox";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { projectQueryConfig } from "../app/projects/[projectId]/hooks/useProject";
interface SettingsControlsProps {
@@ -53,6 +60,15 @@ export const SettingsControls: FC<SettingsControlsProps> = ({
await onConfigChanged();
};
const handleEnterKeyBehaviorChange = async (value: string) => {
const newConfig = {
...config,
enterKeyBehavior: value as "shift-enter-send" | "enter-send",
};
updateConfig(newConfig);
await onConfigChanged();
};
return (
<div className={`space-y-4 ${className}`}>
<div className="flex items-center space-x-2">
@@ -97,6 +113,28 @@ export const SettingsControls: FC<SettingsControlsProps> = ({
title
</p>
)}
<div className="space-y-2">
{showLabels && (
<label className="text-sm font-medium leading-none">
Enter Key Behavior
</label>
)}
<Select value={config?.enterKeyBehavior || "shift-enter-send"} onValueChange={handleEnterKeyBehaviorChange}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select enter key behavior" />
</SelectTrigger>
<SelectContent>
<SelectItem value="shift-enter-send">Shift+Enter to send (default)</SelectItem>
<SelectItem value="enter-send">Enter to send</SelectItem>
</SelectContent>
</Select>
{showDescriptions && (
<p className="text-xs text-muted-foreground mt-1">
Choose how the Enter key behaves in message input
</p>
)}
</div>
</div>
);
};

View File

@@ -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<typeof configSchema>;