diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeFlowController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeFlowController.java index 10ad716..3966951 100644 --- a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeFlowController.java +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeFlowController.java @@ -1,8 +1,12 @@ package com.cmvr.web.controller.test; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson2.JSONObject; import com.cmvr.common.annotation.Anonymous; import com.cmvr.common.core.controller.BaseController; import com.cmvr.common.core.domain.AjaxResult; +import com.cmvr.llm.service.LLMAiAgentPlatformService; import com.cmvr.test.flow.context.TaskContextManager; import com.cmvr.test.flow.control.FlowControlService; import com.cmvr.test.flow.runtime.engine.FlowTaskRuntimeService; @@ -26,9 +30,15 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import javax.annotation.PreDestroy; import javax.validation.Valid; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; @Api(tags = "测试--流程服务") @RestController @@ -36,6 +46,21 @@ import java.util.List; @RequiredArgsConstructor public class TeFlowController extends BaseController { + /** + * TTS 播放接口,当前按需求固定写死在 Controller 中。 + */ + private static final String TTS_PLAY_URL = "http://localhost:8080/tts/play"; + + /** + * TTS 停止接口,停止智能体输出时同步调用。 + */ + private static final String TTS_STOP_URL = "http://localhost:8080/tts/stop"; + + /** + * 商道智能体 AppKey,当前按需求固定写死,不放入配置文件。 + */ + private static final String AI_AGENT_API_KEY = "d9etum54shheenol3apg"; + private final ITeDetectionItemService teDetectionItemService; private final ITeNodeInstService nodeInstService; private final FlowTaskRuntimeService flowTaskRuntimeService; @@ -43,14 +68,29 @@ public class TeFlowController extends BaseController { private final FlowControlService flowControlService; private final FlowActionExecutorService flowActionExecutorService; private final TiTouchOperateService tiTouchOperateService; + private final LLMAiAgentPlatformService llmAiAgentPlatformService; private final List taskInstIdList = new ArrayList<>(); + + /** + * 智能体调用是阻塞式 query,这里用单线程保存当前任务句柄,便于 stop 接口中断当前请求。 + */ + private final ExecutorService aiAgentExecutor = Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(runnable, "flow-ai-agent-query"); + thread.setDaemon(true); + return thread; + }); + + /** + * 当前正在执行的智能体任务。当前接口按“同一时间只保留一个前端智能体调用”处理。 + */ + private volatile Future currentAiAgentFuture; + @ApiOperation("流程发布") @PostMapping("/publish") public AjaxResult publish(@RequestBody TeDetectionItem detectionItem) { return toAjax(teDetectionItemService.publish(detectionItem.getId())); } - @ApiOperation("演示任务执行入口") @PostMapping("/demo") @Anonymous @@ -59,7 +99,7 @@ public class TeFlowController extends BaseController { try { taskInstIdList.forEach(flowControlService::stop); } catch (Exception e) { - // 忽略 + // 演示入口清理旧任务失败时继续启动新任务,避免影响手动调试。 } taskInstIdList.clear(); } @@ -121,10 +161,110 @@ public class TeFlowController extends BaseController { return AjaxResult.ok(flowActionExecutorService.actionExecute(flowActionRequestVO)); } + /** + * 调用商道智能体平台。 + * + *

前端只需要传入 text。后端固定 action、apiKey 和 TTS 参数。 + * 调用会放入可取消任务中,前端可以通过 /flow/llm/stop 停止当前调用并停止 TTS。

+ */ + @GetMapping("/llm/query") + @ApiOperation("调用商道智能体") + public AjaxResult queryAiAgent(String text) { + if (text == null || text.trim().isEmpty()) { + return AjaxResult.error("text不能为空"); + } + + stopCurrentAiAgentFuture(true); + + JSONObject params = new JSONObject(); + params.put("url", TTS_PLAY_URL); + params.put("voice", "x4_yezi"); + params.put("speed", 45); + params.put("volume", 100); + + Future future = aiAgentExecutor.submit(() -> llmAiAgentPlatformService.query( + null, + text.trim(), + AI_AGENT_API_KEY, + true, + params + )); + currentAiAgentFuture = future; + + try { + return AjaxResult.ok(future.get()); + } catch (CancellationException e) { + return AjaxResult.error("智能体调用已停止"); + } catch (InterruptedException e) { + future.cancel(true); + Thread.currentThread().interrupt(); + return AjaxResult.error("智能体调用被中断"); + } catch (ExecutionException e) { + Throwable cause = e.getCause() == null ? e : e.getCause(); + return AjaxResult.error("智能体调用失败:" + cause.getMessage()); + } finally { + if (currentAiAgentFuture == future) { + currentAiAgentFuture = null; + } + } + } + + /** + * 停止当前智能体调用,并通知 TTS 服务停止播放。 + * + *

停止分两步:先取消当前 query 任务,触发底层 SSE 等待线程中断; + * 再调用本地 TTS stop 接口,停止已经进入播放队列的音频。

+ */ + @PostMapping("/llm/stop") + @ApiOperation("停止商道智能体和TTS") + public AjaxResult stopAiAgent() { + stopCurrentAiAgentFuture(false); +// String ttsStopResult = stopTts(); + return AjaxResult.ok("ok"); + } + @GetMapping("/testrun") @ApiOperation("测试接口") public AjaxResult testrun(@RequestParam("imageUrl") String imageUrl, @RequestParam("iconUrl") String iconUrl) { - return AjaxResult.ok(tiTouchOperateService.test(imageUrl,iconUrl)); + return AjaxResult.ok(tiTouchOperateService.test(imageUrl, iconUrl)); } -} \ No newline at end of file + + /** + * 取消当前智能体任务。 + * + * @param stopTts 是否同时停止 TTS 播放 + */ + private void stopCurrentAiAgentFuture(boolean stopTts) { + Future future = currentAiAgentFuture; + if (future != null && !future.isDone()) { + future.cancel(true); + } + currentAiAgentFuture = null; + if (stopTts) { + stopTts(); + } + } + + /** + * 调用本地 TTS 停止接口。 + */ + private String stopTts() { + try (HttpResponse response = HttpRequest.post(TTS_STOP_URL) + .timeout(3000) + .execute()) { + return response.body(); + } catch (Exception e) { + return "调用TTS停止接口失败:" + e.getMessage(); + } + } + + /** + * Spring 容器关闭时释放智能体执行线程。 + */ + @PreDestroy + public void destroy() { + stopCurrentAiAgentFuture(true); + aiAgentExecutor.shutdownNow(); + } +}