From b808ca1b6863681f2a9bc6478a64248df46a7831 Mon Sep 17 00:00:00 2001 From: simos Date: Sun, 13 Jul 2025 20:34:04 +0000 Subject: [PATCH 1/7] Update ChatInterface --- src/components/ChatInterface.jsx | 70 +++++++++++++++++--------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index 2039198..b6dd6b0 100755 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -118,7 +118,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
- {message.isToolUse ? ( + {message.isToolUse && message.toolName !== 'Read' ? (
@@ -423,41 +423,18 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile try { const input = JSON.parse(message.toolInput); if (input.file_path) { - // Extract filename const filename = input.file_path.split('/').pop(); - const pathParts = input.file_path.split('/'); - const directoryPath = pathParts.slice(0, -1).join('/'); - - // Simple heuristic to show only relevant path parts - // Show the last 2-3 directory parts before the filename - const relevantParts = pathParts.slice(-4, -1); // Get up to 3 directories before filename - const relativePath = relevantParts.length > 0 ? relevantParts.join('/') + '/' : ''; return ( -
- - - - - - - - {relativePath} - {filename} - - {showRawParameters && ( -
-
- - View raw parameters - -
-                                    {message.toolInput}
-                                  
-
-
- )} -
+
+ Read{' '} + +
); } } catch (e) { @@ -882,6 +859,33 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
+ ) : message.isToolUse && message.toolName === 'Read' ? ( + // Simple Read tool indicator + (() => { + try { + const input = JSON.parse(message.toolInput); + if (input.file_path) { + const filename = input.file_path.split('/').pop(); + return ( +
+ 📖 Read{' '} + +
+ ); + } + } catch (e) { + return ( +
+ 📖 Read file +
+ ); + } + })() ) : (
{message.type === 'assistant' ? ( From 7db22fae299478fe1941d6d61735e861f19e6ea6 Mon Sep 17 00:00:00 2001 From: simos Date: Sun, 13 Jul 2025 20:43:15 +0000 Subject: [PATCH 2/7] Enhance ChatInterface --- src/components/ChatInterface.jsx | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index b6dd6b0..72319a9 100755 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -118,7 +118,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
- {message.isToolUse && message.toolName !== 'Read' ? ( + {message.isToolUse && !['Read', 'TodoWrite', 'TodoRead'].includes(message.toolName) ? (
@@ -886,6 +886,34 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile ); } })() + ) : message.isToolUse && message.toolName === 'TodoWrite' ? ( + // Simple TodoWrite tool indicator with tasks + (() => { + try { + const input = JSON.parse(message.toolInput); + if (input.todos && Array.isArray(input.todos)) { + return ( +
+
+ 📝 Update todo list +
+ +
+ ); + } + } catch (e) { + return ( +
+ 📝 Update todo list +
+ ); + } + })() + ) : message.isToolUse && message.toolName === 'TodoRead' ? ( + // Simple TodoRead tool indicator +
+ 📋 Read todo list +
) : (
{message.type === 'assistant' ? ( From 36d0add224f6674de8e003467ecf871cf5915e0b Mon Sep 17 00:00:00 2001 From: simos Date: Sun, 13 Jul 2025 20:51:05 +0000 Subject: [PATCH 3/7] Changing logo to the proper one --- src/components/LoginForm.jsx | 6 ++++-- src/components/ProtectedRoute.jsx | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/LoginForm.jsx b/src/components/LoginForm.jsx index e95ca5c..f2a490a 100644 --- a/src/components/LoginForm.jsx +++ b/src/components/LoginForm.jsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useAuth } from '../contexts/AuthContext'; -import ClaudeLogo from './ClaudeLogo'; +import { MessageSquare } from 'lucide-react'; const LoginForm = () => { const [username, setUsername] = useState(''); @@ -37,7 +37,9 @@ const LoginForm = () => { {/* Logo and Title */}
- +
+ +

Welcome Back

diff --git a/src/components/ProtectedRoute.jsx b/src/components/ProtectedRoute.jsx index f9ba988..88b404b 100644 --- a/src/components/ProtectedRoute.jsx +++ b/src/components/ProtectedRoute.jsx @@ -2,13 +2,15 @@ import React from 'react'; import { useAuth } from '../contexts/AuthContext'; import SetupForm from './SetupForm'; import LoginForm from './LoginForm'; -import ClaudeLogo from './ClaudeLogo'; +import { MessageSquare } from 'lucide-react'; const LoadingScreen = () => (

- +
+ +

Claude Code UI

From f28dc0140e415058bd505a68dd08ec076177fa72 Mon Sep 17 00:00:00 2001 From: viper151 Date: Mon, 14 Jul 2025 17:27:02 +0200 Subject: [PATCH 4/7] Add delete functionality for untracked files (#65) - Add delete button for untracked files in GitPanel - Implement deleteUntrackedFile function with confirmation dialog - Add /delete-untracked API endpoint to safely delete untracked files - Update confirmation modal to handle delete actions - Maintain consistent UI patterns with existing discard functionality --- server/routes/git.js | 35 ++++++++++++++++++++++ src/components/GitPanel.jsx | 60 +++++++++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 3 deletions(-) diff --git a/server/routes/git.js b/server/routes/git.js index 2f29ec9..a4a3c53 100755 --- a/server/routes/git.js +++ b/server/routes/git.js @@ -692,4 +692,39 @@ router.post('/discard', async (req, res) => { } }); +// Delete untracked file +router.post('/delete-untracked', async (req, res) => { + const { project, file } = req.body; + + if (!project || !file) { + return res.status(400).json({ error: 'Project name and file path are required' }); + } + + try { + const projectPath = await getActualProjectPath(project); + await validateGitRepository(projectPath); + + // Check if file is actually untracked + const { stdout: statusOutput } = await execAsync(`git status --porcelain "${file}"`, { cwd: projectPath }); + + if (!statusOutput.trim()) { + return res.status(400).json({ error: 'File is not untracked or does not exist' }); + } + + const status = statusOutput.substring(0, 2); + + if (status !== '??') { + return res.status(400).json({ error: 'File is not untracked. Use discard for tracked files.' }); + } + + // Delete the untracked file + await fs.unlink(path.join(projectPath, file)); + + res.json({ success: true, message: `Untracked file ${file} deleted successfully` }); + } catch (error) { + console.error('Git delete untracked error:', error); + res.status(500).json({ error: error.message }); + } +}); + export default router; \ No newline at end of file diff --git a/src/components/GitPanel.jsx b/src/components/GitPanel.jsx index 207c199..8ad2062 100755 --- a/src/components/GitPanel.jsx +++ b/src/components/GitPanel.jsx @@ -294,6 +294,34 @@ function GitPanel({ selectedProject, isMobile }) { } }; + const deleteUntrackedFile = async (filePath) => { + try { + const response = await authenticatedFetch('/api/git/delete-untracked', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + project: selectedProject.name, + file: filePath + }) + }); + + const data = await response.json(); + if (data.success) { + // Remove from selected files and refresh status + setSelectedFiles(prev => { + const newSet = new Set(prev); + newSet.delete(filePath); + return newSet; + }); + fetchGitStatus(); + } else { + console.error('Delete failed:', data.error); + } + } catch (error) { + console.error('Error deleting untracked file:', error); + } + }; + const confirmAndExecute = async () => { if (!confirmAction) return; @@ -305,6 +333,9 @@ function GitPanel({ selectedProject, isMobile }) { case 'discard': await discardChanges(file); break; + case 'delete': + await deleteUntrackedFile(file); + break; case 'commit': await handleCommit(); break; @@ -578,6 +609,23 @@ function GitPanel({ selectedProject, isMobile }) { {isMobile && Discard} )} + {status === 'U' && ( + + )}

{confirmAction.type === 'discard' ? 'Discard Changes' : + confirmAction.type === 'delete' ? 'Delete File' : confirmAction.type === 'commit' ? 'Confirm Commit' : confirmAction.type === 'pull' ? 'Confirm Pull' : 'Confirm Push'}

@@ -1147,7 +1196,7 @@ function GitPanel({ selectedProject, isMobile }) { )} - {/* Push button - show when ahead (primary action when ahead only) */} - {remoteStatus.ahead > 0 && ( - - )} - - {/* Fetch button - show when ahead only or when diverged (secondary action) */} - {(remoteStatus.ahead > 0 || (remoteStatus.behind > 0 && remoteStatus.ahead > 0)) && ( - + {/* Show normal push/pull buttons only if branch has upstream */} + {remoteStatus?.hasUpstream && !remoteStatus?.isUpToDate && ( + <> + {/* Pull button - show when behind (primary action) */} + {remoteStatus.behind > 0 && ( + + )} + + {/* Push button - show when ahead (primary action when ahead only) */} + {remoteStatus.ahead > 0 && ( + + )} + + {/* Fetch button - show when ahead only or when diverged (secondary action) */} + {(remoteStatus.ahead > 0 || (remoteStatus.behind > 0 && remoteStatus.ahead > 0)) && ( + + )} + )} )} @@ -1178,7 +1231,8 @@ function GitPanel({ selectedProject, isMobile }) { {confirmAction.type === 'discard' ? 'Discard Changes' : confirmAction.type === 'delete' ? 'Delete File' : confirmAction.type === 'commit' ? 'Confirm Commit' : - confirmAction.type === 'pull' ? 'Confirm Pull' : 'Confirm Push'} + confirmAction.type === 'pull' ? 'Confirm Pull' : + confirmAction.type === 'publish' ? 'Publish Branch' : 'Confirm Push'}
@@ -1202,6 +1256,8 @@ function GitPanel({ selectedProject, isMobile }) { ? 'bg-blue-600 hover:bg-blue-700' : confirmAction.type === 'pull' ? 'bg-green-600 hover:bg-green-700' + : confirmAction.type === 'publish' + ? 'bg-purple-600 hover:bg-purple-700' : 'bg-orange-600 hover:bg-orange-700' } flex items-center space-x-2`} > @@ -1225,6 +1281,11 @@ function GitPanel({ selectedProject, isMobile }) { Pull + ) : confirmAction.type === 'publish' ? ( + <> + + Publish + ) : ( <> diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 1cca47c..9a1e111 100755 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -374,9 +374,7 @@ function Sidebar({ try { const currentSessionCount = (project.sessions?.length || 0) + (additionalSessions[project.name]?.length || 0); - const response = await fetch( - `/api/projects/${project.name}/sessions?limit=5&offset=${currentSessionCount}` - ); + const response = await api.sessions(project.name, 5, currentSessionCount); if (response.ok) { const result = await response.json(); From d36890be5238316906adff9a8c58cf12b58f2cec Mon Sep 17 00:00:00 2001 From: simos Date: Tue, 15 Jul 2025 13:30:53 +0000 Subject: [PATCH 6/7] Fixes on Claude limit usage reached message --- src/components/ChatInterface.jsx | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index 72319a9..39be27e 100755 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -1432,19 +1432,45 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess toolResult: null // Will be updated when result comes in }]); } else if (part.type === 'text' && part.text?.trim()) { + // Check for usage limit message and format it user-friendly + let content = part.text; + if (content.includes('Claude AI usage limit reached|')) { + const parts = content.split('|'); + if (parts.length === 2) { + const timestamp = parseInt(parts[1]); + if (!isNaN(timestamp)) { + const resetTime = new Date(timestamp * 1000); + content = `Claude AI usage limit reached. The limit will reset on ${resetTime.toLocaleDateString()} at ${resetTime.toLocaleTimeString()}.`; + } + } + } + // Add regular text message setChatMessages(prev => [...prev, { type: 'assistant', - content: part.text, + content: content, timestamp: new Date() }]); } } } else if (typeof messageData.content === 'string' && messageData.content.trim()) { + // Check for usage limit message and format it user-friendly + let content = messageData.content; + if (content.includes('Claude AI usage limit reached|')) { + const parts = content.split('|'); + if (parts.length === 2) { + const timestamp = parseInt(parts[1]); + if (!isNaN(timestamp)) { + const resetTime = new Date(timestamp * 1000); + content = `Claude AI usage limit reached. The limit will reset on ${resetTime.toLocaleDateString()} at ${resetTime.toLocaleTimeString()}.`; + } + } + } + // Add regular text message setChatMessages(prev => [...prev, { type: 'assistant', - content: messageData.content, + content: content, timestamp: new Date() }]); } From 7f4feb182ea0a756abb17c8d6f68a29ca5b2bcb6 Mon Sep 17 00:00:00 2001 From: Natsuki YOKOTA <42149422+sarashinanikki@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:30:53 +0900 Subject: [PATCH 7/7] feat: add ctrl+enter send option & fix IME problen (#62) --- src/App.jsx | 10 ++++++++++ src/components/ChatInterface.jsx | 23 +++++++++++++++++------ src/components/MainContent.jsx | 4 +++- src/components/QuickSettingsPanel.jsx | 26 +++++++++++++++++++++++++- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 68e6e21..b9a9d8c 100755 --- a/src/App.jsx +++ b/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} />
@@ -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} /> )} diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index 39be27e..2d898eb 100755 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -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
{/* Hint text */}
- 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"}
- 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"}
diff --git a/src/components/MainContent.jsx b/src/components/MainContent.jsx index 677248b..30d13a5 100755 --- a/src/components/MainContent.jsx +++ b/src/components/MainContent.jsx @@ -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} />
diff --git a/src/components/QuickSettingsPanel.jsx b/src/components/QuickSettingsPanel.jsx index 6a5d091..76e9514 100755 --- a/src/components/QuickSettingsPanel.jsx +++ b/src/components/QuickSettingsPanel.jsx @@ -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 = ({
+ {/* Input Settings */} +
+

Input Settings

+ + +

+ When enabled, pressing Ctrl+Enter will send the message instead of just Enter. This is useful for IME users to avoid accidental sends. +

+
+ {/* Whisper Dictation Settings - HIDDEN */}

Whisper Dictation