diff --git a/service/file_sync_service.py b/service/file_sync_service.py index d999cde..b8b7c40 100644 --- a/service/file_sync_service.py +++ b/service/file_sync_service.py @@ -1,4 +1,5 @@ import asyncio +import folder_paths from datetime import datetime import platform import tempfile @@ -241,9 +242,6 @@ def count_files_sync(reqJson): if file.is_file(): file_count += 1 return {"success": True, "count": file_count} - - - # Begin: Copy all files to the new directory when switching the save directory @server.PromptServer.instance.routes.post('/workspace/copy_json_files') async def copy_json_files(request): @@ -281,4 +279,33 @@ async def get_unique_dir_name(path): path = f"{original_path}_{counter}" counter += 1 return path -# End: Copy all files to the new directory when switching the save directory \ No newline at end of file +# End: Copy all files to the new directory when switching the save directory + +@server.PromptServer.instance.routes.post('/workspace/images/save') +async def save_images(request): + reader = await request.multipart() + subfolder = "workspace_manager" # 默认子文件夹,实际上你可能会从表单中动态获取 + output_path = folder_paths.get_output_directory() + safe_dir = Path(os.path.join(output_path, subfolder)) + if not safe_dir.exists(): + safe_dir.mkdir(parents=True, exist_ok=True) + + # 用于存储保存的文件路径 + files_saved = [] + # 处理multipart/form-data中的每一部分 + async for part in reader: + if part.name.startswith('images'): + filename = part.filename + if not filename: + continue + file_path = safe_dir / filename + loop = asyncio.get_running_loop() + # 异步写入文件 + with open(file_path, 'wb') as f: + while True: + chunk = await part.read_chunk() # 默认8192 bytes + if not chunk: + break + await loop.run_in_executor(None, f.write, chunk) + files_saved.append(str(file_path)) + return web.Response(text=json.dumps({"imgPaths": files_saved}), content_type='application/json') diff --git a/ui/src/RecentFilesDrawer/MoreActionMenu.tsx b/ui/src/RecentFilesDrawer/MoreActionMenu.tsx index 2f25709..b831244 100644 --- a/ui/src/RecentFilesDrawer/MoreActionMenu.tsx +++ b/ui/src/RecentFilesDrawer/MoreActionMenu.tsx @@ -1,28 +1,30 @@ -import { useState, useContext } from "react"; +import { ChangeEvent, useContext, useRef, useState } from "react"; import { IconButton, Menu, MenuButton, - MenuList, MenuItem, - useToast, + MenuList, Tooltip, + useToast, } from "@chakra-ui/react"; import { - IconDotsVertical, - IconLockOpen, - IconLock, IconCopy, + IconDotsVertical, + IconLock, + IconLockOpen, + IconUpload, } from "@tabler/icons-react"; import AddTagToWorkflowPopover from "./AddTagToWorkflowPopover"; import { Workflow } from "../types/dbTypes"; -import { workflowsTable } from "../db-tables/WorkspaceDB"; +import { mediaTable, workflowsTable } from "../db-tables/WorkspaceDB"; import { WorkspaceContext } from "../WorkspaceContext"; type Props = { workflow: Workflow; }; export default function MoreActionMenu({ workflow }: Props) { + const fileInputRef = useRef(null); const toast = useToast(); const { onDuplicateWorkflow } = useContext(WorkspaceContext); @@ -41,6 +43,28 @@ export default function MoreActionMenu({ workflow }: Props) { }); }; + const handleFileChange = async (event: ChangeEvent) => { + const files = event.target.files; + if (!files) return; + + const body = new FormData(); + + Array.from(files).forEach((file, index) => { + body.append(`images[${index}]`, file, file.name); + }); + body.append("subfolder", "workspace_manager"); + await fetch("/workspace/images/save", { + method: "POST", + body, + }); + Array.from(files).forEach((file) => { + mediaTable?.create({ + workflowID: workflow.id, + localPath: `workspace_manager/${file.name}`, + }); + }); + }; + return ( <> @@ -78,8 +102,27 @@ export default function MoreActionMenu({ workflow }: Props) { {isLocked ? "Unlock" : "Lock"} + } + onClick={() => { + fileInputRef.current?.click(); + }} + > + Upload Thumbnail Image + + { + e.stopPropagation(); + }} + /> ); } diff --git a/ui/src/RecentFilesDrawer/WorkflowListItem.tsx b/ui/src/RecentFilesDrawer/WorkflowListItem.tsx index 3d75166..3c7eac7 100644 --- a/ui/src/RecentFilesDrawer/WorkflowListItem.tsx +++ b/ui/src/RecentFilesDrawer/WorkflowListItem.tsx @@ -57,9 +57,11 @@ export default memo(function WorkflowListItem({ workflow }: Props) { }} > - {workflow.coverMediaPath != null && ( + {(workflow.coverMediaPath != null || workflow.latestImage !== null) && ( { const newMedia = new Set(workflow?.mediaIDs ?? []).add(md.id); await workflowsTable?.updateMetaInfo(input.workflowID, { mediaIDs: Array.from(newMedia), - coverMediaPath: md.localPath, + latestImage: md.localPath, }); // save indexdb indexdb.media.add(md); diff --git a/ui/src/types/dbTypes.ts b/ui/src/types/dbTypes.ts index f95bf8c..07daf7d 100644 --- a/ui/src/types/dbTypes.ts +++ b/ui/src/types/dbTypes.ts @@ -20,6 +20,7 @@ export interface Workflow extends SortableItem { cloudID?: string; // TODO will be deprecated cloudOrigin?: string; saveLock?: boolean; + latestImage?: string; } export interface TableBaseModel {