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.
- package/README.md +6 -2
- package/bin/commands/video.js +26 -11
- package/docs/API.md +91 -5
- package/docs/CLI.md +8 -7
- package/docs/README.ja.md +86 -8
- package/docs/README.ko.md +31 -16
- package/docs/README.zh-CN.md +84 -9
- package/docs/migration/runtime-test-inventory.md +4 -1
- package/lib/agentGenerationPlanner.js +2 -2
- package/lib/agentImageVideoGen.js +19 -2
- package/lib/agentPlannerModel.js +1 -1
- package/lib/agentToolManifest.js +2 -2
- package/lib/capabilities.js +4 -2
- package/lib/grokVideoAdapter.js +22 -11
- package/lib/grokVideoCanvas.js +1 -1
- package/lib/imageModels.js +31 -4
- package/package.json +2 -1
- package/routes/video.js +6 -1
- package/skills/ima2/SKILL.md +6 -6
- package/ui/dist/.vite/manifest.json +12 -12
- package/ui/dist/assets/{AgentWorkspace-CMP9XZrL.js → AgentWorkspace-CKjpho46.js} +1 -1
- package/ui/dist/assets/{CardNewsWorkspace-DtOwgYzf.js → CardNewsWorkspace-BW52bztP.js} +1 -1
- package/ui/dist/assets/{GenerationRequestLogPanel-CB1i5dWd.js → GenerationRequestLogPanel-BPc4gDcY.js} +1 -1
- package/ui/dist/assets/{NodeCanvas-DwX2jRHf.js → NodeCanvas-d7dH6h99.js} +1 -1
- package/ui/dist/assets/{PromptBuilderPanel-B3VhUc5u.js → PromptBuilderPanel-CWbdfzpF.js} +1 -1
- package/ui/dist/assets/{PromptImportDialog-4FQd2YVS.js → PromptImportDialog-DORdifOt.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-CTNr6ft3.js → PromptImportDiscoverySection-XA3ZzwAo.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-zCJ1lBH8.js → PromptImportFolderSection-BjALahD0.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-B784T1KR.js → PromptLibraryPanel-Co9xIawD.js} +2 -2
- package/ui/dist/assets/{SettingsWorkspace-BpM6lWH-.js → SettingsWorkspace-BS_gSTTl.js} +1 -1
- package/ui/dist/assets/index-TRRNueAb.js +23 -0
- package/ui/dist/assets/{index-CwAzer33.js → index-bMxZBOnc.js} +1 -1
- package/ui/dist/index.html +1 -1
- 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 키 없이
|
|
15
|
+
전역 설치 후 ChatGPT 또는 Grok OAuth로 로그인하면 바로 시작됩니다. 기본 OAuth 경로는 API 키 없이 동작하며, 선택적으로 API 키 공급자(`api`, `grok-api`, `gemini-api`, `agy`)도 지원합니다.
|
|
16
16
|
|
|
17
17
|

|
|
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
|
-
##
|
|
71
|
+
## v2.0.4 주요 변경 (요약)
|
|
65
72
|
|
|
66
|
-
-
|
|
67
|
-
-
|
|
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
|
-
- **
|
|
93
|
-
- **
|
|
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
|
|
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
|
-
설정
|
|
122
|
+
설정 화면의 QuotaCard에 Grok billing `$used/$limit` 바와 **Switch Account** 버튼(`POST /api/auth/switch`)이 표시됩니다.
|
|
115
123
|
|
|
116
124
|

|
|
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
|
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
`ima2-gen` 是一个本地 AI 工作室,只需免费 ChatGPT 和 SuperGrok 即可生成图像和视频。
|
|
12
12
|
|
|
13
|
-
全局安装后,通过 ChatGPT 或 Grok OAuth
|
|
13
|
+
全局安装后,通过 ChatGPT 或 Grok OAuth 登录即可开始生成图像和视频。默认 OAuth 路径无需 API 密钥;也可选用 API 密钥提供商(`api`、`grok-api`、`gemini-api`、`agy`)。
|
|
14
14
|
|
|
15
15
|

|
|
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
|
-
- **
|
|
38
|
-
- **
|
|
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
|

|
|
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` | `
|
|
166
|
-
| `IMA2_INFLIGHT_TERMINAL_TTL_MS` | `
|
|
167
|
-
| `OPENAI_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
|
-
|
|
205
|
-
shell
|
|
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:
|
|
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:
|
|
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:
|
|
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);
|
package/lib/agentPlannerModel.js
CHANGED
|
@@ -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).",
|
package/lib/agentToolManifest.js
CHANGED
|
@@ -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"],
|
package/lib/capabilities.js
CHANGED
|
@@ -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
|
|
63
|
-
|
|
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,
|
package/lib/grokVideoAdapter.js
CHANGED
|
@@ -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: [
|
|
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
|
|
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
|
|
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
|
|
340
|
-
if (model === "
|
|
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 !==
|
|
353
|
-
effectiveModel =
|
|
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 });
|
package/lib/grokVideoCanvas.js
CHANGED
|
@@ -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],
|
package/lib/imageModels.js
CHANGED
|
@@ -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
|
|
80
|
-
export const
|
|
81
|
-
export const
|
|
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.
|
|
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",
|