From 8a09073199a36c76bb4a343d42576765b68de2a0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Dec 2025 19:49:10 +0000 Subject: [PATCH 1/2] feat: resizable editor panels with drag handles --- website/src/repl/components/ReplEditor.jsx | 147 ++++++--------- website/src/repl/components/panel/ChatTab.jsx | 175 +++++++----------- website/src/settings.mjs | 2 + 3 files changed, 131 insertions(+), 193 deletions(-) diff --git a/website/src/repl/components/ReplEditor.jsx b/website/src/repl/components/ReplEditor.jsx index 96db909b..6adf6f0e 100644 --- a/website/src/repl/components/ReplEditor.jsx +++ b/website/src/repl/components/ReplEditor.jsx @@ -4,30 +4,56 @@ import { Code } from '@src/repl/components/Code'; import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage'; import { Header } from './Header'; import { useSettings, settingsMap } from '@src/settings.mjs'; -import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; -import { useCallback } from 'react'; +import { useRef } from 'react'; -// Resize handle component with visual feedback -function ResizeHandle({ direction = 'horizontal' }) { +// Resize handle component +function ResizeHandle({ direction, containerRef, settingKey }) { const isHorizontal = direction === 'horizontal'; + const isDragging = useRef(false); + + const handlePointerDown = (e) => { + isDragging.current = true; + e.target.setPointerCapture(e.pointerId); + }; + + const handlePointerMove = (e) => { + if (!isDragging.current || !containerRef.current) return; + + const container = containerRef.current; + const size = isHorizontal ? container.offsetHeight : container.offsetWidth; + const movement = isHorizontal ? -e.movementY : -e.movementX; + const deltaPercent = (movement / size) * 100; + + const currentSize = Number(settingsMap.get()[settingKey]) || (isHorizontal ? 35 : 30); + const newSize = Math.max(5, Math.min(85, currentSize + deltaPercent)); + settingsMap.setKey(settingKey, newSize); + }; + + const handlePointerUp = (e) => { + isDragging.current = false; + e.target.releasePointerCapture(e.pointerId); + }; + return ( - - {/* Visual indicator */}
- +
); } @@ -37,86 +63,35 @@ export default function ReplEditor(Props) { const settings = useSettings(); const { panelPosition, isZen, isPanelOpen, panelSizeBottom, panelSizeRight } = settings; - // Handler for saving panel sizes to localStorage - const handleLayoutChange = useCallback((sizes) => { - if (panelPosition === 'bottom' && sizes.length === 2) { - // sizes[1] is the bottom panel size in percentage - settingsMap.setKey('panelSizeBottom', sizes[1]); - } else if (panelPosition === 'right' && sizes.length === 2) { - // sizes[1] is the right panel size in percentage - settingsMap.setKey('panelSizeRight', sizes[1]); - } - }, [panelPosition]); + const containerElRef = useRef(null); - // Zen mode - no panels - if (isZen) { - return ( -
- -
-
- -
- -
- ); - } + const showRightPanel = !isZen && panelPosition === 'right'; + const showBottomPanel = !isZen && panelPosition === 'bottom'; - // Right panel layout - if (panelPosition === 'right') { - return ( -
- -
- - - - - {isPanelOpen && } - - - - - -
- ); - } - - // Bottom panel layout return ( -
+
- - -
- +
+ + {showRightPanel && isPanelOpen && ( + + )} + {showRightPanel && ( +
+
- - {isPanelOpen && } - - - - + )} +
+ {showBottomPanel && isPanelOpen && ( + + )} + {showBottomPanel && ( +
+ +
+ )}
); } diff --git a/website/src/repl/components/panel/ChatTab.jsx b/website/src/repl/components/panel/ChatTab.jsx index 92e05f5f..32d9dbb2 100644 --- a/website/src/repl/components/panel/ChatTab.jsx +++ b/website/src/repl/components/panel/ChatTab.jsx @@ -87,8 +87,6 @@ async function fetchModels(provider, apiKey) { */ function SettingsPanel({ onClose }) { const settings = useSettings(); - const { panelPosition } = settings; - const isBottomPanel = panelPosition === 'bottom'; const [openaiKey, setOpenaiKey] = useState(settings.openaiApiKey || ''); const [anthropicKey, setAnthropicKey] = useState(settings.anthropicApiKey || ''); const [geminiKey, setGeminiKey] = useState(settings.geminiApiKey || ''); @@ -194,95 +192,65 @@ function SettingsPanel({ onClose }) { const isLoadingCurrentModels = loadingModels[provider]; return ( -
- {/* Left column (or top section for vertical panel) */} -
-

Настройки AI

- - {/* Provider & Model row */} -
- {/* Provider */} -
- +
+

Настройки AI

+ + {/* Provider & Model - horizontal */} +
+
+ + +
+ +
+ +
-
- - {/* Model */} -
- -
- - -
-
-
- - {/* Links & Save (for bottom panel, shown here) */} - {isBottomPanel && ( -
-
- )} +
- {/* Right column (or bottom section for vertical panel) - API Keys */} -
-

API Ключи

- -
- {/* OpenAI Key */} -
+ {/* API Keys */} +
+

API Ключи

+
+
@@ -294,9 +262,7 @@ function SettingsPanel({ onClose }) { className={cx(inputClass, 'text-sm py-1')} />
- - {/* Anthropic Key */} -
+
@@ -308,9 +274,7 @@ function SettingsPanel({ onClose }) { className={cx(inputClass, 'text-sm py-1')} />
- - {/* Gemini Key */} -
+
@@ -323,28 +287,25 @@ function SettingsPanel({ onClose }) { />
- -

Ключи хранятся локально

+

Ключи хранятся локально

- {/* Save button & Links (for vertical/right panel) */} - {!isBottomPanel && ( -
-
- Получить: - OpenAI - Anthropic - Gemini -
- + {/* Save & Links */} +
+ +
+ Получить: + OpenAI + Anthropic + Gemini
- )} +
); } diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 94eb28bb..f3730b61 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -134,6 +134,8 @@ export function useSettings() { ? true : parseBoolean(state.patternAutoStart), masterVolume: Number(state.masterVolume) ?? 1, + panelSizeBottom: Number(state.panelSizeBottom) || 35, + panelSizeRight: Number(state.panelSizeRight) || 30, }; } From 5b1150a667c6115f1ef75eeadda52e6146a4c966 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Dec 2025 19:57:23 +0000 Subject: [PATCH 2/2] fix: API keys layout adapts to panel position, resize range 20-50% MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API keys vertical in right panel, horizontal in bottom panel - Pass isBottomPanel prop through Panel → ChatTab → SettingsPanel - Resize range limited to 20-50% instead of 5-85% --- website/src/repl/components/ReplEditor.jsx | 2 +- website/src/repl/components/panel/ChatTab.jsx | 14 +++++++------- website/src/repl/components/panel/Panel.jsx | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/website/src/repl/components/ReplEditor.jsx b/website/src/repl/components/ReplEditor.jsx index 6adf6f0e..23288d18 100644 --- a/website/src/repl/components/ReplEditor.jsx +++ b/website/src/repl/components/ReplEditor.jsx @@ -25,7 +25,7 @@ function ResizeHandle({ direction, containerRef, settingKey }) { const deltaPercent = (movement / size) * 100; const currentSize = Number(settingsMap.get()[settingKey]) || (isHorizontal ? 35 : 30); - const newSize = Math.max(5, Math.min(85, currentSize + deltaPercent)); + const newSize = Math.max(20, Math.min(50, currentSize + deltaPercent)); settingsMap.setKey(settingKey, newSize); }; diff --git a/website/src/repl/components/panel/ChatTab.jsx b/website/src/repl/components/panel/ChatTab.jsx index 32d9dbb2..efa6434e 100644 --- a/website/src/repl/components/panel/ChatTab.jsx +++ b/website/src/repl/components/panel/ChatTab.jsx @@ -85,7 +85,7 @@ async function fetchModels(provider, apiKey) { * Models are fetched dynamically from provider APIs * Adapts layout for bottom panel (horizontal) vs right panel (vertical) */ -function SettingsPanel({ onClose }) { +function SettingsPanel({ onClose, isBottomPanel }) { const settings = useSettings(); const [openaiKey, setOpenaiKey] = useState(settings.openaiApiKey || ''); const [anthropicKey, setAnthropicKey] = useState(settings.anthropicApiKey || ''); @@ -249,8 +249,8 @@ function SettingsPanel({ onClose }) { {/* API Keys */}

API Ключи

-
-
+
+
@@ -262,7 +262,7 @@ function SettingsPanel({ onClose }) { className={cx(inputClass, 'text-sm py-1')} />
-
+
@@ -274,7 +274,7 @@ function SettingsPanel({ onClose }) { className={cx(inputClass, 'text-sm py-1')} />
-
+
@@ -410,7 +410,7 @@ function Message({ message }) { /** * Main ChatTab component */ -export function ChatTab({ context }) { +export function ChatTab({ context, isBottomPanel }) { const messagesEndRef = useRef(null); const chat = useChatContext(context); const [showSettings, setShowSettings] = useState(false); @@ -450,7 +450,7 @@ export function ChatTab({ context }) { )}
- setShowSettings(false)} /> + setShowSettings(false)} isBottomPanel={isBottomPanel} />
); } diff --git a/website/src/repl/components/panel/Panel.jsx b/website/src/repl/components/panel/Panel.jsx index b7aae6f7..0285158d 100644 --- a/website/src/repl/components/panel/Panel.jsx +++ b/website/src/repl/components/panel/Panel.jsx @@ -25,7 +25,7 @@ export function HorizontalPanel({ context }) { {isPanelOpen ? ( <>
- +
@@ -70,7 +70,7 @@ export function VerticalPanel({ context }) {
- +
) : ( @@ -131,11 +131,11 @@ function PanelNav({ children, className, settings, ...props }) { ); } -function PanelContent({ context, tab }) { +function PanelContent({ context, tab, isBottomPanel }) { useLogger(); switch (tab) { case 'chat': - return ; + return ; case 'patterns': return ; case 'console':