├── .eslintrc.json ├── .gitignore ├── .node-version ├── .prettierrc ├── LICENSE ├── README.md ├── components.json ├── next.config.ts ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public ├── file.svg ├── globe.svg ├── icon.png ├── next.svg ├── og.png ├── vercel.svg └── window.svg ├── src ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ ├── ImageCompareResult.tsx │ ├── ImageUploader.tsx │ ├── ProcessingError.tsx │ ├── ProcessingLoader.tsx │ ├── icons │ │ ├── github.tsx │ │ ├── sparkles.tsx │ │ └── x.tsx │ ├── images │ │ ├── homepage-image-1.tsx │ │ └── homepage-image-2.tsx │ └── ui │ │ ├── button.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── logo.tsx │ │ ├── select.tsx │ │ ├── spinner.tsx │ │ ├── toast.tsx │ │ └── toaster.tsx ├── fonts │ ├── DingTalk_JinBuTi.ttf │ └── DingTalk_Sans.ttf ├── hooks │ └── use-toast.ts └── lib │ ├── backgroundRemoval.ts │ └── utils.ts ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for committing if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 20.12.1 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-tailwindcss"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 hellokaton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # remove-bg 2 | 3 | remove-bg 是一款AI赋能的现代化Web应用,基于 Next.js 构建,致力于提供极致简单、快速精准的在线图片背景移除服务。 4 | 5 | ## 📸 预览 6 | 7 | ![remove-bg-preview](./public/og.png?t=1) 8 | 9 | 预览地址: https://rmbg.hellokaton.me 10 | 11 | ## ✨ 核心功能 12 | 13 | - 🚀 **一键操作**:上传图片即可自动移除背景,简单高效。 14 | - 🖼️ **效果对比**:提供处理前后图片对比,直观查看效果。 15 | - 💾 **轻松下载**:方便下载处理后的无背景图片。 16 | - 📱 **响应式设计**:适配桌面和移动设备,随时随地使用。 17 | 18 | ## 🛠️ 技术栈 19 | 20 | - **框架**: Next.js 15.3 (App Router) 21 | - **开发语言**: TypeScript 22 | - **样式**: Tailwind CSS 23 | - **核心处理**: @imgly/background-removal 24 | - **交互**: 25 | - react-compare-slider 26 | - react-dropzone 27 | 28 | ## 🚀 快速开始 29 | 30 | 1. 克隆仓库: 31 | 32 | ```bash 33 | git clone https://github.com/hellokaton/remove-bg.git 34 | ``` 35 | 36 | 2. 安装依赖: 37 | 38 | ```bash 39 | pnpm install 40 | ``` 41 | 42 | 3. 启动开发服务器: 43 | 44 | ```bash 45 | pnpm dev 46 | ``` 47 | 48 | ## 💡 使用指南 49 | 50 | 1. 点击或拖拽上传您的图片。 51 | 2. 等待应用自动处理图片背景。 52 | 3. 在对比视图中查看移除背景后的效果。 53 | 4. 点击下载按钮保存处理后的图片。 54 | 55 | ## 🤝 贡献指南 56 | 57 | 欢迎贡献!请随时提交 Pull Request。 58 | 59 | ## 📝 许可证 60 | 61 | [MIT](LICENSE) 62 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | async headers() { 5 | return [ 6 | { 7 | source: "/api/:path*", 8 | headers: [ 9 | { key: "Access-Control-Allow-Credentials", value: "true" }, 10 | { key: "Access-Control-Allow-Origin", value: "*" }, 11 | { 12 | key: "Access-Control-Allow-Methods", 13 | value: "GET,OPTIONS,PATCH,DELETE,POST,PUT", 14 | }, 15 | { 16 | key: "Access-Control-Allow-Headers", 17 | value: 18 | "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version", 19 | }, 20 | ], 21 | }, 22 | { 23 | source: "/(.*)", 24 | headers: [ 25 | { 26 | key: "Cross-Origin-Opener-Policy", 27 | value: "same-origin", 28 | }, 29 | { 30 | key: "Cross-Origin-Embedder-Policy", 31 | value: "require-corp", 32 | }, 33 | ], 34 | }, 35 | ]; 36 | }, 37 | }; 38 | 39 | export default nextConfig; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remove-bg", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "update:all": "pnpm update --interactive --latest" 11 | }, 12 | "dependencies": { 13 | "@imgly/background-removal": "^1.6.0", 14 | "@radix-ui/react-label": "^2.1.6", 15 | "@radix-ui/react-select": "^2.1.2", 16 | "@radix-ui/react-slot": "^1.1.0", 17 | "@radix-ui/react-toast": "^1.2.2", 18 | "@tailwindcss/typography": "^0.5.16", 19 | "class-variance-authority": "^0.7.0", 20 | "clsx": "^2.1.1", 21 | "dedent": "^1.5.3", 22 | "lucide-react": "^0.503.0", 23 | "nanoid": "^5.1.5", 24 | "next": "15.3.1", 25 | "next-plausible": "^3.12.4", 26 | "react": "19.1.0", 27 | "react-compare-slider": "^3.1.0", 28 | "react-dom": "19.1.0", 29 | "react-dropzone": "^14.3.5", 30 | "sonner": "^2.0.3", 31 | "tailwind-merge": "^2.5.4", 32 | "tailwindcss-animate": "^1.0.7" 33 | }, 34 | "devDependencies": { 35 | "@types/node": "^22.15.3", 36 | "@types/react": "^19.1.2", 37 | "@types/react-dom": "^19.1.2", 38 | "eslint": "9.25.1", 39 | "eslint-config-next": "15.3.1", 40 | "postcss": "^8", 41 | "prettier": "^3.3.3", 42 | "prettier-plugin-tailwindcss": "^0.6.8", 43 | "tailwindcss": "^3.4.1", 44 | "typescript": "^5" 45 | }, 46 | "engines": { 47 | "node": "22.x" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellokaton/remove-bg/7a26368b03f76680fa43c0fd426dbbb795463ed3/public/icon.png -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellokaton/remove-bg/7a26368b03f76680fa43c0fd426dbbb795463ed3/public/og.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellokaton/remove-bg/7a26368b03f76680fa43c0fd426dbbb795463ed3/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: Arial, Helvetica, sans-serif; 7 | } 8 | 9 | @layer base { 10 | :root { 11 | --background: 37 27% 94%; 12 | --foreground: 0 0% 3.9%; 13 | --card: 0 0% 100%; 14 | --card-foreground: 0 0% 3.9%; 15 | --popover: 0 0% 100%; 16 | --popover-foreground: 0 0% 3.9%; 17 | --primary: 0 2.44% 24.12%; 18 | --primary-foreground: 0 0% 98%; 19 | --secondary: 0 0% 96.1%; 20 | --secondary-foreground: 0 0% 9%; 21 | --muted: 0 0% 96.1%; 22 | --muted-foreground: 0 0% 45.1%; 23 | --accent: 0 0% 96.1%; 24 | --accent-foreground: 0 0% 9%; 25 | --destructive: 0 84.2% 60.2%; 26 | --destructive-foreground: 0 0% 98%; 27 | --border: 0 0% 89.8%; 28 | --input: 0 0% 89.8%; 29 | --ring: 0 0% 3.9%; 30 | --chart-1: 12 76% 61%; 31 | --chart-2: 173 58% 39%; 32 | --chart-3: 197 37% 24%; 33 | --chart-4: 43 74% 66%; 34 | --chart-5: 27 87% 67%; 35 | --radius: 0.5rem; 36 | } 37 | } 38 | 39 | @layer base { 40 | * { 41 | @apply border-border; 42 | } 43 | body { 44 | @apply bg-background text-foreground; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import GithubIcon from "@/components/icons/github"; 2 | import XIcon from "@/components/icons/x"; 3 | import Logo from "@/components/ui/logo"; 4 | import type { Metadata } from "next"; 5 | import "./globals.css"; 6 | import { Toaster as RadixToaster } from "@/components/ui/toaster"; 7 | import { Toaster } from "sonner"; 8 | import Link from "next/link"; 9 | 10 | import { Geist, Geist_Mono } from "next/font/google"; 11 | import localFont from "next/font/local"; 12 | import { cn } from "@/lib/utils"; 13 | 14 | const geistSans = Geist({ 15 | variable: "--font-geist-sans", 16 | subsets: ["latin"], 17 | }); 18 | 19 | const geistMono = Geist_Mono({ 20 | variable: "--font-geist-mono", 21 | subsets: ["latin"], 22 | }); 23 | 24 | const dingTalkFont = localFont({ 25 | src: "../fonts/DingTalk_JinBuTi.ttf", 26 | variable: "--font-dingtalk", 27 | }); 28 | 29 | export const metadata: Metadata = { 30 | title: "智能抠图 | 一键移除图片背景", 31 | description: "上传图片,立即获得背景移除效果,免费高效的AI抠图工具!", 32 | openGraph: { 33 | images: "https://rmbg.hellokaton.me/og.png", 34 | }, 35 | }; 36 | 37 | export default function RootLayout({ 38 | children, 39 | }: Readonly<{ 40 | children: React.ReactNode; 41 | }>) { 42 | return ( 43 | 44 | 52 |
53 | 54 | 55 | 56 |
57 | 58 |
{children}
59 | 60 | 61 | 96 | 97 | 98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import { toast } from "sonner"; 5 | import HomepageImage1 from "@/components/images/homepage-image-1"; 6 | import HomepageImage2 from "@/components/images/homepage-image-2"; 7 | import { ImageUploader } from "@/components/ImageUploader"; 8 | import { ProcessingLoader } from "@/components/ProcessingLoader"; 9 | import { ImageCompareResult } from "@/components/ImageCompareResult"; 10 | import { ProcessingError } from "@/components/ProcessingError"; 11 | import { fileToDataUrl, processImageBackground } from "@/lib/backgroundRemoval"; 12 | 13 | export default function Home() { 14 | const [selectedImage, setSelectedImage] = useState(null); 15 | const [originalImageDataUrl, setOriginalImageDataUrl] = useState< 16 | string | null 17 | >(null); 18 | const [processedImage, setProcessedImage] = useState(null); 19 | const [isLoading, setIsLoading] = useState(false); 20 | 21 | // 清理URL对象,防止内存泄漏 22 | useEffect(() => { 23 | return () => { 24 | if (processedImage && processedImage.startsWith("blob:")) { 25 | URL.revokeObjectURL(processedImage); 26 | } 27 | }; 28 | }, [processedImage]); 29 | 30 | const handleFileChange = async (file: File | null) => { 31 | try { 32 | // 清理之前的状态 33 | setIsLoading(false); 34 | if (processedImage && processedImage.startsWith("blob:")) { 35 | URL.revokeObjectURL(processedImage); 36 | setProcessedImage(null); 37 | } 38 | setOriginalImageDataUrl(null); 39 | 40 | if (!file) { 41 | setSelectedImage(null); 42 | return; 43 | } 44 | 45 | // 将文件转换为 Data URL (不会过期) 46 | const dataUrl = await fileToDataUrl(file); 47 | setSelectedImage(file); 48 | setOriginalImageDataUrl(dataUrl); 49 | setIsLoading(true); 50 | 51 | try { 52 | // 处理图片 53 | const processedUrl = await processImageBackground( 54 | file, 55 | ); 56 | setProcessedImage(processedUrl); 57 | } catch (e) { 58 | console.error("背景移除失败:", e); 59 | toast.error("背景移除失败,请检查图片或稍后再试。"); 60 | } finally { 61 | setIsLoading(false); 62 | } 63 | } catch (err) { 64 | console.error("处理图片时出错:", err); 65 | toast.error("处理图片时出错,请重试。"); 66 | setIsLoading(false); 67 | } 68 | }; 69 | 70 | const resetSelection = () => { 71 | handleFileChange(null); 72 | }; 73 | 74 | return ( 75 |
76 |
77 |

78 | 智能移除图片背景 79 |
一键完成 80 |

81 |

82 | 上传一张图片,即可移除背景并下载。 83 |

84 | 85 |
86 |
87 | 88 |
89 |
90 | 91 |
92 | 93 | {!originalImageDataUrl && !isLoading ? ( 94 | 95 | ) : ( 96 | <> 97 | {/* 加载中 */} 98 | {isLoading && } 99 | 100 | {/* 处理结果 */} 101 | {!isLoading && processedImage && originalImageDataUrl && ( 102 | 108 | )} 109 | 110 | {/* 处理失败 */} 111 | {!isLoading && !processedImage && originalImageDataUrl && ( 112 | 116 | )} 117 | 118 | )} 119 |
120 |
121 |
122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /src/components/ImageCompareResult.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { Download } from "lucide-react"; 3 | import { 4 | ReactCompareSlider, 5 | ReactCompareSliderImage, 6 | } from "react-compare-slider"; 7 | 8 | interface ImageCompareResultProps { 9 | originalImage: string; 10 | processedImage: string; 11 | fileName?: string; 12 | onReset: () => void; 13 | } 14 | 15 | export function ImageCompareResult({ 16 | originalImage, 17 | processedImage, 18 | fileName, 19 | onReset, 20 | }: ImageCompareResultProps) { 21 | const handleDownload = () => { 22 | if (processedImage) { 23 | const link = document.createElement("a"); 24 | link.href = processedImage; 25 | link.download = `bg-removed-${fileName || "image"}.png`; 26 | document.body.appendChild(link); 27 | link.click(); 28 | document.body.removeChild(link); 29 | } 30 | }; 31 | 32 | return ( 33 |
34 |
35 | 47 | } 48 | itemTwo={ 49 | 59 | } 60 | className="rounded-lg" 61 | style={{ 62 | height: "100%", 63 | width: "100%", 64 | }} 65 | position={50} 66 | /> 67 |
68 |
69 | 72 | 79 |
80 |
81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/components/ImageUploader.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { SparklesIcon } from "lucide-react"; 3 | import { useDropzone } from "react-dropzone"; 4 | 5 | interface ImageUploaderProps { 6 | onImageSelected: (file: File | null) => void; 7 | } 8 | 9 | export function ImageUploader({ onImageSelected }: ImageUploaderProps) { 10 | const { getRootProps, getInputProps, isDragActive, open } = useDropzone({ 11 | accept: { 12 | "image/*": [], 13 | }, 14 | multiple: false, 15 | onDrop: (acceptedFiles) => { 16 | if (acceptedFiles && acceptedFiles[0]) { 17 | onImageSelected(acceptedFiles[0]); 18 | } 19 | }, 20 | }); 21 | 22 | return ( 23 |
29 | 30 | 40 |

或拖放图片至此处

41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/components/ProcessingError.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | 3 | interface ProcessingErrorProps { 4 | imageUrl: string; 5 | onReset: () => void; 6 | } 7 | 8 | export function ProcessingError({ imageUrl, onReset }: ProcessingErrorProps) { 9 | return ( 10 |
11 |

处理失败

12 |
13 | 原始图片 18 |
19 |
20 | 23 |
24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/components/ProcessingLoader.tsx: -------------------------------------------------------------------------------- 1 | 2 | export function ProcessingLoader() { 3 | return ( 4 |
5 |
6 |
7 |
8 |

9 | 移除背景中... 10 |

11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/icons/github.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps } from "react"; 2 | 3 | export default function GithubIcon(props: ComponentProps<"svg">) { 4 | return ( 5 | 13 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/components/icons/sparkles.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps } from "react"; 2 | 3 | export default function SparklesIcon(props: ComponentProps<"svg">) { 4 | return ( 5 | 14 | 19 | 20 | 26 | 27 | 28 | 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/icons/x.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps } from "react"; 2 | 3 | export default function XIcon(props: ComponentProps<"svg">) { 4 | return ( 5 | 13 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/components/images/homepage-image-1.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps } from "react"; 2 | 3 | export default function HomepageImage1(props: ComponentProps<"svg">) { 4 | return ( 5 | 14 | 15 | 25 | 35 | 45 | 56 | 61 | 62 | 63 | 73 | 78 | 87 | 96 | 101 | 110 | 119 | 124 | 133 | 142 | 147 | 156 | 165 | 174 | 183 | 192 | 201 | 210 | 219 | 228 | 237 | 246 | 255 | 264 | 273 | 282 | 291 | 300 | 301 | 311 | 320 | 329 | 338 | 347 | 356 | 365 | 374 | 383 | 392 | 401 | 410 | 419 | 428 | 437 | 446 | 455 | 464 | 473 | 482 | 491 | 500 | 509 | 518 | 527 | 536 | 545 | 554 | 563 | 572 | 581 | 590 | 599 | 608 | 617 | 626 | 635 | 644 | 653 | 662 | 671 | 672 | 673 | 683 | 692 | 701 | 710 | 719 | 728 | 737 | 746 | 755 | 764 | 773 | 782 | 787 | 788 | 789 | 799 | 808 | 817 | 826 | 835 | 844 | 853 | 862 | 871 | 880 | 889 | 898 | 907 | 916 | 925 | 934 | 943 | 952 | 961 | 970 | 979 | 988 | 997 | 1006 | 1015 | 1024 | 1033 | 1042 | 1051 | 1060 | 1069 | 1078 | 1087 | 1096 | 1105 | 1114 | 1123 | 1132 | 1141 | 1150 | 1159 | 1168 | 1169 | 1170 | 1176 | 1181 | 1182 | 1183 | 1188 | 1189 | 1196 | 1202 | 1203 | 1208 | 1209 | 1210 | 1219 | 1220 | 1221 | 1225 | 1226 | 1235 | 1236 | 1237 | 1241 | 1242 | 1251 | 1252 | 1253 | 1257 | 1258 | 1264 | 1265 | 1266 | 1274 | 1275 | 1276 | 1277 | 1278 | 1279 | 1284 | 1285 | 1291 | 1292 | 1293 | ); 1294 | } 1295 | -------------------------------------------------------------------------------- /src/components/images/homepage-image-2.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps } from "react"; 2 | 3 | export default function HomepageImage2(props: ComponentProps<"svg">) { 4 | return ( 5 | 13 | 17 | 23 | 24 | 33 | 34 | 45 | 54 | 63 | 72 | 81 | 90 | 99 | 108 | 117 | 126 | 135 | 136 | 145 | 146 | 147 | 151 | 152 | 160 | 161 | 162 | 163 | 171 | 172 | 173 | 174 | 182 | 183 | 184 | 185 | 193 | 194 | 195 | 196 | 204 | 205 | 206 | 207 | 215 | 216 | 217 | 218 | 226 | 227 | 228 | 229 | 237 | 238 | 239 | 240 | 248 | 249 | 250 | 251 | 259 | 260 | 261 | 262 | 270 | 271 | 272 | 273 | 274 | 275 | ); 276 | } 277 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border text-base border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | "outline-active": 19 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 20 | secondary: 21 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 22 | ghost: "hover:bg-accent hover:text-accent-foreground", 23 | link: "text-primary underline-offset-4 hover:underline", 24 | }, 25 | size: { 26 | default: "h-9 px-4 py-2", 27 | sm: "h-8 rounded-md px-3 text-xs", 28 | lg: "h-10 rounded-md px-8", 29 | icon: "h-9 w-9", 30 | }, 31 | }, 32 | defaultVariants: { 33 | variant: "default", 34 | size: "default", 35 | }, 36 | }, 37 | ); 38 | 39 | export interface ButtonProps 40 | extends React.ButtonHTMLAttributes, 41 | VariantProps { 42 | asChild?: boolean; 43 | } 44 | 45 | const Button = React.forwardRef( 46 | ({ className, variant, size, asChild = false, ...props }, ref) => { 47 | const Comp = asChild ? Slot : "button"; 48 | return ( 49 | 54 | ); 55 | }, 56 | ); 57 | Button.displayName = "Button"; 58 | 59 | export { Button, buttonVariants }; 60 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as LabelPrimitive from "@radix-ui/react-label"; 5 | import { cva, type VariantProps } from "class-variance-authority"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 11 | ); 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )); 24 | Label.displayName = LabelPrimitive.Root.displayName; 25 | 26 | export { Label }; 27 | -------------------------------------------------------------------------------- /src/components/ui/logo.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | export default function Logo(props: React.ComponentProps<"div">) { 4 | return ( 5 |
6 | Logo 13 | remove.bg 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SelectPrimitive from "@radix-ui/react-select" 5 | import { Check, ChevronDown, ChevronUp } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Select = SelectPrimitive.Root 10 | 11 | const SelectGroup = SelectPrimitive.Group 12 | 13 | const SelectValue = SelectPrimitive.Value 14 | 15 | const SelectTrigger = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, children, ...props }, ref) => ( 19 | span]:line-clamp-1", 23 | className 24 | )} 25 | {...props} 26 | > 27 | {children} 28 | 29 | 30 | 31 | 32 | )) 33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 34 | 35 | const SelectScrollUpButton = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | 48 | 49 | )) 50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 51 | 52 | const SelectScrollDownButton = React.forwardRef< 53 | React.ElementRef, 54 | React.ComponentPropsWithoutRef 55 | >(({ className, ...props }, ref) => ( 56 | 64 | 65 | 66 | )) 67 | SelectScrollDownButton.displayName = 68 | SelectPrimitive.ScrollDownButton.displayName 69 | 70 | const SelectContent = React.forwardRef< 71 | React.ElementRef, 72 | React.ComponentPropsWithoutRef 73 | >(({ className, children, position = "popper", ...props }, ref) => ( 74 | 75 | 86 | 87 | 94 | {children} 95 | 96 | 97 | 98 | 99 | )) 100 | SelectContent.displayName = SelectPrimitive.Content.displayName 101 | 102 | const SelectLabel = React.forwardRef< 103 | React.ElementRef, 104 | React.ComponentPropsWithoutRef 105 | >(({ className, ...props }, ref) => ( 106 | 111 | )) 112 | SelectLabel.displayName = SelectPrimitive.Label.displayName 113 | 114 | const SelectItem = React.forwardRef< 115 | React.ElementRef, 116 | React.ComponentPropsWithoutRef 117 | >(({ className, children, ...props }, ref) => ( 118 | 126 | 127 | 128 | 129 | 130 | 131 | {children} 132 | 133 | )) 134 | SelectItem.displayName = SelectPrimitive.Item.displayName 135 | 136 | const SelectSeparator = React.forwardRef< 137 | React.ElementRef, 138 | React.ComponentPropsWithoutRef 139 | >(({ className, ...props }, ref) => ( 140 | 145 | )) 146 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 147 | 148 | export { 149 | Select, 150 | SelectGroup, 151 | SelectValue, 152 | SelectTrigger, 153 | SelectContent, 154 | SelectLabel, 155 | SelectItem, 156 | SelectSeparator, 157 | SelectScrollUpButton, 158 | SelectScrollDownButton, 159 | } 160 | -------------------------------------------------------------------------------- /src/components/ui/spinner.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | export default function Spinner({ 4 | loading = true, 5 | children, 6 | className = "", 7 | }: { 8 | loading?: boolean; 9 | children?: ReactNode; 10 | className?: string; 11 | }) { 12 | if (!loading) return children; 13 | 14 | const spinner = ( 15 | <> 16 | 28 | 29 | {Array.from(Array(8).keys()).map((i) => ( 30 | 38 | ))} 39 | 40 | 41 | ); 42 | 43 | if (!children) return spinner; 44 | 45 | return ( 46 | 47 | {children} 48 | 49 | 50 | {spinner} 51 | 52 | 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/components/ui/toast.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ToastPrimitives from "@radix-ui/react-toast" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | import { X } from "lucide-react" 7 | 8 | import { cn } from "@/lib/utils" 9 | 10 | const ToastProvider = ToastPrimitives.Provider 11 | 12 | const ToastViewport = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, ...props }, ref) => ( 16 | 24 | )) 25 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName 26 | 27 | const toastVariants = cva( 28 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", 29 | { 30 | variants: { 31 | variant: { 32 | default: "border bg-background text-foreground", 33 | destructive: 34 | "destructive group border-destructive bg-destructive text-destructive-foreground", 35 | }, 36 | }, 37 | defaultVariants: { 38 | variant: "default", 39 | }, 40 | } 41 | ) 42 | 43 | const Toast = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef & 46 | VariantProps 47 | >(({ className, variant, ...props }, ref) => { 48 | return ( 49 | 54 | ) 55 | }) 56 | Toast.displayName = ToastPrimitives.Root.displayName 57 | 58 | const ToastAction = React.forwardRef< 59 | React.ElementRef, 60 | React.ComponentPropsWithoutRef 61 | >(({ className, ...props }, ref) => ( 62 | 70 | )) 71 | ToastAction.displayName = ToastPrimitives.Action.displayName 72 | 73 | const ToastClose = React.forwardRef< 74 | React.ElementRef, 75 | React.ComponentPropsWithoutRef 76 | >(({ className, ...props }, ref) => ( 77 | 86 | 87 | 88 | )) 89 | ToastClose.displayName = ToastPrimitives.Close.displayName 90 | 91 | const ToastTitle = React.forwardRef< 92 | React.ElementRef, 93 | React.ComponentPropsWithoutRef 94 | >(({ className, ...props }, ref) => ( 95 | 100 | )) 101 | ToastTitle.displayName = ToastPrimitives.Title.displayName 102 | 103 | const ToastDescription = React.forwardRef< 104 | React.ElementRef, 105 | React.ComponentPropsWithoutRef 106 | >(({ className, ...props }, ref) => ( 107 | 112 | )) 113 | ToastDescription.displayName = ToastPrimitives.Description.displayName 114 | 115 | type ToastProps = React.ComponentPropsWithoutRef 116 | 117 | type ToastActionElement = React.ReactElement 118 | 119 | export { 120 | type ToastProps, 121 | type ToastActionElement, 122 | ToastProvider, 123 | ToastViewport, 124 | Toast, 125 | ToastTitle, 126 | ToastDescription, 127 | ToastClose, 128 | ToastAction, 129 | } 130 | -------------------------------------------------------------------------------- /src/components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useToast } from "@/hooks/use-toast" 4 | import { 5 | Toast, 6 | ToastClose, 7 | ToastDescription, 8 | ToastProvider, 9 | ToastTitle, 10 | ToastViewport, 11 | } from "@/components/ui/toast" 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast() 15 | 16 | return ( 17 | 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | 21 |
22 | {title && {title}} 23 | {description && ( 24 | {description} 25 | )} 26 |
27 | {action} 28 | 29 |
30 | ) 31 | })} 32 | 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/fonts/DingTalk_JinBuTi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellokaton/remove-bg/7a26368b03f76680fa43c0fd426dbbb795463ed3/src/fonts/DingTalk_JinBuTi.ttf -------------------------------------------------------------------------------- /src/fonts/DingTalk_Sans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellokaton/remove-bg/7a26368b03f76680fa43c0fd426dbbb795463ed3/src/fonts/DingTalk_Sans.ttf -------------------------------------------------------------------------------- /src/hooks/use-toast.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | // Inspired by react-hot-toast library 4 | import * as React from "react"; 5 | 6 | import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; 7 | 8 | const TOAST_LIMIT = 1; 9 | const TOAST_REMOVE_DELAY = 1000000; 10 | 11 | type ToasterToast = ToastProps & { 12 | id: string; 13 | title?: React.ReactNode; 14 | description?: React.ReactNode; 15 | action?: ToastActionElement; 16 | }; 17 | 18 | type ActionTypes = { 19 | ADD_TOAST: "ADD_TOAST"; 20 | UPDATE_TOAST: "UPDATE_TOAST"; 21 | DISMISS_TOAST: "DISMISS_TOAST"; 22 | REMOVE_TOAST: "REMOVE_TOAST"; 23 | }; 24 | 25 | let count = 0; 26 | 27 | function genId() { 28 | count = (count + 1) % Number.MAX_SAFE_INTEGER; 29 | return count.toString(); 30 | } 31 | 32 | type Action = 33 | | { 34 | type: ActionTypes["ADD_TOAST"]; 35 | toast: ToasterToast; 36 | } 37 | | { 38 | type: ActionTypes["UPDATE_TOAST"]; 39 | toast: Partial; 40 | } 41 | | { 42 | type: ActionTypes["DISMISS_TOAST"]; 43 | toastId?: ToasterToast["id"]; 44 | } 45 | | { 46 | type: ActionTypes["REMOVE_TOAST"]; 47 | toastId?: ToasterToast["id"]; 48 | }; 49 | 50 | interface State { 51 | toasts: ToasterToast[]; 52 | } 53 | 54 | const toastTimeouts = new Map>(); 55 | 56 | const addToRemoveQueue = (toastId: string) => { 57 | if (toastTimeouts.has(toastId)) { 58 | return; 59 | } 60 | 61 | const timeout = setTimeout(() => { 62 | toastTimeouts.delete(toastId); 63 | dispatch({ 64 | type: "REMOVE_TOAST", 65 | toastId: toastId, 66 | }); 67 | }, TOAST_REMOVE_DELAY); 68 | 69 | toastTimeouts.set(toastId, timeout); 70 | }; 71 | 72 | export const reducer = (state: State, action: Action): State => { 73 | switch (action.type) { 74 | case "ADD_TOAST": 75 | return { 76 | ...state, 77 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), 78 | }; 79 | 80 | case "UPDATE_TOAST": 81 | return { 82 | ...state, 83 | toasts: state.toasts.map((t) => 84 | t.id === action.toast.id ? { ...t, ...action.toast } : t, 85 | ), 86 | }; 87 | 88 | case "DISMISS_TOAST": { 89 | const { toastId } = action; 90 | 91 | // ! Side effects ! - This could be extracted into a dismissToast() action, 92 | // but I'll keep it here for simplicity 93 | if (toastId) { 94 | addToRemoveQueue(toastId); 95 | } else { 96 | state.toasts.forEach((toast) => { 97 | addToRemoveQueue(toast.id); 98 | }); 99 | } 100 | 101 | return { 102 | ...state, 103 | toasts: state.toasts.map((t) => 104 | t.id === toastId || toastId === undefined 105 | ? { 106 | ...t, 107 | open: false, 108 | } 109 | : t, 110 | ), 111 | }; 112 | } 113 | case "REMOVE_TOAST": 114 | if (action.toastId === undefined) { 115 | return { 116 | ...state, 117 | toasts: [], 118 | }; 119 | } 120 | return { 121 | ...state, 122 | toasts: state.toasts.filter((t) => t.id !== action.toastId), 123 | }; 124 | } 125 | }; 126 | 127 | const listeners: Array<(state: State) => void> = []; 128 | 129 | let memoryState: State = { toasts: [] }; 130 | 131 | function dispatch(action: Action) { 132 | memoryState = reducer(memoryState, action); 133 | listeners.forEach((listener) => { 134 | listener(memoryState); 135 | }); 136 | } 137 | 138 | type Toast = Omit; 139 | 140 | function toast({ ...props }: Toast) { 141 | const id = genId(); 142 | 143 | const update = (props: ToasterToast) => 144 | dispatch({ 145 | type: "UPDATE_TOAST", 146 | toast: { ...props, id }, 147 | }); 148 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); 149 | 150 | dispatch({ 151 | type: "ADD_TOAST", 152 | toast: { 153 | ...props, 154 | id, 155 | open: true, 156 | onOpenChange: (open) => { 157 | if (!open) dismiss(); 158 | }, 159 | }, 160 | }); 161 | 162 | return { 163 | id: id, 164 | dismiss, 165 | update, 166 | }; 167 | } 168 | 169 | function useToast() { 170 | const [state, setState] = React.useState(memoryState); 171 | 172 | React.useEffect(() => { 173 | listeners.push(setState); 174 | return () => { 175 | const index = listeners.indexOf(setState); 176 | if (index > -1) { 177 | listeners.splice(index, 1); 178 | } 179 | }; 180 | }, [state]); 181 | 182 | return { 183 | ...state, 184 | toast, 185 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), 186 | }; 187 | } 188 | 189 | export { useToast, toast }; 190 | -------------------------------------------------------------------------------- /src/lib/backgroundRemoval.ts: -------------------------------------------------------------------------------- 1 | import { removeBackground } from "@imgly/background-removal"; 2 | 3 | // 将文件转换为 Data URL 4 | export const fileToDataUrl = (file: File): Promise => { 5 | return new Promise((resolve, reject) => { 6 | const reader = new FileReader(); 7 | reader.onload = () => { 8 | if (typeof reader.result === "string") { 9 | resolve(reader.result); 10 | } else { 11 | reject(new Error("Failed to convert file to data URL")); 12 | } 13 | }; 14 | reader.onerror = () => reject(reader.error); 15 | reader.readAsDataURL(file); 16 | }); 17 | }; 18 | 19 | // 处理背景移除并返回处理后的URL 20 | export const processImageBackground = async (file: File): Promise => { 21 | // 配置背景移除选项 22 | const config = { 23 | debug: true, 24 | progress: (key: string, current: number, total: number) => { 25 | // 显示进度 26 | const percentage = Math.round((current / total) * 100); 27 | console.log(`处理进度: ${key} ${percentage}%`); 28 | }, 29 | }; 30 | 31 | // 调用背景移除库 32 | const processedBlob = await removeBackground(file, config); 33 | 34 | // 创建结果URL 35 | return URL.createObjectURL(processedBlob); 36 | }; 37 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | import tailwindcssAnimate from "tailwindcss-animate"; 3 | 4 | export default { 5 | darkMode: ["class"], 6 | content: [ 7 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 9 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 10 | ], 11 | theme: { 12 | extend: { 13 | fontFamily: { 14 | dingtalk: ["var(--font-dingtalk)"], 15 | }, 16 | borderRadius: { 17 | lg: "var(--radius)", 18 | md: "calc(var(--radius) - 2px)", 19 | sm: "calc(var(--radius) - 4px)", 20 | }, 21 | colors: { 22 | gray: { 23 | "100": "#F4F1EC", 24 | "200": "#FAF8F5", 25 | "250": "#E1E1E1", 26 | "300": "#B7B7B7", 27 | "500": "#9A9A9A", 28 | "900": "#3F3C3C", 29 | }, 30 | background: "hsl(var(--background))", 31 | foreground: "hsl(var(--foreground))", 32 | card: { 33 | DEFAULT: "hsl(var(--card))", 34 | foreground: "hsl(var(--card-foreground))", 35 | }, 36 | popover: { 37 | DEFAULT: "hsl(var(--popover))", 38 | foreground: "hsl(var(--popover-foreground))", 39 | }, 40 | primary: { 41 | DEFAULT: "hsl(var(--primary))", 42 | foreground: "hsl(var(--primary-foreground))", 43 | }, 44 | secondary: { 45 | DEFAULT: "hsl(var(--secondary))", 46 | foreground: "hsl(var(--secondary-foreground))", 47 | }, 48 | muted: { 49 | DEFAULT: "hsl(var(--muted))", 50 | foreground: "hsl(var(--muted-foreground))", 51 | }, 52 | accent: { 53 | DEFAULT: "hsl(var(--accent))", 54 | foreground: "hsl(var(--accent-foreground))", 55 | }, 56 | destructive: { 57 | DEFAULT: "hsl(var(--destructive))", 58 | foreground: "hsl(var(--destructive-foreground))", 59 | }, 60 | border: "hsl(var(--border))", 61 | input: "hsl(var(--input))", 62 | ring: "hsl(var(--ring))", 63 | chart: { 64 | "1": "hsl(var(--chart-1))", 65 | "2": "hsl(var(--chart-2))", 66 | "3": "hsl(var(--chart-3))", 67 | "4": "hsl(var(--chart-4))", 68 | "5": "hsl(var(--chart-5))", 69 | }, 70 | }, 71 | }, 72 | }, 73 | plugins: [tailwindcssAnimate, require("@tailwindcss/typography")], 74 | } satisfies Config; 75 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------