diff --git a/msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/config/KieAIConfig.java b/msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/config/KieAIConfig.java index 3cd1870..381a093 100644 --- a/msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/config/KieAIConfig.java +++ b/msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/config/KieAIConfig.java @@ -31,6 +31,11 @@ public class KieAIConfig { */ private String apiUploadBaseUrl = "https://kieai.redpandaai.co"; + /** + * Grok 视频生成基础 URL,默认 https://kie.ai/grok-imagine + */ + private String grokBaseUrl = "https://kie.ai/grok-imagine"; + /** * API 回调 URL */ diff --git a/msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java b/msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java index 7e7ff1e..70f4f6c 100644 --- a/msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java +++ b/msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java @@ -10,6 +10,8 @@ import com.zbkj.common.response.kieai.KieAINanoBananaResponse; import com.zbkj.common.response.kieai.KieAIQueryTaskResponse; import com.zbkj.common.response.kieai.KieAIVideoTaskStatus; import com.zbkj.common.result.CommonResult; +import com.zbkj.service.service.SystemConfigService; +import com.zbkj.service.service.tool.ToolGrokService; import com.zbkj.service.service.tool.ToolKieAIService; import com.zbkj.service.service.tool.ToolSora2Service; import io.swagger.annotations.Api; @@ -22,6 +24,8 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import cn.hutool.core.util.StrUtil; + import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; import java.util.Map; @@ -48,6 +52,12 @@ public class KieAIController { @Autowired private ToolSora2Service toolSora2Service; + @Autowired + private ToolGrokService toolGrokService; + + @Autowired + private SystemConfigService systemConfigService; + @Autowired private KieAIConfig kieAIConfig; @@ -190,7 +200,7 @@ public class KieAIController { // ==================== Sora2 视频生成接口(复刻自 models-integration) ==================== /** - * 创建文生视频任务 + * 创建文生视频任务(打卡视频上传根据 system_config 表 field103 切换模型:grok 用 Grok,其他用 Sora) */ @PostMapping("/text-to-video") @ApiOperation(value = "创建文生视频任务", notes = "通过文本描述创建视频生成任务") @@ -201,11 +211,23 @@ public class KieAIController { if (input == null || input.getPrompt() == null || input.getPrompt().isEmpty()) { return CommonResult.failed("prompt参数不能为空"); } - String taskId = toolSora2Service.createTextToVideoTask( - request.getTenant_id(), request.getTitle(), input.getPrompt(), - input.getAspect_ratio(), input.getN_frames(), input.getRemove_watermark(), - request.getCallBackUrl(), request.getApi_key(), - request.getUid(), request.getNickname()); + String field103 = null; + try { + field103 = systemConfigService.getValueByKey("field103"); + } catch (Exception ignored) { + } + boolean useGrok = StrUtil.isNotBlank(field103) && "grok".equalsIgnoreCase(field103.trim()); + String taskId = useGrok + ? toolGrokService.createTextToVideoTask( + request.getTenant_id(), request.getTitle(), input.getPrompt(), + input.getAspect_ratio(), input.getN_frames(), input.getRemove_watermark(), + request.getCallBackUrl(), request.getApi_key(), + request.getUid(), request.getNickname()) + : toolSora2Service.createTextToVideoTask( + request.getTenant_id(), request.getTitle(), input.getPrompt(), + input.getAspect_ratio(), input.getN_frames(), input.getRemove_watermark(), + request.getCallBackUrl(), request.getApi_key(), + request.getUid(), request.getNickname()); if (taskId != null) { return CommonResult.success(taskId); } @@ -217,7 +239,7 @@ public class KieAIController { } /** - * 创建图生视频任务 + * 创建图生视频任务(打卡视频上传根据 system_config 表 field103 切换模型:grok 用 Grok,其他用 Sora) */ @PostMapping("/image-to-video") @ApiOperation(value = "创建图生视频任务", notes = "通过图片URL创建视频生成任务") @@ -231,11 +253,23 @@ public class KieAIController { if (input.getImage_urls() == null || input.getImage_urls().length == 0) { return CommonResult.failed("image_urls参数不能为空"); } - String taskId = toolSora2Service.createImageToVideoTask( - request.getTenant_id(), request.getTitle(), input.getPrompt(), - input.getImage_urls(), input.getAspect_ratio(), input.getN_frames(), input.getRemove_watermark(), - request.getCallBackUrl(), request.getApi_key(), - request.getUid(), request.getNickname()); + String field103 = null; + try { + field103 = systemConfigService.getValueByKey("field103"); + } catch (Exception ignored) { + } + boolean useGrok = StrUtil.isNotBlank(field103) && "grok".equalsIgnoreCase(field103.trim()); + String taskId = useGrok + ? toolGrokService.createImageToVideoTask( + request.getTenant_id(), request.getTitle(), input.getPrompt(), + input.getImage_urls(), input.getAspect_ratio(), input.getN_frames(), input.getRemove_watermark(), + request.getCallBackUrl(), request.getApi_key(), + request.getUid(), request.getNickname()) + : toolSora2Service.createImageToVideoTask( + request.getTenant_id(), request.getTitle(), input.getPrompt(), + input.getImage_urls(), input.getAspect_ratio(), input.getN_frames(), input.getRemove_watermark(), + request.getCallBackUrl(), request.getApi_key(), + request.getUid(), request.getNickname()); if (taskId != null) { return CommonResult.success(taskId); } @@ -246,6 +280,65 @@ public class KieAIController { } } + // ==================== Grok 视频生成接口(https://kie.ai/grok-imagine) ==================== + + /** + * Grok 创建文生视频任务 + */ + @PostMapping("/grok/text-to-video") + @ApiOperation(value = "Grok创建文生视频任务", notes = "通过文本描述创建Grok视频生成任务") + public CommonResult createGrokTextToVideoTask(@RequestBody Sora2Request request) { + try { + logger.info("Grok创建文生视频任务,参数:{}", request); + Sora2Request.Input input = request.getInput(); + if (input == null || input.getPrompt() == null || input.getPrompt().isEmpty()) { + return CommonResult.failed("prompt参数不能为空"); + } + String taskId = toolGrokService.createTextToVideoTask( + request.getTenant_id(), request.getTitle(), input.getPrompt(), + input.getAspect_ratio(), input.getN_frames(), input.getRemove_watermark(), + request.getCallBackUrl(), request.getApi_key(), + request.getUid(), request.getNickname()); + if (taskId != null) { + return CommonResult.success(taskId); + } + return CommonResult.failed("Grok创建文生视频任务失败"); + } catch (Exception e) { + logger.error("Grok创建文生视频任务失败:{}", e.getMessage(), e); + return CommonResult.failed("Grok创建文生视频任务失败:" + e.getMessage()); + } + } + + /** + * Grok 创建图生视频任务 + */ + @PostMapping("/grok/image-to-video") + @ApiOperation(value = "Grok创建图生视频任务", notes = "通过图片URL创建Grok视频生成任务") + public CommonResult createGrokImageToVideoTask(@RequestBody Sora2Request request) { + try { + logger.info("Grok创建图生视频任务,参数:{}", request); + Sora2Request.Input input = request.getInput(); + if (input == null || input.getPrompt() == null || input.getPrompt().isEmpty()) { + return CommonResult.failed("prompt参数不能为空"); + } + if (input.getImage_urls() == null || input.getImage_urls().length == 0) { + return CommonResult.failed("image_urls参数不能为空"); + } + String taskId = toolGrokService.createImageToVideoTask( + request.getTenant_id(), request.getTitle(), input.getPrompt(), + input.getImage_urls(), input.getAspect_ratio(), input.getN_frames(), input.getRemove_watermark(), + request.getCallBackUrl(), request.getApi_key(), + request.getUid(), request.getNickname()); + if (taskId != null) { + return CommonResult.success(taskId); + } + return CommonResult.failed("Grok创建图生视频任务失败"); + } catch (Exception e) { + logger.error("Grok创建图生视频任务失败:{}", e.getMessage(), e); + return CommonResult.failed("Grok创建图生视频任务失败:" + e.getMessage()); + } + } + /** * 创建专业版本文生视频任务 */ diff --git a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolGrokServiceImpl.java b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolGrokServiceImpl.java new file mode 100644 index 0000000..ccf6f8c --- /dev/null +++ b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolGrokServiceImpl.java @@ -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 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 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 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 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 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 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 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; + } + } +} diff --git a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolGrokService.java b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolGrokService.java new file mode 100644 index 0000000..cbfbd81 --- /dev/null +++ b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolGrokService.java @@ -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); +}