## 主要更新 - ✨ 更新所有依赖到最新稳定版本 - 📝 添加详细的项目文档和模型推荐 - 🔧 配置 VSCode Cloud Studio 预览功能 - 🐛 修复 PyTorch API 弃用警告 ## 依赖更新 - diffusers: 0.27.2 → 0.35.2 - gradio: 4.21.0 → 5.46.0 - peft: 0.7.1 → 0.18.0 - Pillow: 9.5.0 → 11.3.0 - fastapi: 0.108.0 → 0.116.2 ## 新增文件 - CLAUDE.md - 项目架构和开发指南 - UPGRADE_NOTES.md - 详细的升级说明 - .vscode/preview.yml - 预览配置 - .vscode/LAUNCH_GUIDE.md - 启动指南 - .gitignore - 更新的忽略规则 ## 代码修复 - 修复 iopaint/model/ldm.py 中的 torch.cuda.amp.autocast() 弃用警告 ## 文档更新 - README.md - 添加模型推荐和使用指南 - 完整的项目源码(iopaint/) - Web 前端源码(web_app/) 🤖 Generated with Claude Code
168 lines
4.4 KiB
TypeScript
168 lines
4.4 KiB
TypeScript
import { useCallback, useEffect, useRef } from "react"
|
|
|
|
import useInputImage from "@/hooks/useInputImage"
|
|
import { keepGUIAlive } from "@/lib/utils"
|
|
import { getServerConfig } from "@/lib/api"
|
|
import Header from "@/components/Header"
|
|
import Workspace from "@/components/Workspace"
|
|
import FileSelect from "@/components/FileSelect"
|
|
import { Toaster } from "./components/ui/toaster"
|
|
import { useStore } from "./lib/states"
|
|
import { useWindowSize } from "react-use"
|
|
|
|
const SUPPORTED_FILE_TYPE = [
|
|
"image/jpeg",
|
|
"image/png",
|
|
"image/webp",
|
|
"image/bmp",
|
|
"image/tiff",
|
|
]
|
|
function Home() {
|
|
const [file, updateAppState, setServerConfig, setFile] = useStore((state) => [
|
|
state.file,
|
|
state.updateAppState,
|
|
state.setServerConfig,
|
|
state.setFile,
|
|
])
|
|
|
|
const userInputImage = useInputImage()
|
|
|
|
const windowSize = useWindowSize()
|
|
|
|
useEffect(() => {
|
|
if (userInputImage) {
|
|
setFile(userInputImage)
|
|
}
|
|
}, [userInputImage, setFile])
|
|
|
|
useEffect(() => {
|
|
updateAppState({ windowSize })
|
|
}, [windowSize])
|
|
|
|
useEffect(() => {
|
|
const fetchServerConfig = async () => {
|
|
const serverConfig = await getServerConfig()
|
|
setServerConfig(serverConfig)
|
|
if (serverConfig.isDesktop) {
|
|
// Keeping GUI Window Open
|
|
keepGUIAlive()
|
|
}
|
|
}
|
|
fetchServerConfig()
|
|
}, [])
|
|
|
|
const dragCounter = useRef(0)
|
|
|
|
const handleDrag = useCallback((event: any) => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
}, [])
|
|
|
|
const handleDragIn = useCallback((event: any) => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
dragCounter.current += 1
|
|
}, [])
|
|
|
|
const handleDragOut = useCallback((event: any) => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
dragCounter.current -= 1
|
|
if (dragCounter.current > 0) return
|
|
}, [])
|
|
|
|
const handleDrop = useCallback((event: any) => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
|
|
if (event.dataTransfer.files.length > 1) {
|
|
// setToastState({
|
|
// open: true,
|
|
// desc: "Please drag and drop only one file",
|
|
// state: "error",
|
|
// duration: 3000,
|
|
// })
|
|
} else {
|
|
const dragFile = event.dataTransfer.files[0]
|
|
const fileType = dragFile.type
|
|
if (SUPPORTED_FILE_TYPE.includes(fileType)) {
|
|
setFile(dragFile)
|
|
} else {
|
|
// setToastState({
|
|
// open: true,
|
|
// desc: "Please drag and drop an image file",
|
|
// state: "error",
|
|
// duration: 3000,
|
|
// })
|
|
}
|
|
}
|
|
event.dataTransfer.clearData()
|
|
}
|
|
}, [])
|
|
|
|
const onPaste = useCallback((event: any) => {
|
|
// TODO: when sd side panel open, ctrl+v not work
|
|
// https://htmldom.dev/paste-an-image-from-the-clipboard/
|
|
if (!event.clipboardData) {
|
|
return
|
|
}
|
|
const clipboardItems = event.clipboardData.items
|
|
const items: DataTransferItem[] = [].slice
|
|
.call(clipboardItems)
|
|
.filter((item: DataTransferItem) => {
|
|
// Filter the image items only
|
|
return item.type.indexOf("image") !== -1
|
|
})
|
|
|
|
if (items.length === 0) {
|
|
return
|
|
}
|
|
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
|
|
// TODO: add confirm dialog
|
|
|
|
const item = items[0]
|
|
// Get the blob of image
|
|
const blob = item.getAsFile()
|
|
if (blob) {
|
|
setFile(blob)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
window.addEventListener("dragenter", handleDragIn)
|
|
window.addEventListener("dragleave", handleDragOut)
|
|
window.addEventListener("dragover", handleDrag)
|
|
window.addEventListener("drop", handleDrop)
|
|
window.addEventListener("paste", onPaste)
|
|
return function cleanUp() {
|
|
window.removeEventListener("dragenter", handleDragIn)
|
|
window.removeEventListener("dragleave", handleDragOut)
|
|
window.removeEventListener("dragover", handleDrag)
|
|
window.removeEventListener("drop", handleDrop)
|
|
window.removeEventListener("paste", onPaste)
|
|
}
|
|
})
|
|
|
|
return (
|
|
<main className="flex min-h-screen flex-col items-center justify-between w-full bg-[radial-gradient(circle_at_1px_1px,_#8e8e8e8e_1px,_transparent_0)] [background-size:20px_20px] bg-repeat">
|
|
<Toaster />
|
|
<Header />
|
|
<Workspace />
|
|
{!file ? (
|
|
<FileSelect
|
|
onSelection={async (f) => {
|
|
setFile(f)
|
|
}}
|
|
/>
|
|
) : (
|
|
<></>
|
|
)}
|
|
</main>
|
|
)
|
|
}
|
|
|
|
export default Home
|