diff --git a/README.en.md b/README.en.md index cb3c74a..6882cd1 100644 --- a/README.en.md +++ b/README.en.md @@ -1,305 +1,137 @@
-Vivid AI +image2api

image2api

-**Multi-provider AI image / video generation gateway — one OpenAI-compatible API, seven platforms aggregated, a ready-to-run operations system** +**vivid-protocol AI image / video generation gateway — channel-based upstream routing, a ready-to-run operations system** Live instance (brand): [Vivid AI · vividai.run](https://vividai.run) [简体中文](README.md) | **English** -[![Online Demo](https://img.shields.io/badge/Live%20Demo-vividai.run-7c3aed?style=for-the-badge)](https://vividai.run) +[![Online Demo](https://img.shields.io/badge/demo-vividai.run-7c3aed?style=for-the-badge)](https://vividai.run) [![Go](https://img.shields.io/badge/Go-1.26-00ADD8?logo=go&logoColor=white)](https://go.dev) [![Vue 3](https://img.shields.io/badge/Vue-3-42b883?logo=vuedotjs&logoColor=white)](https://vuejs.org) [![Docker](https://img.shields.io/badge/Docker-ready-2496ED?logo=docker&logoColor=white)](#-deployment) -[![OpenAI Compatible](https://img.shields.io/badge/OpenAI-compatible-412991?logo=openai&logoColor=white)](#-openai-compatible-api) -[![HTTPS](https://img.shields.io/badge/HTTPS-your--proxy-lightgrey)](#-deployment) -[![Providers](https://img.shields.io/badge/providers-7-orange)](#-supported-models--providers) [![Self-hosted](https://img.shields.io/badge/self--hosted-yes-success)](#-deployment) [![License](https://img.shields.io/badge/license-MIT-blue)](#-license) -[Live Demo](https://vividai.run) · [Features](#-features) · [Deploy](#-deployment) · [API](#-openai-compatible-api) · [Community](#-community--contact) - -
- -image2api — playground +[Live Demo](https://vividai.run) · [Features](#-features) · [Deploy](#-deployment) · [API](#-vivid-protocol-api)
--- -## 📖 Table of Contents +## ✨ Introduction -- [Overview](#-overview) -- [Screenshots](#-screenshots) -- [Features](#-features) -- [Supported Models / Providers](#-supported-models--providers) -- [OpenAI-Compatible API](#-openai-compatible-api) -- [Deployment](#-deployment) -- [Tech Stack](#-tech-stack) -- [Repository Layout](#-repository-layout) -- [Roadmap](#-roadmap) -- [Community / Contact](#-community--contact) -- [License](#-license) +**image2api** is a homogeneous gateway. Upstream, it connects any number of **vivid-protocol (vividai) instances** as "channels" and routes jobs across them. Downstream, it exposes the same **vivid-protocol API** to callers: `POST /v1/generate` (NDJSON stream) · `POST /v1/uploads` (presigned upload) · `GET /v1/models` · `GET /v1/balance`. No built-in provider code, no account pools — an upstream is just another vividai instance, so gateways can be layered arbitrarily. -## ✨ Overview +It is more than a gateway: built-in **credit billing, CDK top-ups, online payment (epay), invite rewards, reseller pricing, user accounts, an admin console and a modern generation UI** — one command turns it into a production AI generation site. The author's live instance **[Vivid AI · vividai.run](https://vividai.run)** runs on this project. -**image2api** wraps the image / video capabilities of Adobe Firefly, OpenAI, Runway, Grok, Leonardo, Krea and Imagine into **a single OpenAI-compatible API**. Behind it, multi-account pools are scheduled automatically — out of quota → switch account, auth expired → refresh or kill, transient errors → retry, tokens proactively renewed before they expire — to deliver a stable service. +> 💡 Frontend + backend fully open source (MIT). Go + Vue 3. -It's more than an API proxy: it ships with **credit billing, CDK top-ups, referral rewards, a user system, an admin console, and a modern generation frontend**, so a single command turns it into a fully operational AI generation site — the author's live instance **[Vivid AI · vividai.run](https://vividai.run)** (brand) is built on this project. - -> 💡 Both frontend and backend are **fully open-source** (MIT) — Go + Vue 3, free to fork and self-host. - -**At a glance** 🔌 OpenAI-compatible · 🤖 7 platforms, 10+ models · 🔁 auto failover / token keep-alive · 💳 credits + agent pricing · 🎨 generation frontend + admin console · 🐳 one-command deploy (bring your own TLS proxy) - -## 🖼️ Screenshots - -
-🎨 Modern generation frontend · light / dark admin console · data-driven ops dashboard -
- - - - - - - - - - - - - - - - - - -
DashboardModels
📊 Dashboard
Users / volume / provider health / 24h trend
🧩 Model management
Per-model capabilities, pricing & weight
AccountsLogs
🔑 Account pools
Multi-account pools · weight / concurrency · CRUD
📜 Call logs
Success / failure / in-progress · prompts & latency
+**One-liner** 🔁 vivid-protocol channel mesh · 📦 save-a-channel syncs all models · 💳 credits + payment + reseller pricing · 🧩 concurrency groups · 🎨 generation UI + admin console · 🐳 one-command deploy ## 🚀 Features #### 🎨 Generation -- Images + videos in one place, with **image-to-image / reference frames** (first frame, last frame, style reference) -- Multiple resolutions (images 1K / 2K / 4K · videos 720p / 1080p), aspect ratios and video durations — configured and priced per model -- 7 providers, 10+ models, **enable / disable / re-price from the admin console**, no code changes -- **Model aliases**: one model can expose multiple public ids — API calls with any alias resolve to it -- **De-AI fingerprint** (optional): one-click toggle on the playground — generated images get anti-AI-detection post-processing (subtle detail jitter + metadata stripping), charged as a per-tier surcharge (defaults 1K+1 / 2K+2 / 4K+3 credits, admin-configurable, can be disabled globally); processed works carry a "de-AI" badge across the playground, gallery, logs and admin image manager +- Image + video in one place, with multi-modal references (images / first-last frames / video / audio) per model capability +- Multiple resolutions (1K/2K/4K images · 720p/1080p/768p video), aspect ratios, discrete or per-second video durations — all per-model configurable and priced +- **Priced = listed**: no enable switch — a model goes public once every tier is priced and an enabled channel serves it +- **Model aliases**: one upstream model can map to several public ids +- Optional **de-AI** post-processing on generated images (per-tier surcharge) -#### 🔌 OpenAI Compatible -- Text-to-image `/v1/images/generations` · image-to-image `/v1/images/edits` (multipart ref upload) · video `/v1/videos` (Sora-style async: create → poll → `/content`) · `/v1/models` · balance `/v1/user/balance` (remaining / cumulative used) -- **Strict OpenAI params**: `size` drives **both aspect ratio + resolution tier** (images by long edge → 1K/2K/4K, videos by short edge → 720p/1080p) — just swap `base_url` + `api_key` into an existing OpenAI SDK -- Image results returned **inline as base64** — nothing stored server-side, privacy-friendly; the in-app **/docs** ships a size ↔ tier reference table +#### 🔁 Channel routing +- **Save = full sync**: storing a channel pulls the upstream `/v1/models`, binds everything, unbinds what disappeared and deletes orphans +- **Weight + concurrency-aware** dispatch with failover; upstream 4xx rejections pass through in Chinese, transient errors move to the next channel +- Channel keys stored AES-GCM encrypted (`CHANNEL_SECRET`); upstream balance probed live -#### 🔁 Account Pools + Smart Failover -- Round-robin scheduling across the pool; one bad account doesn't break the whole -- **Out of quota → switch** · **auth expired → refresh & retry / kill** · **transient → retry same account ×3** · **bad params → fail fast** -- **Pre-deducted credits**: atomic debit before generation, auto-refunded on failure, no over-spend under concurrency +#### 🔌 vivid-protocol API +- `POST /v1/generate` — NDJSON stream: first frame returns `jobId`, ~25s blank-line heartbeats, terminal `succeeded`/`failed` +- `POST /v1/uploads` — presigned PUT direct-to-storage for reference files +- `GET /v1/models` — bare array with capability specs · `GET /v1/balance` — balance +- **jobId reconnect**: resubmit with only `jobId` to resume a dropped stream — no duplicate job or charge; pending jobs resume after a server restart too +- Unified errors `{"error":{"code","message"}}` (4001 rejected / 4002 insufficient / 4011 unauthorized / 4293 concurrency / 5001 server); API keys are `vk_` + 48 hex -#### 🔐 Automatic Token Keep-alive -- Single-use rotating tokens (Krea / Imagine) are **renewed proactively 10 minutes before expiry**; new tokens persisted automatically -- Adobe cookies exchanged for fresh tokens on a schedule; bare JWTs killed on expiry -- Daily quota recovered at each provider's reset time, then re-probed for the real balance +#### 💳 Billing & operations +- Credits (pre-charge + auto-refund), per model/tier/duration pricing; de-AI surcharge per tier +- **Reseller pricing**: reseller role + per-model reseller prices (API calls included) +- **Online payment (epay)**: WeChat/Alipay QR, fixed + custom amounts, unpaid orders auto-cancel after 30 min, MD5-verified idempotent callbacks +- **CDK codes** · invite rewards · email register / verification / password reset +- **Concurrency groups**: per-group in-flight caps across UI + API (`0` = unlimited) +- Roles: user / reseller / admin -#### 💳 Billing & Operations -- Credit-based (**pre-deduct + refund on failure**), priced per model / resolution / duration; de-AI fingerprint adds a per-tier surcharge -- **Agent pricing**: a user can be set as an "agent" role and models can carry agent prices; agent users (including their API key calls) are billed at the agent price, falling back to the normal price when unset -- **Online top-up (易支付 / epay)**: WeChat / Alipay QR, preset + custom amounts, unpaid orders auto-cancel after 30 min, MD5-verified idempotent callback auto-credits; cumulative top-up tracked -- **CDK redeem codes** · **referral rewards** · email sign-up / verification code / password reset -- **Concurrency groups**: cap a user's simultaneous generations (playground + API key combined, `0` = unlimited), self-healing Redis counters, new users auto-join the default group -- Three roles: regular user / agent / admin (single) +#### 🛠️ Admin console +- Dashboard (trends / DAU / top failures / top spenders) +- **Channels** (save-to-sync, upstream balance, enable, concurrency, weight) · models (standard + reseller pricing, aliases, read-only listable state) · concurrency groups · orders · global logs · users · CDK · media manager · showcase · announcements · site config +- **Banned words**: prompt hits rejected, per-word / per-user trigger stats -#### 🖥️ User Frontend (Vue 3) -- Playground · creations gallery · generation logs (with failure reasons / source tags) -- Gallery **multi-select batch ops**: select page / bulk delete (videos take their frame stills along) / bulk download (multiple files auto-packed into a zip, fetched concurrently) -- Lightbox preview with built-in **copy original / download / close** buttons; one-click copy of the original image from cards -- **Top-up · Orders** (recharge history / resume unpaid) · API docs · API key management · referral · about, light / dark theme -- **In-app announcements**: a Markdown notice pops up after login and re-shows whenever its content changes +**🧰 Engineering**: S3/RustFS media behind an authenticated proxy + retention cleanup · presigned reference uploads · self-healing maintenance (stale-job resume/refund, expired file pruning) · one-command Docker deploy (TLS is your reverse proxy's job). -#### 🛠️ Admin Console -- Overview dashboard (trends / DAU / top failures / top spenders) -- Model management (normal + agent price + aliases) · account management (bulk import / dedup / quota) · **concurrency groups** · **order management** (filter / search / paginate) · site-wide logs · user management (set as agent / assign concurrency group / view cumulative top-up / banned-word hits) · CDK · image management (multi-select bulk delete / zip download) · showcase · **announcements** · site config (incl. epay, de-AI fingerprint toggle & surcharge pricing) -- **Banned words**: add / remove words in the console (paginated + multi-select bulk delete); prompts containing a banned word are rejected outright (playground + API, case-insensitive), with per-word / per-user hit counters +## 🔌 Channel architecture -**🧰 Engineering highlights**: tls-client (Chrome JA3/JA4 fingerprint) reliably passes Cloudflare · media stored in S3/RustFS, served through an authenticated proxy with retention cleanup · self-healing maintenance loop (quota recovery / credential refresh / orphan-job cleanup with refunds) · one-command Docker deploy (TLS via your own reverse proxy). - -## 🤖 Supported Models / Providers - -| Provider | Models (examples) | Type | -|---|---|---| -| **Adobe Firefly** | firefly-image-5 · firefly-gpt-image-2 · flux-kontext-max · nano-banana-2 · nano-banana-pro · firefly-video · firefly-ray · gemini-veo31 | Image / Video | -| **OpenAI** | gpt-image-2 | Image | -| **Runway** | runway-gen4-turbo · runway-nano-banana-2 (Nano Banana 2) · runway-nano-banana-pro (Nano Banana Pro) | Video / Image | -| **Grok (console.x.ai)** | grok-video (imagine text/image-to-video), grok-image / grok-image-quality (imagine text/image-to-image) | Video / Image | -| **Leonardo.ai** | seedream-4.5 | Image | -| **Krea.ai** | flux-klein-2 | Image | -| **Imagine.art** | imagine-1.5 · imagine-1.5pro | Image | - -> Models are enabled and priced dynamically from the admin console — add or remove anytime. - -## 🔌 OpenAI-Compatible API - -```bash -# Text-to-image — pure OpenAI params: size drives both aspect ratio + tier (long edge <1800→1K / <3500→2K / ≥3500→4K) -curl https://your-domain/v1/images/generations \ - -H "Authorization: Bearer sk-xxxx" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "gpt-image-2", - "prompt": "a cute cat on a desk, studio lighting", - "size": "2048x2048" - }' - -# Image-to-image — multipart reference upload (multiple via image[]) -curl https://your-domain/v1/images/edits \ - -H "Authorization: Bearer sk-xxxx" \ - -F model="seedream-4.5" -F prompt="make it cyberpunk" -F image=@input.png +``` +caller ──vivid protocol──▶ image2api ──vivid protocol──▶ upstream vividai A (weight w, conc c) + (this host) ├─ upstream vividai B + └─ …any number ``` -Images return OpenAI-style `{ "created": ..., "data": [{ "b64_json": "..." }] }` (raw base64, no `data:` prefix, nothing stored server-side). **Video** is async: `POST /v1/videos` → poll `GET /v1/videos/{id}` until `completed` → `GET /v1/videos/{id}/content` for the mp4. Full parameters are documented on the in-app **/docs** page. +- A channel = a homogeneous upstream: `base_url` + `vk_` key + enable switch + concurrency cap + routing weight +- Models come from the upstream `/v1/models` sync; locally they only get aliases and pricing; **one model can fail over across channels** +- This service speaks the same protocol externally — it can itself be mounted as someone's channel, infinitely nestable +- **No OpenAI-protocol upstreams**: a channel must be a vivid-protocol instance + +## 📡 vivid-protocol API + +```bash +# generate — NDJSON stream; first frame returns jobId +curl -N https://your-domain/v1/generate \ + -H "Authorization: Bearer vk_xxxxxxxx" \ + -H "Content-Type: application/json" \ + -d '{"model": "seedance-2.0", "prompt": "a cute cat on a desk", "ratio": "16:9", "quality": "720p"}' +# → {"jobId":"…","status":"running"} …heartbeats… {"status":"succeeded","outputs":[{"url":"…"}]} + +# reconnect — resubmit with only jobId, no duplicate job +curl -N https://your-domain/v1/generate -H "Authorization: Bearer vk_xxxx" -d '{"jobId":""}' + +# reference files — presigned upload session, then PUT the bytes +curl https://your-domain/v1/uploads -H "Authorization: Bearer vk_xxxx" -d '{"kind":"image","mime":"image/png"}' + +curl https://your-domain/v1/models -H "Authorization: Bearer vk_xxxx" +curl https://your-domain/v1/balance -H "Authorization: Bearer vk_xxxx" +``` + +Full parameters, the error-code table and Python examples live on the in-app **/docs** page. ## 🚀 Deployment -> Domain + HTTPS are handled by your own reverse proxy (this project issues no certificates). +**Docker (recommended)**: `docker compose up -d --build` brings up PostgreSQL + Redis + RustFS + backend + frontend (nginx listens on container **HTTP :2000**); point your reverse proxy at `http://:2000`. -**Docker (recommended)**: `docker compose up -d --build` brings up PostgreSQL + Redis + RustFS + backend + frontend (nginx serving **HTTP on container port 2000**); point your reverse proxy at `http://:2000` (port via `WEB_PORT`; edit the values (passwords / keys / `CORS_ORIGINS`, and `COOKIE_SECURE=true` when your proxy serves HTTPS) directly in `docker-compose.yml`). +> ⚠️ Change the secrets in `docker-compose.yml` before deploying — especially **`CHANNEL_SECRET`** (encrypts stored channel keys; rotating it makes saved keys undecryptable). -Or **build from source** — bring your own **PostgreSQL · Redis · RustFS (or any S3) · reverse proxy**: +Manual build needs PostgreSQL · Redis · RustFS (or any S3) · a reverse proxy — see `backend/.env.example` for all env vars. -```bash -# 1. Create an empty database (the backend auto-migrates on start) -createdb vivid_ai +After deploy: admin → Channels → add channel (upstream URL + `vk_` key) → models auto-sync → price them in 模型管理 → done. -# 2. Configure and build the backend from source -cat > backend/.env <<'EOF' -APP_ENV=production -HTTP_ADDR=127.0.0.1:6666 -POSTGRES_DSN=host=127.0.0.1 user=postgres password=YOUR_PASSWORD dbname=vivid_ai port=5432 sslmode=disable TimeZone=Asia/Shanghai -REDIS_ADDR=127.0.0.1:6379 -RUSTFS_ENDPOINT=http://127.0.0.1:9000 -RUSTFS_BUCKET=vivid-ai -RUSTFS_ACCESS_KEY=YOUR_AK -RUSTFS_SECRET_KEY=YOUR_SK -CORS_ORIGINS=https://your-domain -COOKIE_SECURE=true -EOF -cd backend && go build -o bin/api ./cmd/api && ./bin/api # listens on 127.0.0.1:6666 +## 🧱 Stack -# 3. Build the frontend (output in frontend/dist) -cd frontend && npm install && npm run build -``` - -Nginx reverse proxy (issue the certificate yourself with certbot / acme.sh): - -```nginx -server { - listen 443 ssl; - server_name your-domain; - ssl_certificate /path/fullchain.pem; - ssl_certificate_key /path/privkey.pem; - root /path/to/frontend/dist; - index index.html; - client_max_body_size 50m; - proxy_read_timeout 600s; # video generation can take a while - - location /assets/ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable"; } - location / { try_files $uri $uri/ /index.html; add_header Cache-Control "no-cache"; } - location ^~ /admin/api/ { proxy_pass http://127.0.0.1:6666; } - location ^~ /images/ { proxy_pass http://127.0.0.1:6666; } - location = /health { proxy_pass http://127.0.0.1:6666; } - location ^~ /v1/ { proxy_pass http://127.0.0.1:6666; add_header Cache-Control "no-store" always; } -} -``` - -> See `backend/.env.example` for the full set of environment variables. - - - -## 🧱 Tech Stack - -| Layer | Technology | +| Layer | Tech | |---|---| -| Backend | Go · gin · gorm (PostgreSQL) · go-redis · tls-client (Chrome fingerprint) | +| Backend | Go · gin · gorm (PostgreSQL) · go-redis | | Frontend | Vue 3 · Vue Router · Vite · Tailwind CSS v4 | -| Infrastructure | PostgreSQL · Redis · RustFS (S3-compatible) · Nginx | +| Infra | PostgreSQL · Redis · RustFS (S3-compatible) · Nginx | -## 📦 Repository Layout - -``` -backend/ Backend source (Go) -├── cmd/ -│ ├── api/ Service entry point (main) -│ └── marklabel/ Ops helper (mark accounts on demand) -├── internal/ -│ ├── bootstrap/ App wiring, scheduled maintenance startup -│ ├── config/ Env-var configuration loading -│ ├── http/ -│ │ ├── handler/ HTTP handlers (v1-compatible API, admin, auth…) -│ │ ├── middleware/ Auth / request-id middleware -│ │ └── router/ Route registration -│ ├── model/ GORM data models -│ ├── provider/ Upstream provider clients -│ │ ├── adobe/ Adobe Firefly (tls-client fingerprint) -│ │ ├── chatgpt/ OpenAI (incl. PoW / turnstile) -│ │ ├── runway/ Runway video + Nano Banana image -│ │ ├── grok/ Grok (console.x.ai DPoP, video / image; account state via grok.com) -│ │ ├── leonardo/ Leonardo -│ │ ├── krea/ Krea -│ │ ├── imagine/ Imagine.art -│ │ ├── custom/ Custom upstream (OpenAI-compatible v1, routed by id) -│ │ └── epay/ 易支付 / epay (mapi order + MD5-verified callback, top-ups) -│ ├── repo/ Data-access layer (users / models / accounts / logs / CDK / orders / concurrency groups…) -│ ├── service/ Business logic (scheduling, billing, account pools, keep-alive, maintenance) -│ └── storage/ RustFS / S3 media storage -├── Dockerfile Multi-stage build (compile source → slim runtime image) -└── .env.example Backend env-var template - -frontend/ Frontend source (Vue 3 + Vite) -├── src/ -│ ├── views/ Pages (playground / accounts / models / users / concurrency / orders / logs / overview / top-up / settings…) -│ ├── components/ Reusable components (modals / selectors / lightbox…) -│ ├── layouts/ Public / admin layouts -│ ├── utils/ Utility functions -│ └── api.js · auth.js … API client, auth, theme, credits, etc. -├── Dockerfile Nginx static hosting (HTTP :2000) + API proxy -└── default.conf.template Nginx site template (reverse proxy + caching) - -docker-compose.yml Docker orchestration (Postgres / Redis / RustFS / backend / frontend) -``` - -## 🗺️ Roadmap - -- [ ] More upstream providers -- [ ] Usage analytics / export -- [ ] Multi-language UI (i18n) -- [ ] Webhook / async callbacks - -## 💬 Community / Contact +## 💬 Contact | | | |---|---| -| 🌐 Website | **[vividai.run](https://vividai.run)** | -| 👥 QQ Group | **1106849765** · [Join](https://qm.qq.com/q/976LeMFoHu) | -| 🐧 QQ | **1114639355** · [Add](https://qm.qq.com/q/ItgCcNA7ac) | -| 🛒 Shop | **[pay.ldxp.cn/shop/chiyi](https://pay.ldxp.cn/shop/chiyi)** | +| 🌐 Site | **[vividai.run](https://vividai.run)** | +| 👥 QQ group | **1106849765** · [join](https://qm.qq.com/q/976LeMFoHu) | +| 🐧 QQ | **1114639355** · [add](https://qm.qq.com/q/ItgCcNA7ac) | | ✉️ Email | vividairun@gmail.com | -## ⭐ Star History - - - - -If you find this useful, give it a ⭐ — uncomment the line above after creating the repo to show the Star History chart. - ## 📄 License -This project (frontend + backend) is open-source under the [MIT](LICENSE) license — free to use, modify, commercialize and redistribute. - -
- -If this project helps you, a ⭐ Star is much appreciated! - -
+MIT — see [LICENSE](LICENSE). diff --git a/README.md b/README.md index 977a16e..b3ecfe8 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@
-Vivid AI +image2api

image2api

-**多供应商 AI 生图 / 生视频网关 —— 一套 OpenAI 兼容 API,聚合七大平台,开箱即用的运营系统** +**vivid 协议 AI 生图 / 生视频网关 —— 渠道化接入同构上游,开箱即用的运营系统** 线上实例(品牌):[Vivid AI · vividai.run](https://vividai.run) @@ -15,13 +15,10 @@ [![Go](https://img.shields.io/badge/Go-1.26-00ADD8?logo=go&logoColor=white)](https://go.dev) [![Vue 3](https://img.shields.io/badge/Vue-3-42b883?logo=vuedotjs&logoColor=white)](https://vuejs.org) [![Docker](https://img.shields.io/badge/Docker-ready-2496ED?logo=docker&logoColor=white)](#-部署) -[![OpenAI Compatible](https://img.shields.io/badge/OpenAI-compatible-412991?logo=openai&logoColor=white)](#-openai-兼容-api) -[![HTTPS](https://img.shields.io/badge/HTTPS-反代自理-lightgrey)](#-部署) -[![Providers](https://img.shields.io/badge/供应商-7%20平台-orange)](#-支持的模型--供应商) [![Self-hosted](https://img.shields.io/badge/self--hosted-yes-success)](#-部署) [![License](https://img.shields.io/badge/license-MIT-blue)](#-license) -[在线体验](https://vividai.run) · [功能](#-核心功能) · [部署](#-部署) · [API 文档](#-openai-兼容-api) · [交流群](#-交流--联系) +[在线体验](https://vividai.run) · [功能](#-核心功能) · [部署](#-部署) · [API 文档](#-vivid-协议-api) · [交流群](#-交流--联系)
@@ -36,24 +33,23 @@ - [简介](#-简介) - [界面预览](#-界面预览) - [核心功能](#-核心功能) -- [支持的模型 / 供应商](#-支持的模型--供应商) -- [OpenAI 兼容 API](#-openai-兼容-api) +- [渠道架构](#-渠道架构) +- [vivid 协议 API](#-vivid-协议-api) - [部署](#-部署) - [技术栈](#-技术栈) - [仓库结构](#-仓库结构) -- [Roadmap](#-roadmap) - [交流 / 联系](#-交流--联系) - [License](#-license) ## ✨ 简介 -**image2api** 把 Adobe Firefly、OpenAI、Runway、Grok、Leonardo、Krea、Imagine 等平台,以及**任意 OpenAI 兼容上游**的图像 / 视频能力,统一封装成**一套 OpenAI 兼容的 API**;背后用多账号池自动调度 —— 权重优先 + 并发感知、额度耗尽自动换号、认证失效自动刷新或判死、临时错误自动重试、token 到期前主动续期 —— 对外提供稳定服务。 +**image2api** 是一个同构网关:对上,把任意多个 **vivid 协议(vividai)实例**接成「渠道」统一调度;对下,对外暴露同一套 **vivid 协议 API**(`POST /v1/generate` NDJSON 流 · `POST /v1/uploads` 预签名直传 · `GET /v1/models` · `GET /v1/balance`)。没有内置供应商代码、没有账号池 —— 上游就是另一个 vividai,套娃式组网任意层叠。 -它不只是 API 代理:自带**积分计费、CDK 充值、邀请奖励、用户体系、管理后台、现代化画图前端**,一条命令即可跑成一个对外运营的 AI 生成站点 —— 作者的线上实例 **[Vivid AI · vividai.run](https://vividai.run)**(品牌)即基于本项目搭建。 +它不只是 API 网关:自带**积分计费、CDK 充值、在线充值(易支付)、邀请奖励、代理价体系、用户体系、管理后台、现代化画图前端**,一条命令即可跑成一个对外运营的 AI 生成站点 —— 作者的线上实例 **[Vivid AI · vividai.run](https://vividai.run)** 即基于本项目搭建。 > 💡 前后端**完全开源**(MIT),Go + Vue 3,可自由二开 / 自部署。 -**一句话亮点** 🔌 OpenAI 兼容 · 🤖 7 平台十余模型 · 🔁 自动换号 / Token 保活 · 💳 积分 + 在线充值(易支付)+ 代理价 · 🧩 并发分组 · 🎨 画图前端 + 管理后台 · 🐳 一键部署(TLS 反代自理) +**一句话亮点** 🔁 vivid 协议渠道组网 · 📦 保存渠道即全量同步模型 · 💳 积分 + 充值 + 代理价 · 🧩 并发分组 · 🎨 画图前端 + 管理后台 · 🐳 一键部署(TLS 反代自理) ## 🖼️ 界面预览 @@ -63,19 +59,13 @@ - - + + + - - - - - - - - - + +
概览看板模型管理概览看板模型管理调用日志
📊 概览看板
用户 / 生成量 / Provider 健康 / 24h 趋势
🧩 模型管理
按模型独立配置能力、定价与权重
账号池管理调用日志
🔑 账号池管理
多账号池 · 权重 / 并发 · 一键增删改
📊 概览看板
用户 / 生成量 / 渠道健康 / 24h 趋势
🧩 模型管理
按模型独立配置能力、定价与权重,配价即上架
📜 调用日志
成功 / 失败 / 进行中 · 提示词与耗时全留痕
@@ -83,117 +73,98 @@ ## 🚀 核心功能 #### 🎨 生成能力 -- 生图 + 生视频一站式,支持**图生图 / 参考图**(首帧、末帧、风格参考) -- 多分辨率(图像 1K / 2K / 4K · 视频 720p / 1080p)、多宽高比、视频多时长,按模型独立配置与定价 -- 7 大供应商、十余模型,后台**动态启用 / 下架 / 改价**,无需改代码 -- **模型别名**:同一模型可配多个对外 id,API 调用任意别名均可命中 -- **去AI特征**(可选):画图台一键开启,生成图片自动做去AI痕迹处理(细节微扰 + 去除元数据),按画质档位加收积分(默认 1K+1 / 2K+2 / 4K+3,后台可改价、可整体关闭);带标记的作品在画图台、创作记录、日志与后台图片管理中均有「去AI特征」标识 +- 生图 + 生视频一站式,支持**参考图 / 首末帧 / 视频 / 音频**等多模态参考(按模型能力声明下发) +- 多分辨率(图像 1K / 2K / 4K · 视频 720p / 1080p / 768p)、多宽高比、视频多时长(离散档或区间档按秒计价),按模型独立配置与定价 +- **配价即上架**:模型无启停开关,全部档位有价 + 绑定了启用渠道即对外可见;不定价 = 隐藏 +- **模型别名**:同一上游模型可配多个对外 id,API 调用任意别名均可命中 +- **去AI特征**(可选):画图台一键开启,生成图片自动做去AI痕迹处理,按画质档位加收积分(后台可改价、可整体关闭) -#### 🔌 OpenAI 兼容 -- 文生图 `/v1/images/generations` · 图生图 `/v1/images/edits`(multipart 上传参考图) · 视频 `/v1/videos`(Sora 式异步:创建→轮询→`/content` 下载) · `/v1/models` · 余额 `/v1/user/balance`(剩余/累计已用) -- **严格 OpenAI 入参**:`size` **同时决定比例 + 分辨率档**(图像看长边 → 1K/2K/4K,视频看短边 → 720p/1080p),改个 `base_url` + `api_key` 即接现有 OpenAI SDK -- 图片结果 **base64 直返**,服务端不留存文件,隐私友好;站内 **/docs** 附「分辨率对照表」直接查 `size` 该传什么 +#### 🔁 渠道调度 +- **保存渠道即全量同步**:填上游 `base_url` + `vk_` key 保存,自动拉取上游 `/v1/models` 全部入库绑定;上游消失的模型自动解绑,无其他渠道的孤儿模型自动清理 +- **权重 + 并发感知调度**:渠道可配权重(加权随机)与并发上限,逐渠道尝试、失败自动故障转移 +- **错误分类**:上游 4xx(参数/额度)直接透传中文拒绝理由、不重试;网络/5xx 换渠道重试 +- 渠道 API Key 以 AES-GCM 加密落库(`CHANNEL_SECRET`),上游余额实时探测 -#### 🔁 多账号池 + 智能故障转移 -- 账号池调度,单账号出错不影响整体 -- **权重优先 + 并发感知**:按账号权重从高到低调度,某账号并发满了才轮到下一个;同权重组内 round-robin 均摊。每账号并发数可配(上游账号),其余系统固定 -- **额度耗尽→换号** · **认证失效→刷新重试 / 判死** · **临时错误→同号重试 ×3** · **参数错→直接报错** -- **预扣额度**:生成前原子扣减,失败自动退回,杜绝并发超额 - -#### 🔗 自定义上游聚合(OpenAI 兼容) -- 把任意 **OpenAI 兼容的 v1 端点**当成一个账号接入(填 `base_url` + `key`),无需写代码 -- **按 model id 自动路由**:上游声明支持哪些 id,生成该 id 时即走对应上游(可覆盖内置 provider);id 留空 = 全部 -- 模型管理里自由新建自定义模型(id / 类型 / 比例 / 分辨率·价 / 时长·价 / 参考图),按本地价计费 -- 调用**直连不走代理**;上游可配权重与并发,与内置池统一调度 - -#### 🔐 Token 自动保活 -- 一次性轮换 token(Krea / Imagine)**到期前 10 分钟主动续期**,新 token 自动落库 -- Adobe cookie 定时换 token;纯 JWT 到期自动判死 -- 每日额度按平台重置时间自动恢复 + 重新探测真实余额 +#### 🔌 vivid 协议 API +- `POST /v1/generate` —— NDJSON 流式生成:首行回执 `jobId`,约 25s 空行心跳,终帧 `succeeded`/`failed` +- `POST /v1/uploads` —— 预签名 PUT 直传参考文件到对象存储 +- `GET /v1/models` —— 裸数组,含能力规格(清晰度档位 / 比例 / 时长 / 参考文件上限) +- `GET /v1/balance` —— 余额查询 +- **jobId 断流重连**:断流后仅带 `jobId` 重发同一请求即可续等,不重复下单、不重复扣费;进程重启后带 `jobId` 的任务也会自动续等 +- 统一错误包 `{"error":{"code","message"}}`(4001 参数拒绝 / 4002 积分不足 / 4011 未授权 / 4293 并发上限 / 5001 服务异常);API Key 为 `vk_` + 48 hex #### 💳 计费与运营 - 积分制(**预扣 + 失败退款**),按模型 / 分辨率 / 时长精细定价;去AI特征按画质另计附加积分 - **代理价体系**:用户可设为「代理」角色,模型可设代理价;代理用户(含其 API Key 调用)自动按代理价计费,未设代理价则回退普通价 -- **在线充值(易支付)**:微信 / 支付宝扫码,金额档位 + 自定义,订单 30 分钟未付自动取消,支付回调 MD5 验签 + 幂等自动到账;累计充值可查 +- **在线充值(易支付)**:微信 / 支付宝扫码,金额档位 + 自定义,订单 30 分钟未付自动取消,支付回调 MD5 验签 + 幂等自动到账 - **CDK 兑换码**充值 · **邀请奖励** · 邮箱注册 / 验证码 / 找回密码 - **并发分组**:按分组限制用户「同时生成数」(画图台 + API Key 合计,`0` = 不限),Redis 自愈计数,新用户自动入默认组 -- 三级角色:普通用户 / 代理 / 管理员(唯一) +- 三级角色:普通用户 / 代理 / 管理员 #### 🖥️ 用户前台(Vue 3) - 画图台 · 创作记录画廊 · 生成日志(含失败原因 / 来源标签) -- 作品画廊**多选批量操作**:全选本页 / 批量删除(视频连带首尾帧图) / 批量下载(多个自动打包 zip,并发拉取) -- 预览大图内置**复制原图 / 下载 / 关闭**按钮,卡片一键复制原图 -- **充值 · 订单**(充值记录 / 未付可继续支付) · API 文档 · API Key 管理 · 邀请 · 关于,亮 / 暗主题 -- **站内公告**:登录后自动弹出 Markdown 公告,内容更新即重推 +- 作品画廊**多选批量操作**:全选本页 / 批量删除 / 批量下载(自动打包 zip) +- **充值 · 订单** · API 文档 · API Key 管理 · 邀请 · 亮 / 暗主题 +- **站内公告**:登录后自动弹出 Markdown 公告 #### 🛠️ 管理后台 - 概览看板(趋势 / DAU / 失败 Top / 消费榜) -- 模型管理(普通价 + 代理价 + 别名) · 账号管理(批量导入 / 去重 / 额度) · **并发分组** · **订单管理**(筛选 / 搜索 / 分页) · 全站日志 · 用户管理(设为代理 / 分配并发组 / 看累计充值 / 违禁触发次数) · CDK · 图片管理(多选批量删除 / zip 打包下载) · 展示位 · **站点公告** · 站点配置(含易支付、去AI特征开关与附加价格) -- **违禁词管理**:后台增删违禁词(分页 + 多选批量删除),提示词命中即拦截(画图台 + API,不区分大小写),按词 / 按用户统计触发次数 +- **渠道管理**(保存即同步模型 / 上游余额 / 启用开关 / 并发 / 权重)· 模型管理(普通价 + 代理价 + 别名,上架状态只读)· **并发分组** · **订单管理** · 全站日志 · 用户管理(设为代理 / 分配并发组)· CDK · 图片管理 · 展示位 · **站点公告** · 站点配置(易支付、去AI特征等) +- **违禁词管理**:提示词命中即拦截,按词 / 按用户统计触发次数 -**🧰 工程亮点**:tls-client(Chrome JA3/JA4 指纹)稳定穿透 Cloudflare · 媒体存 S3/RustFS 经鉴权代理分发 + 保留期清理 · 自愈式维护轮询(恢复额度 / 刷新凭据 / 清理僵死任务并退款) · 一条命令 Docker 部署(TLS 交给你的反代)。 +**🧰 工程亮点**:媒体存 S3/RustFS 经鉴权代理分发 + 保留期清理 · 预签名直传参考文件 · 自愈式维护轮询(僵死任务续等 / 退款、过期文件清理)· 一条命令 Docker 部署(TLS 交给你的反代)。 -## 🤖 支持的模型 / 供应商 +## 🔌 渠道架构 -| 供应商 | 模型(示例) | 类型 | -|---|---|---| -| **Adobe Firefly** | firefly-image-5 · firefly-gpt-image-2 · flux-kontext-max · nano-banana-2 · nano-banana-pro · firefly-video · firefly-ray · gemini-veo31 | 图像 / 视频 | -| **OpenAI** | gpt-image-2 | 图像 | -| **Runway** | runway-gen4-turbo · runway-nano-banana-2 (Nano Banana 2) · runway-nano-banana-pro (Nano Banana Pro) | 视频 / 图像 | -| **Grok(console.x.ai)** | grok-video(imagine 文生 / 图生视频)、grok-image / grok-image-quality(imagine 文生图 / 图生图) | 视频 / 图像 | -| **Leonardo.ai** | seedream-4.5 | 图像 | -| **Krea.ai** | flux-klein-2 | 图像 | -| **Imagine.art** | imagine-1.5 · imagine-1.5pro | 图像 | -| **自定义上游** | 任意 OpenAI 兼容 v1 端点(按 id 路由) | 图像 / 视频 | - -> 模型由管理后台动态启用并定价,可随时增删。自定义上游支持把任何 OpenAI 兼容服务接成账号,按 model id 路由调用。 - -## 🔌 OpenAI 兼容 API - -```bash -# 文生图 —— 纯 OpenAI 参数:size 同时决定比例 + 分辨率档(长边 <1800→1K / <3500→2K / ≥3500→4K) -curl https://你的域名/v1/images/generations \ - -H "Authorization: Bearer sk-xxxx" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "gpt-image-2", - "prompt": "a cute cat on a desk, studio lighting", - "size": "2048x2048" - }' - -# 图生图 —— multipart 上传参考图(可多张 image[]) -curl https://你的域名/v1/images/edits \ - -H "Authorization: Bearer sk-xxxx" \ - -F model="seedream-4.5" -F prompt="改成赛博朋克风格" -F image=@input.png +``` +调用方 ──vivid 协议──▶ image2api ──vivid 协议──▶ 上游 vividai 实例 A (渠道, 权重 w, 并发 c) + (本服务) ├─ 上游 vividai 实例 B + └─ …任意多个 ``` -图片返回 OpenAI 风格 `{ "created": ..., "data": [{ "b64_json": "..." }] }`(原始 base64,无 `data:` 前缀,服务端不留存)。**视频**走异步:`POST /v1/videos` 建任务 → 轮询 `GET /v1/videos/{id}` 至 `completed` → `GET /v1/videos/{id}/content` 取 mp4。完整参数见站内 **/docs** 文档页。 +- 渠道 = 一个同构上游:`base_url` + `vk_` API Key + 启用开关 + 并发上限 + 调度权重 +- 模型来自渠道同步(上游 `/v1/models`),本地只做别名映射与定价;**模型可在多个渠道间故障转移** +- 本服务对外同样说 vivid 协议 —— 所以它自己也能被接成别人的渠道,无限层叠组网 +- **不支持接 OpenAI 协议上游**:上游必须是 vivid 协议实例 + +## 📡 vivid 协议 API + +```bash +# 文生图 / 生视频 —— NDJSON 流,首行回执 jobId +curl -N https://你的域名/v1/generate \ + -H "Authorization: Bearer vk_xxxxxxxx" \ + -H "Content-Type: application/json" \ + -d '{"model": "seedance-2.0", "prompt": "a cute cat on a desk", "ratio": "16:9", "quality": "720p"}' +# → {"jobId":"…","status":"running"} …空行心跳… {"status":"succeeded","outputs":[{"url":"…"}]} + +# 断流重连:只带 jobId 重发,不重复下单 +curl -N https://你的域名/v1/generate -H "Authorization: Bearer vk_xxxx" -d '{"jobId":"<上文 jobId>"}' + +# 参考文件:先拿预签名上传会话,PUT 直传后拿 ref key +curl https://你的域名/v1/uploads -H "Authorization: Bearer vk_xxxx" -d '{"kind":"image","mime":"image/png"}' + +# 模型清单(裸数组)与余额 +curl https://你的域名/v1/models -H "Authorization: Bearer vk_xxxx" +curl https://你的域名/v1/balance -H "Authorization: Bearer vk_xxxx" +``` + +完整参数、错误码表与 Python 示例见站内 **/docs** 文档页。 ## 🚀 部署 > 域名 + HTTPS 由你自己的反向代理处理(本项目不内置证书签发)。 -**Docker(推荐)**:`docker compose up -d --build` 一条命令拉起 PostgreSQL + Redis + RustFS + 后端 + 前端(nginx **HTTP 监听容器 2000 端口**),把你的反向代理指到 `http://<本机>:2000`(端口用 `WEB_PORT` 改;要改密码 / 密钥 / `CORS_ORIGINS`(反代走 HTTPS 时把 `COOKIE_SECURE` 设为 `true`),直接改 `docker-compose.yml` 里对应值即可)。 +**Docker(推荐)**:`docker compose up -d --build` 一条命令拉起 PostgreSQL + Redis + RustFS + 后端 + 前端(nginx **HTTP 监听容器 2000 端口**),把你的反向代理指到 `http://<本机>:2000`。 -也可**从源码手动构建**,自备 **PostgreSQL · Redis · RustFS(或任意 S3)· 反向代理**: +> ⚠️ 部署前把 `docker-compose.yml` 里的密码 / 密钥改掉 —— 特别是 **`CHANNEL_SECRET`**(渠道上游 Key 的加密密钥,变更后已存渠道密钥无法解密)。 + +也可**从源码手动构建**,自备 PostgreSQL · Redis · RustFS(或任意 S3)· 反向代理: ```bash # 1. 创建空库(后端启动自动建表) createdb vivid_ai -# 2. 配置并从源码构建后端 -cat > backend/.env <<'EOF' -APP_ENV=production -HTTP_ADDR=127.0.0.1:6666 -POSTGRES_DSN=host=127.0.0.1 user=postgres password=你的密码 dbname=vivid_ai port=5432 sslmode=disable TimeZone=Asia/Shanghai -REDIS_ADDR=127.0.0.1:6379 -RUSTFS_ENDPOINT=http://127.0.0.1:9000 -RUSTFS_BUCKET=vivid-ai -RUSTFS_ACCESS_KEY=你的AK -RUSTFS_SECRET_KEY=你的SK -CORS_ORIGINS=https://你的域名 -COOKIE_SECURE=true -EOF +# 2. 配置并从源码构建后端 —— 完整环境变量见 backend/.env.example cd backend && go build -o bin/api ./cmd/api && ./bin/api # 监听 127.0.0.1:6666 # 3. 构建前端(产物 frontend/dist) @@ -212,7 +183,6 @@ server { index index.html; client_max_body_size 50m; proxy_read_timeout 600s; # 视频生成耗时长 - location /assets/ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable"; } location / { try_files $uri $uri/ /index.html; add_header Cache-Control "no-cache"; } location ^~ /admin/api/ { proxy_pass http://127.0.0.1:6666; } @@ -222,13 +192,13 @@ server { } ``` -> 完整环境变量见 `backend/.env.example`。 +部署后:后台「渠道」页新建渠道(上游 URL + `vk_` key)→ 保存即自动同步全部上游模型 → 模型管理里配价 → 画图台 / 对外 API 立即可用。 ## 🧱 技术栈 | 层 | 技术 | |---|---| -| 后端 | Go · gin · gorm(PostgreSQL)· go-redis · tls-client(Chrome 指纹) | +| 后端 | Go · gin · gorm(PostgreSQL)· go-redis | | 前端 | Vue 3 · Vue Router · Vite · Tailwind CSS v4 | | 基础设施 | PostgreSQL · Redis · RustFS(S3 兼容)· Nginx | @@ -236,54 +206,34 @@ server { ``` backend/ 后端源码(Go) -├── cmd/ -│ ├── api/ 服务入口(main) -│ └── marklabel/ 运维小工具(按需标记账号) +├── cmd/api/ 服务入口(main) ├── internal/ │ ├── bootstrap/ 应用装配、定时维护任务启动 │ ├── config/ 环境变量配置加载 │ ├── http/ -│ │ ├── handler/ HTTP 处理器(v1 兼容接口、后台、鉴权…) +│ │ ├── handler/ HTTP 处理器(/v1 vivid 协议、渠道管理、鉴权…) │ │ ├── middleware/ 鉴权 / 请求 ID 等中间件 │ │ └── router/ 路由注册 -│ ├── model/ GORM 数据模型 -│ ├── provider/ 各上游供应商客户端 -│ │ ├── adobe/ Adobe Firefly(tls-client 指纹) -│ │ ├── chatgpt/ OpenAI(含 PoW / turnstile) -│ │ ├── runway/ Runway 视频 + Nano Banana 图像 -│ │ ├── grok/ Grok(console.x.ai DPoP,视频 / 图像;账号态走 grok.com) -│ │ ├── leonardo/ Leonardo -│ │ ├── krea/ Krea -│ │ ├── imagine/ Imagine.art -│ │ ├── custom/ 自定义上游(OpenAI 兼容 v1,按 id 路由,直连不走代理) -│ │ └── epay/ 易支付(mapi 下单 + 回调 MD5 验签,积分充值) -│ ├── repo/ 数据访问层(用户 / 模型 / 账号 / 日志 / CDK / 订单 / 并发组…) -│ ├── service/ 业务逻辑(生成调度、计费、账号池、保活、维护) -│ └── storage/ RustFS / S3 媒体存储 -├── Dockerfile 多阶段构建(源码编译 → 精简运行镜像) +│ ├── model/ GORM 数据模型(渠道 / 模型 / 用户 / 事件…) +│ ├── upstream/ vivid 协议上游客户端(NDJSON / 上传 / 模型 / 余额) +│ ├── repo/ 数据访问层 +│ ├── service/ 业务逻辑(渠道调度、计费、维护、v1 job 生命周期) +│ └── storage/ RustFS / S3 媒体存储(预签名 PUT/GET) +├── Dockerfile 多阶段构建 └── .env.example 后端环境变量模板 frontend/ 前端源码(Vue 3 + Vite) ├── src/ -│ ├── views/ 页面(画图台 / 账号 / 模型 / 用户 / 并发组 / 订单 / 日志 / 概览 / 充值 / 设置…) -│ ├── components/ 复用组件(弹窗 / 选择器 / 灯箱…) -│ ├── layouts/ 公共 / 后台布局 -│ ├── utils/ 工具函数 -│ └── api.js · auth.js … 接口封装、鉴权、主题、积分等 +│ ├── views/ 页面(画图台 / 渠道 / 模型 / 用户 / 并发组 / 订单 / 日志 / 概览 / 充值 / 设置…) +│ ├── components/ 复用组件(弹窗 / 选择器 / 灯箱…) +│ ├── layouts/ 公共 / 后台布局 +│ └── api.js · auth.js … 接口封装、鉴权、主题、积分等 ├── Dockerfile Nginx 静态托管(HTTP :2000)+ API 反代 └── default.conf.template Nginx 站点模板(反代 + 缓存策略) docker-compose.yml Docker 编排(Postgres / Redis / RustFS / 后端 / 前端) -.env.example → backend/.env 后端环境变量模板 ``` -## 🗺️ Roadmap - -- [ ] 更多上游供应商接入 -- [ ] 用量统计 / 导出 -- [ ] 多语言界面(i18n) -- [ ] Webhook / 异步回调 - ## 💬 交流 / 联系 | | | @@ -294,13 +244,6 @@ docker-compose.yml Docker 编排(Postgres / Redis / RustFS / 后端 | 🛒 小店 | **[pay.ldxp.cn/shop/chiyi](https://pay.ldxp.cn/shop/chiyi)** | | ✉️ 邮箱 | vividairun@gmail.com | -## ⭐ Star History - - - - -觉得有用就点个 ⭐ 吧 —— 建好仓库后取消上方注释即可展示 Star 趋势图。 - ## 📄 License 本项目(前端 + 后端)基于 [MIT](LICENSE) 协议开源,可自由使用、修改、商用与二次分发。 diff --git a/backend/.env.example b/backend/.env.example index d6754a6..9a48296 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,8 +1,8 @@ -# Vivid AI backend config template. Copy to .env and fill in real values. +# image2api backend config template. Copy to .env and fill in real values. # Real environment variables override .env. APP_ENV=development -APP_TITLE=Vivid AI +APP_TITLE=image2api HTTP_ADDR=:6666 # PostgreSQL (database must already exist; tables auto-migrate on boot) @@ -13,8 +13,22 @@ REDIS_ADDR=127.0.0.1:6379 REDIS_PASSWORD= REDIS_DB=0 +# RustFS / S3-compatible object storage (generated media + reference uploads) +RUSTFS_ENDPOINT=http://127.0.0.1:9000 +RUSTFS_BUCKET=vivid-ai +RUSTFS_ACCESS_KEY=YOUR_AK +RUSTFS_SECRET_KEY=YOUR_SK + +# Channel upstream API keys are stored AES-GCM encrypted with this secret. +# Set a random value in production and keep it stable — changing it makes +# previously saved channel keys undecryptable. +CHANNEL_SECRET=change-me-channel-secret + # Frontend dev origins allowed for CORS CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 -# Optional: override generated media root (defaults to ../../ai-gateway/data/generated) -# GENERATED_ROOT= +# Set to true when serving the site over HTTPS (session cookie Secure flag) +COOKIE_SECURE=false + +# SESSION_COOKIE_NAME=vivid_session +# GENERATED_ROOT= # optional: override generated media root diff --git a/backend/README.md b/backend/README.md index 1ff4a22..4a65088 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,55 +1,27 @@ -# Vivid AI Backend +# image2api backend -Go backend for `vivid-ai`, using: +Go backend for `image2api` — a vivid-protocol gateway that routes generation +jobs across same-protocol upstream channels. Stack: Gin · GORM (PostgreSQL) · +go-redis. -- Gin -- GORM -- PostgreSQL -- Redis +## Architecture sketch -## Current scope - -This is an in-progress rewrite. The current skeleton already includes: - -- app bootstrap -- PostgreSQL and Redis initialization -- GORM auto-migrations -- session storage in Redis -- image access control for `/images/:user/:name` -- public site endpoint: `/admin/api/site` -- public showcase endpoint: `/admin/api/showcase` -- session-based auth endpoint: `/admin/api/auth/me` +- `internal/upstream` — vivid-protocol client (NDJSON generate + jobId resume, + `/v1/uploads`, `/v1/models`, `/v1/balance`, error classification) +- `internal/service/channel.go` — channel CRUD, upstream model sync (save = + full sync), AES-GCM key storage +- `internal/service/v1.go` — channel-weighted dispatch, billing, job lifecycle, + stale-job resume +- `internal/http/handler/v1.go` — public vivid endpoints + (`/v1/generate` `/v1/uploads` `/v1/models` `/v1/balance`) ## Environment -Set these before running: +See `.env.example`. Required: `POSTGRES_DSN`, `REDIS_ADDR`, `RUSTFS_*`. +Important: `CHANNEL_SECRET` encrypts stored channel API keys (AES-GCM) — set a +random value and keep it stable; rotating it makes saved keys undecryptable. -```powershell -$env:POSTGRES_DSN="host=127.0.0.1 user=postgres password=postgres dbname=vivid_ai port=5432 sslmode=disable TimeZone=Asia/Shanghai" -$env:REDIS_ADDR="127.0.0.1:6379" -$env:HTTP_ADDR=":6061" +```bash +cp .env.example .env # fill in values +go build -o bin/api ./cmd/api && ./bin/api ``` - -Optional: - -```powershell -$env:APP_ENV="development" -$env:APP_TITLE="Vivid AI" -$env:SESSION_COOKIE_NAME="vivid_session" -$env:CORS_ORIGINS="http://localhost:5173,http://127.0.0.1:5173" -``` - -## Run - -```powershell -go run ./cmd/api -``` - -## Notes - -- Generated media defaults to `../../ai-gateway/data/generated` relative to the backend working directory. -- Private images require either: - - session cookie - - bearer session token - - bearer API key -- Showcase images are public. diff --git a/backend/internal/repo/event_repo.go b/backend/internal/repo/event_repo.go index 5953ddc..8d77d06 100644 --- a/backend/internal/repo/event_repo.go +++ b/backend/internal/repo/event_repo.go @@ -184,7 +184,7 @@ type DashboardWindow struct { Pending int64 `json:"pending"` Image int64 `json:"image"` Video int64 `json:"video"` - API int64 `json:"api"` // source = 'v1' (OpenAI-compatible key) + API int64 `json:"api"` // source = 'v1' (vk_ external API key) Web int64 `json:"web"` // everything else (web / playground) Spent float64 `json:"spent"` } diff --git a/backend/internal/service/admin_write.go b/backend/internal/service/admin_write.go index 31699a2..6a6ea71 100644 --- a/backend/internal/service/admin_write.go +++ b/backend/internal/service/admin_write.go @@ -653,8 +653,8 @@ func boolValueWithDefault(v any, fallback bool) bool { // resolutionsFromPrices mirrors Python models_store._resolutions_from_prices: // an image model's quality tiers ARE its price keys (the admin form never sends -// `resolutions` for images), returned in canonical 1K/2K/4K order. gpt-image-2, -// for example, only ever has a "1K" price, so it resolves to exactly ["1K"]. +// `resolutions` for images), returned in canonical 1K/2K/4K order. A model that +// only ever has a "1K" price, for example, resolves to exactly ["1K"]. func resolutionsFromPrices(prices datatypes.JSONMap) datatypes.JSON { out := []string{} for _, r := range []string{"1K", "2K", "4K"} { diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index bb1dac4..0f2ac52 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -82,6 +82,8 @@ services: RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY:-vividai_secret_2026} CORS_ORIGINS: ${CORS_ORIGINS:-http://vividai.run,http://www.vividai.run,http://206.168.190.183} COOKIE_SECURE: "false" + # 渠道上游 API Key 的 AES-GCM 加密密钥——务必配置随机值;丢失/变更后已存的渠道密钥无法解密 + CHANNEL_SECRET: ${CHANNEL_SECRET:-change-me-channel-secret} volumes: - generated:/app/data/generated depends_on: diff --git a/docker-compose.yml b/docker-compose.yml index f0a7f44..110a698 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,6 +72,8 @@ services: RUSTFS_SECRET_KEY: vividai-secret-change-me CORS_ORIGINS: http://localhost:2000 COOKIE_SECURE: "false" + # 渠道上游 API Key 的 AES-GCM 加密密钥——生产环境务必改成随机值并保持不变 + CHANNEL_SECRET: change-me-channel-secret volumes: - generated:/app/data/generated depends_on: diff --git a/frontend/README.md b/frontend/README.md index 8ad9c5b..1337e3d 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,7 +1,8 @@ -# ai-gateway frontend +# image2api frontend -Vue 3 + Vite admin console for the ai-gateway backend. This replaces the old -single-file `static/admin.html`. +Vue 3 + Vite frontend for `image2api`: user-facing generation UI (playground, +gallery, docs, billing) plus the admin console (channels, models, users, +orders, logs, config). ## Develop @@ -10,27 +11,10 @@ npm install npm run dev # http://localhost:5173 ``` -The dev server proxies `/admin`, `/health`, `/generated`, `/v1` to the backend. -Start the backend separately: +The dev server proxies `/admin`, `/health`, `/images`, `/v1` to the backend. +Start the Go backend separately (see `backend/README.md`). ```bash -# from repo root -python app.py # http://0.0.0.0:6060 +# from ../backend +go build -o bin/api ./cmd/api && ./bin/api # http://127.0.0.1:6666 ``` - -If the backend runs elsewhere, set `VITE_BACKEND` before `npm run dev`: - -```bash -VITE_BACKEND=http://192.168.1.10:6060 npm run dev -``` - -## Build - -```bash -npm run build # outputs static assets to ./dist -npm run preview # serve the production build locally -``` - -When hosting `dist/` on a different origin than the API, set `VITE_API_BASE` -(e.g. `VITE_API_BASE=http://api-host:6060`) at build time, and add that frontend -origin to the backend's `CORS_ORIGINS` env var. diff --git a/frontend/src/views/DocsView.vue b/frontend/src/views/DocsView.vue index 9c01a55..a7a3007 100644 --- a/frontend/src/views/DocsView.vue +++ b/frontend/src/views/DocsView.vue @@ -30,7 +30,7 @@ const videoModels = computed(() => models.value.filter((m) => m.type === 'video' function pubName(m) { return m?.alias || m?.id || '' } -const sampleImage = computed(() => pubName(imageModels.value[0]) || 'gpt-image-2') +const sampleImage = computed(() => pubName(imageModels.value[0]) || 'image-model') const sampleVideo = computed(() => pubName(videoModels.value[0]) || 'video-model') const sampleQuality = computed(() => (imageModels.value[0]?.resolutions || [])[0] || '1K') const sampleVQuality = computed(() => (videoModels.value[0]?.resolutions || [])[0] || '720p') diff --git a/frontend/src/views/PlaygroundView.vue b/frontend/src/views/PlaygroundView.vue index ac4d2d1..6ce5a22 100644 --- a/frontend/src/views/PlaygroundView.vue +++ b/frontend/src/views/PlaygroundView.vue @@ -116,9 +116,8 @@ const maxRefs = computed(() => { const extra = familyPreset.value ? 0 : maxVideos.value + maxAudios.value return Math.max(a, b) + extra } - // Image-to-image: honor the model's configured max (gpt-image-2=3, - // seedream-4.5=6, flux-klein-2=4 …). Fall back to 1 when image_to_image is on - // but no count was set. + // Image-to-image: honor the model's configured max (e.g. 3 or 6 refs, set per + // model). Fall back to 1 when image_to_image is on but no count was set. const m = Number(model.value?.max_reference_images || 0) if (m > 0) return m return model.value?.image_to_image ? 1 : 0 diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 2474cc8..d19549d 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -26,7 +26,7 @@ onMounted(async () => { } }) -// ---- API Key (REAL: minted + verified server-side; OpenAI-compatible) ---- +// ---- API Key (REAL: minted + verified server-side; vivid /v1 protocol) ---- // The full plaintext is returned only once, right after minting. On reload we // only have the server's masked preview (the plaintext is never stored). const apiKey = ref('') // full plaintext — present only right after minting