feat(canvas): add expanded editor for prompt input and retain edits after closing panel

This commit is contained in:
HouYunFei
2026-08-06 15:48:36 +08:00
parent c23942ddba
commit f8f1b160b0
6 changed files with 50 additions and 18 deletions
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { ArrowUp, LoaderCircle, Square } from "lucide-react";
import { Button } from "antd";
import { ArrowUp, LoaderCircle, Maximize2, Square } from "lucide-react";
import { Button, Modal, Tooltip } from "antd";
import { useTranslation } from "react-i18next";
import { ModelPicker } from "@/components/model-picker";
@@ -40,17 +40,20 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi
const hasTextContent = node.type === CanvasNodeType.Text && Boolean(node.metadata?.content?.trim());
const hasImageContent = node.type === CanvasNodeType.Image && Boolean(node.metadata?.content);
const isEditingExistingContent = hasTextContent || hasImageContent;
const [prompt, setPrompt] = useState(node.metadata?.prompt || "");
const [prompt, setPrompt] = useState(node.metadata?.composerContent ?? node.metadata?.prompt ?? "");
const [expandedPrompt, setExpandedPrompt] = useState("");
const [expanded, setExpanded] = useState(false);
// Restore prompts only when switching nodes; preserve the current input after generation on the same node.
useEffect(() => {
setPrompt(node.metadata?.prompt || "");
setPrompt(node.metadata?.composerContent ?? node.metadata?.prompt ?? "");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [node.id]);
const updatePrompt = (value: string) => {
setPrompt(value);
if (!isEditingExistingContent) onPromptChange(node.id, value);
if (isEditingExistingContent) onConfigChange(node.id, { composerContent: value });
else onPromptChange(node.id, value);
};
const submit = () => {
@@ -59,6 +62,16 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi
onGenerate(node.id, mode, text);
};
const openExpandedEditor = () => {
setExpandedPrompt(prompt);
setExpanded(true);
};
const saveExpandedPrompt = () => {
updatePrompt(expandedPrompt);
setExpanded(false);
};
return (
<div
data-canvas-no-zoom
@@ -68,15 +81,20 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi
onPointerDown={(event) => event.stopPropagation()}
onWheel={(event) => event.stopPropagation()}
>
<CanvasPromptChipInput
value={prompt}
references={mentionReferences}
onChange={updatePrompt}
onSubmit={submit}
className="thin-scrollbar h-40 w-full cursor-text resize-none rounded-xl px-3 py-2 text-sm leading-5 outline-none"
style={{ background: "transparent", color: theme.node.text }}
placeholder={t(`canvas.promptPanel.${mode === "image" && hasImageContent ? "editImage" : mode === "text" && hasTextContent ? "editText" : mode}`)}
/>
<div className="relative">
<CanvasPromptChipInput
value={prompt}
references={mentionReferences}
onChange={updatePrompt}
onSubmit={submit}
className="thin-scrollbar h-40 w-full cursor-text resize-none rounded-xl px-3 py-2 pr-10 text-sm leading-5 outline-none"
style={{ background: "transparent", color: theme.node.text }}
placeholder={t(`canvas.promptPanel.${mode === "image" && hasImageContent ? "editImage" : mode === "text" && hasTextContent ? "editText" : mode}`)}
/>
<Tooltip title={t("canvas.promptPanel.expandEditor")}>
<Button type="text" className="absolute right-1 top-1 !h-8 !w-8 !min-w-8 !rounded-full !bg-transparent !p-0" style={{ color: theme.node.text }} icon={<Maximize2 className="size-3.5" />} onClick={openExpandedEditor} aria-label={t("canvas.promptPanel.expandEditor")} />
</Tooltip>
</div>
<div className="mt-2 flex min-w-0 items-center justify-between gap-2">
<div className="flex min-w-0 items-center gap-2">
@@ -131,6 +149,18 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi
</span>
</Button>
</div>
<Modal title={t("canvas.promptPanel.editorTitle")} open={expanded} centered width={760} okText={t("common.done")} cancelText={t("common.cancel")} onOk={saveExpandedPrompt} onCancel={() => setExpanded(false)} destroyOnHidden>
<div data-canvas-no-zoom className="pt-2" onWheelCapture={(event) => event.stopPropagation()}>
<CanvasPromptChipInput
value={expandedPrompt}
references={mentionReferences}
onChange={setExpandedPrompt}
className="thin-scrollbar h-[52dvh] min-h-80 w-full cursor-text overflow-y-auto rounded-xl border p-4 text-[15px] leading-6 outline-none"
style={{ background: "transparent", borderColor: theme.toolbar.border, color: theme.node.text }}
placeholder={t(`canvas.promptPanel.${mode === "image" && hasImageContent ? "editImage" : mode === "text" && hasTextContent ? "editText" : mode}`)}
/>
</div>
</Modal>
</div>
);
}