Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.git
.output
data
node_modules
*.log
*.db
*.db-shm
*.db-wal
4 changes: 4 additions & 0 deletions .env.16688
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
VITE_GENERATION_PROVIDER_ID=16688
VITE_BANANA_STUDIO_URL=https://banana-studio-cn-20260915.liupingsong89.chatgpt.site
VITE_APP_URL=https://studio.16688.org
VITE_16688_MEDIA_GATEWAY_BASE_URL=http://flow2api-media:8000
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ yarn-error.log*
# local secret files
.env
.env.*
!.env.16688

# typescript
*.tsbuildinfo
Expand Down
27 changes: 27 additions & 0 deletions Dockerfile.16688
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM node:22-bookworm-slim AS builder

WORKDIR /app
RUN corepack enable && corepack prepare pnpm@10.32.1 --activate

COPY . .
RUN corepack pnpm install --frozen-lockfile
RUN mkdir -p data && corepack pnpm db:push --force
RUN corepack pnpm build:16688

FROM node:22-bookworm-slim AS runtime

ENV NODE_ENV=production \
HOST=0.0.0.0 \
PORT=3020

WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends ffmpeg ca-certificates \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/.output ./.output
COPY --from=builder /app/data ./seed-data

EXPOSE 3020

CMD ["sh", "-c", "if [ ! -s /app/data/local.db ] || [ \"$(stat -c %s /app/data/local.db 2>/dev/null || echo 0)\" -le 4096 ]; then cp -a /app/seed-data/. /app/data/; fi; exec node .output/server/index.mjs"]
15 changes: 15 additions & 0 deletions deploy/nginx/studio.16688.org-http.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
server {
listen 80;
listen [::]:80;
server_name studio.16688.org;

location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
default_type text/plain;
try_files $uri =404;
}

location / {
return 301 https://$host$request_uri;
}
}
54 changes: 54 additions & 0 deletions deploy/nginx/studio.16688.org.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
server {
listen 80;
listen [::]:80;
server_name studio.16688.org;

location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
default_type text/plain;
try_files $uri =404;
}

location / {
return 301 https://$host$request_uri;
}
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name studio.16688.org;

ssl_certificate /etc/letsencrypt/live/studio.16688.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/studio.16688.org/privkey.pem;
ssl_client_certificate /etc/nginx/certs/cloudflare-origin-pull-ca.pem;
ssl_verify_client on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:STUDIO_SSL:10m;
ssl_session_timeout 1d;

auth_basic "16688 Studio Beta";
auth_basic_user_file /etc/nginx/auth/16688-studio.htpasswd;

add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy same-origin always;

client_max_body_size 512m;

location / {
proxy_pass http://127.0.0.1:3020;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
proxy_request_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
36 changes: 36 additions & 0 deletions docker-compose.16688.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
services:
studio:
build:
context: .
dockerfile: Dockerfile.16688
image: 16688-studio:local
container_name: 16688-studio
restart: unless-stopped
environment:
HOST: 0.0.0.0
PORT: 3020
BEATDESIGN_ALLOW_REMOTE_SETTINGS: "1"
BEATDESIGN_MEDIA_GATEWAY_HTTP_HOSTS: flow2api-media
ports:
- "127.0.0.1:3020:3020"
volumes:
- ./data:/app/data
networks:
- studio
- flow2api
healthcheck:
test:
- CMD
- node
- -e
- fetch('http://127.0.0.1:3020/zh').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))
interval: 30s
timeout: 10s
retries: 5
start_period: 45s

networks:
studio:
flow2api:
external: true
name: flow2api-media_default
26 changes: 26 additions & 0 deletions docs/PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,32 @@ An API with a different request or polling contract needs its own adapter. Forks

A custom provider definition owns adapter construction, readiness checks, parameter validation, model bindings, upstream model names, and upload paths. Provider credentials must stay server-side. Switching providers does not change Canvas nodes, Editor clips, Asset IDs, or MCP requests; each submitted task also records its provider/model identity so polling does not silently follow a later default-provider change.

## 16688 Studio fork prototype

The `feat/16688-provider-poc` fork registers `16688 AI` as an opt-in provider.
Use the `16688` Vite mode (`pnpm dev:16688` or `pnpm build:16688`) to make it
active while leaving the upstream BeatAPI development and test mode intact.
The provider has two explicit image transports. GPT Image calls the fixed
`https://api.16688.org/v1/images/generations` endpoint with a user-owned New
API key. Nano Banana calls an independently configured Flow2API-compatible
Gemini media gateway with its own key. Both credentials are encrypted in local
SQLite. The provider binds `nano-banana-2` to
`gemini-3.1-flash-image`, `nano-banana-pro` to
`gemini-3.0-pro-image`, and also exposes `gpt-image-2`,
`gpt-image-2.5-flare`, and `gpt-image-2.5-sunburst`.

The media gateway URL must use HTTPS. Plain HTTP is accepted only for
`localhost`, `127.0.0.1`, or `::1`, so configuring a remote gateway cannot
silently turn the local server into a clear-text credential forwarder. The
Provider dialog tests `GET /v1/models` before saving a changed channel.

This stage remains deliberately text-to-image only. Both transports request
inline Base64 output, validate response size and MIME type, and persist results
into the local Project before exposing them as Assets. Reference-image editing,
video, hosted accounts, payments, and shared balances are not claimed by this
prototype. Those capabilities require separate provider contracts and server
work; they must not silently fall back to another model family.

## Storage

Storage is independently configurable from generation:
Expand Down
37 changes: 29 additions & 8 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"product.shell.terms": "Terms",
"product.shell.privacy": "Privacy",
"product.home.eyebrow": "OPEN SOURCE · LOCAL FIRST",
"product.home.metaTitle": "BeatDesign — Open-source AI canvas for image and video creation",
"product.home.metaTitle": "16688 Studio — AI workspace for image, video, and agent workflows",
"product.home.metaDescription": "The open-source, local-first AI canvas for image and video creation.",
"product.home.titleLine1": "Beat complexity.",
"product.home.titleLine2": "Design freely.",
Expand Down Expand Up @@ -56,8 +56,20 @@
"common.pages.not_found": "Page not found",
"AppShell": {
"studio": {
"startHere": {
"ideasLabel": "Starting ideas",
"title": "Create Here",
"description": "Imagine the scene. Shape the mood. Bring it to life.",
"selectIdea": "Select {title}",
"ideas": {
"cinematicLandscape": "Cinematic landscape",
"editorialCharacter": "Editorial character",
"materialStudy": "Material study",
"surrealFashionFilm": "Surreal fashion film"
}
},
"intro": {
"title": "Canvas-first BeatDesign workspace",
"title": "Canvas-first 16688 Studio workspace",
"description": "Select an image, video, or prompt node, then branch the next generation directly from the canvas."
},
"toolbar": {
Expand Down Expand Up @@ -626,6 +638,8 @@
"projects": "Projects",
"projectsLabel": "Open projects",
"githubRepository": "Open BeatDesign on GitHub",
"bananaStudio": "Banana AI",
"bananaStudioLabel": "Open Banana AI",
"workspaceView": "Workspace view",
"create": "Create",
"studio": "Studio",
Expand Down Expand Up @@ -669,24 +683,31 @@
},
"apiConfig": {
"triggerLabel": "API configuration",
"triggerConfiguredLabel": "API configuration — BeatAPI connected",
"loadingConnection": "Loading BeatAPI connection",
"triggerConfiguredLabel": "API configuration — provider connected",
"loadingConnection": "Loading API connection",
"title": "Connections",
"description": "Connect BeatAPI when you want to use remote generation models.",
"description": "Connect your 16688 AI API key to use remote generation models.",
"providerTab": "AI Provider",
"storageTab": "Storage",
"activeProvider": "Active provider",
"defaultBadge": "Default",
"providerDescription": "BeatAPI is the default generation backend for this open-source workspace.",
"providerDescription": "16688 AI is the active generation backend for this workspace.",
"endpointLabel": "Endpoint",
"mediaLabel": "Media",
"mediaValue": "Image + video",
"keyLabel": "API Key",
"keyPlaceholder": "Enter your BeatAPI API key",
"keyPlaceholder": "Enter your 16688 AI API key",
"configuredPlaceholder": "Configured — leave blank to keep",
"mediaGatewayTitle": "Gemini media gateway",
"mediaGatewayHint": "Nano Banana 2 / Pro use a separate Flow2API channel instead of posing as text-only Gemini models.",
"mediaGatewayUrlLabel": "Media gateway URL",
"mediaGatewayUrlPlaceholder": "https://media.example.com",
"mediaGatewayKeyLabel": "Media gateway API key",
"mediaGatewayKeyPlaceholder": "Enter the media gateway API key",
"save": "Save settings",
"saving": "Saving…",
"saved": "API key saved",
"getKey": "Get a BeatAPI API key →",
"getKey": "Get a 16688 AI API key →",
"storage": {
"intro": "Use BeatAPI Files by default, or connect your own public bucket.",
"managedTitle": "BeatAPI Files",
Expand Down
37 changes: 29 additions & 8 deletions messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"product.shell.terms": "利用規約",
"product.shell.privacy": "プライバシー",
"product.home.eyebrow": "オープンソース · ローカルファースト",
"product.home.metaTitle": "BeatDesign — 画像・動画制作のためのオープンソースAI Canvas",
"product.home.metaTitle": "16688 Studio — 画像・動画・エージェント制作のAIワークスペース",
"product.home.metaDescription": "画像・動画制作のための、オープンソースでローカルファーストなAI Canvas。",
"product.home.titleLine1": "複雑さを打ち破り、",
"product.home.titleLine2": "自由にデザイン。",
Expand Down Expand Up @@ -56,8 +56,20 @@
"common.pages.not_found": "ページが見つかりません",
"AppShell": {
"studio": {
"startHere": {
"ideasLabel": "制作アイデア",
"title": "ここから制作",
"description": "シーンを思い描き、ムードを整え、アイデアを形にしましょう。",
"selectIdea": "{title}を選択",
"ideas": {
"cinematicLandscape": "映画のような風景",
"editorialCharacter": "エディトリアルポートレート",
"materialStudy": "素材のスタディ",
"surrealFashionFilm": "シュールなファッション映像"
}
},
"intro": {
"title": "Canvas中心のBeatDesignワークスペース",
"title": "Canvas中心の16688 Studioワークスペース",
"description": "画像、動画、またはプロンプトノードを選択し、Canvasから次の生成へ直接分岐します。"
},
"toolbar": {
Expand Down Expand Up @@ -626,6 +638,8 @@
"projects": "プロジェクト",
"projectsLabel": "プロジェクトを開く",
"githubRepository": "GitHubでBeatDesignを開く",
"bananaStudio": "Banana AI",
"bananaStudioLabel": "Banana AIを開く",
"workspaceView": "ワークスペース表示",
"create": "作成",
"studio": "Studio",
Expand Down Expand Up @@ -669,24 +683,31 @@
},
"apiConfig": {
"triggerLabel": "API設定",
"triggerConfiguredLabel": "API設定 — BeatAPI接続済み",
"loadingConnection": "BeatAPI接続を読み込み中",
"triggerConfiguredLabel": "API設定 — プロバイダー接続済み",
"loadingConnection": "API接続を読み込み中",
"title": "接続",
"description": "リモート生成モデルを使用するときにBeatAPI APIキーを接続します。",
"description": "リモート生成モデルを使用するため、16688 AI APIキーを接続します。",
"providerTab": "AIプロバイダー",
"storageTab": "ストレージ",
"activeProvider": "使用中のプロバイダー",
"defaultBadge": "デフォルト",
"providerDescription": "BeatAPIは、このオープンソースワークスペースのデフォルト生成バックエンドです。",
"providerDescription": "16688 AIは、このワークスペースで使用中の生成バックエンドです。",
"endpointLabel": "エンドポイント",
"mediaLabel": "メディア",
"mediaValue": "画像 + 動画",
"keyLabel": "API Key",
"keyPlaceholder": "BeatAPI APIキーを入力",
"keyPlaceholder": "16688 AI APIキーを入力",
"configuredPlaceholder": "設定済み — 維持する場合は空欄",
"mediaGatewayTitle": "Geminiメディアゲートウェイ",
"mediaGatewayHint": "Nano Banana 2 / Proは、テキスト専用Geminiモデルではなく独立したFlow2APIチャネルを使用します。",
"mediaGatewayUrlLabel": "メディアゲートウェイURL",
"mediaGatewayUrlPlaceholder": "https://media.example.com",
"mediaGatewayKeyLabel": "メディアゲートウェイAPIキー",
"mediaGatewayKeyPlaceholder": "メディアゲートウェイAPIキーを入力",
"save": "設定を保存",
"saving": "保存中…",
"saved": "APIキーを保存しました",
"getKey": "BeatAPI APIキーを取得 →",
"getKey": "16688 AI APIキーを取得 →",
"storage": {
"intro": "デフォルトのBeatAPI Filesを使うか、ご自身の公開バケットに接続します。",
"managedTitle": "BeatAPI Files",
Expand Down
Loading