
简介这是一套基于TypeScript开发的前端应用源码对应在线Sora AI视频展示平台面向Web前端开发者与AI应用学习者。项目完整实现了视频分类展示、多语言支持、用户系统等模块前端界面可直接部署体验同时也为后续接入OpenAI Sora视频生成API预留了清晰的服务层架构。压缩包共63个文件、约2.63MB以ts、tsx源代码为主包含33个TypeScript相关文件另有json配置、svg图标、sql数据库脚本、Dockerfile与nginx部署配置等附有中英文说明文档目录结构规范便于二次开发。目前已有232人学习下载。通过源码可学习主流技术栈的工程化组织方式包括Next.js路由设计、类型定义、国际化方案、数据库初始化脚本以及容器化部署配置适合有一定React基础、希望了解AI视频方向产品落地细节的开发者。1. Sora AI 视频生成器TypeScript源码先看清它是什么再动手把一段文字变成一段可播放的视频听起来是模型层的魔法但落到工程上你会发现大部分活都压在生成器外壳上。Sora AI 视频生成器TypeScript 源码这类项目并不会从零训练一个扩散模型而是用 TypeScript 把“提示词处理、参数下发、任务轮询、结果落盘”这一整条链路组织成可维护的工程。我在拿到这类源码时的第一反应不是急着npm install而是先确认三件事底层模型服务是什么、任务走同步还是异步、类型定义有没有把参数边界钉死。适合谁前端或 Node 后端想给产品接 AI 视频能力的人以及想从一套完整 TS 工程里学模块划分的人。如果你的诉求是“本地一键生成高清视频”那这个源码只是外壳你还得备一个能跑视频扩散模型的后端服务。2. 跑通最小链路从 .env 配置到第一段视频落盘拿到源码第一步先别急着读业务代码把工程结构捋一遍。TypeScript 项目最怕的就是类型定义分散、模块互相循环引用这个生成器在结构上一般会做得很规矩配置、类型、核心逻辑、上游适配完全分离。先花五分钟看懂目录后面能省一个小时。2.1 先读懂 src 目录五个模块各管什么src/ ├── config/ # 读取 .env把字符串参数解析成结构化配置 ├── types/ # 所有请求/响应/任务的类型定义与声明文件 ├── core/ # 生成管线编排校验、提交、轮询、落盘 ├── adapters/ # 对接不同视频模型服务的适配器 └── utils/ # 日志、文件命名、ffmpeg 拼接等工具这种划分的核心思路是“适配器隔离上游差异”。视频生成的底层服务五花八门有人用本地 ComfyUI 工作流有人用云端 REST 接口还有人直接调用 Python 侧封装好的推理脚本。如果没有适配器这一层core 里的轮询逻辑就得为每种上游写一份代码很快就烂掉。types单独成目录是为了让前后端、CLI、HTTP 服务共享同一套类型契约改参数结构时编译器会帮你找出所有受影响的地方。utils里最值得关注的是 ffmpeg 相关工具模型生成的往往是原始帧序列或单段 MP4拼接、转帧率、加字幕全靠它。2.2 最小命令一条指令出片把环境配好后最小出片命令长这样# 安装依赖 npm install # 复制环境变量模板并填写模型服务地址 cp .env.example .env # 执行生成输出到 ./output/cat_window.mp4 npm run generate -- \ --prompt 一只橘猫蹲在窗台看雨镜头缓缓推近 \ --negative-prompt 模糊,变形,文字水印 \ --width 960 --height 544 \ --frames 48 --fps 16 \ --steps 20 --cfg 7.5 --seed 42 \ --out ./outputnpm run generate背后的脚本入口通常指向src/cli.ts它做的事情是解析命令行参数、合并.env里的默认配置、调用core/generate.ts执行生成。宽高取 960×544 是常见下限兼顾画面清晰度和显存占用frames 48配合fps 16正好是 3 秒视频这个时长适合验证 prompt 效果不会让 GPU 排队太久。seed 42固定下来调参时才能对比同一段画面的前后差异。核心的循环逻辑并不复杂// core/generate.ts 简化版提交任务 → 轮询状态 → 落盘 const task await adapter.submit(req); // 提交后立刻拿到 taskId while (true) { const status await adapter.poll(task.taskId); // 轮询上游状态 if (status.state done) { await downloadVideo(status.videoUrl, outPath); // 拿到结果 break; } if (status.state failed) { throw new Error(status.error); } await sleep(2000); // 轮询间隔 }这段逻辑的要点是“同步提交、异步轮询”。视频扩散模型的采样是以分钟计的一个 3 秒片段在普通 GPU 上可能要跑 2~5 分钟HTTP 长连接根本撑不住所以上游服务普遍采用提交后返回任务 ID 的模式。轮询间隔 2 秒是个折中太短容易把上游接口配额打满太长会让人感觉进度迟钝。如果你接的上游对轮询频率有限制把sleep(2000)改成sleep(3000)就行但进度回调的延迟会相应变高。2.3 视频模型服务怎么接适配器是关键跑通之后你迟早要换上游这时候适配器的作用就出来了。统一入口定义成接口// adapters/types.ts 定义统一入口 export interface VideoModelAdapter { submit(req: VideoGenRequest): Promise{ taskId: string }; poll(taskId: string): PromiseGenStatus; }submit负责把统一参数翻译成上游认识的请求格式poll负责把上游的状态响应翻译回统一的GenStatus。翻译这个词是重点不同上游的字段名千奇百怪本地 ComfyUI 叫prompt_id云端服务叫job_id还有叫taskId的。适配器做的就是把这些差异消化在内部。// adapters/comfyui.ts 对接本地 ComfyUI 工作流的适配器骨架 export class ComfyUIAdapter implements VideoModelAdapter { constructor(private baseURL: string) {} async submit(req: VideoGenRequest) { const res await fetch(${this.baseURL}/prompt, { method: POST, body: JSON.stringify({ prompt: 文生视频工作流 ${req.prompt}, width: req.resolution.width, height: req.resolution.height, frame_count: req.frames, cfg: req.cfg, seed: req.seed, }), }); if (!res.ok) throw new Error(提交失败: ${res.status}); return { taskId: (await res.json()).prompt_id }; } }常见做法是本地视频生成服务监听http://127.0.0.1:8188适配器把baseURL指向它即可。参数映射时注意单位差异比如有些服务用frame_count有些用num_frames还有些直接把fps写死成 16 不让你改。做适配器时先抓一次上游的请求日志对照着映射字段比对着文档猜要快得多。参数建议范围作用分辨率512×512 ~ 960×544越低越省显存越高细节越好帧数24~80决定视频时长帧数×fps秒数fps12~24低于 12 会明显卡顿steps16~28太低画面粗糙太高收益递减cfg4~9控制提示词跟随度过高画面过曝seed任意整数固定后可复现同一画面3. 类型系统设计用 .d.ts 和接口继承把参数边界钉死这一章是 TypeScript 工程的核心价值所在。生成器的参数有十几个prompt、分辨率、帧数、采样步数、CFG、seed 各有边界如果类型定义写得松改一处漏一处跑起来就是黑匣子报错。类型系统设计得好的工程参数错误在编译期就被拦住了。3.1 types 文件夹的声明文件怎么组织、如何使用types文件夹里放的一般是.d.ts声明文件只写类型不写实现。核心的video-gen.d.ts通常长这样// types/video-gen.d.ts 核心声明文件 export interface VideoGenRequest { prompt: string; negativePrompt?: string; resolution: Resolution; frames: number; fps: number; steps: number; cfg: number; seed: number; } export interface Resolution { width: number; height: number; } export type GenState queued | running | done | failed; export interface GenStatus { taskId: string; state: GenState; progress: number; // 0 ~ 100 videoUrl?: string; error?: string; }编写.d.ts的要点是用interface描述对象结构用type定义联合类型可空字段必须显式加?。这里最容易犯的错是把videoUrl写成必填结果失败任务返回的响应里根本没有这个字段运行时就是undefined一路穿透下去。使用方式是在业务代码里用import type导入// core/generate.ts 导入类型 import type { VideoGenRequest } from ../types/video-gen; import type { VideoModelAdapter } from ../adapters/types;用import type而不是import是因为编译后这些类型会被完全擦除不产生运行时依赖。这个细节在大型工程里会影响增量编译速度也让文件之间的依赖关系更清晰。如果你发现某个.d.ts文件被到处引用但没人真正 import 它多半是类型定义放错了位置应该挪到离业务最近的地方而不是堆在一个全局types.ts里。3.2 接口继承与重写任务扩展不破坏旧调用生成器不止一种生成模式基础文生视频、带运动控制的文生视频、图生视频它们的参数有交集也有差异。接口继承在这里特别好用// 基础参数 视频扩展参数继承关系清晰 export interface BaseGenParams { prompt: string; seed: number; steps: number; } export interface VideoGenParams extends BaseGenParams { frames: number; fps: number; motionStrength: number; // 新增的运动强度控制 }VideoGenParams继承了BaseGenParams的 prompt、seed、steps又加了自己的 frames、fps、motionStrength。这样做的直接好处是基础参数新增字段时所有继承接口自动获得不用逐个改调用方如果只需要基础参数直接传BaseGenParams类型不会因为多了字段被编译器嫌弃。注意interface继承只适用于对象结构联合类型要用type的交叉类型// 联合类型场景下用交叉类型 type WithMotion BaseGenParams { motionStrength: number };静态方法的继承和重写也是生成器里常用的模式。比如配置解析基类默认了采样步数子类按模型能力覆盖// 配置解析基类静态方法按子类覆盖 export class BaseConfig { static defaultSteps(): number { return 20; } } export class VideoGenConfig extends BaseConfig { static override defaultSteps(): number { return 28; } }override关键字是 TypeScript 4.3 之后引入的显式标记“我就是故意覆盖父类方法”防止父类改了方法名子类还静默继承。生成器里用静态默认值是因为 CLI 和 HTTP 服务都需要读默认参数实例方法还得先new一个对象才能拿静态方法直接VideoGenConfig.defaultSteps()就能取到在config模块里做参数合并时非常顺手。3.3 运行时校验类型检查救不了用户输入类型系统只在编译期有效用户从命令行传进来的字符串、HTTP 请求里 JSON 里的数字全都是运行时才暴露真面目。所以生成器在提交任务前必须做一道运行时校验// utils/validate.ts 极简参数校验 export function validateVideoGenRequest(req: VideoGenRequest): string[] { const errors: string[] []; if (req.frames 8 || req.frames 200) errors.push(frames 超出 8~200 范围); if (req.fps 8 || req.fps 30) errors.push(fps 超出 8~30 范围); if (req.steps 10 || req.steps 40) errors.push(steps 推荐 10~40); if (req.cfg 1 || req.cfg 15) errors.push(cfg 超出 1~15 范围); if (!Number.isInteger(req.seed)) errors.push(seed 必须为整数); return errors; }这段校验的哲学是“早失败比晚失败好”。如果等任务提交上去跑到一半才发现fps100上游服务可能直接报错也可能默默接受然后返回一段 2 秒的快进视频那才是真的翻车。把校验放在core层、 adapter 提交之前等errors数组非空就直接把错误抛给调用方。更重的方案是用 zod 做 schema 校验但手写校验函数在小工程里更轻也容易针对上游的边界条件做定制。我见过不少项目跳过了这步结果出问题时排查了半天最后发现是cfg传了 0模型直接放飞自我生成了一段噪点视频。4. 参数调优与避坑5 个常见翻车现场的排查笔记视频生成器的调参比文本生成玄学得多。文本生成看十几个 token 就能判断好坏视频得等几分钟才能看到结果一次翻车就是几分钟白等。这一章把我自己的踩坑记录整理成固定格式每一条都是花钱买来的血泪经验。4.1 优先动哪个参数调参顺序和默认基线我调视频生成参数有固定顺序先定分辨率和帧数再动 steps然后动 seed 换花样最后才碰 cfg。原因很简单分辨率和帧数决定显存占用和出片时长换个分辨率光排队就多十分钟steps 影响的是画面质量大部分模型在 20~28 步之间差异已经很小cfg 是影响最大的参数也是最好玩坏的参数从 7.5 往上加画面会在某个临界点突然过曝成一片白。参数推荐基线调整方向steps20画面粗糙时加到 28cfg7.5画面过曝时降到 5fps16运动不流畅时加到 24seed42换构图的低成本方式4.2 避坑任务提交后一直 pending现象npm run generate提交成功了taskId 也返回了但轮询两个小时状态始终停在queued。不是崩溃也不是报错就是不动。原因分两种。第一种是上游 GPU 服务前面排了一堆任务你的请求在队列里等着第二种更阴险——上游服务收到请求后参数校验失败但把失败当成“静默丢弃”状态永远停在初始值。第一种原因看上游服务日志就能确认队列长度第二种原因就得自己排查。解决提交后加一个超时机制比如 120 秒内状态没变成running直接判定失败并打出上游返回的原始响应体。绝大多数 silent drop 都会在响应体里留下蛛丝马迹哪怕是空 JSON 也值得看一眼。另外提交参数越严越不容易触发静默丢弃第 3 章的运行时校验在这里能帮你挡住一半问题。4.3 避坑视频画面花屏或瞬间跳变现象出片了但某个镜头里物体形状在帧与帧之间突变或者整个画面周期性发花像老式电视信号不好。这比全黑全白更让人崩溃因为看起来像能用的素材但一放到大屏就穿帮。原因视频扩散模型分步去噪时帧与帧之间的潜空间连续性断了。最常见的诱因是cfg过高。CFG 是“提示词跟随度”调太高会让每一帧都往提示词的方向硬拉相邻帧被拉向不同的文本语义点画面就跳变。另一个诱因是分辨率超过模型训练分辨率太多模型没见过这种尺度。解决先把cfg降到 5 左右这是大部分视频模型的安全区再把seed固定下来用同一 prompt 连续复现几次看跳变是否随机出现。如果固定 seed 后跳变消失说明是采样随机性不用管如果固定 seed 仍在跳检查分辨率是否越界等比缩到模型支持的最大分辨率区间内。4.4 避坑显存 OOM 报错现象跑了三五条 prompt前面几条好好的突然报CUDA out of memory服务进程直接崩掉。重启之后又正常跑几条又崩。原因视频生成任务会同时跑文本编码、潜空间扩散、VAE 解码多个阶段峰值显存出现在扩散采样阶段而这一步的占用和“分辨率×帧数”强相关。你以为单条任务在显存线内但上游服务可能内置了并发批次两条任务同时采样就直接超了。本地部署时最容易踩这个坑。解决把并发数压到 1也就是上游服务只允许同时处理一个生成任务其他提交排队。如果你用的是自己的适配器在submit里加一个互斥锁同一时刻只允许一个任务在跑。另外把分辨率从 960×544 降到 768×432帧数从 48 降到 32显存峰值能掉近一半。OOM 是最不值得花时间调参的问题参数降一档比什么都管用。4.5 避坑进度回调丢失或卡在 99%现象轮询进度一路顺畅爬到 99%然后整整五分钟不动你以为模型死了正要放弃视频又突然落盘了。原因99% 说明扩散采样已经完成剩下的活是 VAE 解码视频帧和编码成 MP4。这一步对 CPU 的负载很高耗时可能和采样本身差不多。有些服务把状态改成done是在视频文件完全生成后但进度百分比只统计了采样部分所以你会看到 99% 停顿很久。如果是异步回调模式WebSocket 连接不稳定也可能丢消息。解决轮询模式在状态为running且进度大于 95% 时把轮询间隔从 2 秒放宽到 5 秒降低对上游接口的无效请求压力。同时在done状态返回的视频 URL 做好下载超时建议 5 分钟超过就重试下载而不是重新生成任务。我在本地部署时遇到过一次回调丢失排查了半天发现是服务端把 WebSocket 消息发错了 topic这类问题从上游日志看比从客户端猜要快得多。5. 进阶把生成器包装成 HTTP 任务服务并编排多模型协作能跑命令行还不够真要给团队或产品用得把生成器包成 HTTP 服务。常见做法是两个接口POST /tasks提交生成任务并返回 taskIdGET /tasks/:id查询状态和结果 URL。这样调用方不用知道轮询细节前端拿到 taskId 自己定时查就行。我在做这个服务时踩过的最大坑是忘了给任务加过期清理队列里堆积了几百个已完成任务内存占用一路飙到 2GB后来加了 24 小时自动清理和定时任务才稳住。更进一步如果你手里有多套模型能力可以用 TypeScript 做个简单的编排层先用一个大模型把用户需求拆成分镜脚本再把每个分镜提交给视频生成器逐段生成最后用 ffmpeg 拼接。这种多模型协作的思路我在实现时发现最关键的是做好上下文传递——每个分镜只生成 3~5 秒片段拼接时统一分辨率、帧数和色彩空间不然最后出来的视频像几个不相关片段硬拼在一起。另一个经验是分镜描述不要超过两句提示词越长模型越容易在细节上跑偏。最后说一个我的个人习惯每次调出新参数组合先把命令存成一个 shell 脚本再跑。不要裸敲命令因为视频生成等待时间长等你发现画面不对想回头看上次用的参数时终端早就翻页翻得看不见了。脚本里写好参数注释下次直接改数值重跑省得从历史记录里捞命令。做生成器这类黑匣子工具凡是能落盘的参数、日志、中间产物一律落盘这是我从无数次后悔药吃出来的教训。希望帮到你。本文还有配套的精品资源点击获取