feat: 集成 KieAI Grok 模型,支持 field103 配置切换视频生成模型

- 新增 ToolGrokService 接口和 ToolGrokServiceImpl 实现
- 对接 https://kie.ai/grok-imagine 文生视频和图生视频接口
- 新增 KieAIConfig.grokBaseUrl 配置项
- 修改 KieAIController,根据 system_config.field103 自动切换 Sora/Grok
- 原有 /text-to-video 和 /image-to-video 接口支持自动模型选择
- 新增 /grok/text-to-video 和 /grok/image-to-video 专用接口

配置说明:
- eb_system_config.name=field103, value=grok 时使用 Grok
- value=sora 或空时使用 Sora(默认)
This commit is contained in:
2026-03-03 16:21:07 +08:00
parent 4be53dcd1b
commit 4646fbc9b5
4 changed files with 370 additions and 12 deletions

View File

@@ -0,0 +1,232 @@
package com.zbkj.service.service.impl.tool;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zbkj.common.config.KieAIConfig;
import com.zbkj.common.model.article.Article;
import com.zbkj.common.response.kieai.KieAIVideoTaskStatus;
import com.zbkj.common.utils.HttpRequestUtils;
import com.zbkj.service.dao.ArticleDao;
import com.zbkj.service.service.tool.ToolGrokService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* KieAI Grok 视频生成服务实现类
* 对接 https://kie.ai/grok-imagine
* URL: /text-to-video 和 /image-to-video
* Model: grok-imagine-text-to-video 和 grok-imagine-image-to-video
*/
@Service
public class ToolGrokServiceImpl implements ToolGrokService {
private static final Logger logger = LoggerFactory.getLogger(ToolGrokServiceImpl.class);
private static final String MODEL_TEXT_TO_VIDEO = "grok-imagine-text-to-video";
private static final String MODEL_IMAGE_TO_VIDEO = "grok-imagine-image-to-video";
@Autowired
private ArticleDao articleDao;
@Autowired
private KieAIConfig kieAIConfig;
@Override
public String createTextToVideoTask(String tenantId, String title, String prompt, String aspectRatio,
Integer nFrames, Boolean removeWatermark, String callBackUrl, String apiKey, String uid, String nickname) {
try {
String baseUrl = kieAIConfig.getGrokBaseUrl();
if (baseUrl == null || baseUrl.isEmpty()) {
baseUrl = "https://kie.ai/grok-imagine";
}
String url = baseUrl + "/text-to-video";
Map<String, Object> input = new HashMap<>();
input.put("prompt", prompt);
if (aspectRatio != null) input.put("aspect_ratio", aspectRatio);
if (removeWatermark != null) input.put("remove_watermark", removeWatermark);
input.put("n_frames", "15");
String effectiveApiKey = (apiKey != null && !apiKey.isEmpty()) ? apiKey : kieAIConfig.getApiToken();
Map<String, Object> payload = new HashMap<>();
payload.put("model", MODEL_TEXT_TO_VIDEO);
payload.put("callBackUrl", callBackUrl != null ? callBackUrl : kieAIConfig.getApiCallbackUrl());
payload.put("input", input);
String jsonPayload = JSON.toJSONString(payload);
logger.info("Grok创建文生视频任务请求参数: {}", jsonPayload);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + effectiveApiKey);
String response = HttpRequestUtils.postWithHeaders(url, jsonPayload, null, headers);
logger.info("Grok创建文生视频任务响应: {}", response);
JSONObject result = JSON.parseObject(response);
if (result != null && result.containsKey("data")) {
JSONObject data = result.getJSONObject("data");
if (data != null && data.containsKey("taskId")) {
String taskId = data.getString("taskId");
try {
Article article = new Article();
article.setTitle(title);
article.setShareTitle(aspectRatio);
article.setAuthor(nickname);
article.setUid(uid);
article.setType(2);
article.setImageInput("");
article.setImage2("");
article.setImage3("");
article.setSynopsis(title);
article.setContent(prompt);
article.setCid(tenantId);
article.setVisit("0");
article.setOriginalTitle(apiKey);
article.setTaskId(taskId);
article.setStatusTask(0);
articleDao.insert(article);
logger.info("Grok文章新增成功, taskId: {}", taskId);
} catch (Exception e) {
logger.error("Grok更新文章表失败: {}", e.getMessage(), e);
}
return taskId;
}
}
return null;
} catch (Exception e) {
logger.error("Grok创建文生视频任务失败: {}", e.getMessage(), e);
return null;
}
}
@Override
public String createImageToVideoTask(String tenantId, String title, String prompt, String[] imageUrls,
String aspectRatio, Integer nFrames, Boolean removeWatermark, String callBackUrl, String apiKey, String uid, String nickname) {
try {
String baseUrl = kieAIConfig.getGrokBaseUrl();
if (baseUrl == null || baseUrl.isEmpty()) {
baseUrl = "https://kie.ai/grok-imagine";
}
String url = baseUrl + "/image-to-video";
Map<String, Object> input = new HashMap<>();
input.put("prompt", prompt);
input.put("image_urls", imageUrls);
if (aspectRatio != null) input.put("aspect_ratio", aspectRatio);
if (removeWatermark != null) input.put("remove_watermark", removeWatermark);
input.put("n_frames", "15");
String effectiveApiKey = (apiKey != null && !apiKey.isEmpty()) ? apiKey : kieAIConfig.getApiToken();
Map<String, Object> payload = new HashMap<>();
payload.put("model", MODEL_IMAGE_TO_VIDEO);
payload.put("callBackUrl", callBackUrl != null ? callBackUrl : kieAIConfig.getApiCallbackUrl());
payload.put("input", input);
String jsonPayload = JSON.toJSONString(payload);
logger.info("Grok创建图生视频任务请求参数: {}", jsonPayload);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + effectiveApiKey);
String response = HttpRequestUtils.postWithHeaders(url, jsonPayload, null, headers);
logger.info("Grok创建图生视频任务响应: {}", response);
JSONObject result = JSON.parseObject(response);
if (result != null && result.containsKey("data")) {
JSONObject data = result.getJSONObject("data");
if (data != null && data.containsKey("taskId")) {
String taskId = data.getString("taskId");
try {
Article article = new Article();
article.setTitle(title);
article.setShareTitle(aspectRatio);
article.setAuthor(nickname);
article.setUid(uid);
article.setType(2);
article.setImageInput(imageUrls != null && imageUrls.length > 0 ? imageUrls[0] : "");
article.setImage2("");
article.setImage3("");
article.setSynopsis(title);
article.setContent(prompt);
article.setCid(tenantId);
article.setVisit("0");
article.setOriginalTitle(taskId);
article.setTaskId(taskId);
article.setStatusTask(0);
articleDao.insert(article);
logger.info("Grok文章新增成功, taskId: {}", taskId);
} catch (Exception e) {
logger.error("Grok更新文章表失败: {}", e.getMessage(), e);
}
return taskId;
}
}
return null;
} catch (Exception e) {
logger.error("Grok创建图生视频任务失败: {}", e.getMessage(), e);
return null;
}
}
@Override
public KieAIVideoTaskStatus getTaskStatus(String taskId, String apiKey) {
try {
String effectiveApiKey = (apiKey != null && !apiKey.isEmpty()) ? apiKey : kieAIConfig.getApiToken();
String url = kieAIConfig.getBaseUrl() + "/api/v1/jobs/recordInfo?taskId=" + taskId;
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + effectiveApiKey);
String response = HttpRequestUtils.getWithHeaders(url, null, headers);
logger.info("Grok查询任务状态响应: {}", response);
JSONObject responseJson = JSON.parseObject(response);
if (responseJson != null && responseJson.containsKey("data")) {
JSONObject data = responseJson.getJSONObject("data");
if (data != null) {
KieAIVideoTaskStatus taskStatus = new KieAIVideoTaskStatus();
taskStatus.setTaskId(data.getString("taskId"));
taskStatus.setModel(data.getString("model"));
taskStatus.setParam(data.getString("param"));
taskStatus.setState(data.getString("state"));
taskStatus.setFailCode(data.getString("failCode"));
taskStatus.setFailMsg(data.getString("failMsg"));
taskStatus.setCompleteTime(data.getLong("completeTime"));
taskStatus.setCreateTime(data.getLong("createTime"));
taskStatus.setUpdateTime(data.getLong("updateTime"));
String resultJsonStr = data.getString("resultJson");
if (resultJsonStr != null && !resultJsonStr.isEmpty()) {
try {
JSONObject resultJson = JSON.parseObject(resultJsonStr);
if (resultJson != null && resultJson.containsKey("resultUrls")) {
com.alibaba.fastjson.JSONArray arr = resultJson.getJSONArray("resultUrls");
if (arr != null) {
String[] urls = new String[arr.size()];
for (int i = 0; i < arr.size(); i++) {
urls[i] = arr.getString(i);
}
taskStatus.setResultUrls(urls);
}
}
} catch (Exception e) {
logger.warn("解析resultJson失败: {}", e.getMessage());
}
}
return taskStatus;
}
}
return null;
} catch (Exception e) {
logger.error("Grok查询任务状态失败: {}", e.getMessage(), e);
return null;
}
}
}

View File

@@ -0,0 +1,28 @@
package com.zbkj.service.service.tool;
import com.zbkj.common.response.kieai.KieAIVideoTaskStatus;
/**
* KieAI Grok 视频生成服务接口
* 对接 https://kie.ai/grok-imagine
* 方法签名与 ToolSora2Service 保持一致
*/
public interface ToolGrokService {
/**
* 创建文生视频任务
*/
String createTextToVideoTask(String tenantId, String title, String prompt, String aspectRatio,
Integer nFrames, Boolean removeWatermark, String callBackUrl, String apiKey, String uid, String nickname);
/**
* 创建图生视频任务
*/
String createImageToVideoTask(String tenantId, String title, String prompt, String[] imageUrls,
String aspectRatio, Integer nFrames, Boolean removeWatermark, String callBackUrl, String apiKey, String uid, String nickname);
/**
* 查询任务状态
*/
KieAIVideoTaskStatus getTaskStatus(String taskId, String apiKey);
}