diff --git a/components/chat-input.tsx b/components/chat-input.tsx index a724ce4..6848dc5 100644 --- a/components/chat-input.tsx +++ b/components/chat-input.tsx @@ -24,6 +24,7 @@ import { useDiagram } from "@/contexts/diagram-context" import { useDictionary } from "@/hooks/use-dictionary" import { formatMessage } from "@/lib/i18n/utils" import { isPdfFile, isTextFile } from "@/lib/pdf-utils" +import { STORAGE_KEYS } from "@/lib/storage" import type { FlattenedModel } from "@/lib/types/model-config" import { extractUrlContent, type UrlData } from "@/lib/url-utils" import { FilePreviewList } from "./file-preview-list" @@ -192,6 +193,7 @@ export function ChatInput({ const [showHistory, setShowHistory] = useState(false) const [showUrlDialog, setShowUrlDialog] = useState(false) const [isExtractingUrl, setIsExtractingUrl] = useState(false) + const [sendShortcut, setSendShortcut] = useState("ctrl-enter") // Allow retry when there's an error (even if status is still "streaming" or "submitted") const isDisabled = (status === "streaming" || status === "submitted") && !error @@ -208,13 +210,36 @@ export function ChatInput({ adjustTextareaHeight() }, [input, adjustTextareaHeight]) + // Load send shortcut preference from localStorage and listen for changes + useEffect(() => { + const stored = localStorage.getItem(STORAGE_KEYS.sendShortcut) + if (stored) setSendShortcut(stored) + + const handleChange = (e: CustomEvent) => + setSendShortcut(e.detail) + window.addEventListener( + "sendShortcutChange", + handleChange as EventListener, + ) + return () => + window.removeEventListener( + "sendShortcutChange", + handleChange as EventListener, + ) + }, []) + const handleChange = (e: React.ChangeEvent) => { onChange(e) adjustTextareaHeight() } const handleKeyDown = (e: React.KeyboardEvent) => { - if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { + const shouldSend = + sendShortcut === "enter" + ? e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey + : (e.metaKey || e.ctrlKey) && e.key === "Enter" + + if (shouldSend) { e.preventDefault() const form = e.currentTarget.closest("form") if (form && input.trim() && !isDisabled) { diff --git a/components/settings-dialog.tsx b/components/settings-dialog.tsx index 1c8fbab..5882675 100644 --- a/components/settings-dialog.tsx +++ b/components/settings-dialog.tsx @@ -25,6 +25,7 @@ import { Switch } from "@/components/ui/switch" import { useDictionary } from "@/hooks/use-dictionary" import { getApiEndpoint } from "@/lib/base-path" import { i18n, type Locale } from "@/lib/i18n/config" +import { STORAGE_KEYS } from "@/lib/storage" // Reusable setting item component for consistent layout function SettingItem({ @@ -103,6 +104,7 @@ function SettingsContent({ () => getStoredAccessCodeRequired() ?? false, ) const [currentLang, setCurrentLang] = useState("en") + const [sendShortcut, setSendShortcut] = useState("ctrl-enter") // Proxy settings state (Electron only) const [httpProxy, setHttpProxy] = useState("") @@ -155,6 +157,11 @@ function SettingsContent({ // Default to true if not set setCloseProtection(storedCloseProtection !== "false") + const storedSendShortcut = localStorage.getItem( + STORAGE_KEYS.sendShortcut, + ) + setSendShortcut(storedSendShortcut || "ctrl-enter") + setError("") // Load proxy settings (Electron only) @@ -425,6 +432,43 @@ function SettingsContent({ + {/* Send Shortcut */} + + + + {/* Proxy Settings - Electron only */} {typeof window !== "undefined" && window.electronAPI?.isElectron && ( diff --git a/lib/i18n/dictionaries/en.json b/lib/i18n/dictionaries/en.json index b544ff2..4e43a39 100644 --- a/lib/i18n/dictionaries/en.json +++ b/lib/i18n/dictionaries/en.json @@ -104,6 +104,10 @@ "closeProtectionDescription": "Show confirmation when leaving the page.", "diagramStyle": "Diagram Style", "diagramStyleDescription": "Toggle between minimal and styled diagram output.", + "sendShortcut": "Send Shortcut", + "sendShortcutDescription": "Choose how to send messages.", + "enterToSend": "Enter to send", + "ctrlEnterToSend": "Cmd/Ctrl+Enter to send", "diagramActions": "Diagram Actions", "diagramActionsDescription": "Manage diagram history and exports", "history": "History", diff --git a/lib/i18n/dictionaries/ja.json b/lib/i18n/dictionaries/ja.json index 5a0cada..bfafb62 100644 --- a/lib/i18n/dictionaries/ja.json +++ b/lib/i18n/dictionaries/ja.json @@ -104,6 +104,10 @@ "closeProtectionDescription": "ページを離れる際に確認を表示します。", "diagramStyle": "ダイアグラムスタイル", "diagramStyleDescription": "ミニマルとスタイル付きの出力を切り替えます。", + "sendShortcut": "送信ショートカット", + "sendShortcutDescription": "メッセージの送信方法を選択します。", + "enterToSend": "Enterで送信", + "ctrlEnterToSend": "Cmd/Ctrl+Enterで送信", "diagramActions": "ダイアグラム操作", "diagramActionsDescription": "ダイアグラムの履歴とエクスポートを管理", "history": "履歴", diff --git a/lib/i18n/dictionaries/zh.json b/lib/i18n/dictionaries/zh.json index 03a3f17..ce431c8 100644 --- a/lib/i18n/dictionaries/zh.json +++ b/lib/i18n/dictionaries/zh.json @@ -104,6 +104,10 @@ "closeProtectionDescription": "离开页面时显示确认。", "diagramStyle": "图表样式", "diagramStyleDescription": "切换简约与精致图表输出模式。", + "sendShortcut": "发送快捷键", + "sendShortcutDescription": "选择发送消息的方式。", + "enterToSend": "回车发送", + "ctrlEnterToSend": "Cmd/Ctrl+回车发送", "diagramActions": "图表操作", "diagramActionsDescription": "管理图表历史记录和导出", "history": "历史记录", diff --git a/lib/storage.ts b/lib/storage.ts index f64d1dd..623ccb4 100644 --- a/lib/storage.ts +++ b/lib/storage.ts @@ -22,4 +22,7 @@ export const STORAGE_KEYS = { // Multi-model configuration modelConfigs: "next-ai-draw-io-model-configs", selectedModelId: "next-ai-draw-io-selected-model-id", + + // Chat input preferences + sendShortcut: "next-ai-draw-io-send-shortcut", } as const