From a4a80f671bc64969b0e34d539afec7fd560b794c Mon Sep 17 00:00:00 2001 From: ss707494 Date: Sat, 9 Mar 2024 23:20:39 +0800 Subject: [PATCH 1/4] feat: upload-thumbnail --- service/file_sync_service.py | 21 ++++++++ ui/src/RecentFilesDrawer/MoreActionMenu.tsx | 53 ++++++++++++++++++--- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/service/file_sync_service.py b/service/file_sync_service.py index c9a27e7..c93bada 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 @@ -242,3 +243,23 @@ def count_files_sync(reqJson): file_count += 1 return {"success": True, "count": file_count} +@server.PromptServer.instance.routes.post("/image/save") +async def save_image(request): + post = await request.post() + image = post.get('image') + subfolder = post.get('subfolder') + filename = post.get('filename') + # 确保上传的是文件对象 + if image is None or not hasattr(image, 'file'): + return web.Response(text="No image provided", status=400) + data = image.file.read() + output_path = folder_paths.get_output_directory() + safe_dir = Path(os.path.join(output_path, "workspace_manager")) + if not safe_dir.exists(): + safe_dir.mkdir(parents=True, exist_ok=True) + file_path = safe_dir / filename + with open(file_path, 'wb') as f: + f.write(data) + return web.Response(text=json.dumps({ + "imgPath": os.path.join(subfolder, filename), + }), content_type='application/json') diff --git a/ui/src/RecentFilesDrawer/MoreActionMenu.tsx b/ui/src/RecentFilesDrawer/MoreActionMenu.tsx index 2f25709..6eb19b1 100644 --- a/ui/src/RecentFilesDrawer/MoreActionMenu.tsx +++ b/ui/src/RecentFilesDrawer/MoreActionMenu.tsx @@ -1,28 +1,29 @@ -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, } 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 +42,25 @@ export default function MoreActionMenu({ workflow }: Props) { }); }; + const handleFileChange = async (event: ChangeEvent) => { + const files = event.target.files; + if (!files) return; + + const body = new FormData(); + const filename = files[0].name; + body.append("image", files[0]); + body.append("subfolder", "workspace_manager"); + body.append("filename", filename); + await fetch("/image/save", { + method: "POST", + body, + }); + mediaTable?.create({ + workflowID: workflow.id, + localPath: `workspace_manager/${filename}`, + }); + }; + return ( <> @@ -78,8 +98,27 @@ export default function MoreActionMenu({ workflow }: Props) { {isLocked ? "Unlock" : "Lock"} + } + onClick={() => { + fileInputRef.current?.click(); + }} + > + Upload + + { + e.stopPropagation(); + }} + /> ); } From 3bc055fda7da71538a8417d523685261c9bd3ecf Mon Sep 17 00:00:00 2001 From: ss707494 Date: Sat, 9 Mar 2024 23:22:49 +0800 Subject: [PATCH 2/4] feat: upload-thumbnail --- ui/src/RecentFilesDrawer/MoreActionMenu.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/src/RecentFilesDrawer/MoreActionMenu.tsx b/ui/src/RecentFilesDrawer/MoreActionMenu.tsx index 6eb19b1..d002dc5 100644 --- a/ui/src/RecentFilesDrawer/MoreActionMenu.tsx +++ b/ui/src/RecentFilesDrawer/MoreActionMenu.tsx @@ -13,6 +13,7 @@ import { IconDotsVertical, IconLock, IconLockOpen, + IconUpload, } from "@tabler/icons-react"; import AddTagToWorkflowPopover from "./AddTagToWorkflowPopover"; import { Workflow } from "../types/dbTypes"; @@ -99,12 +100,12 @@ export default function MoreActionMenu({ workflow }: Props) { } + icon={} onClick={() => { fileInputRef.current?.click(); }} > - Upload + Upload Thumbnail Image From f8d66e94d4f61f98f5d868a08ab74de6da8bce3c Mon Sep 17 00:00:00 2001 From: ss707494 Date: Sun, 10 Mar 2024 11:50:29 +0800 Subject: [PATCH 3/4] feat: upload-thumbnail --- service/file_sync_service.py | 42 ++++++++++++--------- ui/src/RecentFilesDrawer/MoreActionMenu.tsx | 17 +++++---- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/service/file_sync_service.py b/service/file_sync_service.py index c93bada..fef3b1c 100644 --- a/service/file_sync_service.py +++ b/service/file_sync_service.py @@ -243,23 +243,31 @@ def count_files_sync(reqJson): file_count += 1 return {"success": True, "count": file_count} -@server.PromptServer.instance.routes.post("/image/save") -async def save_image(request): - post = await request.post() - image = post.get('image') - subfolder = post.get('subfolder') - filename = post.get('filename') - # 确保上传的是文件对象 - if image is None or not hasattr(image, 'file'): - return web.Response(text="No image provided", status=400) - data = image.file.read() +@server.PromptServer.instance.routes.post("/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, "workspace_manager")) + safe_dir = Path(os.path.join(output_path, subfolder)) if not safe_dir.exists(): safe_dir.mkdir(parents=True, exist_ok=True) - file_path = safe_dir / filename - with open(file_path, 'wb') as f: - f.write(data) - return web.Response(text=json.dumps({ - "imgPath": os.path.join(subfolder, filename), - }), content_type='application/json') + + # 用于存储保存的文件路径 + 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') \ No newline at end of file diff --git a/ui/src/RecentFilesDrawer/MoreActionMenu.tsx b/ui/src/RecentFilesDrawer/MoreActionMenu.tsx index d002dc5..ec82229 100644 --- a/ui/src/RecentFilesDrawer/MoreActionMenu.tsx +++ b/ui/src/RecentFilesDrawer/MoreActionMenu.tsx @@ -48,17 +48,20 @@ export default function MoreActionMenu({ workflow }: Props) { if (!files) return; const body = new FormData(); - const filename = files[0].name; - body.append("image", files[0]); + + Array.from(files).forEach((file, index) => { + body.append(`images[${index}]`, file, file.name); + }); body.append("subfolder", "workspace_manager"); - body.append("filename", filename); - await fetch("/image/save", { + await fetch("/images/save", { method: "POST", body, }); - mediaTable?.create({ - workflowID: workflow.id, - localPath: `workspace_manager/${filename}`, + Array.from(files).forEach((file) => { + mediaTable?.create({ + workflowID: workflow.id, + localPath: `workspace_manager/${file.name}`, + }); }); }; From 10dd69856ebd97789c65ea19dfba6dfe80251e07 Mon Sep 17 00:00:00 2001 From: ss707494 Date: Mon, 11 Mar 2024 23:13:12 +0800 Subject: [PATCH 4/4] feat: upload-thumbnail/add latestImage --- service/file_sync_service.py | 2 +- ui/src/RecentFilesDrawer/MoreActionMenu.tsx | 2 +- ui/src/RecentFilesDrawer/WorkflowListItem.tsx | 6 ++++-- ui/src/db-tables/MediaTable.ts | 2 +- ui/src/types/dbTypes.ts | 1 + 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/service/file_sync_service.py b/service/file_sync_service.py index 459dbe6..b8b7c40 100644 --- a/service/file_sync_service.py +++ b/service/file_sync_service.py @@ -281,7 +281,7 @@ async def get_unique_dir_name(path): return path # End: Copy all files to the new directory when switching the save directory -@server.PromptServer.instance.routes.post("/images/save") +@server.PromptServer.instance.routes.post('/workspace/images/save') async def save_images(request): reader = await request.multipart() subfolder = "workspace_manager" # 默认子文件夹,实际上你可能会从表单中动态获取 diff --git a/ui/src/RecentFilesDrawer/MoreActionMenu.tsx b/ui/src/RecentFilesDrawer/MoreActionMenu.tsx index ec82229..b831244 100644 --- a/ui/src/RecentFilesDrawer/MoreActionMenu.tsx +++ b/ui/src/RecentFilesDrawer/MoreActionMenu.tsx @@ -53,7 +53,7 @@ export default function MoreActionMenu({ workflow }: Props) { body.append(`images[${index}]`, file, file.name); }); body.append("subfolder", "workspace_manager"); - await fetch("/images/save", { + await fetch("/workspace/images/save", { method: "POST", body, }); 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 {