| | |
| | | package com.vincent.rsf.server.ai.controller; |
| | | |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.server.ai.dto.AiChatSessionPinRequest; |
| | | import com.vincent.rsf.server.ai.dto.AiChatSessionRenameRequest; |
| | | import com.vincent.rsf.server.ai.dto.AiChatRequest; |
| | | import com.vincent.rsf.server.ai.service.AiChatService; |
| | | import com.vincent.rsf.server.system.controller.BaseController; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.http.MediaType; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.util.StringUtils; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
| | | |
| | | import java.util.UUID; |
| | | |
| | | @RestController |
| | | @Slf4j |
| | | @RequiredArgsConstructor |
| | | public class AiChatController extends BaseController { |
| | | |
| | | private final AiChatService aiChatService; |
| | | |
| | | /** |
| | | * 返回当前用户在指定 Prompt 场景下的 AI 运行时快照。 |
| | | * 这里不会真正触发模型调用,只负责把当前生效的模型、Prompt、 |
| | | * 已挂载 MCP 以及会话记忆概况一次性返回给前端抽屉初始化使用。 |
| | | */ |
| | | @PreAuthorize("isAuthenticated()") |
| | | @GetMapping("/ai/chat/runtime") |
| | | public R runtime(@RequestParam(required = false) String promptCode, |
| | |
| | | return R.ok().add(aiChatService.getRuntime(promptCode, sessionId, getLoginUserId(), getTenantId())); |
| | | } |
| | | |
| | | /** |
| | | * 查询当前登录用户在指定 Prompt 下的历史会话列表。 |
| | | * 前端左侧会话栏依赖该接口做会话切换、搜索和刷新。 |
| | | */ |
| | | @PreAuthorize("isAuthenticated()") |
| | | @GetMapping("/ai/chat/sessions") |
| | | public R sessions(@RequestParam(required = false) String promptCode) { |
| | | return R.ok().add(aiChatService.listSessions(promptCode, getLoginUserId(), getTenantId())); |
| | | public R sessions(@RequestParam(required = false) String promptCode, |
| | | @RequestParam(required = false) String keyword) { |
| | | return R.ok().add(aiChatService.listSessions(promptCode, keyword, getLoginUserId(), getTenantId())); |
| | | } |
| | | |
| | | /** |
| | | * 软删除单个 AI 会话,同时级联删除会话下的消息记录。 |
| | | */ |
| | | @PreAuthorize("isAuthenticated()") |
| | | @PostMapping("/ai/chat/session/remove/{sessionId}") |
| | | public R removeSession(@PathVariable Long sessionId) { |
| | |
| | | return R.ok("Delete Success").add(sessionId); |
| | | } |
| | | |
| | | /** |
| | | * 更新会话标题,供前端重命名会话时调用。 |
| | | */ |
| | | @PreAuthorize("isAuthenticated()") |
| | | @PostMapping("/ai/chat/session/rename/{sessionId}") |
| | | public R renameSession(@PathVariable Long sessionId, @RequestBody AiChatSessionRenameRequest request) { |
| | | return R.ok("Update Success").add(aiChatService.renameSession(sessionId, request, getLoginUserId(), getTenantId())); |
| | | } |
| | | |
| | | /** |
| | | * 更新会话置顶状态。 |
| | | * 置顶只影响当前用户的会话排序,不改变会话内容和记忆。 |
| | | */ |
| | | @PreAuthorize("isAuthenticated()") |
| | | @PostMapping("/ai/chat/session/pin/{sessionId}") |
| | | public R pinSession(@PathVariable Long sessionId, @RequestBody AiChatSessionPinRequest request) { |
| | | return R.ok("Update Success").add(aiChatService.pinSession(sessionId, request, getLoginUserId(), getTenantId())); |
| | | } |
| | | |
| | | /** |
| | | * 清空指定会话的持久化消息、摘要记忆和事实记忆。 |
| | | * 会话本身保留,便于前端继续在同一个 sessionId 上发起新对话。 |
| | | */ |
| | | @PreAuthorize("isAuthenticated()") |
| | | @PostMapping("/ai/chat/session/memory/clear/{sessionId}") |
| | | public R clearSessionMemory(@PathVariable Long sessionId) { |
| | | aiChatService.clearSessionMemory(sessionId, getLoginUserId(), getTenantId()); |
| | | return R.ok("Clear Success").add(sessionId); |
| | | } |
| | | |
| | | /** |
| | | * 只保留会话最近一轮问答,用于主动裁剪上下文窗口。 |
| | | */ |
| | | @PreAuthorize("isAuthenticated()") |
| | | @PostMapping("/ai/chat/session/memory/retain-latest/{sessionId}") |
| | | public R retainLatestRound(@PathVariable Long sessionId) { |
| | | aiChatService.retainLatestRound(sessionId, getLoginUserId(), getTenantId()); |
| | | return R.ok("Retain Success").add(sessionId); |
| | | } |
| | | |
| | | /** |
| | | * 以 SSE 方式启动 AI 对话。 |
| | | * 控制器只负责生成 requestId、记录入口日志和把鉴权上下文透传给服务层, |
| | | * 真正的流式推理、工具调用和记忆落库都在服务层完成。 |
| | | */ |
| | | @PreAuthorize("isAuthenticated()") |
| | | @PostMapping(value = "/ai/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) |
| | | public SseEmitter stream(@RequestBody AiChatRequest request) { |
| | | String requestId = StringUtils.hasText(request.getRequestId()) |
| | | ? request.getRequestId().trim() |
| | | : UUID.randomUUID().toString().replace("-", ""); |
| | | request.setRequestId(requestId); |
| | | log.info("AI chat request accepted, requestId={}, userId={}, tenantId={}, sessionId={}", |
| | | requestId, getLoginUserId(), getTenantId(), request.getSessionId()); |
| | | return aiChatService.stream(request, getLoginUserId(), getTenantId()); |
| | | } |
| | | } |