feat: integrate Grok2 API as preset provider for image/video generation
Some checks failed
Build & Push Docker Image / build-and-push (push) Has been cancelled

- Add grok2 to PRESET_PROVIDERS with openai-compat gateway route
- Add grok-imagine-1.0 (image) and grok-imagine-1.0-video (video) preset models
- Extend OpenAIImageGenerateSize to support 1280x720 and 720x1280 resolutions
- Add grok2 to COMPATIBLE_PROVIDER_KEYS in router
- Add grok2 to VERIFIABLE_PROVIDER_KEYS for test-connection UI
- Show Base URL input and model tabs for grok2 provider
- Add i18n labels for Grok2 API compatibility layer badge
- Add GROK2_SETUP.md with configuration documentation
This commit is contained in:
shiyue
2026-03-11 14:29:58 +00:00
parent be1853534a
commit 7ec38730e9
11 changed files with 188 additions and 7 deletions

165
GROK2_SETUP.md Normal file
View File

@@ -0,0 +1,165 @@
# Grok2 API 集成说明
## 概述
本项目已集成 Grok2 API 支持,可以使用 Grok2 进行图像和视频生成。
## 配置步骤
### 1. 启动服务
确保 Docker 镜像已构建并启动:
```bash
cd /opt/stacks/waoo
docker-compose up -d
```
### 2. 配置 Grok2 Provider
1. 访问 http://localhost:13000
2. 登录后进入 **个人设置 > API 配置**
3. 找到 **Grok2 API** 提供商
4. 点击 **配置** 按钮
5. 填写以下信息:
- **Base URL**: `http://45.128.210.183:8048`
- **API Key**: (如果需要的话填写你的 API Key
### 3. 配置模型
在 Grok2 API 提供商下,已预置以下模型:
#### 图像生成模型
- **模型 ID**: `grok-imagine-1.0`
- **名称**: Grok Imagine 1.0
- **支持尺寸**:
- 1280x720
- 720x1280
- 1792x1024
- 1024x1792
- 1024x1024
#### 视频生成模型
- **模型 ID**: `grok-imagine-1.0-video`
- **名称**: Grok Imagine 1.0 Video
- **支持时长**: 6-30 秒
- **支持质量**: standard (480p), high (720p)
### 4. 配置视频模型模板(重要)
由于 Grok2 的视频接口与标准 OpenAI 格式不同,需要为视频模型配置模板:
1.**Grok Imagine 1.0 Video** 模型卡片中,点击 **高级设置**
2. 找到 **兼容媒体模板** 选项
3. 点击 **AI 助手****手动配置**
4. 使用以下模板配置:
```json
{
"version": 1,
"mediaType": "video",
"mode": "sync",
"create": {
"method": "POST",
"path": "/v1/videos",
"contentType": "application/json",
"bodyTemplate": {
"model": "{{model}}",
"prompt": "{{prompt}}",
"image_reference": "{{image}}",
"seconds": "{{duration}}",
"quality": "{{quality}}",
"size": "{{size}}"
}
},
"response": {
"outputUrlPath": "data[0].url"
}
}
```
5. 点击 **验证模板** 确保配置正确
6. 保存配置
### 5. 设置默认模型(可选)
**默认模型配置** 区域,可以将 Grok2 模型设置为默认:
- **角色模型**: Grok Imagine 1.0
- **场景模型**: Grok Imagine 1.0
- **分镜模型**: Grok Imagine 1.0
- **视频模型**: Grok Imagine 1.0 Video
## 使用说明
### 图像生成
1. 进入项目的资产库或分镜页面
2. 选择使用 Grok2 图像模型
3. 输入提示词
4. 选择尺寸(支持 Grok2 特有的 1280x720 和 720x1280
5. 点击生成
### 视频生成
1. 准备一张起始图片
2. 在视频生成界面选择 Grok Imagine 1.0 Video 模型
3. 输入提示词(可选)
4. 设置时长6-30 秒)
5. 选择质量standard 或 high
6. 点击生成
## 注意事项
1. **图像生成**Grok2 图像生成使用标准 OpenAI 兼容接口,可以直接使用
2. **视频生成**:必须配置模板才能正常工作,因为 Grok2 的视频接口路径和参数与标准格式不同
3. **API 地址**:确保 `http://45.128.210.183:8048` 可以从 Docker 容器内访问
4. **网络问题**:如果遇到连接问题,检查防火墙和网络配置
## 故障排查
### 图像生成失败
1. 检查 Base URL 是否正确
2. 检查 API Key 是否有效(如果需要)
3. 查看浏览器控制台或服务器日志
### 视频生成失败
1. 确认已正确配置视频模型模板
2. 检查模板中的字段映射是否正确
3. 确认 Grok2 API 服务正常运行
4. 查看详细错误信息
### 测试连接
在配置 Provider 后,可以点击 **测试连接** 按钮验证配置是否正确。
## 技术细节
### 实现方式
- Grok2 被实现为一个预设 Provider使用 OpenAI 兼容网关
- 图像生成通过扩展尺寸白名单支持 Grok2 特有尺寸
- 视频生成通过模板系统映射 Grok2 的自定义接口格式
### 路由配置
- Provider Key: `grok2`
- Gateway Route: `openai-compat`
- API Mode: `openai-official`
### 字段映射
视频生成字段映射:
- `duration``seconds`
- `image``image_reference`
- `quality``quality` (standard/high)
- `size``size` (宽高比)
## 更新日志
- 2026-03-11: 初始集成 Grok2 API 支持
- 添加图像生成支持1280x720, 720x1280 尺寸)
- 添加视频生成支持6-30 秒standard/high 质量)
- 配置模板系统适配 Grok2 视频接口

View File

@@ -9,6 +9,7 @@
"connect": "Connect", "connect": "Connect",
"compatibilityLayerOpenAI": "OpenAI Compatible Layer", "compatibilityLayerOpenAI": "OpenAI Compatible Layer",
"compatibilityLayerGemini": "Gemini Compatible Layer", "compatibilityLayerGemini": "Gemini Compatible Layer",
"compatibilityLayerGrok2": "Grok2 API",
"show": "Show", "show": "Show",
"hide": "Hide", "hide": "Hide",
"capability": "Models", "capability": "Models",

View File

@@ -9,6 +9,7 @@
"connect": "连接", "connect": "连接",
"compatibilityLayerOpenAI": "OpenAI 兼容层", "compatibilityLayerOpenAI": "OpenAI 兼容层",
"compatibilityLayerGemini": "Gemini 兼容层", "compatibilityLayerGemini": "Gemini 兼容层",
"compatibilityLayerGrok2": "Grok2 API",
"show": "显示", "show": "显示",
"hide": "隐藏", "hide": "隐藏",
"capability": "能力", "capability": "能力",

View File

@@ -64,7 +64,7 @@ const MODEL_TYPES: readonly ProviderCardModelType[] = ['llm', 'image', 'video',
export function getAddableModelTypesForProvider(providerId: string): ProviderCardModelType[] { export function getAddableModelTypesForProvider(providerId: string): ProviderCardModelType[] {
const providerKey = getProviderKey(providerId) const providerKey = getProviderKey(providerId)
if (providerKey === 'openai-compatible') return ['llm', 'image', 'video'] if (providerKey === 'openai-compatible' || providerKey === 'grok2') return ['image', 'video']
return ['llm', 'image', 'video', 'audio'] return ['llm', 'image', 'video', 'audio']
} }
@@ -72,12 +72,12 @@ export function shouldShowOpenAICompatVideoHint(
providerId: string, providerId: string,
type: ProviderCardModelType | null, type: ProviderCardModelType | null,
): boolean { ): boolean {
return getProviderKey(providerId) === 'openai-compatible' && type === 'video' return (getProviderKey(providerId) === 'openai-compatible' || getProviderKey(providerId) === 'grok2') && type === 'video'
} }
function shouldShowDefaultTabs(providerId: string): boolean { function shouldShowDefaultTabs(providerId: string): boolean {
const providerKey = getProviderKey(providerId) const providerKey = getProviderKey(providerId)
return providerKey === 'openai-compatible' || providerKey === 'gemini-compatible' return providerKey === 'openai-compatible' || providerKey === 'gemini-compatible' || providerKey === 'grok2'
} }
export function getVisibleModelTypesForProvider( export function getVisibleModelTypesForProvider(

View File

@@ -27,6 +27,7 @@ export function getCompatibilityLayerBadgeLabel(
const providerKey = getProviderKey(providerId) const providerKey = getProviderKey(providerId)
if (providerKey === 'openai-compatible') return t('compatibilityLayerOpenAI') if (providerKey === 'openai-compatible') return t('compatibilityLayerOpenAI')
if (providerKey === 'gemini-compatible') return t('compatibilityLayerGemini') if (providerKey === 'gemini-compatible') return t('compatibilityLayerGemini')
if (providerKey === 'grok2') return t('compatibilityLayerGrok2')
return null return null
} }

View File

@@ -176,7 +176,7 @@ export function buildProviderConnectionPayload(params: {
const compatibleBaseUrl = params.baseUrl?.trim() const compatibleBaseUrl = params.baseUrl?.trim()
const llmModel = params.llmModel?.trim() const llmModel = params.llmModel?.trim()
const isCompatibleProvider = const isCompatibleProvider =
params.providerKey === 'openai-compatible' || params.providerKey === 'gemini-compatible' params.providerKey === 'openai-compatible' || params.providerKey === 'gemini-compatible' || params.providerKey === 'grok2'
if (isCompatibleProvider && compatibleBaseUrl) { if (isCompatibleProvider && compatibleBaseUrl) {
return { return {
@@ -386,7 +386,7 @@ export function useProviderCardState({
(presetProvider) => presetProvider.id === provider.id, (presetProvider) => presetProvider.id === provider.id,
) )
const showBaseUrlEdit = const showBaseUrlEdit =
['gemini-compatible', 'openai-compatible'].includes(providerKey) && ['gemini-compatible', 'openai-compatible', 'grok2'].includes(providerKey) &&
Boolean(onUpdateBaseUrl) Boolean(onUpdateBaseUrl)
const tutorial = getProviderTutorial(provider.id) const tutorial = getProviderTutorial(provider.id)

View File

@@ -58,5 +58,5 @@ export type ProviderCardTranslator = (
export const VERIFIABLE_PROVIDER_KEYS = new Set([ export const VERIFIABLE_PROVIDER_KEYS = new Set([
'ark', 'google', 'openrouter', 'minimax', 'fal', 'vidu', 'ark', 'google', 'openrouter', 'minimax', 'fal', 'vidu',
'bailian', 'siliconflow', 'bailian', 'siliconflow',
'openai-compatible', 'gemini-compatible', 'openai-compatible', 'gemini-compatible', 'grok2',
]) ])

View File

@@ -182,6 +182,10 @@ export const PRESET_MODELS: PresetModel[] = [
{ modelId: 'viduq1', name: 'Vidu Q1', type: 'video', provider: 'vidu' }, { modelId: 'viduq1', name: 'Vidu Q1', type: 'video', provider: 'vidu' },
{ modelId: 'viduq1-classic', name: 'Vidu Q1 Classic', type: 'video', provider: 'vidu' }, { modelId: 'viduq1-classic', name: 'Vidu Q1 Classic', type: 'video', provider: 'vidu' },
{ modelId: 'vidu2.0', name: 'Vidu 2.0', type: 'video', provider: 'vidu' }, { modelId: 'vidu2.0', name: 'Vidu 2.0', type: 'video', provider: 'vidu' },
// Grok2 模型
{ modelId: 'grok-imagine-1.0', name: 'Grok Imagine 1.0', type: 'image', provider: 'grok2' },
{ modelId: 'grok-imagine-1.0-video', name: 'Grok Imagine 1.0 Video', type: 'video', provider: 'grok2' },
] ]
const PRESET_COMING_SOON_MODEL_KEYS = new Set<string>([ const PRESET_COMING_SOON_MODEL_KEYS = new Set<string>([
@@ -205,6 +209,7 @@ export const PRESET_PROVIDERS: Omit<Provider, 'apiKey' | 'hasApiKey'>[] = [
{ id: 'minimax', name: 'MiniMax Hailuo', baseUrl: 'https://api.minimaxi.com/v1' }, { id: 'minimax', name: 'MiniMax Hailuo', baseUrl: 'https://api.minimaxi.com/v1' },
{ id: 'vidu', name: 'Vidu' }, { id: 'vidu', name: 'Vidu' },
{ id: 'fal', name: 'FAL' }, { id: 'fal', name: 'FAL' },
{ id: 'grok2', name: 'Grok2 API', apiMode: 'openai-official', gatewayRoute: 'openai-compat' },
] ]
const ZH_PROVIDER_NAME_MAP: Record<string, string> = { const ZH_PROVIDER_NAME_MAP: Record<string, string> = {
@@ -213,6 +218,7 @@ const ZH_PROVIDER_NAME_MAP: Record<string, string> = {
vidu: '生数科技 Vidu', vidu: '生数科技 Vidu',
bailian: '阿里云百炼', bailian: '阿里云百炼',
siliconflow: '硅基流动', siliconflow: '硅基流动',
grok2: 'Grok2 API',
} }
function isZhLocale(locale?: string): boolean { function isZhLocale(locale?: string): boolean {

View File

@@ -190,6 +190,7 @@ const OPTIONAL_PRICING_PROVIDER_KEYS = new Set([
'gemini-compatible', 'gemini-compatible',
'bailian', 'bailian',
'siliconflow', 'siliconflow',
'grok2',
]) ])
const OFFICIAL_ONLY_PROVIDER_KEYS = new Set(['bailian', 'siliconflow']) const OFFICIAL_ONLY_PROVIDER_KEYS = new Set(['bailian', 'siliconflow'])
const RETIRED_PROVIDER_KEYS = new Set(['qwen']) const RETIRED_PROVIDER_KEYS = new Set(['qwen'])
@@ -472,6 +473,7 @@ function resolveProviderGatewayRoute(
const providerKey = getProviderKey(providerId) const providerKey = getProviderKey(providerId)
const isOpenAICompatibleProvider = providerKey === 'openai-compatible' const isOpenAICompatibleProvider = providerKey === 'openai-compatible'
const isGeminiCompatibleProvider = providerKey === 'gemini-compatible' const isGeminiCompatibleProvider = providerKey === 'gemini-compatible'
const isGrok2Provider = providerKey === 'grok2'
if (rawGatewayRoute !== undefined && !isGatewayRoute(rawGatewayRoute)) { if (rawGatewayRoute !== undefined && !isGatewayRoute(rawGatewayRoute)) {
throw new ApiError('INVALID_PARAMS', { throw new ApiError('INVALID_PARAMS', {
@@ -479,7 +481,7 @@ function resolveProviderGatewayRoute(
}) })
} }
if (isOpenAICompatibleProvider) { if (isOpenAICompatibleProvider || isGrok2Provider) {
if (rawGatewayRoute === 'official') { if (rawGatewayRoute === 'official') {
throw new ApiError('INVALID_PARAMS', { throw new ApiError('INVALID_PARAMS', {
code: 'PROVIDER_GATEWAY_ROUTE_INVALID', code: 'PROVIDER_GATEWAY_ROUTE_INVALID',

View File

@@ -19,6 +19,8 @@ type OpenAIImageGenerateSize =
| '512x512' | '512x512'
| '1792x1024' | '1792x1024'
| '1024x1792' | '1024x1792'
| '1280x720'
| '720x1280'
const OPENAI_IMAGE_OPTION_KEYS = new Set([ const OPENAI_IMAGE_OPTION_KEYS = new Set([
'provider', 'provider',
@@ -81,6 +83,8 @@ function normalizeOpenAIImageSize(value: string | undefined): OpenAIImageGenerat
|| value === '512x512' || value === '512x512'
|| value === '1792x1024' || value === '1792x1024'
|| value === '1024x1792' || value === '1024x1792'
|| value === '1280x720'
|| value === '720x1280'
) { ) {
return value return value
} }

View File

@@ -3,6 +3,7 @@ import type { ModelGatewayRoute } from './types'
const COMPATIBLE_PROVIDER_KEYS = new Set([ const COMPATIBLE_PROVIDER_KEYS = new Set([
'openai-compatible', 'openai-compatible',
'grok2',
]) ])
const OFFICIAL_ONLY_PROVIDER_KEYS = new Set([ const OFFICIAL_ONLY_PROVIDER_KEYS = new Set([
'bailian', 'bailian',