diff --git a/components/canvas/RealtimeCanvasDialog.tsx b/components/canvas/RealtimeCanvasDialog.tsx index d0f308ede..2e20831fa 100644 --- a/components/canvas/RealtimeCanvasDialog.tsx +++ b/components/canvas/RealtimeCanvasDialog.tsx @@ -97,6 +97,7 @@ type SavedRealtimeProject = { canvasBackground: string; imageLayers: ImageLayer[]; drawingLayerDataUrl: string; + coverImageUrl?: string; previewUrl?: string; }; @@ -123,6 +124,19 @@ function getResizeCursor(handle: LayerResizeHandle) { return handle === "nw" || handle === "se" ? "nwse-resize" : "nesw-resize"; } +function buildSuggestedProjectName(prompt: string) { + const trimmed = prompt.trim(); + if (trimmed) { + return trimmed.slice(0, 24); + } + return `实时项目 ${new Date().toLocaleString("zh-CN", { + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + })}`; +} + function readRealtimeProjectsFromStorage() { if (typeof window === "undefined") return [] as SavedRealtimeProject[]; try { @@ -710,6 +724,8 @@ export function RealtimeCanvasDialog({ const [brushSize, setBrushSize] = useState(6); const [savedProjects, setSavedProjects] = useState([]); const [projectPanelOpen, setProjectPanelOpen] = useState(false); + const [saveProjectDialogOpen, setSaveProjectDialogOpen] = useState(false); + const [pendingProjectName, setPendingProjectName] = useState(""); const [isDragImportActive, setIsDragImportActive] = useState(false); const [adaptiveThrottleMs, setAdaptiveThrottleMs] = useState( defaultRealtimeConfig.transport.throttleInterval @@ -1508,25 +1524,34 @@ export function RealtimeCanvasDialog({ toast.success("项目已删除"); }, []); - const saveCurrentProject = useCallback(async () => { + const saveCurrentProject = useCallback(() => { if (!hasRenderableInput(true)) { toast.error("空白画布无需保存"); return; } + setPendingProjectName(buildSuggestedProjectName(prompt)); + setSaveProjectDialogOpen(true); + }, [hasRenderableInput, prompt]); + + const confirmSaveCurrentProject = useCallback(async () => { + const normalizedProjectName = pendingProjectName.trim(); + if (!normalizedProjectName) { + toast.error("请输入项目名称"); + return; + } + const existingProjects = readRealtimeProjectsFromStorage(); const projectId = `rtc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const compositeCanvas = await buildCompositeCanvas(); + const coverImageUrl = + imageLayersRef.current[0]?.src || + previewUrl || + compositeCanvas?.toDataURL("image/jpeg", 0.72) || + ""; const project: SavedRealtimeProject = { id: projectId, - name: - prompt.trim().slice(0, 24) || - `实时项目 ${new Date().toLocaleString("zh-CN", { - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - })}`, + name: normalizedProjectName, updatedAt: new Date().toISOString(), prompt: prompt.trim() || FALLBACK_PROMPT, brushColor, @@ -1534,11 +1559,13 @@ export function RealtimeCanvasDialog({ canvasBackground: canvasBackgroundRef.current, imageLayers: cloneImageLayers(imageLayersRef.current), drawingLayerDataUrl: exportDrawingLayerDataUrl(), + coverImageUrl, previewUrl: previewUrl || compositeCanvas?.toDataURL("image/jpeg", 0.72) || "", }; const nextProjects = [project, ...existingProjects].slice(0, 30); writeRealtimeProjectsToStorage(nextProjects); setSavedProjects(nextProjects); + setSaveProjectDialogOpen(false); setProjectPanelOpen(true); toast.success("项目已保存"); }, [ @@ -1546,7 +1573,7 @@ export function RealtimeCanvasDialog({ brushSize, buildCompositeCanvas, exportDrawingLayerDataUrl, - hasRenderableInput, + pendingProjectName, previewUrl, prompt, ]); @@ -2669,6 +2696,55 @@ export function RealtimeCanvasDialog({ + {saveProjectDialogOpen ? ( +
+
+
+
保存项目
+
请输入项目名称后保存当前画布
+
+ +
+ setPendingProjectName(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + void confirmSaveCurrentProject(); + } + }} + placeholder="请输入项目名称" + className="mt-4 h-11 rounded-2xl border-black/10" + autoFocus + /> +
+ + +
+
+ ) : null} {projectPanelOpen ? (
@@ -2686,7 +2762,7 @@ export function RealtimeCanvasDialog({
-
+
{savedProjects.length === 0 ? (
暂无保存的项目 @@ -2696,22 +2772,24 @@ export function RealtimeCanvasDialog({
- {project.previewUrl ? ( - {project.name} + {project.coverImageUrl || project.previewUrl ? ( + {project.name} ) : (
无预览
)}
-
+
{project.name}
-
- {project.prompt || "未填写提示词"} -
-
- {new Date(project.updatedAt).toLocaleString("zh-CN")} -
@@ -2841,12 +2919,12 @@ export function RealtimeCanvasDialog({
-
+
-
+
{imageLayers.length === 0 ? (
暂无图层 @@ -2858,9 +2936,9 @@ export function RealtimeCanvasDialog({ type="button" draggable className={cn( - "flex w-full items-center justify-center rounded-[16px] p-1 transition hover:bg-zinc-50", + "mx-auto flex h-12 w-12 shrink-0 items-center justify-center rounded-[18px] p-1.5 transition hover:bg-zinc-50", selectedLayerId === layer.id - ? "bg-zinc-100 ring-2 ring-black/6" + ? "bg-zinc-100 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.08)]" : "" )} onClick={() => setSelectedLayerId(layer.id)} @@ -2887,7 +2965,7 @@ export function RealtimeCanvasDialog({ {layer.name} ))