一个完整的前端 Demo,基于 MiniMax 视频生成 API 实现文生视频 / 图生视频,自带历史持久化、本地视频缓存、过期检测、导入导出等扩展能力。
- 📝 文生视频 — 纯文本描述生成视频
- 🖼️ 图生视频 — 上传首帧图片,自动切到图生模型
- 🎬 15 种运镜指令 —
[推进] [拉远] [左移] [右移] [左摇] [右摇] [上升] [下降] [固定]等
- 📜 完整 CRUD — 新增 / 查询 / 修改 / 删除 / 清空
- 💾 本地视频缓存 — 生成后自动下载到
data/videos/,云端过期仍可播放 - 🔍 过期检测 — 刷新状态时识别云端失效,自动标记
- 📥📤 导入 / 导出 — JSON / CSV 双向迁移,跨设备同步
- 🗑️ 清理未关联视频 — 列表式选择清理孤儿文件
- 🎨 去 AI 味设计 — 克制现代风(参考 Linear / Vercel / Raycast)
- 🖼️ 完全自定义下拉 — 替换原生
<select>,含键盘导航 - 💬 自研 dialog 组件 — 替代原生
alert/confirm,支持富文本 + 勾选框 - 🪟 视频 / 列表弹窗 — ESC / 背景 / × 三种关闭方式
- 📱 拖拽上传 — 头像、首帧图均支持
.
├── server.mjs # Node.js 代理 + 历史持久化服务器
├── package.json # 项目元数据(零运行时依赖)
├── README.md # 本文件
├── public/ # 前端(无需打包,原生 ES Modules)
│ ├── index.html # HTML 骨架
│ ├── css/
│ │ ├── base.css # 基础布局、表单、按钮、状态
│ │ └── components.css # 自定义 select、历史、弹窗、对话框
│ └── js/
│ ├── main.js # 入口、初始化、window 挂载
│ ├── utils.js # 工具函数 + 全局 state
│ ├── api.js # History 对象(后端 API 客户端)
│ ├── components.js # 自定义下拉 + 通用 dialog
│ ├── modal.js # 视频弹窗 + 清理未关联弹窗
│ ├── history.js # 历史渲染 + 操作 + 导入导出
│ └── generate.js # 视频生成 + 轮询 + 刷新
└── data/ # 运行后自动创建
├── history.json # 历史记录
└── videos/ # 本地视频缓存
└── <file_id>.mp4
- Node.js 18+(需要内置
fetchAPI,无运行时依赖) - 一个 MiniMax 账号和 API Key
# 直接启动
node server.mjs
# 或使用 npm script
npm start
# 自定义端口
PORT=8080 node server.mjs启动成功:
🎬 MiniMax 视频生成 Demo 已启动
📺 打开浏览器访问: http://localhost:3000
🔧 代理后端路径: /api/* -> https://api.minimaxi.com/v1/*
💾 历史存储: /Users/.../data/history.json
📹 视频本地: /Users/.../data/videos
- 浏览器打开 http://localhost:3000
- 填入 API Key
- 输入视频描述 / 上传首帧图片 / 选择模型
- 点击「开始生成」
- 等待 1~5 分钟,视频在弹窗内播放,可下载
浏览器 (localhost:3000)
│ ES Modules + fetch
│
▼
server.mjs (Node.js 代理)
│ 1. 解决 CORS(浏览器不能直接调 api.minimaxi.com)
│ 2. 用 API Key 鉴权(API Key 不暴露给浏览器外的网络)
│ 3. 视频流代理(避免 CDN CORS)
│ 4. 历史持久化(JSON 文件)
│ 5. 视频本地缓存(去重 + 原子写入)
│
▼
api.minimaxi.com + 视频 CDN
| 步骤 | 接口 | 说明 |
|---|---|---|
| 1. 创建任务 | POST /v1/video_generation |
返回 task_id |
| 2. 查询状态 | GET /v1/query/video_generation?task_id=xxx |
5s 轮询,直到 Success 拿到 file_id |
| 3. 下载视频 | GET /v1/files/retrieve?file_id=xxx |
拿 download_url(有效期 1 小时),流式下载 |
| 方法 | 路径 | 说明 |
|---|---|---|
| * | /api/video_generation |
创建视频任务(图生 / 文生) |
| * | /api/query/video_generation?task_id=xxx |
查询任务状态 |
| * | /api/files/retrieve?file_id=xxx |
获取文件元信息 |
| GET | /api/video_stream?file_id=xxx |
视频流代理(优先本地) |
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/history |
列出全部(自动附加 local_saved、local_bytes 字段) |
POST |
/api/history |
新增单条 / 批量合并({items: [...]}) |
PATCH |
/api/history/:taskId |
更新部分字段 |
DELETE |
/api/history/:taskId?delete_local=boolean |
删除单条(可选是否删本地) |
DELETE |
/api/history?delete_local=boolean |
清空全部 |
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/video_save/:fileId |
下载到 data/videos/<file_id>.mp4(去重) |
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/orphan_videos |
列出(videos 目录有但 history.json 无关联) |
POST |
/api/orphan_videos/clean body {file_ids:[]} |
选择性清理 |
DELETE |
/api/orphan_videos |
全清 |
[
{
"task_id": "404782172164312",
"prompt": "...",
"model": "MiniMax-Hailuo-2.3",
"duration": 6,
"resolution": "768P",
"status": "Success",
"file_id": "404748092842338",
"created_at": 1780377391125,
"finished_at": 1780377470754,
"local_saved": true,
"local_bytes": 2097152,
"cloud_expired": false
}
]字段说明:
| 字段 | 来源 | 说明 |
|---|---|---|
task_id / prompt / model / duration / resolution |
用户输入 | 创建时记录 |
status |
轮询更新 | Queueing / Processing / Success / Fail |
file_id |
API 返回 | 成功时才有 |
created_at / finished_at |
客户端时间戳 | 毫秒 |
local_saved |
后端附加 | 本地是否有 data/videos/<file_id>.mp4 |
local_bytes |
后端附加 | 本地文件大小 |
cloud_expired |
客户端标记 | 刷新失败 / download_url 失效时置 true |
- 临时文件 +
rename原子替换,防止半写入 Promise串行化写锁,避免并发覆盖- 上限 1000 条,超出按
created_at截断 - 同
task_id自动合并(POST 单条覆盖、POST 批量合并计数)
首次访问新版页面时自动执行:把旧 localStorage 数据导入后端 → 合并 / 去重 → 删除 localStorage。
- 用
<script type="module">加载,作用域隔离 main.js入口处用Object.assign(window, {...})把需要的函数挂到 window- HTML 内联
onclick仍能工作(改动最小)
main.js
├─ utils.js (state + 工具)
├─ api.js (History API 客户端) ← utils
├─ components.js (自定义下拉 + dialog) ← utils
├─ modal.js (视频弹窗 + 清理弹窗) ← utils + components
├─ history.js (历史渲染 + 操作) ← utils + api + components + modal
└─ generate.js (生成流程) ← utils + api + components + modal + history
- 数据源仍是原生
<select>,通过Object.defineProperty拦截valuesetter - 这样
sel.value = 'x'既有原生行为,又自动同步自定义 UI - 现有 JS 不用改:
document.getElementById('model').value仍能读到正确值 - 支持键盘 ↑↓ Enter Esc Space
| 键 | 行为 |
|---|---|
ESC |
关闭顶层弹窗(视频 → 清理 → dialog) |
↑ / ↓ |
自定义下拉内切换选项 |
Enter |
确认下拉选择 / dialog 确认 |
| 错误码 | 含义 | 解决 |
|---|---|---|
1002 |
触发限流 | 稍后再试 |
1004 |
API Key 错误 | 检查 Key 是否正确 |
1008 |
余额不足 | 充值 |
1026 |
内容敏感 | 调整 prompt |
2013 |
参数异常 | 检查图片 / 模型 / 时长 / 分辨率组合 |
1027 |
输出内容错误 | 重试 |
Q: 视频生成需要多久? A: 通常 1~5 分钟,10 秒视频可能更久。
Q: 为什么不用 npm install?
A: 项目只用 Node.js 内置模块(http、fs、path)+ 浏览器原生 ES Modules,零依赖,免打包。
Q: 想部署到服务器?
A: 上传整个目录,运行 node server.mjs,开放 3000 端口即可(也支持 PORT 环境变量)。
Q: 想团队共用 / 跨设备同步?
A: 后端 data/ 目录共享即可(同机多用户直接访问,跨机可用 NFS / S3 挂载)。所有数据都持久化在后端,换设备访问同一 server 即可。
Q: 云端视频会过期吗?
A: download_url 有效期 1 小时(平台文档明确),但 file_id 本身保留期未公开(数天到数周不等)。建议生成后立即用「刷新状态」触发本地缓存,永久可用。
Q: 怎么从云端恢复本地视频?
A: 历史里的任务如果 local_saved=false 但 cloud_expired=false,点「刷新状态」会重新查询,成功后自动保存到本地。
Q: 怎么清理 data/videos 里的孤儿文件? A: 历史 ⋯ 菜单 → 「清理未关联视频」,列表式勾选要清的(默认全选,可反选)。
Q: 自定义端口?
A: PORT=8080 node server.mjs
Q: 想让多个用户用同一个 server? A: 当前每个用户用自己的 API Key(在浏览器保存),数据共享在后端。如需多用户隔离,把 API Key 改为后端 session 管理(需扩展)。
⚠️ API Key 仅存浏览器 localStorage,生产环境建议改为后端 session 管理,不要暴露给前端⚠️ 默认绑定0.0.0.0监听,生产环境请用反向代理 + HTTPS⚠️ 没有用户认证,任何能访问 3000 端口的人都能看到所有历史和数据⚠️ data/目录建议加入.gitignore(包含用户视频和元数据)
- 后端:改
server.mjs,保持单文件,清晰加注释 - 前端:
- 改
public/index.html骨架(一般不改) - 改
public/css/base.css/components.css样式 - 改
public/js/<模块>.js对应业务 - 新增 HTML 内联
onclick用的函数 → 必须在main.js挂载到 window
- 改
- 后端日志输出在终端,看
console.log - 前端 devtools 看 Network 标签,所有请求都到
localhost:3000/api/* - ES module 报错时,看 console 的具体模块路径
- 多用户隔离(API Key 后端 session)
- WebSocket 实时推送生成进度
- 视频自动播放上一个生成结果
- 提示词模板库
- 批量生成队列
- Docker 镜像
MIT