InkFrame 是一个用 Flutter Desktop 写的 Local-first AI 影视创作工作站。一个节点画布把 文字 / 图像 / 视频 节点串成可追溯的生成流水线,数据和 API Key 不出本机。
目前版本 v0.1.0-alpha.8,已支持 7 家真实 Provider(阿里万相 4 款 + 快手可灵 2 款 + Google Gemini)。仓库已公开,邀请你加入一期开发。
为什么要再造一个
市面上做 AI 视频生成的工具大致两类:
- 网页 SaaS:Runway / Pika / 即梦——交互直观,但素材、Prompt、Key 全在它们服务器,单 Provider 锁死,价格不透明。
- 节点工作流:ComfyUI——能力极强,但天生为本地模型设计,调云端 API 很拧巴;UI 偏工程师审美,导演/编剧用着不舒服。
我自己做内容创作时遇到的真问题:
- 多 Provider 是常态。同一个项目里,万相生图、可灵生视频、Gemini 做风格参考——它们各自有控制台、各自有计费、各自有 Key 管理界面。要同时用,体验是割裂的。
- 生成是个流水线,不是单点。一段镜头要先文生图当关键帧,再图生视频,再后面接首尾帧 → 视频。每一步的输入、参数、输出、状态都要可追溯。
- Key 不想交给中间层。我的 DashScope Key 直接给阿里就行,没必要再给某个 SaaS 的服务器一份。
- 数据要留在本地。生成的素材、Prompt 历史、项目结构,是创作资产,不是别人家服务器上的临时文件。
这就是 InkFrame 想解决的东西——桌面端、节点画布、直连官方 API、数据全本地。

一句话说清楚它是什么
一个 macOS / Windows 桌面应用,把多家 AI 图像/视频 Provider 整合在一个节点画布里。直连官方 API,本机存数据,单 Dart 代码库跨平台。
技术栈一栏:
| 平台 | macOS 12+ / Windows 10+ |
| 语言 | Dart 3.11+ / Flutter 3.41+ |
| 状态管理 | Riverpod |
| 数据库 | 嵌入式 PostgreSQL 17(应用启动时自动拉起) |
| Key 存储 | macOS Keychain / Windows Credential Manager |
| License | MIT |
| 当前版本 | v0.1.0-alpha.8 |
已经能干什么
7 家真实 Provider,一个统一接口
| ID | 厂商 | 类型 | 用途 |
|---|---|---|---|
wanx-image | 阿里通义万相 | 图像 | 文生图 |
wanx-t2v | 阿里通义万相 | 视频 | 文生视频 |
wanx-i2v | 阿里通义万相 | 视频 | 图生视频(首帧) |
wanx-r2v | 阿里通义万相 | 视频 | 参考图生视频 |
kling-v3 | 快手可灵 | 视频 | 文/图生视频 |
kling-v3-omni | 快手可灵 | 视频 | 多模态视频生成 |
gemini-image | Google Gemini | 图像 | 多模态图像生成 |
fake | 内置 | 图/视频 | 开发测试,返回公共示例素材 |
接入用的是能力分离接口(Submittable / Pollable / Cancellable / KeyValidatable / QuotaAware),哪家支持什么就 mixin 什么——加一个新 Provider 是一个新文件,不动现有代码。
双层并发 + 速率限制
视频生成动辄几十秒一张,并发管理是绕不开的问题:
全局并发上限(性能档位)
Power=1 / Balanced=2 / Performance=3 / Max=4
↕ 取较小值
单 Provider 并发上限(ProviderCapabilities.maxConcurrentJobs)
↕
单 Provider 令牌桶(QPS / Burst)
任务状态机:
pending ──► submitted ──► polling ──► success / error / timeout
│ │
└── cancelled_by_user │
│
任意阶段 ───────────────────────────► cancelled_on_exit(应用退出)
应用退出时所有 in-flight 任务被打上 cancelled_on_exit,下次启动看一眼状态就知道上次哪些跑完了、哪些没跑完,不会有"幽灵任务"。
嵌入式 PostgreSQL 17
应用启动时自动 initdb + pg_ctl start,监听 127.0.0.1,认证 trust——零外部 DB 依赖,一次安装即用。退出时优雅关闭。
数据库里存的是相对路径(images/node-abc.png),运行时由 FileResolverService 拼接数据根目录。这样换一个数据目录,引用不会断。
系统级安全存储
| 构建类型 | 后端 |
|---|---|
| Release | macOS Keychain / Windows Credential Manager |
| Debug (macOS) | ~/InkFrame/config/secrets.dev.json(绕开 ad-hoc 签名构建的 Keychain 限制) |
Key 验证结果在内存缓存 1 小时省额度,用户随时可以手动 Re-validate。
设计 Token + 主题切换
三套语义色(深色 / 浅色 / 高对比),全部走设计 token。Feature 代码里禁止 Color(0xFF...)、禁止硬编码 EdgeInsets.all(N)、禁止硬编码 fontSize——pre-commit hook 直接拦。
i18n 100% 覆盖
中英 ARB 文件 key 集合在 CI 里强制相等。系统 Prompt 也走 i18n,不是只有 UI 文案。
设计哲学:三条不动摇的线
Local-first
项目、生成素材、缩略图、日志全在 ~/InkFrame/。唯一对外的网络流量是你主动调用的那家 Provider 的官方 endpoint。没有中间层,没有埋点,没有第三方遥测。
直连官方 API
InkFrame 进程直接打到 DashScope / Kling / Gemini 的官方接口。这意味着:
- 计费来自官方控制台,账单透明
- 模型迭代不用等 InkFrame 跟进——官方 API 通了就能用
- 没有"InkFrame 服务器宕机你就用不了"这种事
零向后兼容
Schema 改了就改代码,不写 migration helper、不留 deprecated API。应用还在 alpha,向后兼容只会拖慢迭代。等到了 1.0 再谈稳定承诺。
工程纪律:把事做正确的硬规则
这部分是我在主分支保护里写得最严的一块。pre-commit hook 是真的会拦人:
| Hook | 拦什么 |
|---|---|
check-magic-strings.sh | 硬编码 UI 字符串 / 魔术数字 / 状态字符串比较 |
check-inline-styles.sh | Color(0xFF...) / 硬编码 EdgeInsets / BoxShadow |
check-direct-instantiation.sh | Widget/Service 内部 new ConcreteClass() |
check-disposable-cleanup.sh | StreamSubscription / Timer / Controller 没 dispose |
check-i18n-coverage.sh | ARB key 漂移 / 空值 / TODO 翻译 |
check-updated-at.sh | UPDATE 语句缺 updated_at |
check-keybindings.sh | 默认快捷键撞 OS 保留键 |
--no-verify 跳 hook 不被允许。pre-push hook 跑全量测试套件——慢,但是底线。
CI 还会再跑一遍 flutter analyze --fatal-infos 和 flutter test --coverage,覆盖率 Repository 层 ≥ 75%、其余 ≥ 70%。
约束是给未来的自己留的台阶——一年后回来改代码,仍然能立刻跑通、立刻看懂。
五层架构:依赖只往下走
┌─────────────────────────────────────────────────────────────┐
│ Widget Layer lib/features/*/widgets/, theme/ │
│ 只渲染 + 派发事件,零业务 │
├─────────────────────────────────────────────────────────────┤
│ ViewModel Layer lib/features/*/providers/ │
│ Riverpod Notifiers,编排 Service │
├─────────────────────────────────────────────────────────────┤
│ Service Layer lib/services/ │
│ 纯 Dart,零 Flutter import │
├─────────────────────────────────────────────────────────────┤
│ Repository Layer lib/storage/repositories/ │
│ 抽象接口在 core/interfaces/ │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure lib/storage/, lib/providers/ │
│ PostgreSQL / dio / Keychain / ffmpeg │
└─────────────────────────────────────────────────────────────┘
依赖只往下流,上层不能 import 下层之外的层。这不是教科书摆设——它直接决定了"换一个数据库"或"换一种 Key 存储后端"的难度。Repository 层是接口,换实现就是换一个 Riverpod provider 注入。
怎么跑起来
拉代码
| |
第一次跑(不烧额度)
| |
INKFRAME_FAKE_PROVIDERS=1 把所有真实 Provider 换成本地 FakeGenerationProvider,返回公开示例素材。强烈建议第一次跑 UI 用这个——不会因为没配 Key 直接崩。
接真 Key
去掉 INKFRAME_FAKE_PROVIDERS 环境变量,重启,进 Settings → Providers 填:
- DashScope API Key(覆盖 4 款 wanx-*)
- Kling Access Key + Secret Key(覆盖 2 款 kling-*)
- Gemini API Key(覆盖 gemini-image)
测试
| |
邀请你加入一期开发
InkFrame 现在的状态:alpha 已经在跑,核心架构定型,正在从 alpha 推向 beta。
下面是 ROADMAP 里 Help Wanted 的方向,按贡献门槛由低到高排:
📚 文档 / 翻译(最低门槛,1-2 小时即可)
- README / CONTRIBUTING 翻译到第 3 语言(日 / 韩 / 西班牙语优先)
app_zh.arb中文翻译 review——目前一人翻的,需要母语者抽查硬翻味app_en.arb英文 polish——同上,需要 native English speaker
🔌 新 Provider 接入(中等门槛,1-2 天)
不需要懂整个代码库,读 docs/PROVIDER-API.md 实现一个文件就行:
| Provider | 类型 | 优先级 |
|---|---|---|
| Stable Diffusion (本地 ComfyUI) | image | High |
| Runway Gen-3 / Gen-4 | video | High |
| Midjourney (Discord API) | image | Medium |
| OpenAI DALL-E 3 / GPT-Image | image | Medium |
| Pika Labs | video | Medium |
| Luma Dream Machine | video | Medium |
🎨 Canvas / 编辑器(需要懂 Flutter)
- Undo/Redo 完整覆盖——当前部分操作未入 undo stack
- 节点 Group / Collapse——大画布折叠
- 画布缩放性能优化——节点 > 200 时 frame drop
💻 平台 / 跨平台
- Windows 烟测自动化——macOS 已手动跑,Windows 缺 reproducible 流程
- Linux 桌面端可行性评估——社区有需求即启动
🧪 测试基建
- Golden test 补齐——当前
goldenjob 是占位 - E2E:完整 script → storyboard → export 流程
- Coverage 70% 门槛之上的提升路径
协作约定,先看再动
不要直接闷头 PR 大功能——这条写在 ROADMAP 第一句。维护者一个人对齐 scope 比合 PR 快得多,先开 issue 或在 Discussions 留言"我来",我会在 24-72h 内 ack。
分支模型简化版 git-flow:
main 只接 release-quality,每次合入打 tag
└─ dev 集成主干,所有 feature/fix 进这里
├─ feature/<scope>-<kebab-desc>
├─ fix/<issue-id>-<desc>
└─ docs/<desc>
线性历史强制——main / dev 禁止任何 merge commit。feature 走 Squash merge,release/hotfix 走 Rebase merge。GitHub Branch Protection 已开 Require linear history,非线性 push 会被远端直接拒。
第一次贡献找 good first issue 标签。
链接
- 仓库:github.com/KerroKapple/InkFrame
- Roadmap:ROADMAP.md
- Provider 接入文档:docs/PROVIDER-API.md
- 架构总览:docs/ARCHITECTURE.md
- Discussions:github.com/KerroKapple/InkFrame/discussions
最后
我做这个项目最初的动机很简单——自己想用一个能把多家 AI 视频 Provider 整合到一个工作流里、还不用把 Key 交给中间层的桌面应用。市面没有,那就自己写一个。
写到 alpha.8,从单画布、单 Provider,长到现在节点系统、双层并发、嵌入式 PG、系统 Keychain、双语 i18n、设计 token——一个人能把骨架立起来,但真正的多样性需要更多人加入。Provider 越多越好用,平台越广越好用,本地化越细越好用。
如果你做内容创作、做独立产品、用 Flutter、用 AI 视频——欢迎来 issue 区拍砖、来 Discussions 提需求、来 PR 一个 Provider 实现。
Star 一个仓库 30 秒,但能告诉一个独立维护者「这事值得做」。 ⭐ 给 InkFrame 加 Star
