From af5b5b209706416ebe28c0271542f72dddf68d4f Mon Sep 17 00:00:00 2001 From: ddCat <2695092045@qq.com> Date: Fri, 18 Sep 2026 13:03:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(canvas):=20=E6=8F=90=E7=A4=BA=E8=AF=8D?= =?UTF-8?q?=E7=BC=96=E8=BE=91=20-=20=E6=94=AF=E6=8C=81=E6=94=BE=E5=A4=A7?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=E8=B0=83=E6=95=B4=E5=B0=BA=E5=AF=B8=E4=B8=8E?= =?UTF-8?q?=E9=94=AE=E7=9B=98=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canvas/canvas-node-prompt-panel.tsx | 100 ++++++++++++++++-- 1 file changed, 93 insertions(+), 7 deletions(-) diff --git a/web/src/components/canvas/canvas-node-prompt-panel.tsx b/web/src/components/canvas/canvas-node-prompt-panel.tsx index 9ad80cbf3..f86c452eb 100644 --- a/web/src/components/canvas/canvas-node-prompt-panel.tsx +++ b/web/src/components/canvas/canvas-node-prompt-panel.tsx @@ -57,12 +57,19 @@ type CanvasTheme = (typeof canvasThemes)[keyof typeof canvasThemes]; const PROMPT_REFERENCE_SHELF_HEIGHT = 58; // Keep the compact editor readable at rest: three 20px lines plus 12px vertical padding. const PROMPT_EDITOR_MIN_HEIGHT = 72; -const PROMPT_EDITOR_EXPANDED_MIN_HEIGHT = 76; +const PROMPT_EDITOR_EXPANDED_MIN_HEIGHT = 200; const PROMPT_EDITOR_LINE_HEIGHT = 20; const PROMPT_EDITOR_EXPANDED_LINE_HEIGHT = 24; const PROMPT_EDITOR_VERTICAL_PADDING = 12; const PROMPT_EDITOR_EXPANDED_VERTICAL_PADDING = 20; const PROMPT_EDITOR_MAX_LINES = 8; +const PROMPT_EDITOR_EXPANDED_MAX_LINES = 14; +const PROMPT_EDITOR_MODAL_WIDTH = "min(1200px, 92vw)"; +const PROMPT_EDITOR_MODAL_DEFAULT_WIDTH = 1200; +const PROMPT_EDITOR_MODAL_DEFAULT_HEIGHT = 420; +const PROMPT_EDITOR_MODAL_MIN_WIDTH = 560; +const PROMPT_EDITOR_MODAL_MIN_HEIGHT = 320; +const PROMPT_EDITOR_MODAL_VIEWPORT_MARGIN = 24; export function CanvasNodePromptPanel({ projectId, node, isRunning, onPromptChange, onConfigChange, onGenerate, mentionReferences = [], onAddReference, onRemoveReference, onReorderReferences, onReplaceReference, onReplaceReferenceFiles, onClose, onNodeMouseDown, onImageSettingsOpenChange, workspaceMode = "professional" }: CanvasNodePromptPanelProps) { const globalConfig = useEffectiveConfig(); @@ -80,6 +87,8 @@ export function CanvasNodePromptPanel({ projectId, node, isRunning, onPromptChan const [presetOpen, setPresetOpen] = useState(false); const [expandedPresetOpen, setExpandedPresetOpen] = useState(false); const [expandedPromptOpen, setExpandedPromptOpen] = useState(false); + const [expandedModalSize, setExpandedModalSize] = useState<{ width: number; height: number } | null>(null); + const expandedModalRef = useRef(null); const [promptContentHeight, setPromptContentHeight] = useState(() => estimatePromptContentHeight(savedPrompt, false)); const [expandedPromptContentHeight, setExpandedPromptContentHeight] = useState(() => estimatePromptContentHeight(savedPrompt, true)); const [manualPromptHeight, setManualPromptHeight] = useState(null); @@ -164,6 +173,11 @@ export function CanvasNodePromptPanel({ projectId, node, isRunning, onPromptChan const expandedPromptBounds = promptEditorBounds(true, activeReferenceCount > 0); const composerHeight = clampPromptHeight(manualPromptHeight ?? promptContentHeight + (activeReferenceCount ? PROMPT_REFERENCE_SHELF_HEIGHT : 0), promptBounds); const expandedComposerHeight = clampPromptHeight(manualExpandedPromptHeight ?? expandedPromptContentHeight + (activeReferenceCount ? PROMPT_REFERENCE_SHELF_HEIGHT : 0), expandedPromptBounds); + const measureExpandedModalSize = () => { + const rect = expandedModalRef.current?.getBoundingClientRect(); + if (!rect?.width || !rect.height) return { width: PROMPT_EDITOR_MODAL_DEFAULT_WIDTH, height: PROMPT_EDITOR_MODAL_DEFAULT_HEIGHT }; + return { width: Math.round(rect.width), height: Math.round(rect.height) }; + }; const isSubmitDisabled = !isRunning && !prompt.trim(); const canExpandPrompt = mode === "image" || mode === "video"; const canOptimizePrompt = Boolean(promptOptimizerProvider) && canExpandPrompt; @@ -179,6 +193,7 @@ export function CanvasNodePromptPanel({ projectId, node, isRunning, onPromptChan useEffect(() => { setExpandedPromptOpen(false); setExpandedPresetOpen(false); + setExpandedModalSize(null); setPromptContentHeight(estimatePromptContentHeight(normalizedSavedPrompt, false)); setExpandedPromptContentHeight(estimatePromptContentHeight(normalizedSavedPrompt, true)); setManualPromptHeight(null); @@ -435,12 +450,12 @@ export function CanvasNodePromptPanel({ projectId, node, isRunning, onPromptChan ); - const renderPromptEditor = (expanded: boolean) => { + const renderPromptEditor = (expanded: boolean, fill = false) => { const bounds = expanded ? expandedPromptBounds : promptBounds; const height = expanded ? expandedComposerHeight : composerHeight; return ( <> -
+
{ setExpandedPresetOpen(false); @@ -546,15 +561,16 @@ export function CanvasNodePromptPanel({ projectId, node, isRunning, onPromptChan body: { minHeight: 0, padding: 0 }, }} > -
+
{renderComposerHeader(true)}
- {renderPromptEditor(true)} + {renderPromptEditor(true, Boolean(expandedModalSize))} {hasVideoPromptTools ? (
onConfigChange(node.id, patch)} />
) : null}
{renderComposerControls(true)}
+
@@ -916,10 +932,80 @@ function PromptResizeHandle({ height, min, max, onResize }: { height: number; mi ); } +function clampExpandedModalSize(size: { width: number; height: number }) { + const maxWidth = Math.max(PROMPT_EDITOR_MODAL_MIN_WIDTH, window.innerWidth - PROMPT_EDITOR_MODAL_VIEWPORT_MARGIN); + const maxHeight = Math.max(PROMPT_EDITOR_MODAL_MIN_HEIGHT, window.innerHeight - PROMPT_EDITOR_MODAL_VIEWPORT_MARGIN); + return { + width: Math.min(maxWidth, Math.max(PROMPT_EDITOR_MODAL_MIN_WIDTH, Math.round(size.width))), + height: Math.min(maxHeight, Math.max(PROMPT_EDITOR_MODAL_MIN_HEIGHT, Math.round(size.height))), + }; +} + +function PromptModalResizeHandle({ size, measure, onResize, accent }: { size: { width: number; height: number } | null; measure: () => { width: number; height: number }; onResize: (size: { width: number; height: number }) => void; accent: string }) { + const dragRef = useRef<{ pointerId: number; startX: number; startY: number; width: number; height: number } | null>(null); + + const finishResize = (event: ReactPointerEvent) => { + if (dragRef.current?.pointerId !== event.pointerId) return; + if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId); + dragRef.current = null; + }; + + const handleKeyDown = (event: ReactKeyboardEvent) => { + const step = event.shiftKey ? 40 : 12; + const widthDelta = event.key === "ArrowLeft" ? -step : event.key === "ArrowRight" ? step : 0; + const heightDelta = event.key === "ArrowUp" ? -step : event.key === "ArrowDown" ? step : 0; + if (!widthDelta && !heightDelta) return; + event.preventDefault(); + event.stopPropagation(); + const base = size ?? measure(); + onResize(clampExpandedModalSize({ width: base.width + widthDelta, height: base.height + heightDelta })); + }; + + return ( + + ); +} + function promptEditorBounds(expanded: boolean, hasReferences: boolean) { const shelfHeight = hasReferences ? PROMPT_REFERENCE_SHELF_HEIGHT : 0; const min = (expanded ? PROMPT_EDITOR_EXPANDED_MIN_HEIGHT : PROMPT_EDITOR_MIN_HEIGHT) + shelfHeight; - const max = (expanded ? PROMPT_EDITOR_EXPANDED_LINE_HEIGHT * PROMPT_EDITOR_MAX_LINES + PROMPT_EDITOR_EXPANDED_VERTICAL_PADDING : PROMPT_EDITOR_LINE_HEIGHT * PROMPT_EDITOR_MAX_LINES + PROMPT_EDITOR_VERTICAL_PADDING) + shelfHeight; + const max = (expanded ? PROMPT_EDITOR_EXPANDED_LINE_HEIGHT * PROMPT_EDITOR_EXPANDED_MAX_LINES + PROMPT_EDITOR_EXPANDED_VERTICAL_PADDING : PROMPT_EDITOR_LINE_HEIGHT * PROMPT_EDITOR_MAX_LINES + PROMPT_EDITOR_VERTICAL_PADDING) + shelfHeight; return { min, max }; }