misonL commited on
Commit
9030f57
·
verified ·
1 Parent(s): c7d34c1

Deploy Next.js Docker Space from 5bdfbf8

Browse files
.env.agent.example ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agent API 访问令牌。正式部署请使用足够长的随机值。
2
+ AGENT_API_TOKEN=
3
+
4
+ # 状态后端:HF Space 免费层或临时演示用 memory,本地单实例用 sqlite,高并发状态用 postgres。多实例还需共享 generated-images。
5
+ AGENT_STATE_BACKEND=sqlite
6
+
7
+ # SQLite 默认状态文件。保留在 generated-images 下,方便 Docker volume 备份同时包含元数据。
8
+ AGENT_SQLITE_PATH=generated-images/.agent-state/agent.sqlite
9
+
10
+ # PostgreSQL 配置。Docker Compose 模式优先使用 secret file;手动部署也可以直接提供 AGENT_DATABASE_URL。
11
+ # GPT_IMAGE_POSTGRES_PASSWORD=<database-password>
12
+ # AGENT_DB_HOST=postgres
13
+ # AGENT_DB_PORT=5432
14
+ # AGENT_DB_NAME=gpt_image_playground
15
+ # AGENT_DB_USER=gpt_image
16
+ # AGENT_DB_PASSWORD=<database-password>
17
+ # AGENT_DATABASE_URL=postgres://gpt_image:<database-password>@postgres:5432/gpt_image_playground
18
+
19
+ # 请求租约和幂等记录保留时间。
20
+ AGENT_REQUEST_LEASE_MS=600000
21
+ AGENT_REQUEST_TTL_SECONDS=86400
22
+ AGENT_RECOVERY_INTERVAL_MS=30000
23
+
24
+ # 可选:供 OpenAPI 客户端使用的公开基础地址。
25
+ # AGENT_PUBLIC_BASE_URL=http://localhost:4783
26
+
27
+ # 现有应用配置仍然生效。
28
+ OPENAI_API_KEY=
29
+ OPENAI_API_BASE_URL=https://api.openai.com/v1
30
+ APP_PASSWORD=
.env.example ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 可选:如果不想在网页右上角“API 设置”里填写,可以在这里配置默认值。
2
+ # 使用时复制本文件为 .env.local,再填写真实内容。
3
+
4
+ # OpenAI 或 OpenAI 兼容接口 Key
5
+ OPENAI_API_KEY=
6
+
7
+ # 可选:OpenAI 兼容接口根地址,通常以 /v1 结尾。
8
+ # 示例:https://api.openai.com/v1
9
+ OPENAI_API_BASE_URL=
10
+
11
+ # 可选:服务端多渠道多 key 配置。配置任意 OPENAI_CHANNEL_N_* 后,会优先于 OPENAI_API_KEY。
12
+ # 页面右上角“API 设置”里手动填写的 API Key/API URL 仍然拥有最高优先级。
13
+ #
14
+ # 路由策略:
15
+ # - sticky:默认值。按请求来源稳定映射到同一个渠道 key,适合减少同一用户跳渠道。
16
+ # - round_robin:按请求顺序轮询所有渠道 key,适合简单均摊流量。
17
+ # - random:每次随机选择一个渠道 key,适合轻量分散请求。
18
+ # OPENAI_ROUTING_STRATEGY=sticky
19
+ #
20
+ # 渠道配置规则:
21
+ # - N 从 1 开始递增,例如 OPENAI_CHANNEL_1_*、OPENAI_CHANNEL_2_*。
22
+ # - ID 只用于日志排查,不会暴露 API Key。
23
+ # - BASE_URL 是 OpenAI 兼容接口根地址,通常以 /v1 结尾。
24
+ # - API_KEYS 支持一个或多个 key,多个 key 用英文逗号分隔。
25
+ # - FAILURE_COOLDOWN_MS 可选,覆盖该渠道失败后的冷却时间。
26
+ # - API Key 本身不要包含逗号。
27
+ #
28
+ # 示例:
29
+ # OPENAI_CHANNEL_1_ID=official
30
+ # OPENAI_CHANNEL_1_BASE_URL=https://api.openai.com/v1
31
+ # OPENAI_CHANNEL_1_API_KEYS=sk-key-1,sk-key-2
32
+ # OPENAI_CHANNEL_1_FAILURE_COOLDOWN_MS=60000
33
+ #
34
+ # OPENAI_CHANNEL_2_ID=backup
35
+ # OPENAI_CHANNEL_2_BASE_URL=https://your-compatible-api.example.com/v1
36
+ # OPENAI_CHANNEL_2_API_KEYS=sk-backup-1
37
+
38
+ # 可选:并发流式批处理。默认关闭。
39
+ # 开启后,流式模式下 n>1 会拆成多个 n=1 的独立流式任务,并按服务端 key 容量并发执行。
40
+ # 默认 sticky 路由按单个 credential 容量推荐并发;round_robin/random 才会使用完整 credential 池。
41
+ # key 出现鉴权、额度或限流类错误后会短暂冷却;渠道出现 5xx、CDN 超时或连接错误后会冷却整个渠道。
42
+ # ENABLE_STREAMING_BATCH=true
43
+ # OPENAI_MAX_STREAMS_PER_CREDENTIAL=1
44
+ # OPENAI_CHANNEL_FAILURE_COOLDOWN_MS=60000
45
+
46
+ # 可选:实验 Responses API 图片后端。默认关闭。
47
+ # 开启后仍需请求显式传入 imageBackend=responses,并单独配置 /responses 顶层模型。
48
+ # 表单 model 只作为 image_generation 工具模型,不会复用为 /responses 顶层模型。
49
+ # ENABLE_RESPONSES_IMAGE_BACKEND=true
50
+ # OPENAI_RESPONSES_API_MODEL=
51
+
52
+ # 可选:给网页加一个访问密码。公网部署时建议一定要设置。
53
+ APP_PASSWORD=
54
+
55
+ # 可选:给 /api/agent/* 使用的 Bearer token。公网或内网共享部署时建议设置。
56
+ # AGENT_API_TOKEN=
57
+
58
+ # 可选:Agent 状态后端。memory 适合临时演示,sqlite 适合单实例本地部署,postgres 适合状态并发;多实例还需共享 generated-images。
59
+ # AGENT_STATE_BACKEND=sqlite
60
+ # AGENT_SQLITE_PATH=generated-images/.agent-state/agent.sqlite
61
+ # AGENT_DATABASE_URL=postgres://gpt_image:<database-password>@localhost:5432/gpt_image_playground
62
+ # AGENT_REQUEST_LEASE_MS=600000
63
+ # AGENT_REQUEST_TTL_SECONDS=86400
64
+
65
+ # 可选:服务端日志等级。可选 debug、info、warn、error。生产环境默认 warn,其他环境默认 info。
66
+ # APP_LOG_LEVEL=warn
67
+
68
+ # 本地运行一般不用设置;部署到 Vercel 时建议使用 indexeddb。
69
+ # NEXT_PUBLIC_IMAGE_STORAGE_MODE=indexeddb
.prettierrc.json CHANGED
@@ -1,22 +1,22 @@
1
- {
2
- "arrowParens": "always",
3
- "bracketSpacing": true,
4
- "htmlWhitespaceSensitivity": "css",
5
- "insertPragma": false,
6
- "bracketSameLine": true,
7
- "jsxSingleQuote": true,
8
- "printWidth": 120,
9
- "proseWrap": "preserve",
10
- "quoteProps": "as-needed",
11
- "requirePragma": false,
12
- "semi": true,
13
- "singleQuote": true,
14
- "tabWidth": 4,
15
- "trailingComma": "none",
16
- "useTabs": false,
17
- "endOfLine": "auto",
18
- "plugins": [
19
- "@trivago/prettier-plugin-sort-imports",
20
- "prettier-plugin-tailwindcss"
21
- ]
22
  }
 
1
+ {
2
+ "arrowParens": "always",
3
+ "bracketSpacing": true,
4
+ "htmlWhitespaceSensitivity": "css",
5
+ "insertPragma": false,
6
+ "bracketSameLine": true,
7
+ "jsxSingleQuote": true,
8
+ "printWidth": 120,
9
+ "proseWrap": "preserve",
10
+ "quoteProps": "as-needed",
11
+ "requirePragma": false,
12
+ "semi": true,
13
+ "singleQuote": true,
14
+ "tabWidth": 4,
15
+ "trailingComma": "none",
16
+ "useTabs": false,
17
+ "endOfLine": "auto",
18
+ "plugins": [
19
+ "@trivago/prettier-plugin-sort-imports",
20
+ "prettier-plugin-tailwindcss"
21
+ ]
22
  }
LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Aljosa Asanovic
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.
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Aljosa Asanovic
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.
components.json CHANGED
@@ -1,21 +1,21 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema.json",
3
- "style": "new-york",
4
- "rsc": true,
5
- "tsx": true,
6
- "tailwind": {
7
- "config": "",
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
  }
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
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
  }
postcss.config.mjs CHANGED
@@ -1,5 +1,5 @@
1
- const config = {
2
- plugins: ["@tailwindcss/postcss"],
3
- };
4
-
5
- export default config;
 
1
+ const config = {
2
+ plugins: ["@tailwindcss/postcss"],
3
+ };
4
+
5
+ export default config;
public/favicon.svg CHANGED
src/app/api/auth-status/route.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { NextResponse } from 'next/server';
2
-
3
- export async function GET() {
4
- const appPasswordSet = !!process.env.APP_PASSWORD;
5
- return NextResponse.json({ passwordRequired: appPasswordSet });
6
- }
 
1
+ import { NextResponse } from 'next/server';
2
+
3
+ export async function GET() {
4
+ const appPasswordSet = !!process.env.APP_PASSWORD;
5
+ return NextResponse.json({ passwordRequired: appPasswordSet });
6
+ }
src/components/theme-provider.tsx CHANGED
@@ -1,8 +1,8 @@
1
- 'use client';
2
-
3
- import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from 'next-themes';
4
- import * as React from 'react';
5
-
6
- export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
7
- return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
8
- }
 
1
+ 'use client';
2
+
3
+ import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from 'next-themes';
4
+ import * as React from 'react';
5
+
6
+ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
7
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
8
+ }
src/components/ui/alert.tsx CHANGED
@@ -1,66 +1,66 @@
1
- import * as React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
3
-
4
- import { cn } from "@/lib/utils"
5
-
6
- const alertVariants = cva(
7
- "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
8
- {
9
- variants: {
10
- variant: {
11
- default: "bg-card text-card-foreground",
12
- destructive:
13
- "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
14
- },
15
- },
16
- defaultVariants: {
17
- variant: "default",
18
- },
19
- }
20
- )
21
-
22
- function Alert({
23
- className,
24
- variant,
25
- ...props
26
- }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
27
- return (
28
- <div
29
- data-slot="alert"
30
- role="alert"
31
- className={cn(alertVariants({ variant }), className)}
32
- {...props}
33
- />
34
- )
35
- }
36
-
37
- function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
- return (
39
- <div
40
- data-slot="alert-title"
41
- className={cn(
42
- "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
43
- className
44
- )}
45
- {...props}
46
- />
47
- )
48
- }
49
-
50
- function AlertDescription({
51
- className,
52
- ...props
53
- }: React.ComponentProps<"div">) {
54
- return (
55
- <div
56
- data-slot="alert-description"
57
- className={cn(
58
- "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
59
- className
60
- )}
61
- {...props}
62
- />
63
- )
64
- }
65
-
66
- export { Alert, AlertTitle, AlertDescription }
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-card text-card-foreground",
12
+ destructive:
13
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ function Alert({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
27
+ return (
28
+ <div
29
+ data-slot="alert"
30
+ role="alert"
31
+ className={cn(alertVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ )
35
+ }
36
+
37
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
+ return (
39
+ <div
40
+ data-slot="alert-title"
41
+ className={cn(
42
+ "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ )
48
+ }
49
+
50
+ function AlertDescription({
51
+ className,
52
+ ...props
53
+ }: React.ComponentProps<"div">) {
54
+ return (
55
+ <div
56
+ data-slot="alert-description"
57
+ className={cn(
58
+ "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ export { Alert, AlertTitle, AlertDescription }
src/components/ui/card.tsx CHANGED
@@ -1,92 +1,92 @@
1
- import * as React from "react"
2
-
3
- import { cn } from "@/lib/utils"
4
-
5
- function Card({ className, ...props }: React.ComponentProps<"div">) {
6
- return (
7
- <div
8
- data-slot="card"
9
- className={cn(
10
- "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
11
- className
12
- )}
13
- {...props}
14
- />
15
- )
16
- }
17
-
18
- function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
- return (
20
- <div
21
- data-slot="card-header"
22
- className={cn(
23
- "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
24
- className
25
- )}
26
- {...props}
27
- />
28
- )
29
- }
30
-
31
- function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32
- return (
33
- <div
34
- data-slot="card-title"
35
- className={cn("leading-none font-semibold", className)}
36
- {...props}
37
- />
38
- )
39
- }
40
-
41
- function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42
- return (
43
- <div
44
- data-slot="card-description"
45
- className={cn("text-muted-foreground text-sm", className)}
46
- {...props}
47
- />
48
- )
49
- }
50
-
51
- function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52
- return (
53
- <div
54
- data-slot="card-action"
55
- className={cn(
56
- "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
57
- className
58
- )}
59
- {...props}
60
- />
61
- )
62
- }
63
-
64
- function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65
- return (
66
- <div
67
- data-slot="card-content"
68
- className={cn("px-6", className)}
69
- {...props}
70
- />
71
- )
72
- }
73
-
74
- function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75
- return (
76
- <div
77
- data-slot="card-footer"
78
- className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
79
- {...props}
80
- />
81
- )
82
- }
83
-
84
- export {
85
- Card,
86
- CardHeader,
87
- CardFooter,
88
- CardTitle,
89
- CardAction,
90
- CardDescription,
91
- CardContent,
92
- }
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn(
23
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
24
+ className
25
+ )}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32
+ return (
33
+ <div
34
+ data-slot="card-title"
35
+ className={cn("leading-none font-semibold", className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="card-description"
45
+ className={cn("text-muted-foreground text-sm", className)}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52
+ return (
53
+ <div
54
+ data-slot="card-action"
55
+ className={cn(
56
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
57
+ className
58
+ )}
59
+ {...props}
60
+ />
61
+ )
62
+ }
63
+
64
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65
+ return (
66
+ <div
67
+ data-slot="card-content"
68
+ className={cn("px-6", className)}
69
+ {...props}
70
+ />
71
+ )
72
+ }
73
+
74
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75
+ return (
76
+ <div
77
+ data-slot="card-footer"
78
+ className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
79
+ {...props}
80
+ />
81
+ )
82
+ }
83
+
84
+ export {
85
+ Card,
86
+ CardHeader,
87
+ CardFooter,
88
+ CardTitle,
89
+ CardAction,
90
+ CardDescription,
91
+ CardContent,
92
+ }
src/components/ui/label.tsx CHANGED
@@ -1,24 +1,24 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import * as LabelPrimitive from "@radix-ui/react-label"
5
-
6
- import { cn } from "@/lib/utils"
7
-
8
- function Label({
9
- className,
10
- ...props
11
- }: React.ComponentProps<typeof LabelPrimitive.Root>) {
12
- return (
13
- <LabelPrimitive.Root
14
- data-slot="label"
15
- className={cn(
16
- "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
17
- className
18
- )}
19
- {...props}
20
- />
21
- )
22
- }
23
-
24
- export { Label }
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Label({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
12
+ return (
13
+ <LabelPrimitive.Root
14
+ data-slot="label"
15
+ className={cn(
16
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ export { Label }
src/components/ui/textarea.tsx CHANGED
@@ -1,18 +1,18 @@
1
- import * as React from "react"
2
-
3
- import { cn } from "@/lib/utils"
4
-
5
- function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
- return (
7
- <textarea
8
- data-slot="textarea"
9
- className={cn(
10
- "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
11
- className
12
- )}
13
- {...props}
14
- />
15
- )
16
- }
17
-
18
- export { Textarea }
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export { Textarea }
src/components/ui/toggle-group.tsx CHANGED
@@ -1,73 +1,73 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
5
- import { type VariantProps } from "class-variance-authority"
6
-
7
- import { cn } from "@/lib/utils"
8
- import { toggleVariants } from "@/components/ui/toggle"
9
-
10
- const ToggleGroupContext = React.createContext<
11
- VariantProps<typeof toggleVariants>
12
- >({
13
- size: "default",
14
- variant: "default",
15
- })
16
-
17
- function ToggleGroup({
18
- className,
19
- variant,
20
- size,
21
- children,
22
- ...props
23
- }: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
24
- VariantProps<typeof toggleVariants>) {
25
- return (
26
- <ToggleGroupPrimitive.Root
27
- data-slot="toggle-group"
28
- data-variant={variant}
29
- data-size={size}
30
- className={cn(
31
- "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
32
- className
33
- )}
34
- {...props}
35
- >
36
- <ToggleGroupContext.Provider value={{ variant, size }}>
37
- {children}
38
- </ToggleGroupContext.Provider>
39
- </ToggleGroupPrimitive.Root>
40
- )
41
- }
42
-
43
- function ToggleGroupItem({
44
- className,
45
- children,
46
- variant,
47
- size,
48
- ...props
49
- }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
50
- VariantProps<typeof toggleVariants>) {
51
- const context = React.useContext(ToggleGroupContext)
52
-
53
- return (
54
- <ToggleGroupPrimitive.Item
55
- data-slot="toggle-group-item"
56
- data-variant={context.variant || variant}
57
- data-size={context.size || size}
58
- className={cn(
59
- toggleVariants({
60
- variant: context.variant || variant,
61
- size: context.size || size,
62
- }),
63
- "min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
64
- className
65
- )}
66
- {...props}
67
- >
68
- {children}
69
- </ToggleGroupPrimitive.Item>
70
- )
71
- }
72
-
73
- export { ToggleGroup, ToggleGroupItem }
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
5
+ import { type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+ import { toggleVariants } from "@/components/ui/toggle"
9
+
10
+ const ToggleGroupContext = React.createContext<
11
+ VariantProps<typeof toggleVariants>
12
+ >({
13
+ size: "default",
14
+ variant: "default",
15
+ })
16
+
17
+ function ToggleGroup({
18
+ className,
19
+ variant,
20
+ size,
21
+ children,
22
+ ...props
23
+ }: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
24
+ VariantProps<typeof toggleVariants>) {
25
+ return (
26
+ <ToggleGroupPrimitive.Root
27
+ data-slot="toggle-group"
28
+ data-variant={variant}
29
+ data-size={size}
30
+ className={cn(
31
+ "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ <ToggleGroupContext.Provider value={{ variant, size }}>
37
+ {children}
38
+ </ToggleGroupContext.Provider>
39
+ </ToggleGroupPrimitive.Root>
40
+ )
41
+ }
42
+
43
+ function ToggleGroupItem({
44
+ className,
45
+ children,
46
+ variant,
47
+ size,
48
+ ...props
49
+ }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
50
+ VariantProps<typeof toggleVariants>) {
51
+ const context = React.useContext(ToggleGroupContext)
52
+
53
+ return (
54
+ <ToggleGroupPrimitive.Item
55
+ data-slot="toggle-group-item"
56
+ data-variant={context.variant || variant}
57
+ data-size={context.size || size}
58
+ className={cn(
59
+ toggleVariants({
60
+ variant: context.variant || variant,
61
+ size: context.size || size,
62
+ }),
63
+ "min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
64
+ className
65
+ )}
66
+ {...props}
67
+ >
68
+ {children}
69
+ </ToggleGroupPrimitive.Item>
70
+ )
71
+ }
72
+
73
+ export { ToggleGroup, ToggleGroupItem }
src/lib/db.ts CHANGED
@@ -1,22 +1,22 @@
1
- import Dexie, { type EntityTable } from 'dexie';
2
-
3
- export interface ImageRecord {
4
- filename: string;
5
- blob: Blob;
6
- }
7
-
8
- export class ImageDB extends Dexie {
9
- images!: EntityTable<ImageRecord, 'filename'>;
10
-
11
- constructor() {
12
- super('ImageDB');
13
-
14
- this.version(1).stores({
15
- images: '&filename'
16
- });
17
-
18
- this.images = this.table('images');
19
- }
20
- }
21
-
22
- export const db = new ImageDB();
 
1
+ import Dexie, { type EntityTable } from 'dexie';
2
+
3
+ export interface ImageRecord {
4
+ filename: string;
5
+ blob: Blob;
6
+ }
7
+
8
+ export class ImageDB extends Dexie {
9
+ images!: EntityTable<ImageRecord, 'filename'>;
10
+
11
+ constructor() {
12
+ super('ImageDB');
13
+
14
+ this.version(1).stores({
15
+ images: '&filename'
16
+ });
17
+
18
+ this.images = this.table('images');
19
+ }
20
+ }
21
+
22
+ export const db = new ImageDB();
src/lib/utils.ts CHANGED
@@ -1,6 +1,6 @@
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
- }
 
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
+ }
tsconfig.json CHANGED
@@ -1,40 +1,40 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2017",
4
- "lib": [
5
- "dom",
6
- "dom.iterable",
7
- "esnext"
8
- ],
9
- "allowJs": true,
10
- "skipLibCheck": true,
11
- "strict": true,
12
- "noEmit": true,
13
- "esModuleInterop": true,
14
- "module": "esnext",
15
- "moduleResolution": "bundler",
16
- "resolveJsonModule": true,
17
- "isolatedModules": true,
18
- "jsx": "react-jsx",
19
- "incremental": true,
20
- "plugins": [
21
- {
22
- "name": "next"
23
- }
24
- ],
25
- "paths": {
26
- "@/*": [
27
- "./src/*"
28
- ]
29
- }
30
- },
31
- "include": [
32
- "next-env.d.ts",
33
- "**/*.ts",
34
- "**/*.tsx",
35
- ".next/types/**/*.ts",
36
- ".next/dev/types/**/*.ts"
37
- ],
38
  "exclude": [
39
  "node_modules",
40
  "dist"
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "noEmit": true,
13
+ "esModuleInterop": true,
14
+ "module": "esnext",
15
+ "moduleResolution": "bundler",
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true,
18
+ "jsx": "react-jsx",
19
+ "incremental": true,
20
+ "plugins": [
21
+ {
22
+ "name": "next"
23
+ }
24
+ ],
25
+ "paths": {
26
+ "@/*": [
27
+ "./src/*"
28
+ ]
29
+ }
30
+ },
31
+ "include": [
32
+ "next-env.d.ts",
33
+ "**/*.ts",
34
+ "**/*.tsx",
35
+ ".next/types/**/*.ts",
36
+ ".next/dev/types/**/*.ts"
37
+ ],
38
  "exclude": [
39
  "node_modules",
40
  "dist"