@weirdfingers/baseboards 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +887 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/templates/README.md +120 -0
- package/templates/api/.env.example +62 -0
- package/templates/api/Dockerfile +32 -0
- package/templates/api/README.md +132 -0
- package/templates/api/alembic/env.py +106 -0
- package/templates/api/alembic/script.py.mako +28 -0
- package/templates/api/alembic/versions/20250101_000000_initial_schema.py +448 -0
- package/templates/api/alembic/versions/20251022_174729_remove_provider_name_from_generations.py +71 -0
- package/templates/api/alembic/versions/20251023_165852_switch_to_declarative_base_and_mapping.py +411 -0
- package/templates/api/alembic/versions/2025925_62735_add_seed_data_for_default_tenant.py +85 -0
- package/templates/api/alembic.ini +36 -0
- package/templates/api/config/generators.yaml +25 -0
- package/templates/api/config/storage_config.yaml +26 -0
- package/templates/api/docs/ADDING_GENERATORS.md +409 -0
- package/templates/api/docs/GENERATORS_API.md +502 -0
- package/templates/api/docs/MIGRATIONS.md +472 -0
- package/templates/api/docs/storage_providers.md +337 -0
- package/templates/api/pyproject.toml +165 -0
- package/templates/api/src/boards/__init__.py +10 -0
- package/templates/api/src/boards/api/app.py +171 -0
- package/templates/api/src/boards/api/auth.py +75 -0
- package/templates/api/src/boards/api/endpoints/__init__.py +3 -0
- package/templates/api/src/boards/api/endpoints/jobs.py +76 -0
- package/templates/api/src/boards/api/endpoints/setup.py +505 -0
- package/templates/api/src/boards/api/endpoints/sse.py +129 -0
- package/templates/api/src/boards/api/endpoints/storage.py +74 -0
- package/templates/api/src/boards/api/endpoints/tenant_registration.py +296 -0
- package/templates/api/src/boards/api/endpoints/webhooks.py +13 -0
- package/templates/api/src/boards/auth/__init__.py +15 -0
- package/templates/api/src/boards/auth/adapters/__init__.py +20 -0
- package/templates/api/src/boards/auth/adapters/auth0.py +220 -0
- package/templates/api/src/boards/auth/adapters/base.py +73 -0
- package/templates/api/src/boards/auth/adapters/clerk.py +172 -0
- package/templates/api/src/boards/auth/adapters/jwt.py +122 -0
- package/templates/api/src/boards/auth/adapters/none.py +102 -0
- package/templates/api/src/boards/auth/adapters/oidc.py +284 -0
- package/templates/api/src/boards/auth/adapters/supabase.py +110 -0
- package/templates/api/src/boards/auth/context.py +35 -0
- package/templates/api/src/boards/auth/factory.py +115 -0
- package/templates/api/src/boards/auth/middleware.py +221 -0
- package/templates/api/src/boards/auth/provisioning.py +129 -0
- package/templates/api/src/boards/auth/tenant_extraction.py +278 -0
- package/templates/api/src/boards/cli.py +354 -0
- package/templates/api/src/boards/config.py +116 -0
- package/templates/api/src/boards/database/__init__.py +7 -0
- package/templates/api/src/boards/database/cli.py +110 -0
- package/templates/api/src/boards/database/connection.py +252 -0
- package/templates/api/src/boards/database/models.py +19 -0
- package/templates/api/src/boards/database/seed_data.py +182 -0
- package/templates/api/src/boards/dbmodels/__init__.py +455 -0
- package/templates/api/src/boards/generators/__init__.py +57 -0
- package/templates/api/src/boards/generators/artifacts.py +53 -0
- package/templates/api/src/boards/generators/base.py +140 -0
- package/templates/api/src/boards/generators/implementations/__init__.py +12 -0
- package/templates/api/src/boards/generators/implementations/audio/__init__.py +3 -0
- package/templates/api/src/boards/generators/implementations/audio/whisper.py +66 -0
- package/templates/api/src/boards/generators/implementations/image/__init__.py +3 -0
- package/templates/api/src/boards/generators/implementations/image/dalle3.py +93 -0
- package/templates/api/src/boards/generators/implementations/image/flux_pro.py +85 -0
- package/templates/api/src/boards/generators/implementations/video/__init__.py +3 -0
- package/templates/api/src/boards/generators/implementations/video/lipsync.py +70 -0
- package/templates/api/src/boards/generators/loader.py +253 -0
- package/templates/api/src/boards/generators/registry.py +114 -0
- package/templates/api/src/boards/generators/resolution.py +515 -0
- package/templates/api/src/boards/generators/testmods/class_gen.py +34 -0
- package/templates/api/src/boards/generators/testmods/import_side_effect.py +35 -0
- package/templates/api/src/boards/graphql/__init__.py +7 -0
- package/templates/api/src/boards/graphql/access_control.py +136 -0
- package/templates/api/src/boards/graphql/mutations/root.py +136 -0
- package/templates/api/src/boards/graphql/queries/root.py +116 -0
- package/templates/api/src/boards/graphql/resolvers/__init__.py +8 -0
- package/templates/api/src/boards/graphql/resolvers/auth.py +12 -0
- package/templates/api/src/boards/graphql/resolvers/board.py +1055 -0
- package/templates/api/src/boards/graphql/resolvers/generation.py +889 -0
- package/templates/api/src/boards/graphql/resolvers/generator.py +50 -0
- package/templates/api/src/boards/graphql/resolvers/user.py +25 -0
- package/templates/api/src/boards/graphql/schema.py +81 -0
- package/templates/api/src/boards/graphql/types/board.py +102 -0
- package/templates/api/src/boards/graphql/types/generation.py +130 -0
- package/templates/api/src/boards/graphql/types/generator.py +17 -0
- package/templates/api/src/boards/graphql/types/user.py +47 -0
- package/templates/api/src/boards/jobs/repository.py +104 -0
- package/templates/api/src/boards/logging.py +195 -0
- package/templates/api/src/boards/middleware.py +339 -0
- package/templates/api/src/boards/progress/__init__.py +4 -0
- package/templates/api/src/boards/progress/models.py +25 -0
- package/templates/api/src/boards/progress/publisher.py +64 -0
- package/templates/api/src/boards/py.typed +0 -0
- package/templates/api/src/boards/redis_pool.py +118 -0
- package/templates/api/src/boards/storage/__init__.py +52 -0
- package/templates/api/src/boards/storage/base.py +363 -0
- package/templates/api/src/boards/storage/config.py +187 -0
- package/templates/api/src/boards/storage/factory.py +278 -0
- package/templates/api/src/boards/storage/implementations/__init__.py +27 -0
- package/templates/api/src/boards/storage/implementations/gcs.py +340 -0
- package/templates/api/src/boards/storage/implementations/local.py +201 -0
- package/templates/api/src/boards/storage/implementations/s3.py +294 -0
- package/templates/api/src/boards/storage/implementations/supabase.py +218 -0
- package/templates/api/src/boards/tenant_isolation.py +446 -0
- package/templates/api/src/boards/validation.py +262 -0
- package/templates/api/src/boards/workers/__init__.py +1 -0
- package/templates/api/src/boards/workers/actors.py +201 -0
- package/templates/api/src/boards/workers/cli.py +125 -0
- package/templates/api/src/boards/workers/context.py +188 -0
- package/templates/api/src/boards/workers/middleware.py +58 -0
- package/templates/api/src/py.typed +0 -0
- package/templates/compose.dev.yaml +39 -0
- package/templates/compose.yaml +109 -0
- package/templates/docker/env.example +23 -0
- package/templates/web/.env.example +28 -0
- package/templates/web/Dockerfile +51 -0
- package/templates/web/components.json +22 -0
- package/templates/web/imageLoader.js +18 -0
- package/templates/web/next-env.d.ts +5 -0
- package/templates/web/next.config.js +36 -0
- package/templates/web/package.json +37 -0
- package/templates/web/postcss.config.mjs +7 -0
- package/templates/web/public/favicon.ico +0 -0
- package/templates/web/src/app/boards/[boardId]/page.tsx +232 -0
- package/templates/web/src/app/globals.css +120 -0
- package/templates/web/src/app/layout.tsx +21 -0
- package/templates/web/src/app/page.tsx +35 -0
- package/templates/web/src/app/providers.tsx +18 -0
- package/templates/web/src/components/boards/ArtifactInputSlots.tsx +142 -0
- package/templates/web/src/components/boards/ArtifactPreview.tsx +125 -0
- package/templates/web/src/components/boards/GenerationGrid.tsx +45 -0
- package/templates/web/src/components/boards/GenerationInput.tsx +251 -0
- package/templates/web/src/components/boards/GeneratorSelector.tsx +89 -0
- package/templates/web/src/components/header.tsx +30 -0
- package/templates/web/src/components/ui/button.tsx +58 -0
- package/templates/web/src/components/ui/card.tsx +92 -0
- package/templates/web/src/components/ui/navigation-menu.tsx +168 -0
- package/templates/web/src/lib/utils.ts +6 -0
- package/templates/web/tsconfig.json +47 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Docker Compose Configuration for Baseboards
|
|
2
|
+
name: ${PROJECT_NAME:-baseboards}
|
|
3
|
+
|
|
4
|
+
services:
|
|
5
|
+
db:
|
|
6
|
+
image: postgres:16
|
|
7
|
+
env_file: docker/.env
|
|
8
|
+
volumes:
|
|
9
|
+
- db-data:/var/lib/postgresql/data
|
|
10
|
+
healthcheck:
|
|
11
|
+
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
|
|
12
|
+
interval: 5s
|
|
13
|
+
timeout: 5s
|
|
14
|
+
retries: 20
|
|
15
|
+
networks:
|
|
16
|
+
- internal
|
|
17
|
+
|
|
18
|
+
cache:
|
|
19
|
+
image: redis:7
|
|
20
|
+
command: ["redis-server", "--appendonly", "yes"]
|
|
21
|
+
healthcheck:
|
|
22
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
23
|
+
interval: 5s
|
|
24
|
+
timeout: 3s
|
|
25
|
+
retries: 20
|
|
26
|
+
networks:
|
|
27
|
+
- internal
|
|
28
|
+
|
|
29
|
+
api:
|
|
30
|
+
build:
|
|
31
|
+
context: ./api
|
|
32
|
+
dockerfile: Dockerfile
|
|
33
|
+
image: baseboards-api:local
|
|
34
|
+
env_file:
|
|
35
|
+
- docker/.env
|
|
36
|
+
- api/.env
|
|
37
|
+
depends_on:
|
|
38
|
+
db:
|
|
39
|
+
condition: service_healthy
|
|
40
|
+
cache:
|
|
41
|
+
condition: service_healthy
|
|
42
|
+
ports:
|
|
43
|
+
- "${API_PORT:-8800}:8800"
|
|
44
|
+
healthcheck:
|
|
45
|
+
test: ["CMD", "curl", "-f", "http://localhost:8800/health"]
|
|
46
|
+
interval: 30s
|
|
47
|
+
timeout: 3s
|
|
48
|
+
retries: 50
|
|
49
|
+
networks:
|
|
50
|
+
- internal
|
|
51
|
+
|
|
52
|
+
worker:
|
|
53
|
+
build:
|
|
54
|
+
context: ./api
|
|
55
|
+
dockerfile: Dockerfile
|
|
56
|
+
image: baseboards-api:local
|
|
57
|
+
command:
|
|
58
|
+
[
|
|
59
|
+
"boards-worker",
|
|
60
|
+
"--log-level",
|
|
61
|
+
"debug",
|
|
62
|
+
"--processes",
|
|
63
|
+
"1",
|
|
64
|
+
"--threads",
|
|
65
|
+
"1",
|
|
66
|
+
]
|
|
67
|
+
env_file:
|
|
68
|
+
- docker/.env
|
|
69
|
+
- api/.env
|
|
70
|
+
depends_on:
|
|
71
|
+
db:
|
|
72
|
+
condition: service_healthy
|
|
73
|
+
cache:
|
|
74
|
+
condition: service_healthy
|
|
75
|
+
networks:
|
|
76
|
+
- internal
|
|
77
|
+
|
|
78
|
+
web:
|
|
79
|
+
build:
|
|
80
|
+
context: ./web
|
|
81
|
+
dockerfile: Dockerfile
|
|
82
|
+
args:
|
|
83
|
+
- NEXT_PUBLIC_API_URL=http://localhost:${API_PORT:-8800}
|
|
84
|
+
- NEXT_PUBLIC_GRAPHQL_URL=http://localhost:${API_PORT:-8800}/graphql
|
|
85
|
+
image: baseboards-web:local
|
|
86
|
+
env_file: web/.env
|
|
87
|
+
environment:
|
|
88
|
+
- NEXT_PUBLIC_API_URL=http://localhost:${API_PORT:-8800}
|
|
89
|
+
- NEXT_PUBLIC_GRAPHQL_URL=http://localhost:${API_PORT:-8800}/graphql
|
|
90
|
+
- INTERNAL_API_URL=http://api:8800
|
|
91
|
+
depends_on:
|
|
92
|
+
api:
|
|
93
|
+
condition: service_healthy
|
|
94
|
+
ports:
|
|
95
|
+
- "${WEB_PORT:-3300}:3000"
|
|
96
|
+
healthcheck:
|
|
97
|
+
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
|
|
98
|
+
interval: 30s
|
|
99
|
+
timeout: 3s
|
|
100
|
+
retries: 50
|
|
101
|
+
networks:
|
|
102
|
+
- internal
|
|
103
|
+
|
|
104
|
+
networks:
|
|
105
|
+
internal:
|
|
106
|
+
driver: bridge
|
|
107
|
+
|
|
108
|
+
volumes:
|
|
109
|
+
db-data:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Docker Compose Environment Variables
|
|
2
|
+
# Generated by CLI - DO NOT EDIT MANUALLY
|
|
3
|
+
|
|
4
|
+
# Project configuration
|
|
5
|
+
PROJECT_NAME=baseboards
|
|
6
|
+
|
|
7
|
+
# Service ports
|
|
8
|
+
WEB_PORT=3300
|
|
9
|
+
API_PORT=8800
|
|
10
|
+
|
|
11
|
+
# PostgreSQL configuration
|
|
12
|
+
POSTGRES_USER=baseboards
|
|
13
|
+
POSTGRES_PASSWORD=REPLACE_WITH_GENERATED_PASSWORD
|
|
14
|
+
POSTGRES_DB=baseboards
|
|
15
|
+
|
|
16
|
+
# Database URL (for API container)
|
|
17
|
+
BOARDS_DATABASE_URL=postgresql://baseboards:REPLACE_WITH_GENERATED_PASSWORD@db:5432/baseboards
|
|
18
|
+
|
|
19
|
+
# Redis URL (for API container)
|
|
20
|
+
BOARDS_REDIS_URL=redis://cache:6379/0
|
|
21
|
+
|
|
22
|
+
# Image version (matches CLI version)
|
|
23
|
+
VERSION=latest
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Frontend Environment Variables
|
|
2
|
+
|
|
3
|
+
# API Configuration
|
|
4
|
+
NEXT_PUBLIC_API_URL=http://localhost:8800
|
|
5
|
+
NEXT_PUBLIC_GRAPHQL_URL=http://localhost:8800/graphql
|
|
6
|
+
|
|
7
|
+
# Internal API URL for Docker
|
|
8
|
+
# When running in Docker, this URL is used for server-side requests (like image optimization)
|
|
9
|
+
# to access the API via the internal Docker network instead of localhost
|
|
10
|
+
INTERNAL_API_URL=http://api:8800
|
|
11
|
+
|
|
12
|
+
# Auth Provider
|
|
13
|
+
# For local development, 'none' allows unauthenticated access
|
|
14
|
+
NEXT_PUBLIC_AUTH_PROVIDER=none
|
|
15
|
+
|
|
16
|
+
# Clerk
|
|
17
|
+
# NEXT_PUBLIC_AUTH_PROVIDER=clerk
|
|
18
|
+
# NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
|
|
19
|
+
|
|
20
|
+
# Supabase
|
|
21
|
+
# NEXT_PUBLIC_AUTH_PROVIDER=supabase
|
|
22
|
+
# NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
|
23
|
+
# NEXT_PUBLIC_SUPABASE_ANON_KEY=...
|
|
24
|
+
|
|
25
|
+
# Auth0
|
|
26
|
+
# NEXT_PUBLIC_AUTH_PROVIDER=auth0
|
|
27
|
+
# NEXT_PUBLIC_AUTH0_DOMAIN=...
|
|
28
|
+
# NEXT_PUBLIC_AUTH0_CLIENT_ID=...
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
FROM node:20-alpine AS base
|
|
2
|
+
|
|
3
|
+
# Install dependencies only when needed
|
|
4
|
+
FROM base AS deps
|
|
5
|
+
RUN apk add --no-cache libc6-compat
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
|
|
8
|
+
# Copy package files
|
|
9
|
+
COPY package.json package-lock.json* pnpm-lock.yaml* ./
|
|
10
|
+
RUN corepack enable pnpm && pnpm install
|
|
11
|
+
|
|
12
|
+
# Rebuild the source code only when needed
|
|
13
|
+
FROM base AS builder
|
|
14
|
+
WORKDIR /app
|
|
15
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
16
|
+
COPY . .
|
|
17
|
+
|
|
18
|
+
# Accept build args for Next.js public env vars
|
|
19
|
+
ARG NEXT_PUBLIC_API_URL
|
|
20
|
+
ARG NEXT_PUBLIC_GRAPHQL_URL
|
|
21
|
+
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
|
22
|
+
ENV NEXT_PUBLIC_GRAPHQL_URL=${NEXT_PUBLIC_GRAPHQL_URL}
|
|
23
|
+
|
|
24
|
+
RUN corepack enable pnpm && pnpm run build
|
|
25
|
+
|
|
26
|
+
# Production image, copy all the files and run next
|
|
27
|
+
FROM base AS runner
|
|
28
|
+
WORKDIR /app
|
|
29
|
+
|
|
30
|
+
ENV NODE_ENV=production
|
|
31
|
+
|
|
32
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
33
|
+
RUN adduser --system --uid 1001 nextjs
|
|
34
|
+
|
|
35
|
+
# Enable pnpm for dev mode (must be done as root before USER switch)
|
|
36
|
+
RUN corepack enable pnpm
|
|
37
|
+
|
|
38
|
+
# Automatically leverage output traces to reduce image size
|
|
39
|
+
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
|
40
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
41
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
42
|
+
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
|
43
|
+
|
|
44
|
+
USER nextjs
|
|
45
|
+
|
|
46
|
+
EXPOSE 3000
|
|
47
|
+
|
|
48
|
+
ENV PORT=3000
|
|
49
|
+
ENV HOSTNAME="0.0.0.0"
|
|
50
|
+
|
|
51
|
+
CMD ["node", "server.js"]
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"aliases": {
|
|
15
|
+
"components": "@/components",
|
|
16
|
+
"utils": "@/lib/utils",
|
|
17
|
+
"ui": "@/components/ui",
|
|
18
|
+
"lib": "@/lib",
|
|
19
|
+
"hooks": "@/hooks"
|
|
20
|
+
},
|
|
21
|
+
"registries": {}
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom image loader for Next.js
|
|
3
|
+
* Rewrites NEXT_PUBLIC_API_URL to INTERNAL_API_URL in the image URL query parameter
|
|
4
|
+
* so that when Next.js's server-side image optimizer fetches the image,
|
|
5
|
+
* it uses the internal Docker network URL instead of localhost
|
|
6
|
+
*/
|
|
7
|
+
export default function imageLoader({ src, width, quality }) {
|
|
8
|
+
const publicApiUrl = process.env.NEXT_PUBLIC_API_URL;
|
|
9
|
+
const internalApiUrl = process.env.INTERNAL_API_URL;
|
|
10
|
+
|
|
11
|
+
// If we have an internal API URL and the src uses the public one, rewrite it
|
|
12
|
+
let targetSrc = src;
|
|
13
|
+
if (internalApiUrl && publicApiUrl && src.includes(publicApiUrl)) {
|
|
14
|
+
targetSrc = src.replace(publicApiUrl, internalApiUrl);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return `/_next/image?url=${encodeURIComponent(targetSrc)}&w=${width}&q=${quality || 75}`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** @type {import('next').NextConfig} */
|
|
2
|
+
const nextConfig = {
|
|
3
|
+
output: "standalone",
|
|
4
|
+
reactStrictMode: true,
|
|
5
|
+
transpilePackages: ["@weirdfingers/boards"],
|
|
6
|
+
images: {
|
|
7
|
+
unoptimized: true, // Disable server-side optimization for Docker compatibility
|
|
8
|
+
// Use custom loader when INTERNAL_API_URL is set (Docker environment)
|
|
9
|
+
// This allows server-side image optimization to fetch from internal network
|
|
10
|
+
...(process.env.INTERNAL_API_URL && {
|
|
11
|
+
loader: "custom",
|
|
12
|
+
loaderFile: "./imageLoader.js",
|
|
13
|
+
}),
|
|
14
|
+
remotePatterns: [
|
|
15
|
+
{
|
|
16
|
+
protocol: "http",
|
|
17
|
+
hostname: "localhost",
|
|
18
|
+
port: "8800",
|
|
19
|
+
pathname: "/api/storage/**",
|
|
20
|
+
},
|
|
21
|
+
// Allow internal Docker hostname for image optimization
|
|
22
|
+
...(process.env.INTERNAL_API_URL
|
|
23
|
+
? [
|
|
24
|
+
{
|
|
25
|
+
protocol: "http",
|
|
26
|
+
hostname: "api",
|
|
27
|
+
port: "8800",
|
|
28
|
+
pathname: "/api/storage/**",
|
|
29
|
+
},
|
|
30
|
+
]
|
|
31
|
+
: []),
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
module.exports = nextConfig;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "baseboards",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev -p ${PORT:-3033}",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start -p ${PORT:-3033}",
|
|
9
|
+
"lint": "next lint",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@radix-ui/react-navigation-menu": "^1.2.14",
|
|
14
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
15
|
+
"@tailwindcss/postcss": "^4.1.13",
|
|
16
|
+
"@weirdfingers/boards": "^0.2.0",
|
|
17
|
+
"class-variance-authority": "^0.7.1",
|
|
18
|
+
"clsx": "^2.0.0",
|
|
19
|
+
"graphql": "^16.11.0",
|
|
20
|
+
"lucide-react": "^0.544.0",
|
|
21
|
+
"next": "14.2.5",
|
|
22
|
+
"react": "^18.3.1",
|
|
23
|
+
"react-dom": "^18.3.1",
|
|
24
|
+
"tailwind-merge": "^3.3.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.14.10",
|
|
28
|
+
"@types/react": "^18.3.3",
|
|
29
|
+
"@types/react-dom": "^18.3.0",
|
|
30
|
+
"eslint": "^8.57.0",
|
|
31
|
+
"eslint-config-next": "14.2.5",
|
|
32
|
+
"postcss": "^8.5.6",
|
|
33
|
+
"tailwindcss": "^4.1.13",
|
|
34
|
+
"tw-animate-css": "^1.4.0",
|
|
35
|
+
"typescript": "^5.5.3"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useParams } from "next/navigation";
|
|
5
|
+
import { useBoard, useGenerators, useGeneration } from "@weirdfingers/boards";
|
|
6
|
+
import { GenerationGrid } from "@/components/boards/GenerationGrid";
|
|
7
|
+
import { GenerationInput } from "@/components/boards/GenerationInput";
|
|
8
|
+
|
|
9
|
+
export default function BoardPage() {
|
|
10
|
+
const params = useParams();
|
|
11
|
+
const boardId = params.boardId as string;
|
|
12
|
+
console.log("[BoardPage] Rendering with boardId:", boardId);
|
|
13
|
+
|
|
14
|
+
const boardHookResult = useBoard(boardId);
|
|
15
|
+
console.log("[BoardPage] useBoard result:", boardHookResult);
|
|
16
|
+
const {
|
|
17
|
+
board,
|
|
18
|
+
loading: boardLoading,
|
|
19
|
+
error: boardError,
|
|
20
|
+
refresh: refreshBoard,
|
|
21
|
+
} = boardHookResult;
|
|
22
|
+
|
|
23
|
+
// Fetch available generators
|
|
24
|
+
const generatorsHookResult = useGenerators();
|
|
25
|
+
console.log("[BoardPage] useGenerators result:", generatorsHookResult);
|
|
26
|
+
const {
|
|
27
|
+
generators,
|
|
28
|
+
loading: generatorsLoading,
|
|
29
|
+
error: generatorsError,
|
|
30
|
+
} = generatorsHookResult;
|
|
31
|
+
|
|
32
|
+
// Use generation hook for submitting generations and real-time progress
|
|
33
|
+
const {
|
|
34
|
+
submit,
|
|
35
|
+
isGenerating,
|
|
36
|
+
progress,
|
|
37
|
+
error: generationError,
|
|
38
|
+
result,
|
|
39
|
+
} = useGeneration();
|
|
40
|
+
|
|
41
|
+
console.log("[BoardPage] board:", board);
|
|
42
|
+
console.log("[BoardPage] boardError:", boardError);
|
|
43
|
+
console.log("[BoardPage] board is null/undefined?", !board);
|
|
44
|
+
|
|
45
|
+
// Refresh board when a generation completes or fails
|
|
46
|
+
// MUST be before conditional returns to satisfy Rules of Hooks
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
if (
|
|
49
|
+
progress &&
|
|
50
|
+
(progress.status === "completed" || progress.status === "failed")
|
|
51
|
+
) {
|
|
52
|
+
console.log(
|
|
53
|
+
"[BoardPage] Generation finished, refreshing board:",
|
|
54
|
+
progress.status
|
|
55
|
+
);
|
|
56
|
+
refreshBoard();
|
|
57
|
+
}
|
|
58
|
+
}, [progress, refreshBoard]);
|
|
59
|
+
|
|
60
|
+
// Merge database generations with live progress for real-time updates
|
|
61
|
+
// MUST be before conditional returns to satisfy Rules of Hooks
|
|
62
|
+
const generations = React.useMemo(() => {
|
|
63
|
+
const dbGenerations = board?.generations || [];
|
|
64
|
+
|
|
65
|
+
// If we have live progress, update the matching generation's status
|
|
66
|
+
if (progress) {
|
|
67
|
+
return dbGenerations.map((gen) => {
|
|
68
|
+
if (gen.id === progress.jobId) {
|
|
69
|
+
// Map SSE status to GraphQL status format (lowercase to UPPERCASE)
|
|
70
|
+
const statusMap: Record<string, string> = {
|
|
71
|
+
queued: "PENDING",
|
|
72
|
+
processing: "PROCESSING",
|
|
73
|
+
completed: "COMPLETED",
|
|
74
|
+
failed: "FAILED",
|
|
75
|
+
cancelled: "CANCELLED",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
...gen,
|
|
80
|
+
status: statusMap[progress.status] || gen.status,
|
|
81
|
+
errorMessage:
|
|
82
|
+
progress.status === "failed"
|
|
83
|
+
? progress.message || "Generation failed"
|
|
84
|
+
: gen.errorMessage,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return gen;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return dbGenerations;
|
|
92
|
+
}, [board?.generations, progress]);
|
|
93
|
+
|
|
94
|
+
// Handle board error
|
|
95
|
+
if (boardError) {
|
|
96
|
+
console.error("[BoardPage] Board error:", boardError);
|
|
97
|
+
return (
|
|
98
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
99
|
+
<div className="bg-red-50 border border-red-200 rounded-lg p-6 max-w-lg">
|
|
100
|
+
<h2 className="text-red-800 text-xl font-semibold mb-2">
|
|
101
|
+
Error Loading Board
|
|
102
|
+
</h2>
|
|
103
|
+
<p className="text-red-600">{boardError.message}</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (generatorsError) {
|
|
109
|
+
console.error("[BoardPage] Generators error:", generatorsError);
|
|
110
|
+
return (
|
|
111
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
112
|
+
<div className="bg-red-50 border border-red-200 rounded-lg p-6 max-w-lg">
|
|
113
|
+
<h2 className="text-red-800 text-xl font-semibold mb-2">
|
|
114
|
+
Error Loading Generators
|
|
115
|
+
</h2>
|
|
116
|
+
<p className="text-red-600">{generatorsError.message}</p>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Handle loading state
|
|
123
|
+
if (boardLoading || !board) {
|
|
124
|
+
console.log(
|
|
125
|
+
"[BoardPage] Showing loading spinner - boardLoading:",
|
|
126
|
+
boardLoading,
|
|
127
|
+
"board:",
|
|
128
|
+
board
|
|
129
|
+
);
|
|
130
|
+
return (
|
|
131
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
132
|
+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900"></div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log("[BoardPage] Board loaded successfully:", board);
|
|
138
|
+
|
|
139
|
+
// Filter completed generations that can be used as inputs
|
|
140
|
+
const availableArtifacts = generations.filter(
|
|
141
|
+
(gen) => gen.status === "COMPLETED" && gen.storageUrl
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const handleGenerationSubmit = async (params: {
|
|
145
|
+
generatorName: string;
|
|
146
|
+
prompt: string;
|
|
147
|
+
artifacts: Map<string, unknown>;
|
|
148
|
+
settings: Record<string, unknown>;
|
|
149
|
+
}) => {
|
|
150
|
+
try {
|
|
151
|
+
// Find the selected generator to validate it exists
|
|
152
|
+
const selectedGenerator = generators.find(
|
|
153
|
+
(g) => g.name === params.generatorName
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (!selectedGenerator) {
|
|
157
|
+
throw new Error(`Generator ${params.generatorName} not found`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Build inputs object with prompt and artifact references
|
|
161
|
+
const inputs: Record<string, unknown> = {
|
|
162
|
+
prompt: params.prompt,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Add artifact inputs (convert artifact objects to their IDs)
|
|
166
|
+
params.artifacts.forEach((artifact, slotName) => {
|
|
167
|
+
if (artifact && typeof artifact === "object" && "id" in artifact) {
|
|
168
|
+
inputs[slotName] = artifact.id;
|
|
169
|
+
} else if (artifact) {
|
|
170
|
+
inputs[slotName] = artifact;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Submit generation using the hook
|
|
175
|
+
// Provider is typically encoded in the generator name or use "default"
|
|
176
|
+
await submit({
|
|
177
|
+
boardId,
|
|
178
|
+
model: params.generatorName,
|
|
179
|
+
artifactType: selectedGenerator.artifactType,
|
|
180
|
+
inputs: inputs as { prompt: string; [key: string]: unknown },
|
|
181
|
+
options: params.settings,
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error("Failed to create generation:", error);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<main className="min-h-screen bg-gray-50">
|
|
190
|
+
<div className="container mx-auto px-4 py-6 max-w-7xl">
|
|
191
|
+
{/* Header */}
|
|
192
|
+
<div className="mb-6">
|
|
193
|
+
<h1 className="text-3xl font-bold text-gray-900">{board.title}</h1>
|
|
194
|
+
{board.description && (
|
|
195
|
+
<p className="text-gray-600 mt-2">{board.description}</p>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{/* Generation Grid */}
|
|
200
|
+
<div className="mb-8">
|
|
201
|
+
<GenerationGrid
|
|
202
|
+
generations={generations}
|
|
203
|
+
onGenerationClick={(gen) => {
|
|
204
|
+
console.log("Clicked generation:", gen);
|
|
205
|
+
// TODO: Open generation detail modal
|
|
206
|
+
}}
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{/* Generation Input */}
|
|
211
|
+
<div className="sticky bottom-6 z-10">
|
|
212
|
+
{generatorsLoading ? (
|
|
213
|
+
<div className="bg-white rounded-lg shadow-lg p-6 text-center">
|
|
214
|
+
<p className="text-gray-500">Loading generators...</p>
|
|
215
|
+
</div>
|
|
216
|
+
) : generators.length === 0 ? (
|
|
217
|
+
<div className="bg-white rounded-lg shadow-lg p-6 text-center">
|
|
218
|
+
<p className="text-gray-500">No generators available</p>
|
|
219
|
+
</div>
|
|
220
|
+
) : (
|
|
221
|
+
<GenerationInput
|
|
222
|
+
generators={generators}
|
|
223
|
+
availableArtifacts={availableArtifacts}
|
|
224
|
+
onSubmit={handleGenerationSubmit}
|
|
225
|
+
isGenerating={isGenerating}
|
|
226
|
+
/>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</main>
|
|
231
|
+
);
|
|
232
|
+
}
|