import React, { useState, useEffect, useRef, useCallback } from 'react'; import { ChevronLeft, ChevronRight, Maximize2, Eye, Settings2, Moon, Sun, ArrowDown, Mic, Brain, Sparkles, FileText, Languages, GripVertical } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import DarkModeToggle from './DarkModeToggle'; import { useTheme } from '../contexts/ThemeContext'; import LanguageSelector from './LanguageSelector'; const QuickSettingsPanel = ({ isOpen, onToggle, autoExpandTools, onAutoExpandChange, showRawParameters, onShowRawParametersChange, showThinking, onShowThinkingChange, autoScrollToBottom, onAutoScrollChange, sendByCtrlEnter, onSendByCtrlEnterChange, isMobile }) => { const { t } = useTranslation('settings'); const [localIsOpen, setLocalIsOpen] = useState(isOpen); const [whisperMode, setWhisperMode] = useState(() => { return localStorage.getItem('whisperMode') || 'default'; }); const { isDarkMode } = useTheme(); // Draggable handle state const [handlePosition, setHandlePosition] = useState(() => { const saved = localStorage.getItem('quickSettingsHandlePosition'); if (saved) { try { const parsed = JSON.parse(saved); return parsed.y ?? 50; } catch { // Remove corrupted data localStorage.removeItem('quickSettingsHandlePosition'); return 50; } } return 50; // Default to 50% (middle of screen) }); const [isDragging, setIsDragging] = useState(false); const [dragStartY, setDragStartY] = useState(0); const [dragStartPosition, setDragStartPosition] = useState(0); const [hasMoved, setHasMoved] = useState(false); // Track if user has moved during drag const handleRef = useRef(null); const constraintsRef = useRef({ min: 10, max: 90 }); // Percentage constraints const dragThreshold = 5; // Pixels to move before it's considered a drag useEffect(() => { setLocalIsOpen(isOpen); }, [isOpen]); // Save handle position to localStorage when it changes useEffect(() => { localStorage.setItem('quickSettingsHandlePosition', JSON.stringify({ y: handlePosition })); }, [handlePosition]); // Calculate position from percentage const getPositionStyle = useCallback(() => { if (isMobile) { // On mobile, convert percentage to pixels from bottom const bottomPixels = (window.innerHeight * handlePosition) / 100; return { bottom: `${bottomPixels}px` }; } else { // On desktop, use top with percentage return { top: `${handlePosition}%`, transform: 'translateY(-50%)' }; } }, [handlePosition, isMobile]); // Handle mouse/touch start const handleDragStart = useCallback((e) => { // Don't prevent default yet - we want to allow click if no drag happens e.stopPropagation(); const clientY = e.type.includes('touch') ? e.touches[0].clientY : e.clientY; setDragStartY(clientY); setDragStartPosition(handlePosition); setHasMoved(false); setIsDragging(false); // Don't set dragging until threshold is passed }, [handlePosition]); // Handle mouse/touch move const handleDragMove = useCallback((e) => { if (dragStartY === 0) return; // Not in a potential drag const clientY = e.type.includes('touch') ? e.touches[0].clientY : e.clientY; const deltaY = Math.abs(clientY - dragStartY); // Check if we've moved past threshold if (!isDragging && deltaY > dragThreshold) { setIsDragging(true); setHasMoved(true); document.body.style.cursor = 'grabbing'; document.body.style.userSelect = 'none'; // Prevent body scroll on mobile during drag if (e.type.includes('touch')) { document.body.style.overflow = 'hidden'; document.body.style.position = 'fixed'; document.body.style.width = '100%'; } } if (!isDragging) return; // Prevent scrolling on touch move if (e.type.includes('touch')) { e.preventDefault(); } const actualDeltaY = clientY - dragStartY; // For top-based positioning (desktop), moving down increases top percentage // For bottom-based positioning (mobile), we need to invert let percentageDelta; if (isMobile) { // On mobile, moving down should decrease bottom position (increase percentage from top) percentageDelta = -(actualDeltaY / window.innerHeight) * 100; } else { // On desktop, moving down should increase top position percentageDelta = (actualDeltaY / window.innerHeight) * 100; } let newPosition = dragStartPosition + percentageDelta; // Apply constraints newPosition = Math.max(constraintsRef.current.min, Math.min(constraintsRef.current.max, newPosition)); setHandlePosition(newPosition); }, [isDragging, dragStartY, dragStartPosition, isMobile, dragThreshold]); // Handle mouse/touch end const handleDragEnd = useCallback(() => { setIsDragging(false); setDragStartY(0); document.body.style.cursor = ''; document.body.style.userSelect = ''; // Restore body scroll on mobile document.body.style.overflow = ''; document.body.style.position = ''; document.body.style.width = ''; }, []); // Cleanup body styles on unmount in case component unmounts while dragging useEffect(() => { return () => { document.body.style.cursor = ''; document.body.style.userSelect = ''; document.body.style.overflow = ''; document.body.style.position = ''; document.body.style.width = ''; }; }, []); // Set up global event listeners for drag useEffect(() => { if (dragStartY !== 0) { // Mouse events const handleMouseMove = (e) => handleDragMove(e); const handleMouseUp = () => handleDragEnd(); // Touch events const handleTouchMove = (e) => handleDragMove(e); const handleTouchEnd = () => handleDragEnd(); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); document.addEventListener('touchmove', handleTouchMove, { passive: false }); document.addEventListener('touchend', handleTouchEnd); return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); document.removeEventListener('touchmove', handleTouchMove); document.removeEventListener('touchend', handleTouchEnd); }; } }, [dragStartY, handleDragMove, handleDragEnd]); const handleToggle = (e) => { // Don't toggle if user was dragging if (hasMoved) { e.preventDefault(); setHasMoved(false); return; } const newState = !localIsOpen; setLocalIsOpen(newState); onToggle(newState); }; return ( <> {/* Pull Tab - Combined drag handle and toggle button */} {/* Panel */}
{t('quickSettings.sendByCtrlEnterDescription')}