mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-10 05:19:39 +00:00
feat: add ctrl+enter send option & fix IME problen (#62)
This commit is contained in:
10
src/App.jsx
10
src/App.jsx
@@ -64,6 +64,10 @@ function AppContent() {
|
||||
const saved = localStorage.getItem('autoScrollToBottom');
|
||||
return saved !== null ? JSON.parse(saved) : true;
|
||||
});
|
||||
const [sendByCtrlEnter, setSendByCtrlEnter] = useState(() => {
|
||||
const saved = localStorage.getItem('sendByCtrlEnter');
|
||||
return saved !== null ? JSON.parse(saved) : false;
|
||||
});
|
||||
// Session Protection System: Track sessions with active conversations to prevent
|
||||
// automatic project updates from interrupting ongoing chats. When a user sends
|
||||
// a message, the session is marked as "active" and project updates are paused
|
||||
@@ -586,6 +590,7 @@ function AppContent() {
|
||||
autoExpandTools={autoExpandTools}
|
||||
showRawParameters={showRawParameters}
|
||||
autoScrollToBottom={autoScrollToBottom}
|
||||
sendByCtrlEnter={sendByCtrlEnter}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -617,6 +622,11 @@ function AppContent() {
|
||||
setAutoScrollToBottom(value);
|
||||
localStorage.setItem('autoScrollToBottom', JSON.stringify(value));
|
||||
}}
|
||||
sendByCtrlEnter={sendByCtrlEnter}
|
||||
onSendByCtrlEnterChange={(value) => {
|
||||
setSendByCtrlEnter(value);
|
||||
localStorage.setItem('sendByCtrlEnter', JSON.stringify(value));
|
||||
}}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1016,7 +1016,7 @@ const ImageAttachment = ({ file, onRemove, uploadProgress, error }) => {
|
||||
// - onReplaceTemporarySession: Called to replace temporary session ID with real WebSocket session ID
|
||||
//
|
||||
// This ensures uninterrupted chat experience by pausing sidebar refreshes during conversations.
|
||||
function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, messages, onFileOpen, onInputFocusChange, onSessionActive, onSessionInactive, onReplaceTemporarySession, onNavigateToSession, onShowSettings, autoExpandTools, showRawParameters, autoScrollToBottom }) {
|
||||
function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, messages, onFileOpen, onInputFocusChange, onSessionActive, onSessionInactive, onReplaceTemporarySession, onNavigateToSession, onShowSettings, autoExpandTools, showRawParameters, autoScrollToBottom, sendByCtrlEnter }) {
|
||||
const [input, setInput] = useState(() => {
|
||||
if (typeof window !== 'undefined' && selectedProject) {
|
||||
return localStorage.getItem(`draft_input_${selectedProject.name}`) || '';
|
||||
@@ -2024,14 +2024,21 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
|
||||
// Handle Enter key: Ctrl+Enter (Cmd+Enter on Mac) sends, Shift+Enter creates new line
|
||||
if (e.key === 'Enter') {
|
||||
// If we're in composition, don't send message
|
||||
if (e.nativeEvent.isComposing) {
|
||||
return; // Let IME handle the Enter key
|
||||
}
|
||||
|
||||
if ((e.ctrlKey || e.metaKey) && !e.shiftKey) {
|
||||
// Ctrl+Enter or Cmd+Enter: Send message
|
||||
e.preventDefault();
|
||||
handleSubmit(e);
|
||||
} else if (!e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
||||
// Plain Enter: Also send message (keeping original behavior)
|
||||
e.preventDefault();
|
||||
handleSubmit(e);
|
||||
// Plain Enter: Send message only if not in IME composition
|
||||
if (!sendByCtrlEnter) {
|
||||
e.preventDefault();
|
||||
handleSubmit(e);
|
||||
}
|
||||
}
|
||||
// Shift+Enter: Allow default behavior (new line)
|
||||
}
|
||||
@@ -2462,12 +2469,16 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
</div>
|
||||
{/* Hint text */}
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 text-center mt-2 hidden sm:block">
|
||||
Press Enter to send • Shift+Enter for new line • Tab to change modes • @ to reference files
|
||||
{sendByCtrlEnter
|
||||
? "Ctrl+Enter to send (IME safe) • Shift+Enter for new line • Tab to change modes • @ to reference files"
|
||||
: "Press Enter to send • Shift+Enter for new line • Tab to change modes • @ to reference files"}
|
||||
</div>
|
||||
<div className={`text-xs text-gray-500 dark:text-gray-400 text-center mt-2 sm:hidden transition-opacity duration-200 ${
|
||||
isInputFocused ? 'opacity-100' : 'opacity-0'
|
||||
}`}>
|
||||
Enter to send • Tab for modes • @ for files
|
||||
{sendByCtrlEnter
|
||||
? "Ctrl+Enter to send (IME safe) • Tab for modes • @ for files"
|
||||
: "Enter to send • Tab for modes • @ for files"}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,8 @@ function MainContent({
|
||||
onShowSettings, // Show tools settings panel
|
||||
autoExpandTools, // Auto-expand tool accordions
|
||||
showRawParameters, // Show raw parameters in tool accordions
|
||||
autoScrollToBottom // Auto-scroll to bottom when new messages arrive
|
||||
autoScrollToBottom, // Auto-scroll to bottom when new messages arrive
|
||||
sendByCtrlEnter // Send by Ctrl+Enter mode for East Asian language input
|
||||
}) {
|
||||
const [editingFile, setEditingFile] = useState(null);
|
||||
|
||||
@@ -285,6 +286,7 @@ function MainContent({
|
||||
autoExpandTools={autoExpandTools}
|
||||
showRawParameters={showRawParameters}
|
||||
autoScrollToBottom={autoScrollToBottom}
|
||||
sendByCtrlEnter={sendByCtrlEnter}
|
||||
/>
|
||||
</div>
|
||||
<div className={`h-full overflow-hidden ${activeTab === 'files' ? 'block' : 'hidden'}`}>
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
Mic,
|
||||
Brain,
|
||||
Sparkles,
|
||||
FileText
|
||||
FileText,
|
||||
Languages
|
||||
} from 'lucide-react';
|
||||
import DarkModeToggle from './DarkModeToggle';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
@@ -25,6 +26,8 @@ const QuickSettingsPanel = ({
|
||||
onShowRawParametersChange,
|
||||
autoScrollToBottom,
|
||||
onAutoScrollChange,
|
||||
sendByCtrlEnter,
|
||||
onSendByCtrlEnterChange,
|
||||
isMobile
|
||||
}) => {
|
||||
const [localIsOpen, setLocalIsOpen] = useState(isOpen);
|
||||
@@ -142,6 +145,27 @@ const QuickSettingsPanel = ({
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Input Settings */}
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-2">Input Settings</h4>
|
||||
|
||||
<label className="flex items-center justify-between p-3 rounded-lg bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer transition-colors border border-transparent hover:border-gray-300 dark:hover:border-gray-600">
|
||||
<span className="flex items-center gap-2 text-sm text-gray-900 dark:text-white">
|
||||
<Languages className="h-4 w-4 text-gray-600 dark:text-gray-400" />
|
||||
Send by Ctrl+Enter
|
||||
</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={sendByCtrlEnter}
|
||||
onChange={(e) => onSendByCtrlEnterChange(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 dark:focus:ring-blue-400 dark:bg-gray-800 dark:checked:bg-blue-600"
|
||||
/>
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 ml-3">
|
||||
When enabled, pressing Ctrl+Enter will send the message instead of just Enter. This is useful for IME users to avoid accidental sends.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Whisper Dictation Settings - HIDDEN */}
|
||||
<div className="space-y-2" style={{ display: 'none' }}>
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-2">Whisper Dictation</h4>
|
||||
|
||||
Reference in New Issue
Block a user