diff --git a/server/index.js b/server/index.js index 25b25fd..44ebb95 100755 --- a/server/index.js +++ b/server/index.js @@ -496,7 +496,13 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => { name: item.name, type: 'directory' })) - .slice(0, 20); // Limit results + .sort((a, b) => { + const aHidden = a.name.startsWith('.'); + const bHidden = b.name.startsWith('.'); + if (aHidden && !bHidden) return 1; + if (!aHidden && bHidden) return -1; + return a.name.localeCompare(b.name); + }); // Add common directories if browsing home directory const suggestions = []; diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index 9c5e4e7..3a22238 100644 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -36,6 +36,7 @@ import ClaudeStatus from './ClaudeStatus'; import TokenUsagePie from './TokenUsagePie'; import { MicButton } from './MicButton.jsx'; import { api, authenticatedFetch } from '../utils/api'; +import ThinkingModeSelector, { thinkingModes } from './ThinkingModeSelector.jsx'; import Fuse from 'fuse.js'; import CommandMenu from './CommandMenu'; import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants'; @@ -1922,6 +1923,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess const [slashPosition, setSlashPosition] = useState(-1); const [visibleMessageCount, setVisibleMessageCount] = useState(100); const [claudeStatus, setClaudeStatus] = useState(null); + const [thinkingMode, setThinkingMode] = useState('none'); const [provider, setProvider] = useState(() => { return localStorage.getItem('selected-provider') || 'claude'; }); @@ -4264,6 +4266,13 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess e.preventDefault(); if (!input.trim() || isLoading || !selectedProject) return; + // Apply thinking mode prefix if selected + let messageContent = input; + const selectedThinkingMode = thinkingModes.find(mode => mode.id === thinkingMode); + if (selectedThinkingMode && selectedThinkingMode.prefix) { + messageContent = `${selectedThinkingMode.prefix}: ${input}`; + } + // Upload images first if any let uploadedImages = []; if (attachedImages.length > 0) { @@ -4403,6 +4412,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess setUploadingImages(new Map()); setImageErrors(new Map()); setIsTextareaExpanded(false); + setThinkingMode('none'); // Reset thinking mode after sending // Reset textarea height if (textareaRef.current) { @@ -5218,6 +5228,17 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess + + {/* Thinking Mode Selector */} + { + provider === 'claude' && ( + + + )} {/* Token usage pie chart - positioned next to mode indicator */} { const { t } = useTranslation(); // Wizard state const [step, setStep] = useState(1); // 1: Choose type, 2: Configure, 3: Confirm - const [workspaceType, setWorkspaceType] = useState(null); // 'existing' or 'new' + const [workspaceType, setWorkspaceType] = useState('existing'); // 'existing' or 'new' - default to 'existing' // Form state const [workspacePath, setWorkspacePath] = useState(''); @@ -25,6 +25,11 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => { const [loadingTokens, setLoadingTokens] = useState(false); const [pathSuggestions, setPathSuggestions] = useState([]); const [showPathDropdown, setShowPathDropdown] = useState(false); + const [showFolderBrowser, setShowFolderBrowser] = useState(false); + const [browserCurrentPath, setBrowserCurrentPath] = useState('~'); + const [browserFolders, setBrowserFolders] = useState([]); + const [loadingFolders, setLoadingFolders] = useState(false); + const [showHiddenFolders, setShowHiddenFolders] = useState(false); // Load available GitHub tokens when needed useEffect(() => { @@ -157,6 +162,37 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => { setShowPathDropdown(false); }; + const openFolderBrowser = async () => { + setShowFolderBrowser(true); + await loadBrowserFolders('~'); + }; + + const loadBrowserFolders = async (path) => { + try { + setLoadingFolders(true); + setBrowserCurrentPath(path); + const response = await api.browseFilesystem(path); + const data = await response.json(); + setBrowserFolders(data.suggestions || []); + } catch (error) { + console.error('Error loading folders:', error); + } finally { + setLoadingFolders(false); + } + }; + + const selectFolder = (folderPath, advanceToConfirm = false) => { + setWorkspacePath(folderPath); + setShowFolderBrowser(false); + if (advanceToConfirm) { + setStep(3); + } + }; + + const navigateToFolder = async (folderPath) => { + await loadBrowserFolders(folderPath); + }; + return (
@@ -292,28 +328,39 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => { -
- setWorkspacePath(e.target.value)} - placeholder={workspaceType === 'existing' ? t('projectWizard.step2.existingPlaceholder') : t('projectWizard.step2.newPlaceholder')} - className="w-full" - /> - {showPathDropdown && pathSuggestions.length > 0 && ( -
- {pathSuggestions.map((suggestion, index) => ( - - ))} -
- )} +
+
+ setWorkspacePath(e.target.value)} + placeholder={workspaceType === 'existing' ? '/path/to/existing/workspace' : '/path/to/new/workspace'} + className="w-full" + /> + {showPathDropdown && pathSuggestions.length > 0 && ( +
+ {pathSuggestions.map((suggestion, index) => ( + + ))} +
+ )} +
+

{workspaceType === 'existing' @@ -565,6 +612,121 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {

+ + {/* Folder Browser Modal */} + {showFolderBrowser && ( +
+
+ {/* Browser Header */} +
+
+
+ +
+

+ Select Folder +

+
+
+ + +
+
+ + {/* Folder List */} +
+ {loadingFolders ? ( +
+ +
+ ) : browserFolders.length === 0 ? ( +
+ No folders found +
+ ) : ( +
+ {/* Parent Directory */} + {browserCurrentPath !== '~' && browserCurrentPath !== '/' && ( + + )} + + {/* Folders */} + {browserFolders + .filter(folder => showHiddenFolders || !folder.name.startsWith('.')) + .sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())) + .map((folder, index) => ( +
+ + +
+ ))} +
+ )} +
+ + {/* Browser Footer with Current Path */} +
+
+ Path: + + {browserCurrentPath} + +
+
+ + +
+
+
+
+ )}
); }; diff --git a/src/components/ThinkingModeSelector.jsx b/src/components/ThinkingModeSelector.jsx new file mode 100644 index 0000000..758ad40 --- /dev/null +++ b/src/components/ThinkingModeSelector.jsx @@ -0,0 +1,164 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Brain, Zap, Sparkles, Atom, X } from 'lucide-react'; + +const thinkingModes = [ + { + id: 'none', + name: 'Standard', + description: 'Regular Claude response', + icon: null, + prefix: '', + color: 'text-gray-600' + }, + { + id: 'think', + name: 'Think', + description: 'Basic extended thinking', + icon: Brain, + prefix: 'think', + color: 'text-blue-600' + }, + { + id: 'think-hard', + name: 'Think Hard', + description: 'More thorough evaluation', + icon: Zap, + prefix: 'think hard', + color: 'text-purple-600' + }, + { + id: 'think-harder', + name: 'Think Harder', + description: 'Deep analysis with alternatives', + icon: Sparkles, + prefix: 'think harder', + color: 'text-indigo-600' + }, + { + id: 'ultrathink', + name: 'Ultrathink', + description: 'Maximum thinking budget', + icon: Atom, + prefix: 'ultrathink', + color: 'text-red-600' + } +]; + +function ThinkingModeSelector({ selectedMode, onModeChange, onClose, className = '' }) { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsOpen(false); + if (onClose) onClose(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [onClose]); + + const currentMode = thinkingModes.find(mode => mode.id === selectedMode) || thinkingModes[0]; + const IconComponent = currentMode.icon || Brain; + + return ( +
+ + + {isOpen && ( +
+
+
+

+ Thinking Mode +

+ +
+

+ Extended thinking gives Claude more time to evaluate alternatives +

+
+ +
+ {thinkingModes.map((mode) => { + const ModeIcon = mode.icon; + const isSelected = mode.id === selectedMode; + + return ( + + ); + })} +
+ +
+

+ Tip: Higher thinking modes take more time but provide more thorough analysis +

+
+
+ )} +
+ ); +} + +export default ThinkingModeSelector; +export { thinkingModes }; \ No newline at end of file