ima2-gen 2.0.4 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +6 -2
  2. package/bin/commands/video.js +26 -11
  3. package/docs/API.md +91 -5
  4. package/docs/CLI.md +8 -7
  5. package/docs/README.ja.md +86 -8
  6. package/docs/README.ko.md +31 -16
  7. package/docs/README.zh-CN.md +84 -9
  8. package/docs/migration/runtime-test-inventory.md +4 -1
  9. package/lib/agentGenerationPlanner.js +2 -2
  10. package/lib/agentImageVideoGen.js +19 -2
  11. package/lib/agentPlannerModel.js +1 -1
  12. package/lib/agentToolManifest.js +2 -2
  13. package/lib/capabilities.js +4 -2
  14. package/lib/grokVideoAdapter.js +22 -11
  15. package/lib/grokVideoCanvas.js +1 -1
  16. package/lib/imageModels.js +31 -4
  17. package/package.json +2 -1
  18. package/routes/video.js +6 -1
  19. package/skills/ima2/SKILL.md +6 -6
  20. package/ui/dist/.vite/manifest.json +12 -12
  21. package/ui/dist/assets/{AgentWorkspace-CMP9XZrL.js → AgentWorkspace-CKjpho46.js} +1 -1
  22. package/ui/dist/assets/{CardNewsWorkspace-DtOwgYzf.js → CardNewsWorkspace-BW52bztP.js} +1 -1
  23. package/ui/dist/assets/{GenerationRequestLogPanel-CB1i5dWd.js → GenerationRequestLogPanel-BPc4gDcY.js} +1 -1
  24. package/ui/dist/assets/{NodeCanvas-DwX2jRHf.js → NodeCanvas-d7dH6h99.js} +1 -1
  25. package/ui/dist/assets/{PromptBuilderPanel-B3VhUc5u.js → PromptBuilderPanel-CWbdfzpF.js} +1 -1
  26. package/ui/dist/assets/{PromptImportDialog-4FQd2YVS.js → PromptImportDialog-DORdifOt.js} +2 -2
  27. package/ui/dist/assets/{PromptImportDiscoverySection-CTNr6ft3.js → PromptImportDiscoverySection-XA3ZzwAo.js} +1 -1
  28. package/ui/dist/assets/{PromptImportFolderSection-zCJ1lBH8.js → PromptImportFolderSection-BjALahD0.js} +1 -1
  29. package/ui/dist/assets/{PromptLibraryPanel-B784T1KR.js → PromptLibraryPanel-Co9xIawD.js} +2 -2
  30. package/ui/dist/assets/{SettingsWorkspace-BpM6lWH-.js → SettingsWorkspace-BS_gSTTl.js} +1 -1
  31. package/ui/dist/assets/index-TRRNueAb.js +23 -0
  32. package/ui/dist/assets/{index-CwAzer33.js → index-bMxZBOnc.js} +1 -1
  33. package/ui/dist/index.html +1 -1
  34. package/ui/dist/assets/index-BzjrmmIG.js +0 -23
package/docs/README.ko.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  `ima2-gen`은 무료 ChatGPT와 SuperGrok만으로 이미지와 영상을 만드는 로컬 AI 스튜디오입니다.
14
14
 
15
- 전역 설치 후 ChatGPT 또는 Grok OAuth로 로그인하면 바로 시작됩니다. API 키 없이 이미지 생성, 비디오 생성, 노드 분기, 멀티모드 배치, Canvas 정리까지 전부 가능합니다.
15
+ 전역 설치 후 ChatGPT 또는 Grok OAuth로 로그인하면 바로 시작됩니다. 기본 OAuth 경로는 API 키 없이 동작하며, 선택적으로 API 공급자(`api`, `grok-api`, `gemini-api`, `agy`)도 지원합니다.
16
16
 
17
17
  ![프롬프트 작성창, 생성 이미지, 모델 표시, 결과 메타데이터가 보이는 ima2-gen 클래식 생성 화면](../assets/screenshots/classic-generate-light.png)
18
18
 
@@ -26,6 +26,13 @@ ima2 serve
26
26
 
27
27
  그다음 `http://localhost:3333`을 엽니다.
28
28
 
29
+ CLI에서 영상 생성:
30
+
31
+ ```bash
32
+ ima2 video "고양이가 피아노 치는 장면" --duration 5 --resolution 720p
33
+ ima2 video "이 장면을 애니메이션으로" --ref photo.png --duration 10
34
+ ```
35
+
29
36
  `3333`이 이미 사용 중이면 다음 사용 가능한 포트로 열리고 실제 URL은 `~/.ima2/server.json`에 기록됩니다. 포트를 추측하지 말고 터미널에 출력된 URL이나 `ima2 open`을 사용하세요.
30
37
 
31
38
  > **npx로 실행하고 싶다면?** [NPX_QUICKSTART.md](NPX_QUICKSTART.md)를 참고하세요.
@@ -49,7 +56,7 @@ irm https://lidge-jun.github.io/ima2-gen/install-windows.ps1 | iex
49
56
  curl -fsSL https://lidge-jun.github.io/ima2-gen/install-linux.sh | bash
50
57
  ```
51
58
 
52
- 각 스크립트가 nvm/fnm/brew/winget을 감지하고, 없으면 Node LTS를 자동 설치한 뒤, ima2-gen을 설치합니다.
59
+ 각 스크립트가 nvm/fnm/brew/winget을 감지하고, 없으면 Node LTS를 자동 설치한 뒤, ima2-gen을 설치합니다. 잔여 프로세스 정리도 자동으로 처리합니다.
53
60
 
54
61
  ### 업데이트
55
62
 
@@ -61,15 +68,12 @@ npm install -g ima2-gen@latest
61
68
 
62
69
  v1.1.22부터 Ctrl+C가 DB, 소켓, 자식 프로세스를 깨끗하게 정리합니다. 이전 버전이거나 Windows에서 `EBUSY` 에러가 나면 위의 설치 스크립트를 다시 실행하세요 — 잔여 프로세스를 자동으로 정리합니다.
63
70
 
64
- ## v1.1.22 주요 변경
71
+ ## v2.0.4 주요 변경 (요약)
65
72
 
66
- - **스토리보드 모드**: 컴포저 토글로 인물/장면 연속성 유지. 이미지와 비디오 파이프라인 모두 지원.
67
- - **플래너 모델 선택**: 비디오 설정 또는 `--planner-model` CLI 플래그로 Grok 플래너 모델 변경 가능.
68
- - **비디오 프레임 복사**: 처음/중간/마지막 프레임 추출 버튼.
69
- - **다중 인물 대사**: 플래너가 인물을 이름이 아닌 외형(옷, 체형, 소품)으로 구분.
70
- - **Graceful shutdown**: Ctrl+C가 DB, 소켓, 자식 프로세스를 정리 — Windows EBUSY 해결.
71
- - **크로스플랫폼 설치 스크립트**: macOS/Windows/Linux 원클릭 설치.
72
- - **Atomic sidecar writes**: 메타데이터 파일 크래시 방지.
73
+ - **OIDC npm publish**: GitHub Release `latest`, `preview` 브랜치 `preview` dist-tag
74
+ - **결과 메타데이터 인스펙터** (#108), **생성 요청 로그** (#95, dev UI)
75
+ - **OAuth size directive**: LANDSCAPE/PORTRAIT/SQUARE orientation 강조
76
+ - **추가 공급자**: `grok-api`, `agy`, `gemini-api` (Vertex 우선)
73
77
 
74
78
  ### 설정
75
79
 
@@ -80,7 +84,7 @@ v1.1.22부터 Ctrl+C가 DB, 소켓, 자식 프로세스를 깨끗하게 정리
80
84
  3. **Both** — GPT + Grok 둘 다 (전체 기능)
81
85
  4. **Web setup** — 웹 UI에서 전체 설정
82
86
 
83
- 영상 생성은 Grok OAuth(2번 또는 3번)가 필요합니다.
87
+ 영상 생성은 Grok OAuth(2번 또는 3번)가 필요합니다. GPT OAuth만 설정한 뒤 영상을 추가하려면 `ima2 grok login`을 별도로 실행하세요.
84
88
 
85
89
  ## 무엇을 할 수 있나요?
86
90
 
@@ -88,9 +92,10 @@ v1.1.22부터 Ctrl+C가 DB, 소켓, 자식 프로세스를 깨끗하게 정리
88
92
  - **Node mode**: 마음에 드는 이미지를 여러 방향으로 분기해 실험합니다.
89
93
  - **Multimode batches**: 하나의 프롬프트에서 여러 후보 슬롯을 동시에 만들고, 가장 좋은 결과에서 이어갑니다.
90
94
  - **Canvas Mode**: 확대/이동, 주석, 지우개, 배경 정리, 투명 체크보드 미리보기, alpha/matte export를 지원합니다.
91
- - **Video 생성**: 텍스트, 이미지, 또는 여러 레퍼런스에서 짧은 영상을 만듭니다. 기획→제출→진행률→완료까지 실시간으로 보여줍니다.
92
- - **Local gallery**: 생성물을 컴퓨터에 저장하고 세션별 히스토리로 봅니다.
93
- - **Reference images**: 레퍼런스를 드래그, 붙여넣기, 파일 선택으로 추가합니다. 이미지는 업로드 전에 자동 압축됩니다.
95
+ - **Video 생성**: 텍스트, 이미지, 또는 여러 레퍼런스에서 짧은 영상을 만듭니다. SSE로 기획→제출→진행률→완료를 실시간 표시합니다. 생성된 영상에서 First/Mid/Last 프레임 복사 버튼으로 키프레임을 추출할 수 있습니다.
96
+ - **Storyboard mode**: 컴포저에서 스토리보드 모드를 켜면 연속 프레임의 인물·장면 연속성을 유지합니다. 이미지와 영상 생성 모두 지원합니다.
97
+ - **Local gallery**: 생성물을 컴퓨터에 저장하고 세션별 히스토리로 봅니다. 기본적으로 현재 세션만 보이며 All Images 토글로 전체 히스토리를 볼 수 있습니다. 각 이미지의 생성 시간·reasoning effort가 메타데이터에 기록됩니다.
98
+ - **Reference images**: 레퍼런스를 드래그, 붙여넣기, 파일 선택으로 추가합니다. 이미지 최대 5장, 영상 최대 7장. 큰 이미지는 업로드 전에 자동 압축됩니다.
94
99
  - **Prompt library imports**: 로컬 prompt pack, GitHub folder, curated GPT-image hint를 내장 prompt library로 가져옵니다.
95
100
  - **Mobile shell**: 작은 화면에서는 app bar, compose sheet, compact settings toggle로 조작합니다.
96
101
  - **Observable jobs**: 진행 중인 작업과 최근 완료된 작업을 request ID로 추적합니다.
@@ -106,12 +111,15 @@ v1.1.22부터 Ctrl+C가 DB, 소켓, 자식 프로세스를 깨끗하게 정리
106
111
  - `provider: "oauth"`는 로컬 Codex OAuth 프록시를 사용합니다.
107
112
  - `provider: "api"`는 OpenAI Responses API의 `image_generation` 도구를 사용합니다.
108
113
  - `provider: "grok"`는 번들 `progrok`을 `127.0.0.1:18645`에서 띄우고, xAI Web Search와 플래너(기본: `grok-4.3`, 설정 또는 `--planner-model`로 변경 가능)를 거친 뒤 xAI Images API를 호출합니다.
114
+ - `provider: "grok-api"`는 `XAI_API_KEY`로 xAI Images API를 직접 호출합니다 (progrok OAuth 없음).
115
+ - `provider: "agy"`는 로컬 Antigravity CLI(`agy -p`)로 Gemini `nano-banana-2` 이미지를 생성합니다 (`IMA2_AGY_BIN`).
116
+ - `provider: "gemini-api"`는 Google Generative Language API 또는 Vertex AI를 사용합니다 (`GEMINI_API_KEY` / `VERTEX_SERVICE_ACCOUNT_JSON`; 둘 다 있으면 Vertex 우선).
109
117
 
110
118
  Grok은 Classic, Node, Agent 흐름을 지원합니다. Classic 레퍼런스, Node 부모 이미지, Agent 현재 이미지가 있으면 최종 Grok 호출은 xAI image edit 경로로 전환되어 image-to-image 맥락을 유지합니다. 기본 모델은 `grok-imagine-image`이고, `quality: "high"`에서는 `grok-imagine-image-quality`를 사용합니다.
111
119
 
112
- Grok video 생성(T2V/I2V/ref2v) v1.1.16부터 사용 가능합니다. 텍스트 프롬프트, 단일 이미지, 또는 최대 7장의 레퍼런스에서 짧은 영상을 만들 있으며, 실시간 진행률 스트리밍을 지원합니다.
120
+ Grok video `grok-imagine-video`(기본) 또는 정식 `grok-imagine-video-1.5`를 사용합니다. 기존 `grok-imagine-video-1.5-preview` 문자열은 호환 alias로 계속 받습니다. 레퍼런스 수에 따라 T2V(0), I2V(1), Ref2V(2-7, 최대 10초)가 자동 선택되며, 1080p는 `grok-imagine-video-1.5` 프롬프트 전용 T2V와 단일 이미지/프레임 I2V에서 활성화됩니다. 프롬프트 전용 1.5 T2V는 upstream 요청 전에 내부 캔버스 I2V shim을 사용합니다. 1.5는 Ref2V, V2V edit, extension 지원을 추가하지 않으므로 해당 경로는 기본 모델만 사용합니다. duration(1-15s), resolution(480p/720p/지원 시 1080p), aspect ratio 컨트롤을 제공합니다.
113
121
 
114
- 설정 화면에 **API key provider available**이나 **Grok provider available**이 보이면 해당 공급자가 감지됐고 생성 요청에 사용할 수 있다는 뜻입니다.
122
+ 설정 화면의 QuotaCard에 Grok billing `$used/$limit` 바와 **Switch Account** 버튼(`POST /api/auth/switch`)이 표시됩니다.
115
123
 
116
124
  ![GPT OAuth 활성화와 API 키 비활성 상태를 보여주는 설정 화면](../assets/screenshots/settings-oauth-generation.png)
117
125
 
@@ -189,6 +197,7 @@ Settings 워크스페이스는 계정, 모델, 테마, 언어 설정을 생성
189
197
  | `ima2 setup` | 인증 설정 다시 구성 |
190
198
  | `ima2 status` | config와 OAuth 상태 확인 |
191
199
  | `ima2 doctor` | Node, 패키지, config, auth 진단 |
200
+ | `ima2 doctor image-probe [--json]` | 이미지 없이 진단용 sanitized probe 실행 |
192
201
  | `ima2 open` | 웹 UI 열기 |
193
202
  | `ima2 reset` | 저장된 config 삭제 |
194
203
 
@@ -201,6 +210,7 @@ Settings 워크스페이스는 계정, 모델, 테마, 언어 설정을 생성
201
210
  | `ima2 gen <prompt>` | CLI에서 이미지 생성 |
202
211
  | `ima2 edit <file> --prompt <text>` | 기존 이미지 수정 |
203
212
  | `ima2 multimode <prompt>` | 멀티 이미지 SSE 생성 |
213
+ | `ima2 video <prompt>` | Grok 영상 생성 (SSE 진행률) |
204
214
  | `ima2 ls [--session <id>] [--favorites]` | 로컬 히스토리 보기 |
205
215
  | `ima2 show <name> [--metadata]` | 생성 파일 열기 |
206
216
  | `ima2 prompt ls -q <검색어>` | 프롬프트 라이브러리 검색 |
@@ -239,6 +249,11 @@ environment variables > ~/.ima2/config.json > built-in defaults
239
249
  | `IMA2_LOG_LEVEL` | `info` | 일반 `serve`는 `info`, dev 모드는 `debug`. `debug`, `info`, `warn`, `error`, `silent` 지원 |
240
250
  | `IMA2_INFLIGHT_TERMINAL_TTL_MS` | `300000` | 디버그용 최근 작업 보존 시간 (5분) |
241
251
  | `OPENAI_API_KEY` | — | `provider: "api"` Responses 이미지 경로와 보조 기능용 API 키 |
252
+ | `XAI_API_KEY` | — | `provider: "grok-api"` 직접 xAI Images API 경로 |
253
+ | `GEMINI_API_KEY` | — | `provider: "gemini-api"` Generative Language API |
254
+ | `VERTEX_SERVICE_ACCOUNT_JSON` | — | Vertex AI 서비스 계정 JSON (API 키보다 우선) |
255
+ | `IMA2_AGY_BIN` | PATH의 `agy` | `provider: "agy"` 바이너리 경로 |
256
+ | `IMA2_MAX_PARALLEL` | `24` | 서버 전역 병렬 생성 상한 |
242
257
 
243
258
  ### 로그 모드
244
259
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  `ima2-gen` 是一个本地 AI 工作室,只需免费 ChatGPT 和 SuperGrok 即可生成图像和视频。
12
12
 
13
- 全局安装后,通过 ChatGPT 或 Grok OAuth 登录即可开始生成图像和视频。无需 API 密钥,节点分支、multimode 批量、Grok Video、Canvas Mode 全部可用。
13
+ 全局安装后,通过 ChatGPT 或 Grok OAuth 登录即可开始生成图像和视频。默认 OAuth 路径无需 API 密钥;也可选用 API 密钥提供商(`api`、`grok-api`、`gemini-api`、`agy`)。
14
14
 
15
15
  ![显示 prompt 输入区、生成图片、模型标签和结果元数据的 ima2-gen classic 界面](../assets/screenshots/classic-generate-light.png)
16
16
 
@@ -24,30 +24,93 @@ ima2 serve
24
24
 
25
25
  然后打开 `http://localhost:3333`。
26
26
 
27
+ CLI 生成视频:
28
+
29
+ ```bash
30
+ ima2 video "猫弹钢琴" --duration 5 --resolution 720p
31
+ ima2 video "让这张图动起来" --ref photo.png --duration 10
32
+ ```
33
+
27
34
  如果 `3333` 已经被占用,server 会绑定下一个可用端口,并把实际 URL 写入 `~/.ima2/server.json`。不要假设端口固定,请使用终端输出的 URL 或 `ima2 open`。
28
35
 
29
36
  > **想用 npx 运行?** 请参阅 [NPX_QUICKSTART.md](NPX_QUICKSTART.md)。
30
37
 
38
+ ### 一键安装(无需 npm)
39
+
40
+ 没有 Node.js 或 npm 也可以:平台安装脚本会检测环境,必要时安装 Node LTS,然后安装 ima2-gen。
41
+
42
+ **macOS:**
43
+ ```bash
44
+ curl -fsSL https://lidge-jun.github.io/ima2-gen/install-mac.sh | bash
45
+ ```
46
+
47
+ **Windows (PowerShell):**
48
+ ```powershell
49
+ irm https://lidge-jun.github.io/ima2-gen/install-windows.ps1 | iex
50
+ ```
51
+
52
+ **Linux / WSL:**
53
+ ```bash
54
+ curl -fsSL https://lidge-jun.github.io/ima2-gen/install-linux.sh | bash
55
+ ```
56
+
57
+ 各脚本会检查 nvm/fnm/brew/winget,用最佳方式安装 Node LTS,并自动清理残留进程。
58
+
59
+ ### 设置
60
+
61
+ `ima2 setup` 提供四种认证方式:
62
+
63
+ 1. **GPT OAuth** — 使用 ChatGPT 账号登录(免费,仅图像)
64
+ 2. **Grok OAuth** — 使用 xAI/Grok 账号登录(图像 + 视频)
65
+ 3. **Both** — GPT + Grok 同时配置(完整功能)
66
+ 4. **Web setup** — 在 Web UI 中完成全部设置
67
+
68
+ 视频生成需要 Grok OAuth(选项 2 或 3)。若已配置 GPT OAuth 并想追加视频,请单独运行 `ima2 grok login`。
69
+
70
+ ### 更新
71
+
72
+ 用 Ctrl+C 停止正在运行的 server,然后:
73
+
74
+ ```bash
75
+ npm install -g ima2-gen@latest
76
+ ```
77
+
78
+ Ctrl+C 会执行干净关闭——关闭数据库、停止子进程并释放文件锁。旧版本(< 1.1.22)或 Windows 上出现 `EBUSY` 时,安装脚本会自动清理残留进程。
79
+
31
80
  ## 能做什么
32
81
 
33
82
  - **Classic mode**:快速生成、编辑,并把当前图片继续作为参考图使用。
34
83
  - **Node mode**:从一张满意的图出发,向多个方向分支探索。
35
84
  - **Multimode batches**:用同一个 prompt 同时生成多个候选 slot,并从最好的结果继续。
36
85
  - **Canvas Mode**:支持缩放/平移、标注、橡皮擦、背景清理、透明 checkerboard 预览,以及 alpha/matte export。
37
- - **Local gallery**:将生成结果保存在本地,并按会话 (session) 查看历史。
38
- - **Reference images**:支持拖放、粘贴和文件选择;大图会在上传前自动压缩。
86
+ - **Video generation**:从文本、单图或多张参考图生成短视频;SSE 显示 planning→submitted→progress→done;支持 First/Mid/Last 帧复制。
87
+ - **Storyboard mode**:在 composer 中开启 storyboard 模式,保持连续帧的角色与场景一致性(图像与视频均支持)。
88
+ - **Local gallery**:本地保存生成结果;默认显示当前 session,All Images 切换显示全部历史;记录生成时间与 reasoning effort 元数据。
89
+ - **Reference images**:拖放、粘贴、文件选择;图像最多 5 张,视频最多 7 张;大图上传前自动压缩。
39
90
  - **Prompt library imports**:把本地 prompt pack、GitHub folder、curated GPT-image hint 导入内置 prompt library。
40
91
  - **Mobile shell**:小屏幕使用 app bar、compose sheet 和 compact settings toggle。
41
92
  - **Observable jobs**:用 request ID 追踪进行中和最近完成的任务。
42
93
 
94
+ ### SSE 多路复用
95
+
96
+ Web UI 通过单个 `GET /api/events` Server-Sent Events 连接接收所有生成进度。Multimode、node、video 请求以异步 POST(`202 { requestId }`)提交,进度事件经共享事件总线多路复用。这消除了浏览器 6 连接限制导致的并发生成时图库 hang。未发送 `async: true` 的 CLI 客户端仍可收到每个请求的 SSE 流以保持兼容。
97
+
43
98
  ## 图像生成支持 OAuth 和 API key
44
99
 
45
100
  默认图像生成通过本地 Codex/ChatGPT OAuth 路径执行。
46
101
 
47
102
  如果 env/config 里有 API key,生成接口可以通过 `provider: "api"` 使用 Responses API 的 `image_generation` tool。
48
103
 
104
+ - `provider: "grok-api"` — 通过 `XAI_API_KEY` 直连 xAI Images API
105
+ - `provider: "agy"` — 本地 Antigravity CLI(`IMA2_AGY_BIN`)
106
+ - `provider: "gemini-api"` — `GEMINI_API_KEY` 或 Vertex(`VERTEX_SERVICE_ACCOUNT_JSON`,Vertex 优先)
107
+
49
108
  如果设置页显示 **API key provider available**,意思是检测到了 API key,并且可用于生成、编辑、multimode 和 node 请求。
50
109
 
110
+ Grok 视频使用 `grok-imagine-video`(默认)或正式名 `grok-imagine-video-1.5`;旧的 `grok-imagine-video-1.5-preview` 字符串仍作为兼容 alias 接受。根据参考图数量自动选择 T2V(0)、I2V(1)、Ref2V(2-7,最长 10 秒)。1080p 仅在 `grok-imagine-video-1.5` 单图/单帧 I2V 时可用。1.5 不新增 Ref2V、V2V edit 或 extension 支持,这些路径仍仅使用默认模型。支持 duration(1-15s)、resolution(480p/720p/受支持时 1080p) 和 aspect ratio。
111
+
112
+ 设置页的 QuotaCard 显示 Grok billing `$used/$limit` 进度条和 **Switch Account** 按钮(`POST /api/auth/switch`)。
113
+
51
114
  ![显示 OAuth active 与 API key provider available 状态的设置页](../assets/screenshots/settings-oauth-generation.png)
52
115
 
53
116
  ## 模型建议
@@ -120,6 +183,7 @@ Settings workspace 会把账号、模型、主题和语言设置从生成面板
120
183
  | `ima2 setup` | 重新配置认证 |
121
184
  | `ima2 status` | 查看 config 和 OAuth 状态 |
122
185
  | `ima2 doctor` | 诊断 Node、package、config、auth |
186
+ | `ima2 doctor image-probe [--json]` | 无图像时的 sanitized 诊断 probe |
123
187
  | `ima2 open` | 打开 Web UI |
124
188
  | `ima2 reset` | 删除已保存的 config |
125
189
 
@@ -132,6 +196,7 @@ Settings workspace 会把账号、模型、主题和语言设置从生成面板
132
196
  | `ima2 gen <prompt>` | 从 CLI 生成图片 |
133
197
  | `ima2 edit <file> --prompt <text>` | 编辑已有图片 |
134
198
  | `ima2 multimode <prompt>` | 多图 SSE 生成 |
199
+ | `ima2 video <prompt>` | Grok 视频生成(SSE 进度) |
135
200
  | `ima2 ls [--session <id>] [--favorites]` | 查看本地历史 |
136
201
  | `ima2 show <name> [--metadata]` | 打开生成文件 |
137
202
  | `ima2 prompt ls -q <搜索>` | 搜索 prompt library |
@@ -162,9 +227,13 @@ environment variables > ~/.ima2/config.json > built-in defaults
162
227
  | `IMA2_GENERATED_DIR` | `~/.ima2/generated` | 生成图片目录 |
163
228
  | `IMA2_IMAGE_MODEL_DEFAULT` | `gpt-5.4-mini` | Server fallback 图像模型 |
164
229
  | `IMA2_NO_OAUTH_PROXY` | — | 设为 `1` 时关闭 OAuth proxy 自动启动 |
165
- | `IMA2_LOG_LEVEL` | `warn` | 普通 `serve` 默认为 `warn`,dev mode 默认为 `debug`;支持 `debug`, `info`, `warn`, `error`, `silent` |
166
- | `IMA2_INFLIGHT_TERMINAL_TTL_MS` | `30000` | 调试用 recent job 保留时间 |
167
- | `OPENAI_API_KEY` | — | 辅助功能用 API key,不用于图像生成 |
230
+ | `IMA2_LOG_LEVEL` | `info` | 普通 `serve` 默认为 `info`,dev mode 默认为 `debug`;支持 `debug`, `info`, `warn`, `error`, `silent` |
231
+ | `IMA2_INFLIGHT_TERMINAL_TTL_MS` | `300000` | 调试用 recent job 保留时间 |
232
+ | `OPENAI_API_KEY` | — | `provider: "api"` Responses 图像路径及辅助功能 |
233
+ | `XAI_API_KEY` | — | `provider: "grok-api"` 直连 xAI Images API |
234
+ | `GEMINI_API_KEY` | — | `provider: "gemini-api"` Generative Language API |
235
+ | `VERTEX_SERVICE_ACCOUNT_JSON` | — | Vertex AI 服务账号 JSON(优先于 API 密钥) |
236
+ | `IMA2_AGY_BIN` | PATH 中的 `agy` | `provider: "agy"` 二进制路径 |
168
237
 
169
238
  ### Logging modes
170
239
 
@@ -187,11 +256,14 @@ environment variables > ~/.ima2/config.json > built-in defaults
187
256
  重新运行 `ima2 setup`(选项 1),用 `ima2 status` 确认状态,然后重启 `ima2 serve`。
188
257
 
189
258
  **在代理/VPN 网络下反复出现 `fetch failed`**
190
- 请先确认本地 OAuth proxy 可以访问。如果你的网络需要代理,请在代理客户端里开启 TUN/TURN 类似的转发模式,然后重试 `openai-oauth --port 10531`。如果仍然失败,请在运行 `ima2 serve` 或 `openai-oauth` 的同一个终端里设置 `HTTP_PROXY` 和 `HTTPS_PROXY`。
259
+ 请先确认本地 OAuth proxy 可以访问。如果你的网络需要代理,请在代理客户端里开启 TUN/TURN 类似的转发模式,然后重试 `openai-oauth --port 10531`。如果仍然失败,请在运行 `ima2 serve` 或 `openai-oauth` 的同一个终端里设置 `HTTP_PROXY` 和 `HTTPS_PROXY`。在 Windows 上,SecretDNS 等 DNS/分片绕过工具也可能破坏 OAuth 或流式图像响应。
191
260
 
192
261
  **生成图片时返回 `API_KEY_REQUIRED`**
193
262
  `provider: "api"` 请求没有可用 API key。请配置 API key,或切换到 OAuth provider。
194
263
 
264
+ **图像生成返回 `EMPTY_RESPONSE` 或没有图像数据**
265
+ 运行 `ima2 doctor image-probe --json > ima2-image-probe.json`,提 issue 时附上安全 JSON。GPT OAuth 场景下,请在 `ima2 serve` 运行时额外捕获 `ima2 gen "猫" --no-web-search --json` 和 `ima2 gen "猫" --json`。不要分享 ChatGPT cookie、OAuth 令牌文件、API key、原始 upstream 响应、prompt 历史或生成 base64。详见 [FAQ 支持包](FAQ.md#what-should-i-share-when-oauth-image-generation-returns-no-image)。
266
+
195
267
  **大参考图上传失败**
196
268
  JPEG/PNG 会在上传前自动压缩。如果仍然失败,请转成更低分辨率的 JPEG/PNG。HEIC/HEIF 不支持浏览器路径。
197
269
 
@@ -201,8 +273,11 @@ JPEG/PNG 会在上传前自动压缩。如果仍然失败,请转成更低分
201
273
  **只有 `gpt-5.5` 失败**
202
274
  请先更新 Codex CLI 后再试。如果仍然失败,说明当前账号或后端路径下 `gpt-5.5` 的图像 capability 或额度策略可能还不同;稳定替代方案是使用 `gpt-5.4`。
203
275
 
204
- **端口突然变成 `3457`**
205
- shell 可能继承了其他本地工具的 `PORT=3457`。运行 `unset PORT`,或使用 `IMA2_PORT=3333 ima2 serve`。
276
+ **应用在不同端口打开**
277
+ 如果请求的服务端口被占用,ima2-gen 会 fallback 到下一个可用端口并写入 `~/.ima2/server.json`。若端口意外变成 `3457`,可能是 shell 继承了其他本地工具的 `PORT=3457`。运行 `unset PORT`,或使用 `IMA2_PORT=3333 ima2 serve`。
278
+
279
+ **Windows 上端口 `10531` 已被占用**
280
+ 部分 Windows 安全工具(如 `AnySign4PC.exe`)可能占用默认 OAuth proxy 端口。当前版本会跟踪实际 fallback OAuth 端口。如需手动覆盖,请使用 `IMA2_OAUTH_PROXY_PORT=11531 ima2 serve` 启动,并用 `ima2 doctor` 确认。
206
281
 
207
282
  更多面向新手的排查步骤请查看 [FAQ](FAQ.md)。
208
283
 
@@ -4,7 +4,7 @@ Generated by `npm run test:inventory` (script: `scripts/classify-tests.mjs`).
4
4
 
5
5
  _Tests considered "runtime-importing" if they import from `../lib/`, `../routes/`, `../bin/`, `../server`, or `../config`._
6
6
 
7
- Total: 201 (runtime: 71, contract: 130)
7
+ Total: 204 (runtime: 71, contract: 133)
8
8
 
9
9
  ## Runtime-importing tests
10
10
  - `tests/agent-mode-auto-planner-contract.test.ts`
@@ -85,6 +85,7 @@ Total: 201 (runtime: 71, contract: 130)
85
85
  - `tests/agent-mode-right-sidebar-contract.test.js`
86
86
  - `tests/agent-mode-run-progress-contract.test.ts`
87
87
  - `tests/agent-mode-tool-folding-contract.test.js`
88
+ - `tests/api-docs-contract.test.js`
88
89
  - `tests/app-weight-splitting-contract.test.js`
89
90
  - `tests/async-capacity-retry-behavior.test.ts`
90
91
  - `tests/async-capacity-retry-contract.test.js`
@@ -203,10 +204,12 @@ Total: 201 (runtime: 71, contract: 130)
203
204
  - `tests/size-presets.test.js`
204
205
  - `tests/slash-command-menu-contract.test.ts`
205
206
  - `tests/storage-open-generated-dir.test.js`
207
+ - `tests/structure-line-counts-contract.test.js`
206
208
  - `tests/style-feature-removal-contract.test.js`
207
209
  - `tests/toast-stack-contract.test.js`
208
210
  - `tests/ui-error-code-contract.test.js`
209
211
  - `tests/video-continuity-ui-contract.test.js`
210
212
  - `tests/video-gallery-refresh-contract.test.ts`
213
+ - `tests/video-ui-source-count.test.ts`
211
214
  - `tests/vite-dev-port-contract.test.js`
212
215
  - `tests/web-search-toggle-contract.test.js`
@@ -230,7 +230,7 @@ export function cleanVideoParams(value) {
230
230
  const duration = typeof input.duration === "number" ? input.duration : Number(input.duration);
231
231
  if (Number.isFinite(duration))
232
232
  params.duration = Math.max(1, Math.min(15, Math.round(duration)));
233
- if (input.resolution === "480p" || input.resolution === "720p")
233
+ if (input.resolution === "480p" || input.resolution === "720p" || input.resolution === "1080p")
234
234
  params.resolution = input.resolution;
235
235
  if (typeof input.aspectRatio === "string" && /^(auto|16:9|9:16|4:3|3:4|3:2|2:3|1:1)$/.test(input.aspectRatio)) {
236
236
  params.aspectRatio = input.aspectRatio;
@@ -278,7 +278,7 @@ function isVideoIntent(prompt) {
278
278
  return VIDEO_INTENT_PATTERN.test(prompt);
279
279
  }
280
280
  const DURATION_PATTERN = /(\d{1,2})\s*(?:s|sec|seconds?|초)/i;
281
- const RESOLUTION_PATTERN = /(720p|480p)/i;
281
+ const RESOLUTION_PATTERN = /(1080p|720p|480p)/i;
282
282
  const ASPECT_PATTERN = /(16:9|9:16|4:3|3:4|3:2|2:3|1:1)/;
283
283
  export function parseVideoParams(prompt) {
284
284
  const params = {};
@@ -12,6 +12,7 @@ import { generateViaResponses } from "./responsesImageAdapter.js";
12
12
  import { generateViaGrok } from "./grokImageAdapter.js";
13
13
  import { generateViaAgy } from "./agyImageAdapter.js";
14
14
  import { generateVideoViaGrok } from "./grokVideoAdapter.js";
15
+ import { GROK_VIDEO_MODEL_15, GROK_VIDEO_MODEL_BASE } from "./imageModels.js";
15
16
  import { parseVideoParams } from "./agentGenerationPlanner.js";
16
17
  import { appendAgentTurn, getAgentImages, getAgentSession, importAgentImage, } from "./agentStore.js";
17
18
  import { errInfo } from "./errInfo.js";
@@ -196,8 +197,11 @@ export async function runAgentVideoGeneration(ctx, sessionId, prompt, options =
196
197
  resolution: options.videoParams?.resolution ?? parsedParams.resolution,
197
198
  aspectRatio: options.videoParams?.aspectRatio ?? parsedParams.aspectRatio,
198
199
  };
200
+ const videoModel = videoParams.resolution === "1080p"
201
+ ? GROK_VIDEO_MODEL_15
202
+ : GROK_VIDEO_MODEL_BASE;
199
203
  const result = await generateVideoViaGrok(prompt, ctx, {
200
- model: "grok-imagine-video",
204
+ model: videoModel,
201
205
  mode,
202
206
  sourceImage,
203
207
  duration: videoParams.duration ?? 5,
@@ -254,10 +258,23 @@ async function persistAgentVideo(ctx, sessionId, prompt, requestId, result) {
254
258
  userPrompt: prompt,
255
259
  revisedPrompt: result.revisedPrompt,
256
260
  provider: "grok",
257
- model: "grok-imagine-video",
261
+ model: result.effectiveModel,
262
+ requestedModel: result.requestedModel,
263
+ effectiveModel: result.effectiveModel,
264
+ modelFallback: result.modelFallback,
258
265
  createdAt: Date.now(),
259
266
  usage: result.usage,
260
267
  webSearchCalls: result.webSearchCalls,
268
+ video: {
269
+ duration: result.duration,
270
+ resolution: result.resolution,
271
+ aspectRatio: result.aspectRatio,
272
+ mode: result.mode,
273
+ xaiVideoRequestId: result.xaiVideoRequestId,
274
+ requestedModel: result.requestedModel,
275
+ effectiveModel: result.effectiveModel,
276
+ modelFallback: result.modelFallback,
277
+ },
261
278
  };
262
279
  const filePath = join(ctx.config.storage.generatedDir, filename);
263
280
  await writeFile(filePath, result.videoBuffer);
@@ -36,7 +36,7 @@ function buildPlannerDeveloperPrompt(hasSourceImage, imageCount) {
36
36
  "- sourceImagePolicy for single/fanout: use none for new/fresh/separate/from-scratch requests, including '새로', '별도', 'i2i 말고', '새로운 방식', 'new image', 'from scratch', 'without reference'.",
37
37
  "- sourceImagePolicy for single/fanout: use current only when the user explicitly asks to use/edit/modify/transform/reference the current image, including '이 이미지', '현재 이미지', '방금 그거', '참조', 'reference', 'i2i', 'image-to-image', '유지해서'.",
38
38
  "- sourceImagePolicy for plain image requests with no explicit reference wording is none.",
39
- "- mode video: one video via ima2.generate_video. Choose it only when the user asks to CREATE a video. prompts has exactly 1 entry. Extract duration (1-15 s), resolution (480p|720p), aspectRatio (auto|1:1|16:9|9:16|4:3|3:4|3:2|2:3) from the request into videoParams; omit fields the user did not specify.",
39
+ "- mode video: one video via ima2.generate_video. Choose it only when the user asks to CREATE a video. prompts has exactly 1 entry. Extract duration (1-15 s), resolution (480p|720p|1080p), aspectRatio (auto|1:1|16:9|9:16|4:3|3:4|3:2|2:3) from the request into videoParams; omit fields the user did not specify. 1080p uses Grok Video 1.5; prompt-only requests are valid because the server injects a white-canvas I2V shim.",
40
40
  "- mode question: the user is NOT requesting generation — a question (capabilities, how-to, status), small talk, a greeting, or feedback — e.g. '영상 생성가능하니?', 'can you make videos?', '고마워'. prompts must be []. Write the full answer in assistantText. Mentioning a media word like 'video' or '영상' inside a question does NOT make it a generation request.",
41
41
  "- mode errors: the user is asking why a previous generation failed or about recent errors. prompts must be [].",
42
42
  "- assistantText: REQUIRED for every mode, written in the user's language. For question/errors it is the full reply. For single/fanout/video it is a short natural chat reply telling the user what you are creating (1-2 sentences, no markdown headings).",
@@ -48,13 +48,13 @@ export const AGENT_TOOL_MANIFEST = [
48
48
  },
49
49
  {
50
50
  name: "ima2.generate_video",
51
- description: "Generate a single video with Grok Imagine. If the session has a last image, it is used as the image-to-video source automatically.",
51
+ description: "Generate a single video with Grok Imagine. If the session has a last image, it is used as the image-to-video source automatically; prompt-only Grok Video 1.5 uses the server white-canvas shim.",
52
52
  parameters: {
53
53
  type: "object",
54
54
  properties: {
55
55
  prompt: { type: "string", description: "Video prompt describing visual flow and motion." },
56
56
  duration: { type: "integer", minimum: 1, maximum: 15, description: "Video duration in seconds. Default 5." },
57
- resolution: { type: "string", enum: ["480p", "720p"], description: "Output resolution. Default 480p." },
57
+ resolution: { type: "string", enum: ["480p", "720p", "1080p"], description: "Output resolution. Default 480p. 1080p uses Grok Video 1.5; prompt-only requests use the white-canvas I2V shim." },
58
58
  aspectRatio: {
59
59
  type: "string",
60
60
  enum: ["auto", "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"],
@@ -59,8 +59,10 @@ export function buildIma2Capabilities({ appConfig = runtimeConfigDefault, packag
59
59
  geminiSupported: ["nano-banana-2", "nano-banana-pro"],
60
60
  },
61
61
  videoModels: {
62
- supported: ["grok-imagine-video", "grok-imagine-video-1.5-preview"],
63
- resolutions: ["480p", "720p"],
62
+ supported: ["grok-imagine-video", "grok-imagine-video-1.5"],
63
+ aliases: { "grok-imagine-video-1.5-preview": "grok-imagine-video-1.5" },
64
+ resolutions: ["480p", "720p", "1080p"],
65
+ resolutionNotes: { "1080p": "grok-imagine-video-1.5 text-to-video canvas shim or image-to-video; reference-to-video unsupported" },
64
66
  aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "auto"],
65
67
  durationRange: [1, 15],
66
68
  maxReferences: 7,
@@ -5,10 +5,13 @@ import { detectImageMimeFromB64 } from "./refs.js";
5
5
  import { aspectToCanvas, generateWhiteCanvasB64 } from "./grokVideoCanvas.js";
6
6
  import { downloadVideo } from "./grokVideoDownload.js";
7
7
  import { buildGrokVideoPlannerSystemPrompt, formatDurationPacingGuidance } from "./grokVideoPlannerPrompt.js";
8
- import { MAX_REF2V_REFERENCES } from "./imageModels.js";
8
+ import { GROK_VIDEO_MODEL_15, GROK_VIDEO_MODEL_15_PREVIEW_ALIAS, GROK_VIDEO_MODEL_BASE, MAX_REF2V_REFERENCES, validateVideoResolutionForRequest, } from "./imageModels.js";
9
9
  import { formatVideoContinuityForPlanner } from "./videoContinuity.js";
10
10
  export { downloadVideo } from "./grokVideoDownload.js";
11
11
  const STALE_PROGRESS_MS = 180_000;
12
+ function canonicalVideoModel(model) {
13
+ return model === GROK_VIDEO_MODEL_15_PREVIEW_ALIAS ? GROK_VIDEO_MODEL_15 : model;
14
+ }
12
15
  function videoConfig(ctx) {
13
16
  const g = ctx.config.grokProvider || {};
14
17
  return {
@@ -117,11 +120,11 @@ export function buildGrokVideoPlannerPayload(prompt, opts) {
117
120
  type: "object",
118
121
  properties: {
119
122
  prompt: { type: "string", description: "Final video-generation prompt to send to xAI Videos API." },
120
- model: { type: "string", enum: ["grok-imagine-video"] },
123
+ model: { type: "string", enum: [GROK_VIDEO_MODEL_BASE, GROK_VIDEO_MODEL_15] },
121
124
  mode: { type: "string", enum: ["text-to-video", "image-to-video", "reference-to-video"] },
122
125
  duration: { type: "number" },
123
126
  aspect_ratio: { type: "string" },
124
- resolution: { type: "string", enum: ["480p", "720p"] },
127
+ resolution: { type: "string", enum: ["480p", "720p", "1080p"] },
125
128
  },
126
129
  required: ["prompt"],
127
130
  },
@@ -156,10 +159,11 @@ export async function planGrokVideo(prompt, ctx, options = {}) {
156
159
  const resolution = options.resolution || "480p";
157
160
  const aspectRatio = options.aspectRatio || "auto";
158
161
  const plannerModel = options.plannerModel || cfg.plannerModel;
162
+ const model = canonicalVideoModel(options.model || cfg.model);
159
163
  const search = await searchGrokVisualContext(prompt, ctx, { signal: options.signal, requestId: options.requestId, directApiKey: options.directApiKey, plannerModel });
160
164
  const referenceImageUrls = (options.referenceImages ?? []).map((img) => sourceImageUrl(img, undefined));
161
165
  const payload = buildGrokVideoPlannerPayload(prompt, {
162
- model: cfg.model,
166
+ model,
163
167
  mode,
164
168
  duration,
165
169
  resolution,
@@ -197,6 +201,7 @@ export async function planGrokVideo(prompt, ctx, options = {}) {
197
201
  }
198
202
  }
199
203
  export function buildVideoGenerationPayload(plan, opts) {
204
+ const model = canonicalVideoModel(opts.model);
200
205
  if (plan.mode === "image-to-video" && !opts.sourceImageUrl) {
201
206
  throw grokError("image-to-video requires a source image", 400, "GROK_VIDEO_INVALID_MODE");
202
207
  }
@@ -209,7 +214,11 @@ export function buildVideoGenerationPayload(plan, opts) {
209
214
  if (opts.sourceImageUrl)
210
215
  throw grokError("reference-to-video cannot be combined with a single source image", 400, "GROK_VIDEO_INVALID_MODE");
211
216
  }
212
- const payload = { model: opts.model, prompt: plan.prompt, duration: plan.duration, resolution: plan.resolution };
217
+ const resolutionCheck = validateVideoResolutionForRequest(model, plan.resolution, plan.mode);
218
+ if (!("ok" in resolutionCheck)) {
219
+ throw grokError(resolutionCheck.error, resolutionCheck.status, resolutionCheck.code);
220
+ }
221
+ const payload = { model, prompt: plan.prompt, duration: plan.duration, resolution: plan.resolution };
213
222
  if (plan.aspectRatio && plan.aspectRatio !== "auto")
214
223
  payload.aspect_ratio = plan.aspectRatio;
215
224
  if (plan.mode === "image-to-video")
@@ -318,7 +327,7 @@ export async function pollVideoUntilDone(ctx, requestId, options) {
318
327
  }
319
328
  export async function generateVideoViaGrok(prompt, ctx, options = {}) {
320
329
  const cfg = videoConfig(ctx);
321
- const model = options.model || cfg.model;
330
+ const model = canonicalVideoModel(options.model || cfg.model);
322
331
  const srcUrl = options.sourceImage ? sourceImageUrl(options.sourceImage, options.sourceMime) : undefined;
323
332
  const refUrls = (options.referenceImages ?? []).map((img) => sourceImageUrl(img, undefined));
324
333
  options.onEvent?.({ phase: "planning" });
@@ -332,25 +341,27 @@ export async function generateVideoViaGrok(prompt, ctx, options = {}) {
332
341
  webSearchCalls: options.webSearchCalls ?? 1,
333
342
  }
334
343
  : await planGrokVideo(prompt, ctx, options);
335
- const payload = buildVideoGenerationPayload(plan, { model, sourceImageUrl: srcUrl, referenceImageUrls: refUrls });
336
344
  let xaiVideoRequestId;
337
345
  let effectiveModel = model;
338
346
  // grokv1.5 doesn't support T2V — inject a white canvas as source image to use I2V path
339
- let effectivePayload = payload;
340
- if (model === "grok-imagine-video-1.5-preview" && !srcUrl && refUrls.length === 0) {
347
+ let effectivePayload;
348
+ if (model === GROK_VIDEO_MODEL_15 && plan.mode === "text-to-video" && !srcUrl && refUrls.length === 0) {
341
349
  const { width, height } = aspectToCanvas(plan.aspectRatio, plan.resolution);
342
350
  const whiteCanvas = await generateWhiteCanvasB64(width, height);
343
351
  const canvasSrcUrl = `data:image/png;base64,${whiteCanvas}`;
344
352
  effectivePayload = buildVideoGenerationPayload({ ...plan, mode: "image-to-video", prompt: `${plan.prompt}. This is not a start frame — generate freely as a new video.` }, { model, sourceImageUrl: canvasSrcUrl, referenceImageUrls: [] });
345
353
  logEvent("grok", "video:1.5-t2v-canvas", { requestId: options.requestId, width, height });
346
354
  }
355
+ else {
356
+ effectivePayload = buildVideoGenerationPayload(plan, { model, sourceImageUrl: srcUrl, referenceImageUrls: refUrls });
357
+ }
347
358
  try {
348
359
  xaiVideoRequestId = await startVideoRequest(ctx, effectivePayload, options);
349
360
  }
350
361
  catch (e) {
351
362
  // Fallback: if 1.5-preview still fails, retry with base model
352
- if (model !== "grok-imagine-video" && e?.status === 400) {
353
- effectiveModel = "grok-imagine-video";
363
+ if (model !== GROK_VIDEO_MODEL_BASE && e?.status === 400) {
364
+ effectiveModel = GROK_VIDEO_MODEL_BASE;
354
365
  const fallbackPayload = buildVideoGenerationPayload(plan, { model: effectiveModel, sourceImageUrl: srcUrl, referenceImageUrls: refUrls });
355
366
  xaiVideoRequestId = await startVideoRequest(ctx, fallbackPayload, options);
356
367
  logEvent("grok", "video:fallback", { requestId: options.requestId, from: model, to: effectiveModel });
@@ -1,6 +1,6 @@
1
1
  import sharp from "sharp";
2
2
  export function aspectToCanvas(aspectRatio, resolution) {
3
- const base = resolution === "720p" ? 720 : 480;
3
+ const base = resolution === "1080p" ? 1080 : resolution === "720p" ? 720 : 480;
4
4
  const ratios = {
5
5
  "16:9": [16, 9], "9:16": [9, 16], "4:3": [4, 3], "3:4": [3, 4],
6
6
  "3:2": [3, 2], "2:3": [2, 3], "1:1": [1, 1], "auto": [16, 9],
@@ -76,9 +76,16 @@ export function normalizeGeminiApiModel(rawModel) {
76
76
  // ── Grok video (T2V/I2V) ─────────────────────────────────────────────────
77
77
  // Video is a separate generation kind, not an image model. Keep it out of the
78
78
  // image model unions/helpers above so `grok-` image classification is unaffected.
79
- const GROK_FALLBACK_VIDEO_MODEL = "grok-imagine-video";
80
- export const VALID_GROK_VIDEO_MODELS = new Set(["grok-imagine-video", "grok-imagine-video-1.5-preview"]);
81
- export const VALID_VIDEO_RESOLUTIONS = new Set(["480p", "720p"]);
79
+ export const GROK_VIDEO_MODEL_BASE = "grok-imagine-video";
80
+ export const GROK_VIDEO_MODEL_15 = "grok-imagine-video-1.5";
81
+ export const GROK_VIDEO_MODEL_15_PREVIEW_ALIAS = "grok-imagine-video-1.5-preview";
82
+ const GROK_FALLBACK_VIDEO_MODEL = GROK_VIDEO_MODEL_BASE;
83
+ export const VALID_GROK_VIDEO_MODELS = new Set([
84
+ GROK_VIDEO_MODEL_BASE,
85
+ GROK_VIDEO_MODEL_15,
86
+ GROK_VIDEO_MODEL_15_PREVIEW_ALIAS,
87
+ ]);
88
+ export const VALID_VIDEO_RESOLUTIONS = new Set(["480p", "720p", "1080p"]);
82
89
  export const VALID_VIDEO_ASPECT_RATIOS = new Set([
83
90
  "1:1",
84
91
  "16:9",
@@ -120,7 +127,7 @@ export function normalizeGrokVideoModel(rawModel) {
120
127
  status: 400,
121
128
  };
122
129
  }
123
- return { model: rawModel };
130
+ return { model: rawModel === GROK_VIDEO_MODEL_15_PREVIEW_ALIAS ? GROK_VIDEO_MODEL_15 : rawModel };
124
131
  }
125
132
  export function normalizeVideoResolution(raw) {
126
133
  if (raw === undefined || raw === null || raw === "")
@@ -134,6 +141,26 @@ export function normalizeVideoResolution(raw) {
134
141
  }
135
142
  return { resolution: raw };
136
143
  }
144
+ export function usesGrokVideo15TextCanvasShim(model, mode) {
145
+ const canonicalModel = model === GROK_VIDEO_MODEL_15_PREVIEW_ALIAS ? GROK_VIDEO_MODEL_15 : model;
146
+ return canonicalModel === GROK_VIDEO_MODEL_15 && mode === "text-to-video";
147
+ }
148
+ export function validateVideoResolutionForRequest(model, resolution, mode, options = {}) {
149
+ if (resolution !== "1080p")
150
+ return { ok: true };
151
+ const canonicalModel = model === GROK_VIDEO_MODEL_15_PREVIEW_ALIAS ? GROK_VIDEO_MODEL_15 : model;
152
+ if (canonicalModel === GROK_VIDEO_MODEL_15 && mode === "image-to-video") {
153
+ return { ok: true };
154
+ }
155
+ if (options.allowTextCanvasShim && usesGrokVideo15TextCanvasShim(canonicalModel, mode)) {
156
+ return { ok: true };
157
+ }
158
+ return {
159
+ error: "1080p video resolution requires grok-imagine-video-1.5 text-to-video with the canvas shim or image-to-video",
160
+ code: "INVALID_VIDEO_RESOLUTION",
161
+ status: 400,
162
+ };
163
+ }
137
164
  export function normalizeVideoAspectRatio(raw) {
138
165
  if (raw === undefined || raw === null || raw === "")
139
166
  return { aspectRatio: "auto" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ima2-gen",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "Local OAuth image generation studio with classic and node workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "prepack": "npm run ui:build && npm run build:server && npm run build:cli",
22
22
  "prepublishOnly": "npm run typecheck && npm run typecheck:tests && npm run test:inventory && npm run ui:build && npm run build:server && npm run build:cli && npm run lint:pkg && npm run test:package-install",
23
23
  "lint:pkg": "node -e \"const p=require('./package.json'); const req=['name','version','bin']; for(const k of req){if(!p[k])throw new Error('missing '+k)} const mustInclude=['bin/**/*.js','lib/**/*.js','routes/**/*.js','skills/','integrations/comfyui/ima2_gen_bridge/__init__.py','integrations/comfyui/ima2_gen_bridge/nodes.py','integrations/comfyui/ima2_gen_bridge/README.md','assets/card-news/templates/','vendor/','server.js','LICENSE']; for(const f of mustInclude){if(!p.files.includes(f))throw new Error('files[] must include '+f)}\"",
24
+ "docs:refresh-line-counts": "node scripts/refresh-structure-line-counts.mjs",
24
25
  "publish:dry-run": "node scripts/publish-dry-run.mjs",
25
26
  "release:patch": "npm version patch && npm publish && git push origin main --tags",
26
27
  "release:minor": "npm version minor && npm publish && git push origin main --tags",