resourceSummary) {
+ /** @param value 实体 @return 安全绑定 */
+ static SkillBindingView from(AgentSkillBinding value) {
+ return new SkillBindingView(value.getId(), value.getSkillId(), value.getSortNo(),
+ value.getResourceSummary());
+ }
+ }
+}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/agent/AgentDraftSaveRequest.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/agent/AgentDraftSaveRequest.java
new file mode 100644
index 00000000..23e2d44c
--- /dev/null
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/agent/AgentDraftSaveRequest.java
@@ -0,0 +1,109 @@
+package tech.easyflow.admin.controller.agent;
+
+import tech.easyflow.agent.entity.Agent;
+import tech.easyflow.agent.entity.AgentKnowledgeBinding;
+import tech.easyflow.agent.entity.AgentSkillBinding;
+import tech.easyflow.agent.entity.AgentToolBinding;
+
+import java.util.List;
+
+/**
+ * Agent 设计器原子保存请求。
+ *
+ * 绑定变更标记由设计器基于加载后的稳定业务字段计算。服务端仍会执行权限、状态与幂等比较,
+ * 标记为未变化的绑定不会进入查询、外部资源校验或整组重写流程。
+ */
+public class AgentDraftSaveRequest {
+
+ private Agent agent;
+ private List toolBindings;
+ private boolean replaceToolBindings;
+ private List knowledgeBindings;
+ private boolean replaceKnowledgeBindings;
+ private List skillBindings;
+ private boolean replaceSkillBindings;
+
+ /** 创建空请求。 */
+ public AgentDraftSaveRequest() {
+ }
+
+ /** @return Agent 草稿 */
+ public Agent getAgent() {
+ return agent;
+ }
+
+ /** @param agent Agent 草稿 */
+ public void setAgent(Agent agent) {
+ this.agent = agent;
+ }
+
+ /** @return 工具绑定 */
+ public List getToolBindings() {
+ return toolBindings;
+ }
+
+ /** @param toolBindings 工具绑定 */
+ public void setToolBindings(List toolBindings) {
+ this.toolBindings = toolBindings;
+ }
+
+ /** @return 是否替换工具绑定 */
+ public boolean isReplaceToolBindings() {
+ return replaceToolBindings;
+ }
+
+ /** @param replaceToolBindings 是否替换工具绑定 */
+ public void setReplaceToolBindings(boolean replaceToolBindings) {
+ this.replaceToolBindings = replaceToolBindings;
+ }
+
+ /** @return 知识库绑定 */
+ public List getKnowledgeBindings() {
+ return knowledgeBindings;
+ }
+
+ /** @param knowledgeBindings 知识库绑定 */
+ public void setKnowledgeBindings(List knowledgeBindings) {
+ this.knowledgeBindings = knowledgeBindings;
+ }
+
+ /** @return 是否替换知识库绑定 */
+ public boolean isReplaceKnowledgeBindings() {
+ return replaceKnowledgeBindings;
+ }
+
+ /** @param replaceKnowledgeBindings 是否替换知识库绑定 */
+ public void setReplaceKnowledgeBindings(boolean replaceKnowledgeBindings) {
+ this.replaceKnowledgeBindings = replaceKnowledgeBindings;
+ }
+
+ /** @return Skill 绑定 */
+ public List getSkillBindings() {
+ return skillBindings;
+ }
+
+ /** @param skillBindings Skill 绑定 */
+ public void setSkillBindings(List skillBindings) {
+ this.skillBindings = skillBindings;
+ }
+
+ /** @return 是否替换 Skill 绑定 */
+ public boolean isReplaceSkillBindings() {
+ return replaceSkillBindings;
+ }
+
+ /** @param replaceSkillBindings 是否替换 Skill 绑定 */
+ public void setReplaceSkillBindings(boolean replaceSkillBindings) {
+ this.replaceSkillBindings = replaceSkillBindings;
+ }
+
+ /**
+ * 将 Skill 白名单引用转换为领域绑定。
+ *
+ * @return 最小 Skill 绑定列表
+ */
+ public List toSkillBindings() {
+ return skillBindings == null
+ ? List.of() : skillBindings.stream().map(AgentSkillBindingUpdateRequest.Binding::toEntity).toList();
+ }
+}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/agent/AgentSessionController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/agent/AgentSessionController.java
index 7259de13..c506a466 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/agent/AgentSessionController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/agent/AgentSessionController.java
@@ -1,16 +1,21 @@
package tech.easyflow.admin.controller.agent;
+import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
import org.springframework.web.bind.annotation.*;
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceConversationView;
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionDetailView;
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionPage;
import tech.easyflow.admin.service.agent.AgentSessionService;
+import tech.easyflow.agent.service.AgentOptionQueryService;
+import tech.easyflow.agent.vo.AgentOptionView;
+import tech.easyflow.agent.vo.AgentResourceOptionsView;
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
import tech.easyflow.chatlog.domain.query.ChatPageQuery;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
+import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import java.math.BigInteger;
@@ -21,17 +26,42 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/v1/agent/session")
+@SaCheckPermission("/api/v1/agent/session/query")
public class AgentSessionController {
private final AgentSessionService agentSessionService;
+ private final AgentOptionQueryService agentOptionQueryService;
/**
* 创建 Agent 管理端会话控制器。
*
* @param agentSessionService Agent 会话服务
+ * @param agentOptionQueryService Agent 安全选项服务
*/
- public AgentSessionController(AgentSessionService agentSessionService) {
+ public AgentSessionController(AgentSessionService agentSessionService,
+ AgentOptionQueryService agentOptionQueryService) {
this.agentSessionService = agentSessionService;
+ this.agentOptionQueryService = agentOptionQueryService;
+ }
+
+ /**
+ * 查询正式聊天可使用的已发布 Agent。
+ *
+ * @return Agent 安全选项
+ */
+ @GetMapping("/options")
+ public Result> options() {
+ return Result.ok(agentOptionQueryService.listAgentOptions(true));
+ }
+
+ /**
+ * 查询正式聊天可附加的知识库。
+ *
+ * @return 知识库安全选项
+ */
+ @GetMapping("/knowledgeOptions")
+ public Result> knowledgeOptions() {
+ return Result.ok(agentOptionQueryService.listKnowledgeOptions());
}
/**
@@ -130,7 +160,17 @@ public class AgentSessionController {
return Result.ok();
}
+ /**
+ * 获取当前登录账号。
+ *
+ * @return 当前登录账号
+ * @throws BusinessException 登录信息失效时抛出
+ */
private LoginAccount currentAccount() {
- return SaTokenUtil.getLoginAccount();
+ LoginAccount account = SaTokenUtil.getLoginAccount();
+ if (account == null || account.getId() == null || account.getTenantId() == null) {
+ throw new BusinessException("当前登录状态失效,请重新登录后再试");
+ }
+ return account;
}
}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/agent/AgentSkillBindingUpdateRequest.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/agent/AgentSkillBindingUpdateRequest.java
new file mode 100644
index 00000000..73f3ce67
--- /dev/null
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/agent/AgentSkillBindingUpdateRequest.java
@@ -0,0 +1,84 @@
+package tech.easyflow.admin.controller.agent;
+
+import tech.easyflow.agent.entity.AgentSkillBinding;
+
+import java.math.BigInteger;
+import java.util.List;
+
+/**
+ * Agent Skill 整组替换请求。
+ *
+ * 使用标准 JavaBean 以兼容 {@code @JsonBody} 的 Fastjson 1 嵌套列表转换。
+ */
+public class AgentSkillBindingUpdateRequest {
+
+ private BigInteger agentId;
+ private List bindings;
+
+ /** 创建空请求。 */
+ public AgentSkillBindingUpdateRequest() {
+ }
+
+ /**
+ * 创建 Agent Skill 绑定请求。
+ *
+ * @param agentId Agent ID
+ * @param bindings Skill 引用
+ */
+ public AgentSkillBindingUpdateRequest(BigInteger agentId, List bindings) {
+ this.agentId = agentId;
+ this.bindings = bindings;
+ }
+
+ /** @return Agent ID */
+ public BigInteger getAgentId() { return agentId; }
+ /** @param agentId Agent ID */
+ public void setAgentId(BigInteger agentId) { this.agentId = agentId; }
+ /** @return Skill 引用 */
+ public List getBindings() { return bindings; }
+ /** @param bindings Skill 引用 */
+ public void setBindings(List bindings) { this.bindings = bindings; }
+
+ /** 客户端允许提交的最小 Skill 引用。 */
+ public static class Binding {
+
+ private BigInteger skillId;
+ private Integer sortNo;
+
+ /** 创建空绑定。 */
+ public Binding() {
+ }
+
+ /**
+ * 创建最小 Skill 绑定。
+ *
+ * @param skillId Skill ID
+ * @param sortNo 排序号
+ */
+ public Binding(BigInteger skillId, Integer sortNo) {
+ this.skillId = skillId;
+ this.sortNo = sortNo;
+ }
+
+ /** @return Skill ID */
+ public BigInteger getSkillId() { return skillId; }
+ /** @param skillId Skill ID */
+ public void setSkillId(BigInteger skillId) { this.skillId = skillId; }
+ /** @return 排序号 */
+ public Integer getSortNo() { return sortNo; }
+ /** @param sortNo 排序号 */
+ public void setSortNo(Integer sortNo) { this.sortNo = sortNo; }
+
+ /**
+ * 转换为不含任何服务端快照的领域引用。
+ *
+ * @return 最小 Skill 绑定
+ */
+ public AgentSkillBinding toEntity() {
+ AgentSkillBinding value = new AgentSkillBinding();
+ value.setSkillId(skillId);
+ value.setSortNo(sortNo);
+ return value;
+ }
+ }
+}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java
index 534013a0..37be4678 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java
@@ -18,7 +18,7 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
-import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
+import tech.easyflow.admin.controller.ai.support.BotResourceCreatorNameSupport;
import tech.easyflow.admin.service.ai.ChatWorkspaceService;
import tech.easyflow.ai.chattime.availability.ChatTimeToolAvailabilityContext;
import tech.easyflow.ai.easyagents.listener.PromptChoreChatStreamListener;
@@ -82,9 +82,9 @@ public class BotController extends BaseCurdController {
@Resource
private ChatRoundOperateService chatRoundOperateService;
@Resource
- private AiResourceApprovalStateService aiResourceApprovalStateService;
+ private BotApprovalStateService botApprovalStateService;
@Resource
- private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;
+ private BotResourceCreatorNameSupport botResourceCreatorNameSupport;
@Resource
private ChatWorkspaceService chatWorkspaceService;
@@ -97,6 +97,16 @@ public class BotController extends BaseCurdController {
this.botMessageService = botMessageService;
}
+ /**
+ * 获取智能体列表关键字搜索字段。
+ *
+ * @return 标题和描述属性
+ */
+ @Override
+ protected String[] getKeywordSearchProperties() {
+ return new String[]{"title", "description"};
+ }
+
@Resource
private BotPluginService botPluginService;
@@ -240,7 +250,7 @@ public class BotController extends BaseCurdController {
bot = botService.toPublishedView(rawBot);
}
if (StpUtil.isLogin()) {
- aiResourceApprovalStateService.fillBotApprovalState(bot);
+ botApprovalStateService.fillApprovalState(bot);
}
return Result.ok(bot);
}
@@ -275,7 +285,7 @@ public class BotController extends BaseCurdController {
if (data.getModelId() == null) {
if (StpUtil.isLogin()) {
- aiResourceApprovalStateService.fillBotApprovalState(data);
+ botApprovalStateService.fillApprovalState(data);
}
return Result.ok(data);
}
@@ -286,7 +296,7 @@ public class BotController extends BaseCurdController {
if (llm == null) {
data.setModelId(null);
if (StpUtil.isLogin()) {
- aiResourceApprovalStateService.fillBotApprovalState(data);
+ botApprovalStateService.fillApprovalState(data);
}
return Result.ok(data);
}
@@ -302,21 +312,43 @@ public class BotController extends BaseCurdController {
}
if (StpUtil.isLogin()) {
- aiResourceApprovalStateService.fillBotApprovalState(data);
+ botApprovalStateService.fillApprovalState(data);
}
return Result.ok(data);
}
+ /**
+ * 提交聊天助手发布审批。
+ *
+ * @param id 助手 ID
+ * @param applicationReason 审批说明
+ * @return 审批实例 ID
+ */
@PostMapping("/submitPublishApproval")
@SaCheckPermission("/api/v1/bot/save")
- public Result submitPublishApproval(@JsonBody("id") BigInteger id) {
+ public Result submitPublishApproval(
+ @JsonBody("id") BigInteger id,
+ @JsonBody("applicationReason") String applicationReason
+ ) {
return buildApprovalActionResult(
- botPublishAppService.submitPublishApproval(id),
+ botPublishAppService.submitPublishApproval(id, applicationReason),
"已提交发布审批",
"已直接发布"
);
}
+ /**
+ * 预检聊天助手发布是否命中审批流。
+ *
+ * @param id 助手 ID
+ * @return 是否需要审批
+ */
+ @GetMapping("/publishApprovalRequirement")
+ @SaCheckPermission("/api/v1/bot/save")
+ public Result publishApprovalRequirement(@RequestParam BigInteger id) {
+ return Result.ok(botPublishAppService.isPublishApprovalRequired(id));
+ }
+
@PostMapping("/submitOfflineApproval")
@SaCheckPermission("/api/v1/bot/save")
public Result submitOfflineApproval(@JsonBody("id") BigInteger id) {
@@ -347,7 +379,7 @@ public class BotController extends BaseCurdController {
if (isPublishedOnlyRequest()) {
bots = bots.stream().map(botService::toPublishedView).toList();
}
- aiResourceApprovalStateService.fillBotApprovalState(bots);
+ botApprovalStateService.fillApprovalState(bots);
return Result.ok(bots);
}
@@ -359,8 +391,8 @@ public class BotController extends BaseCurdController {
if (isPublishedOnlyRequest()) {
result.setRecords(result.getRecords().stream().map(botService::toPublishedView).toList());
}
- aiResourceApprovalStateService.fillBotApprovalState(result.getRecords());
- aiResourceCreatorNameSupport.fillBotCreatorNames(result.getRecords());
+ botApprovalStateService.fillApprovalState(result.getRecords());
+ botResourceCreatorNameSupport.fillCreatorNames(result.getRecords());
return result;
}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotPluginItemController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotPluginItemController.java
new file mode 100644
index 00000000..984393f8
--- /dev/null
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotPluginItemController.java
@@ -0,0 +1,64 @@
+package tech.easyflow.admin.controller.ai;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import tech.easyflow.ai.entity.PluginItem;
+import tech.easyflow.ai.service.BotPluginService;
+import tech.easyflow.common.annotation.UsePermission;
+import tech.easyflow.common.domain.Result;
+import tech.easyflow.common.web.jsonbody.JsonBody;
+
+import java.math.BigInteger;
+import java.util.List;
+
+/**
+ * 旧 Bot 插件工具绑定查询控制器。
+ *
+ * 保留历史接口地址,同时将 Bot 表依赖限制在 Bot 专属代码中。
+ */
+@RestController
+@RequestMapping("/api/v1/pluginItem")
+@UsePermission(moduleName = "/api/v1/plugin")
+public class BotPluginItemController {
+
+ private final BotPluginService botPluginService;
+
+ /**
+ * 创建 Bot 插件工具绑定查询控制器。
+ *
+ * @param botPluginService Bot 插件绑定服务
+ */
+ public BotPluginItemController(BotPluginService botPluginService) {
+ this.botPluginService = botPluginService;
+ }
+
+ /**
+ * 查询插件工具,并标记指定 Bot 已绑定的工具。
+ *
+ * @param pluginId 插件 ID
+ * @param botId Bot ID
+ * @return 插件工具列表
+ */
+ @PostMapping("/toolsList")
+ @SaCheckPermission("/api/v1/plugin/query")
+ public Result> searchPluginTools(
+ @JsonBody(value = "pluginId", required = true) BigInteger pluginId,
+ @JsonBody(value = "botId", required = false) BigInteger botId) {
+ return Result.ok(botPluginService.searchPluginTools(pluginId, botId));
+ }
+
+ /**
+ * 查询指定 Bot 已绑定的插件工具。
+ *
+ * @param botId Bot ID
+ * @return 已绑定插件工具列表
+ */
+ @PostMapping("/tool/list")
+ @SaCheckPermission("/api/v1/plugin/query")
+ public Result> getPluginTools(
+ @JsonBody(value = "botId", required = true) BigInteger botId) {
+ return Result.ok(botPluginService.getPluginTools(botId));
+ }
+}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ChatHistoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ChatHistoryController.java
index fb6fef2f..3b02aee0 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ChatHistoryController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ChatHistoryController.java
@@ -1,10 +1,13 @@
package tech.easyflow.admin.controller.ai;
+import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import tech.easyflow.agent.service.AgentOptionQueryService;
+import tech.easyflow.agent.vo.AgentOptionView;
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
import tech.easyflow.chatlog.domain.dto.ChatSessionPage;
@@ -16,46 +19,133 @@ import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.jsonbody.JsonBody;
+import tech.easyflow.system.service.CategoryPermissionService;
import java.math.BigInteger;
import java.util.List;
@RestController
@RequestMapping("/api/v1/chatHistory")
+@SaCheckPermission("/api/v1/chatHistory/query")
public class ChatHistoryController {
private final ChatHistoryManageService chatHistoryManageService;
+ private final CategoryPermissionService categoryPermissionService;
+ private final AgentOptionQueryService agentOptionQueryService;
- public ChatHistoryController(ChatHistoryManageService chatHistoryManageService) {
+ /**
+ * 创建聊天历史控制器。
+ *
+ * @param chatHistoryManageService 聊天历史管理服务
+ * @param categoryPermissionService 账号权限服务
+ * @param agentOptionQueryService Agent 安全选项服务
+ */
+ public ChatHistoryController(ChatHistoryManageService chatHistoryManageService,
+ CategoryPermissionService categoryPermissionService,
+ AgentOptionQueryService agentOptionQueryService) {
this.chatHistoryManageService = chatHistoryManageService;
+ this.categoryPermissionService = categoryPermissionService;
+ this.agentOptionQueryService = agentOptionQueryService;
}
+ /**
+ * 查询聊天记录筛选可使用的 Agent。
+ *
+ * @return Agent 安全选项
+ */
+ @GetMapping("/agentOptions")
+ public Result> agentOptions() {
+ return Result.ok(agentOptionQueryService.listAgentOptions(false));
+ }
+
+ /**
+ * 分页查询当前账号可见的 Agent 会话。
+ *
+ * @param query 会话筛选条件
+ * @return 会话分页结果
+ */
@GetMapping("/sessions")
public Result listSessions(ChatSessionFilterQuery query) {
- return Result.ok(chatHistoryManageService.queryAdminSessions(query));
+ LoginAccount account = SaTokenUtil.getLoginAccount();
+ return Result.ok(chatHistoryManageService.queryAdminSessions(
+ account.getId(),
+ categoryPermissionService.isSuperAdmin(account),
+ query
+ ));
}
+ /**
+ * 获取当前账号可见的 Agent 会话详情。
+ *
+ * @param sessionId 会话 ID
+ * @return 会话详情
+ */
@GetMapping("/sessions/{sessionId}")
public Result getSession(@PathVariable BigInteger sessionId) {
- return Result.ok(chatHistoryManageService.getAdminSession(sessionId));
+ LoginAccount account = SaTokenUtil.getLoginAccount();
+ return Result.ok(chatHistoryManageService.getAdminSession(
+ account.getId(),
+ categoryPermissionService.isSuperAdmin(account),
+ sessionId
+ ));
}
+ /**
+ * 分页查询当前账号可见会话的消息。
+ *
+ * @param sessionId 会话 ID
+ * @param query 消息分页条件
+ * @return 消息分页结果
+ */
@GetMapping("/sessions/{sessionId}/messages")
public Result queryMessages(@PathVariable BigInteger sessionId, ChatPageQuery query) {
- return Result.ok(chatHistoryManageService.queryAdminMessages(sessionId, query));
+ LoginAccount account = SaTokenUtil.getLoginAccount();
+ return Result.ok(chatHistoryManageService.queryAdminMessages(
+ account.getId(),
+ categoryPermissionService.isSuperAdmin(account),
+ sessionId,
+ query
+ ));
}
+ /**
+ * 查询当前账号可见会话的答案版本。
+ *
+ * @param sessionId 会话 ID
+ * @param roundId 对话轮次 ID
+ * @return 答案版本列表
+ */
@GetMapping("/sessions/{sessionId}/rounds/{roundId}/variants")
public Result> listRoundVariants(@PathVariable BigInteger sessionId,
@PathVariable BigInteger roundId) {
- return Result.ok(chatHistoryManageService.listAdminRoundVariants(sessionId, roundId));
+ LoginAccount account = SaTokenUtil.getLoginAccount();
+ return Result.ok(chatHistoryManageService.listAdminRoundVariants(
+ account.getId(),
+ categoryPermissionService.isSuperAdmin(account),
+ sessionId,
+ roundId
+ ));
}
+ /**
+ * 选择当前账号可见会话的答案版本。
+ *
+ * @param sessionId 会话 ID
+ * @param roundId 对话轮次 ID
+ * @param variantIndex 目标版本索引
+ * @return 选中的答案记录
+ */
@PostMapping("/sessions/{sessionId}/rounds/{roundId}/selectVariant")
public Result selectRoundVariant(@PathVariable BigInteger sessionId,
@PathVariable BigInteger roundId,
@JsonBody(value = "variantIndex", required = true) Integer variantIndex) {
LoginAccount account = SaTokenUtil.getLoginAccount();
- return Result.ok(chatHistoryManageService.selectAdminRoundVariant(sessionId, roundId, variantIndex, account.getId()));
+ return Result.ok(chatHistoryManageService.selectAdminRoundVariant(
+ account.getId(),
+ categoryPermissionService.isSuperAdmin(account),
+ sessionId,
+ roundId,
+ variantIndex
+ ));
}
}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionController.java
index 59dcf159..70523874 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionController.java
@@ -14,10 +14,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
+import tech.easyflow.agent.entity.AgentKnowledgeBinding;
+import tech.easyflow.agent.service.AgentKnowledgeBindingService;
import tech.easyflow.ai.permission.KnowledgeVisibilityQueryHelper;
import tech.easyflow.ai.documentimport.DocumentImportDtos;
import tech.easyflow.ai.dto.KnowledgeSearchResultItem;
-import tech.easyflow.ai.entity.BotDocumentCollection;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.enums.PublishStatus;
@@ -27,7 +28,6 @@ import tech.easyflow.ai.vo.OfflineImpactCheckVo;
import tech.easyflow.approval.entity.vo.ApprovalActionResult;
import tech.easyflow.ai.rag.KnowledgeRetrievalRequest;
import tech.easyflow.ai.rag.KnowledgeRetrievalModes;
-import tech.easyflow.ai.service.BotDocumentCollectionService;
import tech.easyflow.ai.service.DocumentChunkService;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.ModelService;
@@ -68,7 +68,7 @@ public class DocumentCollectionController extends BaseCurdController onSaveOrUpdateBefore(DocumentCollection entity, boolean isSave) {
normalizeVisibilityScope(entity, isSave);
@@ -169,11 +179,11 @@ public class DocumentCollectionController extends BaseCurdController submitPublishApproval(@JsonBody("id") BigInteger id) {
+ public Result submitPublishApproval(
+ @JsonBody("id") BigInteger id,
+ @JsonBody("applicationReason") String applicationReason
+ ) {
return buildApprovalActionResult(
- knowledgePublishAppService.submitPublishApproval(id),
+ knowledgePublishAppService.submitPublishApproval(id, applicationReason),
"已提交发布审批",
"已直接发布"
);
}
+ /**
+ * 预检知识库发布是否命中审批流。
+ *
+ * @param id 知识库 ID
+ * @return 是否需要审批
+ */
+ @GetMapping("/publishApprovalRequirement")
+ @SaCheckPermission("/api/v1/documentCollection/save")
+ public Result publishApprovalRequirement(@RequestParam BigInteger id) {
+ return Result.ok(knowledgePublishAppService.isPublishApprovalRequired(id));
+ }
+
/**
* 提交下线审批。
*
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentController.java
index 694ca72f..ad92f16b 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentController.java
@@ -11,8 +11,11 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+import tech.easyflow.ai.documentimport.DocumentImportBatchDtos;
import tech.easyflow.ai.documentimport.DocumentImportDtos;
+import tech.easyflow.ai.documentimport.task.DocumentImportBatchAppService;
import tech.easyflow.ai.documentimport.task.DocumentImportTaskStatusStreamService;
import tech.easyflow.ai.entity.Document;
import tech.easyflow.ai.entity.DocumentCollection;
@@ -83,6 +86,9 @@ public class DocumentController extends BaseCurdController> documentList(@RequestParam(name="title", required = false) String fileName, @RequestParam(name="pageSize") int pageSize, @RequestParam(name = "pageNumber") int pageNumber) {
+ public Result> documentList(
+ @RequestParam(name = "keyword", required = false) String keyword,
+ @RequestParam(name = "title", required = false) String legacyTitle,
+ @RequestParam(name = "pageSize") int pageSize,
+ @RequestParam(name = "pageNumber") int pageNumber) {
String kbSlug = RequestUtil.getParamAsString("id");
if (StringUtil.noText(kbSlug)) {
throw new BusinessException("知识库id不能为空");
}
DocumentCollection knowledge = getDocumentCollection(kbSlug, ResourceAction.READ, "无权限访问知识库");
- Page documentList = documentService.getDocumentList(knowledge.getId().toString(), pageSize, pageNumber,fileName);
+ String effectiveKeyword = StringUtil.hasText(keyword) ? keyword : legacyTitle;
+ Page documentList = documentService.getDocumentList(
+ knowledge.getId().toString(), pageSize, pageNumber, effectiveKeyword);
return Result.ok(documentList);
}
@@ -312,6 +334,142 @@ public class DocumentController extends BaseCurdController createImportBatch(
+ @JsonBody DocumentImportBatchDtos.CreateRequest request) {
+ if (request == null || request.getKnowledgeId() == null) {
+ throw new BusinessException("知识库id不能为空");
+ }
+ getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
+ return Result.ok(documentImportBatchAppService.createBatch(request));
+ }
+
+ /**
+ * 上传一个批次文件。
+ *
+ * @param batchId 批次 ID
+ * @param itemId 文件项 ID
+ * @param knowledgeId 知识库 ID
+ * @param file 上传文件
+ * @return 文件项状态
+ */
+ @PostMapping(value = "import/batch/{batchId}/item/{itemId}/upload",
+ consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ @SaCheckPermission("/api/v1/documentCollection/save")
+ public Result uploadImportBatchItem(
+ @PathVariable BigInteger batchId,
+ @PathVariable BigInteger itemId,
+ @RequestParam BigInteger knowledgeId,
+ @RequestPart("file") MultipartFile file) {
+ getDocumentCollection(knowledgeId.toString(), ResourceAction.MANAGE, "无权限管理知识库");
+ return Result.ok(documentImportBatchAppService.uploadItem(
+ knowledgeId, batchId, itemId, file));
+ }
+
+ /**
+ * 启动手动或自动批量导入。
+ *
+ * @param request 启动请求
+ * @return 批次状态
+ */
+ @PostMapping("import/batch/start")
+ @SaCheckPermission("/api/v1/documentCollection/save")
+ public Result startImportBatch(
+ @JsonBody DocumentImportBatchDtos.StartRequest request) {
+ if (request == null || request.getKnowledgeId() == null) {
+ throw new BusinessException("知识库id不能为空");
+ }
+ getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
+ return Result.ok(documentImportBatchAppService.startBatch(request));
+ }
+
+ /**
+ * 取消一个尚未启动的上传批次。
+ *
+ * @param knowledgeId 知识库 ID
+ * @param batchId 批次 ID
+ * @return 空结果
+ */
+ @PostMapping("import/batch/cancel")
+ @SaCheckPermission("/api/v1/documentCollection/save")
+ public Result cancelImportBatch(
+ @JsonBody(value = "knowledgeId", required = true) BigInteger knowledgeId,
+ @JsonBody(value = "batchId", required = true) BigInteger batchId) {
+ getDocumentCollection(knowledgeId.toString(), ResourceAction.MANAGE, "无权限管理知识库");
+ documentImportBatchAppService.cancelBatch(knowledgeId, batchId);
+ return Result.ok();
+ }
+
+ /**
+ * 查询批次状态。
+ *
+ * @param knowledgeId 知识库 ID
+ * @param batchId 批次 ID
+ * @return 批次状态
+ */
+ @GetMapping("import/batch/status")
+ @SaCheckPermission("/api/v1/documentCollection/query")
+ public Result getImportBatchStatus(
+ @RequestParam BigInteger knowledgeId,
+ @RequestParam BigInteger batchId) {
+ getDocumentCollection(knowledgeId.toString(), ResourceAction.READ, "无权限访问知识库");
+ return Result.ok(documentImportBatchAppService.getBatchStatus(knowledgeId, batchId));
+ }
+
+ /**
+ * 查询知识库最近一个自动导入批次。
+ *
+ * @param knowledgeId 知识库 ID
+ * @return 最近批次状态
+ */
+ @GetMapping("import/batch/current")
+ @SaCheckPermission("/api/v1/documentCollection/query")
+ public Result getCurrentImportBatch(
+ @RequestParam BigInteger knowledgeId) {
+ getDocumentCollection(knowledgeId.toString(), ResourceAction.READ, "无权限访问知识库");
+ return Result.ok(documentImportBatchAppService.getLatestAutoBatch(knowledgeId));
+ }
+
+ /**
+ * 继续中断或部分失败的自动导入批次。
+ *
+ * @param knowledgeId 知识库 ID
+ * @param batchId 批次 ID
+ * @return 继续后的批次状态
+ */
+ @PostMapping("import/batch/continue")
+ @SaCheckPermission("/api/v1/documentCollection/save")
+ public Result continueImportBatch(
+ @JsonBody(value = "knowledgeId", required = true) BigInteger knowledgeId,
+ @JsonBody(value = "batchId", required = true) BigInteger batchId) {
+ getDocumentCollection(knowledgeId.toString(), ResourceAction.MANAGE, "无权限管理知识库");
+ return Result.ok(documentImportBatchAppService.continueBatch(knowledgeId, batchId));
+ }
+
+ /**
+ * 根据解析、分块或向量化失败阶段统一重试。
+ *
+ * @param request 重试请求
+ * @return 重试任务状态
+ */
+ @PostMapping("import/task/retry")
+ @SaCheckPermission("/api/v1/documentCollection/save")
+ public Result retryImportTask(
+ @JsonBody DocumentImportDtos.TaskRetryRequest request) {
+ if (request == null || request.getKnowledgeId() == null || request.getDocumentId() == null) {
+ throw new BusinessException("重试信息不完整");
+ }
+ getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
+ return documentService.retryFailedTask(request);
+ }
+
/**
* 更新 entity
*
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FaqItemController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FaqItemController.java
index 70f18757..27580be0 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FaqItemController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FaqItemController.java
@@ -2,6 +2,7 @@ package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -47,7 +48,7 @@ import java.util.Set;
@UsePermission(moduleName = "/api/v1/documentCollection")
public class FaqItemController extends BaseCurdController {
- private static final long MAX_IMAGE_SIZE_BYTES = 5L * 1024L * 1024L;
+ private static final long MAX_IMAGE_SIZE_BYTES = 20L * 1024L * 1024L;
private static final Set ALLOWED_IMAGE_TYPES = new HashSet<>(Arrays.asList(
"image/jpeg",
"image/png",
@@ -114,9 +115,14 @@ public class FaqItemController extends BaseCurdController MAX_IMAGE_SIZE_BYTES) {
- throw new BusinessException("图片大小不能超过5MB");
+ throw new BusinessException("图片大小不能超过20MB");
}
if (!isAllowedImageType(file)) {
throw new BusinessException("仅支持 JPG/PNG/WEBP/GIF 图片");
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/McpController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/McpController.java
index 1ede9e94..0b1ecabe 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/McpController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/McpController.java
@@ -9,16 +9,19 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import tech.easyflow.ai.entity.BotMcp;
import tech.easyflow.ai.entity.Mcp;
-import tech.easyflow.ai.service.BotMcpService;
+import tech.easyflow.ai.service.AgentResourceReferenceService;
import tech.easyflow.ai.service.McpService;
import tech.easyflow.common.domain.Result;
+import tech.easyflow.common.entity.LoginAccount;
+import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
+import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import javax.annotation.Resource;
import java.io.Serializable;
+import java.math.BigInteger;
/**
* 控制层。
@@ -33,8 +36,18 @@ public class McpController extends BaseCurdController {
super(service);
}
+ /**
+ * 获取 MCP 列表关键字搜索字段。
+ *
+ * @return 标题和描述属性
+ */
+ @Override
+ protected String[] getKeywordSearchProperties() {
+ return new String[]{"title", "description"};
+ }
+
@Resource
- private BotMcpService botMcpService;
+ private AgentResourceReferenceService agentResourceReferenceService;
@Override
public Result> save(Mcp entity) {
return service.saveMcp(entity);
@@ -45,11 +58,29 @@ public class McpController extends BaseCurdController {
return service.updateMcp(entity);
}
+ /**
+ * 删除未被 Agent 绑定的 MCP。
+ *
+ * @param id MCP ID
+ * @return 删除结果
+ */
@Override
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
public Result> remove(Serializable id) {
+ LoginAccount account = SaTokenUtil.getLoginAccount();
+ if (account == null || account.getTenantId() == null) {
+ throw new BusinessException("当前登录状态失效,请重新登录后再试");
+ }
+ // 锁定 MCP 资源行,与 Agent 绑定校验串行,避免检查后并发写入绑定。
+ Mcp mcp = service.getOne(QueryWrapper.create()
+ .eq(Mcp::getId, id)
+ .eq(Mcp::getTenantId, account.getTenantId())
+ .forUpdate());
+ if (mcp == null) {
+ throw new BusinessException("MCP 不存在或无权删除");
+ }
+ agentResourceReferenceService.assertMcpUnused(new BigInteger(String.valueOf(id)));
service.removeMcp(id);
- botMcpService.remove(QueryWrapper.create().eq(BotMcp::getMcpId, id));
return Result.ok();
}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelController.java
index d9728ac5..a7bf1633 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelController.java
@@ -9,15 +9,21 @@ import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import tech.easyflow.ai.dto.ModelInvokeConfigDtos;
import tech.easyflow.ai.entity.Model;
-import tech.easyflow.ai.entity.ModelProvider;
import tech.easyflow.ai.entity.table.ModelTableDef;
import tech.easyflow.ai.mapper.ModelMapper;
+import tech.easyflow.ai.service.AgentResourceReferenceService;
import tech.easyflow.ai.service.ModelService;
+import tech.easyflow.admin.model.ai.ModelGatewayConfigView;
+import tech.easyflow.common.satoken.util.SaTokenUtil;
+import tech.easyflow.system.entity.SysOption;
+import tech.easyflow.system.service.SysOptionService;
+import tech.easyflow.ai.service.capability.ModelCapabilityResolution;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.web.controller.BaseCurdController;
+import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import javax.annotation.Resource;
@@ -26,7 +32,6 @@ import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -39,6 +44,11 @@ import java.util.stream.Collectors;
@RequestMapping("/api/v1/model")
public class ModelController extends BaseCurdController {
+ private static final String CHAT_PUBLISH_BASE_URL = "chat_publish_base_url";
+
+ @Autowired
+ private SysOptionService sysOptionService;
+
public ModelController(ModelService service) {
super(service);
}
@@ -46,22 +56,30 @@ public class ModelController extends BaseCurdController {
@Autowired
ModelService modelService;
+ /**
+ * 查询模型统一网关页面所需的安全配置。
+ *
+ * @return 仅包含模型发布基础地址的配置
+ */
+ @GetMapping("/gatewayConfig")
+ @SaCheckPermission("/api/v1/model/query")
+ public Result gatewayConfig() {
+ SysOption option = sysOptionService.getByOptionKey(
+ CHAT_PUBLISH_BASE_URL,
+ SaTokenUtil.getLoginAccount().getTenantId()
+ );
+ return Result.ok(new ModelGatewayConfigView(option == null ? null : option.getValue()));
+ }
+
@Resource
ModelMapper modelMapper;
+ @Resource
+ AgentResourceReferenceService agentResourceReferenceService;
@GetMapping("list")
@SaCheckPermission("/api/v1/model/query")
public Result> list(Model entity, Boolean asTree, String sortKey, String sortType) {
- QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
- queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
- List list = Tree.tryToTree(modelMapper.selectListWithRelationsByQuery(queryWrapper), asTree);
- list.forEach(item -> {
- String providerName = Optional.ofNullable(item.getModelProvider())
- .map(ModelProvider::getProviderName)
- .orElse("-");
- item.setTitle(providerName + "/" + item.getTitle());
- });
- return Result.ok(list);
+ return Result.ok(service.listSelectableModels(entity, asTree, sortKey, sortType));
}
@GetMapping("getList")
@@ -92,9 +110,39 @@ public class ModelController extends BaseCurdController {
return Result.ok(modelService.verifyModelConfig(model));
}
+ /**
+ * 根据模型 ID 返回自动识别的类型和能力。
+ *
+ * @param providerId 供应商 ID
+ * @param modelName 模型 ID
+ * @return 模型能力识别结果
+ */
+ @GetMapping("capabilities")
+ @SaCheckPermission("/api/v1/model/query")
+ public Result resolveCapabilities(
+ @RequestParam(required = false) BigInteger providerId,
+ @RequestParam String modelName) {
+ return Result.ok(modelService.resolveModelCapabilities(providerId, modelName));
+ }
+
@PostMapping("/removeByEntity")
@SaCheckPermission("/api/v1/model/remove")
+ @Transactional(rollbackFor = Exception.class)
public Result> removeByEntity(@RequestBody Model entity) {
+ LoginAccount account = requireAccount();
+ List models = service.list(QueryWrapper.create()
+ .select(Model::getId)
+ .eq(Model::getProviderId, entity.getProviderId())
+ .eq(Model::getGroupName, entity.getGroupName())
+ .eq(Model::getTenantId, account.getTenantId())
+ .orderBy(Model::getId, true)
+ .forUpdate());
+ if (models.isEmpty()) {
+ throw new BusinessException("模型不存在或无权删除");
+ }
+ agentResourceReferenceService.assertModelsUnused(
+ models.stream().map(Model::getId).toList());
+ entity.setTenantId(account.getTenantId());
modelService.removeByEntity(entity);
return Result.ok();
}
@@ -145,8 +193,10 @@ public class ModelController extends BaseCurdController {
QueryWrapper queryWrapper = QueryWrapper.create();
queryWrapper.eq(Model::getProviderId, providerId);
queryWrapper.eq(Model::getModelType, modelType);
- if (StringUtils.hasLength(selectText)) {
- queryWrapper.and(ModelTableDef.MODEL.TITLE.like(selectText).or(ModelTableDef.MODEL.MODEL_NAME.like(selectText)));
+ String keyword = normalizeSearchKeyword(selectText);
+ if (StringUtils.hasText(keyword)) {
+ queryWrapper.and(buildLiteralContainsCondition(
+ keyword, ModelTableDef.MODEL.TITLE, ModelTableDef.MODEL.MODEL_NAME));
}
List totalList = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
Map> groupList = totalList.stream().collect(Collectors.groupingBy(Model::getGroupName));
@@ -167,11 +217,39 @@ public class ModelController extends BaseCurdController {
}
@PostMapping("removeLlmByIds")
- @Transactional
+ @SaCheckPermission("/api/v1/model/remove")
+ @Transactional(rollbackFor = Exception.class)
public Result> removeLlm(@JsonBody(value = "id", required = true) Serializable id) {
+ LoginAccount account = requireAccount();
List ids = Collections.singletonList(id);
- QueryWrapper queryWrapper = QueryWrapper.create().in(Model::getId, ids);
- service.remove(queryWrapper);
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .in(Model::getId, ids)
+ .eq(Model::getTenantId, account.getTenantId())
+ .orderBy(Model::getId, true)
+ .forUpdate();
+ List models = service.list(queryWrapper);
+ if (models.isEmpty()) {
+ throw new BusinessException("模型不存在或无权删除");
+ }
+ agentResourceReferenceService.assertModelsUnused(
+ models.stream().map(Model::getId).toList());
+ service.remove(QueryWrapper.create()
+ .in(Model::getId, ids)
+ .eq(Model::getTenantId, account.getTenantId()));
return Result.ok();
}
+
+ /**
+ * 获取带租户信息的当前登录账号。
+ *
+ * @return 当前登录账号
+ * @throws BusinessException 登录状态无效时抛出
+ */
+ private LoginAccount requireAccount() {
+ LoginAccount account = SaTokenUtil.getLoginAccount();
+ if (account == null || account.getTenantId() == null) {
+ throw new BusinessException("当前登录状态失效,请重新登录后再试");
+ }
+ return account;
+ }
}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelProviderController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelProviderController.java
index 6a6df634..e6c76032 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelProviderController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelProviderController.java
@@ -1,21 +1,33 @@
package tech.easyflow.admin.controller.ai;
+import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import tech.easyflow.ai.dto.RemoteModelImportRequest;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.entity.ModelProvider;
import tech.easyflow.ai.service.ModelProviderService;
import tech.easyflow.ai.service.ModelService;
+import tech.easyflow.ai.service.discovery.RemoteModelDiscoveryService;
+import tech.easyflow.ai.service.discovery.RemoteModelImportResult;
+import tech.easyflow.ai.service.discovery.RemoteModelImportService;
+import tech.easyflow.ai.service.discovery.RemoteModelListResult;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
+import tech.easyflow.common.entity.LoginAccount;
+import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import java.io.Serializable;
+import java.math.BigInteger;
/**
* 控制层。
@@ -28,12 +40,33 @@ import java.io.Serializable;
@UsePermission(moduleName = "/api/v1/model")
public class ModelProviderController extends BaseCurdController {
private final ModelService modelService;
+ private final RemoteModelDiscoveryService remoteModelDiscoveryService;
+ private final RemoteModelImportService remoteModelImportService;
- public ModelProviderController(ModelProviderService service, ModelService modelService) {
+ /**
+ * 创建模型服务商控制器。
+ *
+ * @param service 模型服务商服务
+ * @param modelService 模型服务
+ * @param remoteModelDiscoveryService 远端模型发现服务
+ * @param remoteModelImportService 远端模型一键添加服务
+ */
+ public ModelProviderController(ModelProviderService service,
+ ModelService modelService,
+ RemoteModelDiscoveryService remoteModelDiscoveryService,
+ RemoteModelImportService remoteModelImportService) {
super(service);
this.modelService = modelService;
+ this.remoteModelDiscoveryService = remoteModelDiscoveryService;
+ this.remoteModelImportService = remoteModelImportService;
}
+ /**
+ * 删除没有子模型的服务商。
+ *
+ * @param id 服务商 ID
+ * @return 删除结果
+ */
@Override
@PostMapping("remove")
@Transactional
@@ -45,4 +78,35 @@ public class ModelProviderController extends BaseCurdController remoteModels(@PathVariable BigInteger providerId) {
+ return Result.ok(remoteModelDiscoveryService.discover(providerId));
+ }
+
+ /**
+ * 幂等添加单个远端模型。
+ *
+ * @param providerId 服务商 ID
+ * @param request 一键添加请求
+ * @return 创建或已存在结果
+ */
+ @PostMapping("{providerId}/remoteModels/import")
+ @SaCheckPermission("/api/v1/model/save")
+ public Result importRemoteModel(
+ @PathVariable BigInteger providerId,
+ @RequestBody RemoteModelImportRequest request) {
+ LoginAccount account = SaTokenUtil.getLoginAccount();
+ Model model = new Model();
+ commonFiled(model, account.getId(), account.getTenantId(), account.getDeptId());
+ String modelId = request == null ? null : request.getModelId();
+ return Result.ok(remoteModelImportService.importModel(providerId, modelId, model));
+ }
+}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java
index 509b6d36..ca1ca71d 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java
@@ -2,6 +2,7 @@ package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
@@ -12,6 +13,7 @@ import tech.easyflow.ai.entity.Plugin;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.plugin.workflow.snapshot.WorkflowPluginSnapshotResolver;
import tech.easyflow.ai.service.ModelService;
+import tech.easyflow.ai.service.PluginItemService;
import tech.easyflow.ai.service.PluginVisibilityService;
import tech.easyflow.ai.permission.WorkflowVisibilityQueryHelper;
import tech.easyflow.ai.service.WorkflowService;
@@ -47,8 +49,24 @@ import static tech.easyflow.ai.entity.table.PluginTableDef.PLUGIN;
@RestController
@RequestMapping("/api/v1/plugin")
public class PluginController extends BaseCurdController {
+ /**
+ * 创建插件控制器。
+ *
+ * @param service 插件服务
+ */
public PluginController(PluginService service) {
super(service);
+ this.pluginService = service;
+ }
+
+ /**
+ * 获取插件列表关键字搜索字段。
+ *
+ * @return 插件名称和描述属性
+ */
+ @Override
+ protected String[] getKeywordSearchProperties() {
+ return new String[]{"name", "description"};
}
@Resource
@@ -89,10 +107,15 @@ public class PluginController extends BaseCurdController
return Result.ok(pluginService.updatePlugin(plugin));
}
+ /**
+ * 删除插件。
+ *
+ * @param id 插件 ID
+ * @return 删除结果
+ */
@PostMapping("/plugin/remove")
@SaCheckPermission("/api/v1/plugin/remove")
public Result removePlugin(@JsonBody(value = "id", required = true) String id){
-
return Result.ok(pluginService.removePlugin(id));
}
@@ -105,9 +128,24 @@ public class PluginController extends BaseCurdController
return Result.ok(pluginService.preparePluginsForCurrentUser(plugins, true, false));
}
+ /**
+ * 按分类分页查询插件,并支持按名称、描述模糊查询。
+ *
+ * @param request 当前请求
+ * @param sortKey 排序字段
+ * @param sortType 排序方向
+ * @param pageNumber 页码
+ * @param pageSize 每页数量
+ * @param category 分类 ID,0 表示全部分类
+ * @param keyword 插件名称或描述关键字
+ * @param name 兼容旧客户端的插件名称关键字
+ * @return 插件分页结果
+ */
@GetMapping("/pageByCategory")
@SaCheckPermission("/api/v1/plugin/query")
- public Result> pageByCategory(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize, int category) {
+ public Result> pageByCategory(HttpServletRequest request, String sortKey, String sortType,
+ Long pageNumber, Long pageSize, int category,
+ String keyword, String name) {
if (pageNumber == null || pageNumber < 1) {
pageNumber = 1L;
}
@@ -120,7 +158,10 @@ public class PluginController extends BaseCurdController
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper));
} else {
- Result> result = pluginService.pageByCategory(pageNumber, pageSize, category);
+ String effectiveKeyword = normalizeSearchKeyword(
+ keyword == null || keyword.isBlank() ? name : keyword);
+ Result> result = pluginService.pageByCategory(
+ pageNumber, pageSize, category, effectiveKeyword);
if (result != null && result.getData() != null) {
aiResourceCreatorNameSupport.fillPluginCreatorNames(result.getData().getRecords());
}
@@ -135,7 +176,7 @@ public class PluginController extends BaseCurdController
workflowVisibilityQueryHelper.applyReadableAccess(queryWrapper);
queryWrapper.eq("publish_status", tech.easyflow.ai.enums.PublishStatus.PUBLISHED.getCode());
if (keyword != null && !keyword.isBlank()) {
- queryWrapper.like("title", keyword.trim());
+ queryWrapper.and(buildLiteralContainsCondition(keyword, new QueryColumn("title")));
}
queryWrapper.orderBy("modified desc");
LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginItemController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginItemController.java
index 7b59a555..30e898e6 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginItemController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginItemController.java
@@ -19,19 +19,20 @@ import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage;
import tech.easyflow.ai.easyagentsflow.service.TinyFlowService;
import tech.easyflow.ai.easyagentsflow.service.WorkflowCheckService;
import tech.easyflow.ai.easyagentsflow.support.PublishedWorkflowDefinitionIds;
-import tech.easyflow.ai.entity.BotPlugin;
import tech.easyflow.ai.entity.Plugin;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.enums.PluginType;
import tech.easyflow.ai.plugin.workflow.snapshot.WorkflowPluginSnapshotResolver;
-import tech.easyflow.ai.service.BotPluginService;
import tech.easyflow.ai.service.PluginService;
import tech.easyflow.ai.service.PluginItemService;
+import tech.easyflow.ai.service.AgentResourceReferenceService;
+import tech.easyflow.ai.service.PluginVisibilityService;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
+import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
@@ -42,8 +43,10 @@ import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collection;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* 控制层。
@@ -55,18 +58,35 @@ import java.util.Map;
@RequestMapping("/api/v1/pluginItem")
@UsePermission(moduleName = "/api/v1/plugin")
public class PluginItemController extends BaseCurdController {
+ /**
+ * 创建插件工具控制器。
+ *
+ * @param service 插件工具服务
+ */
public PluginItemController(PluginItemService service) {
super(service);
}
+ /**
+ * 获取插件工具列表关键字搜索字段。
+ *
+ * @return 工具名称和描述属性
+ */
+ @Override
+ protected String[] getKeywordSearchProperties() {
+ return new String[]{"name", "description"};
+ }
+
@Resource
private PluginItemService pluginItemService;
@Resource
- private BotPluginService botPluginService;
+ private AgentResourceReferenceService agentResourceReferenceService;
@Resource
private PluginService pluginService;
@Resource
+ private PluginVisibilityService pluginVisibilityService;
+ @Resource
private WorkflowPluginSnapshotResolver workflowPluginSnapshotResolver;
@Resource
private WorkflowService workflowService;
@@ -91,25 +111,12 @@ public class PluginItemController extends BaseCurdController> searchPluginToolByPluginId(@JsonBody(value = "pluginId", required = true) BigInteger pluginId,
- @JsonBody(value = "botId", required = false) BigInteger botId){
- return Result.ok(pluginItemService.searchPluginToolByPluginId(pluginId, botId));
- }
-
@PostMapping("/tool/update")
@SaCheckPermission("/api/v1/plugin/save")
public Result updatePlugin(@JsonBody PluginItem pluginItem){
return Result.ok(pluginItemService.updatePlugin(pluginItem));
}
- @PostMapping("/tool/list")
- @SaCheckPermission("/api/v1/plugin/query")
- public Result> getPluginToolList(@JsonBody(value = "botId", required = true) BigInteger botId){
- return Result.ok(pluginItemService.getPluginToolList(botId));
- }
-
@GetMapping("/getTinyFlowData")
@SaCheckPermission("/api/v1/plugin/query")
public Result> getTinyFlowData(BigInteger id) {
@@ -275,26 +282,64 @@ public class PluginItemController extends BaseCurdController onRemoveBefore(Collection ids) {
-
- QueryWrapper queryWrapper = QueryWrapper.create();
- queryWrapper.in(BotPlugin::getPluginItemId, ids);
-
- boolean exists = botPluginService.exists(queryWrapper);
- if (exists){
- return Result.fail(1, "此工具还关联着bot,请先取消关联!");
- }
- if (ids.size() == 1) {
- PluginItem pluginItem = pluginItemService.getById(ids.iterator().next());
- if (pluginItem != null) {
- Plugin plugin = pluginService.getById(pluginItem.getPluginId());
- if (plugin != null && PluginType.isWorkflow(plugin.getType())) {
- return Result.fail(1, "工作流插件工具由系统自动维护,不支持删除");
+ Set uniquePluginItemIds = new LinkedHashSet<>();
+ try {
+ for (Serializable id : ids) {
+ if (id == null) {
+ throw new NumberFormatException("null");
}
+ uniquePluginItemIds.add(new BigInteger(String.valueOf(id)));
+ }
+ } catch (NumberFormatException exception) {
+ throw new BusinessException("插件工具 ID 不合法");
+ }
+
+ // BaseCurdController#remove 已开启事务;锁定工具行后校验权限和 Agent 引用。
+ List lockedPluginItems = pluginItemService.list(QueryWrapper.create()
+ .in(PluginItem::getId, uniquePluginItemIds)
+ .orderBy(PluginItem::getId, true)
+ .forUpdate());
+ if (lockedPluginItems == null || lockedPluginItems.size() != uniquePluginItemIds.size()) {
+ throw new BusinessException("插件工具不存在或已被删除");
+ }
+
+ LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
+ if (loginAccount == null || loginAccount.getTenantId() == null) {
+ throw new BusinessException("当前登录信息无效");
+ }
+ Map plugins = new HashMap<>();
+ for (PluginItem pluginItem : lockedPluginItems) {
+ if (pluginItem.getPluginId() == null) {
+ throw new BusinessException("插件工具关联的插件不存在");
+ }
+ Plugin plugin = plugins.get(pluginItem.getPluginId());
+ if (plugin == null) {
+ plugin = pluginService.getById(pluginItem.getPluginId());
+ if (plugin == null || plugin.getTenantId() == null
+ || !loginAccount.getTenantId().toString().equals(plugin.getTenantId().toString())) {
+ throw new BusinessException("无权限删除该插件工具");
+ }
+ pluginVisibilityService.assertPluginVisible(
+ plugin.getCreatedBy(),
+ plugin.getId(),
+ "无权限删除该插件工具"
+ );
+ plugins.put(pluginItem.getPluginId(), plugin);
+ }
+ if (PluginType.isWorkflow(plugin.getType())) {
+ return Result.fail(1, "工作流插件工具由系统自动维护,不支持删除");
}
}
+ agentResourceReferenceService.assertPluginItemsUnused(List.copyOf(uniquePluginItemIds));
return null;
}
}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java
index a885cede..ce5066bd 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java
@@ -45,6 +45,16 @@ public class ResourceController extends BaseCurdController onSaveOrUpdateBefore(Resource entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ShareKnowledgeController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ShareKnowledgeController.java
index 9718b6e5..f3d2f0ec 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ShareKnowledgeController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ShareKnowledgeController.java
@@ -6,6 +6,7 @@ import com.easyagents.core.store.DocumentStore;
import com.easyagents.core.store.StoreOptions;
import com.easyagents.core.store.StoreResult;
import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -47,6 +48,7 @@ import tech.easyflow.ai.vo.FaqImportResultVo;
import tech.easyflow.ai.vo.KnowledgeShareAuthContext;
import tech.easyflow.ai.vo.KnowledgeShareViewDetail;
import tech.easyflow.common.domain.Result;
+import tech.easyflow.common.util.SearchKeywordUtil;
import tech.easyflow.common.filestorage.FileStorageService;
import tech.easyflow.common.vo.UploadResVo;
import tech.easyflow.common.web.exceptions.BusinessException;
@@ -75,7 +77,7 @@ import java.util.Set;
@RequestMapping("/api/v1/share/knowledge")
public class ShareKnowledgeController {
- private static final long MAX_IMAGE_SIZE_BYTES = 5L * 1024L * 1024L;
+ private static final long MAX_IMAGE_SIZE_BYTES = 20L * 1024L * 1024L;
private static final Set ALLOWED_IMAGE_TYPES = new HashSet<>(Arrays.asList(
"image/jpeg",
"image/png",
@@ -668,9 +670,15 @@ public class ShareKnowledgeController {
faqCategoryService.ensureDefaultCategory(context.getKnowledge().getId());
QueryWrapper queryWrapper = QueryWrapper.create()
.eq(FaqItem::getCollectionId, context.getKnowledge().getId());
+ String keyword = request.getParameter("keyword");
String question = request.getParameter("question");
- if (StringUtils.hasText(question)) {
- queryWrapper.like(FaqItem::getQuestion, question.trim());
+ if (StringUtils.hasText(keyword)) {
+ String pattern = SearchKeywordUtil.literalContainsPattern(keyword);
+ queryWrapper.and(new QueryColumn("question").likeRaw(pattern)
+ .or(new QueryColumn("answer_text").likeRaw(pattern)));
+ } else if (StringUtils.hasText(question)) {
+ queryWrapper.and(new QueryColumn("question")
+ .likeRaw(SearchKeywordUtil.literalContainsPattern(question)));
}
String categoryId = request.getParameter("categoryId");
if (StringUtils.hasText(categoryId)) {
@@ -785,7 +793,7 @@ public class ShareKnowledgeController {
throw new BusinessException("图片不能为空");
}
if (file.getSize() > MAX_IMAGE_SIZE_BYTES) {
- throw new BusinessException("图片大小不能超过5MB");
+ throw new BusinessException("图片大小不能超过20MB");
}
if (!isAllowedImageType(file)) {
throw new BusinessException("仅支持 JPG/PNG/WEBP/GIF 图片");
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkFlowNodeController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkFlowNodeController.java
index f61c21a6..a60ed0a7 100644
--- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkFlowNodeController.java
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkFlowNodeController.java
@@ -1,88 +1,45 @@
package tech.easyflow.admin.controller.ai;
-import cn.hutool.core.util.IdUtil;
-import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONArray;
+import cn.dev33.satoken.annotation.SaCheckPermission;
import com.alibaba.fastjson2.JSONObject;
-import com.easyagents.flow.core.chain.ChainDefinition;
-import com.easyagents.flow.core.chain.Node;
-import com.easyagents.flow.core.node.ConfirmNode;
-import com.easyagents.flow.core.node.EndNode;
-import com.easyagents.flow.core.node.StartNode;
-import com.easyagents.flow.core.parser.ChainParser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import tech.easyflow.ai.easyagentsflow.service.WorkflowDatacenterContentService;
-import tech.easyflow.ai.entity.Workflow;
-import tech.easyflow.ai.service.WorkflowService;
+import tech.easyflow.admin.service.ai.WorkflowDesignerOptionService;
import tech.easyflow.common.domain.Result;
-import tech.easyflow.common.web.exceptions.BusinessException;
-import javax.annotation.Resource;
-import java.util.List;
+import java.math.BigInteger;
+/**
+ * 工作流节点兼容接口。
+ */
@RequestMapping("/api/v1/workflowNode")
@RestController
public class WorkFlowNodeController {
- @Resource
- private WorkflowService workflowService;
- @Resource
- private ChainParser chainParser;
- @Resource
- private WorkflowDatacenterContentService workflowDatacenterContentService;
+ private final WorkflowDesignerOptionService workflowDesignerOptionService;
- @GetMapping("/getChainParams")
- public Result> getChainParams(String currentId, String workflowId) {
- if (workflowId.equals(currentId)) {
- throw new BusinessException("工作流不能作为自身子节点");
- }
- JSONObject nodeData = new JSONObject();
- Workflow workflow = workflowService.getById(workflowId);
- if (workflow == null) {
- throw new BusinessException("工作流不存在: " + workflowId);
- }
- nodeData.put("workflowId", workflow.getId());
- nodeData.put("workflowName", workflow.getTitle());
-
- ChainDefinition definition = chainParser.parse(workflowDatacenterContentService.prepareContent(workflow.getContent()));
- List nodes = definition.getNodes();
- JSONArray inputs = new JSONArray();
- JSONArray outputs = new JSONArray();
- for (Node node : nodes) {
- if (node instanceof StartNode) {
- inputs = JSON.parseArray(JSON.toJSONString(node.getParameters()));
- handleArray(inputs);
- }
- if (node instanceof EndNode) {
- outputs = JSON.parseArray(JSON.toJSONString(((EndNode) node).getOutputDefs()));
- handleArray(outputs);
- }
- if (node instanceof ConfirmNode) {
- throw new BusinessException("工作流存在【确认节点】,暂不支持作为子节点");
- }
- }
- nodeData.put("parameters", inputs);
- nodeData.put("outputDefs", outputs);
- return Result.ok(nodeData);
+ /**
+ * 创建工作流节点兼容控制器。
+ *
+ * @param workflowDesignerOptionService 工作流设计器选项服务
+ */
+ public WorkFlowNodeController(WorkflowDesignerOptionService workflowDesignerOptionService) {
+ this.workflowDesignerOptionService = workflowDesignerOptionService;
}
- private void handleArray(JSONArray array) {
- if (array != null) {
- for (Object o : array) {
- JSONObject obj = (JSONObject) o;
- obj.put("id", IdUtil.simpleUUID());
- obj.put("nameDisabled", true);
- obj.put("dataTypeDisabled", true);
- obj.put("deleteDisabled", true);
- obj.put("addChildDisabled", true);
- obj.put("refType", "ref");
- JSONArray children = obj.getJSONArray("children");
- if (children != null) {
- handleArray(children);
- }
- }
- }
+ /**
+ * 查询子流程输入输出定义。
+ *
+ * @param currentId 当前工作流 ID
+ * @param workflowId 子流程 ID
+ * @return 子流程节点配置
+ * @deprecated 请使用 {@code /api/v1/workflow/designer/childWorkflow}
+ */
+ @Deprecated
+ @GetMapping("/getChainParams")
+ @SaCheckPermission("/api/v1/workflow/query")
+ public Result getChainParams(BigInteger currentId, BigInteger workflowId) {
+ return Result.ok(workflowDesignerOptionService.getChildWorkflowNodeData(currentId, workflowId));
}
}
diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowChatController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowChatController.java
new file mode 100644
index 00000000..e2091282
--- /dev/null
+++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowChatController.java
@@ -0,0 +1,353 @@
+package tech.easyflow.admin.controller.ai;
+
+import com.easyagents.flow.core.chain.ChainStatus;
+import com.easyagents.flow.core.chain.runtime.ChainExecutor;
+import com.mybatisflex.core.query.QueryWrapper;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+import tech.easyflow.admin.service.ai.WorkflowChatEventStream;
+import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage;
+import tech.easyflow.ai.easyagentsflow.service.WorkflowCheckService;
+import tech.easyflow.ai.easyagentsflow.service.WorkflowRunningParameterResolver;
+import tech.easyflow.ai.easyagentsflow.support.PublishedWorkflowDefinitionIds;
+import tech.easyflow.ai.entity.Workflow;
+import tech.easyflow.ai.entity.WorkflowExecResult;
+import tech.easyflow.ai.entity.WorkflowExecStep;
+import tech.easyflow.ai.enums.PublishStatus;
+import tech.easyflow.ai.service.WorkflowExecResultService;
+import tech.easyflow.ai.service.WorkflowExecStepService;
+import tech.easyflow.ai.service.WorkflowService;
+import tech.easyflow.ai.service.WorkflowShareService;
+import tech.easyflow.ai.share.WorkflowSharePolicy;
+import tech.easyflow.ai.utils.WorkFlowUtil;
+import tech.easyflow.common.constant.Constants;
+import tech.easyflow.common.domain.Result;
+import tech.easyflow.common.entity.LoginAccount;
+import tech.easyflow.common.satoken.util.SaTokenUtil;
+import tech.easyflow.common.web.exceptions.BusinessException;
+import tech.easyflow.common.web.jsonbody.JsonBody;
+import tech.easyflow.system.enums.CategoryResourceType;
+import tech.easyflow.system.enums.ResourceAction;
+import tech.easyflow.system.service.ResourceAccessService;
+
+import javax.annotation.Resource;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 工作流管理端与分享端的对话运行接口。
+ */
+@RestController
+@RequestMapping("/api/v1/workflowChat")
+public class WorkflowChatController {
+
+ @Resource
+ private WorkflowService workflowService;
+ @Resource
+ private WorkflowShareService workflowShareService;
+ @Resource
+ private WorkflowCheckService workflowCheckService;
+ @Resource
+ private WorkflowRunningParameterResolver parameterResolver;
+ @Resource
+ private ResourceAccessService resourceAccessService;
+ @Resource
+ private WorkflowChatEventStream eventStream;
+ @Resource
+ private ChainExecutor chainExecutor;
+ @Resource
+ private WorkflowExecResultService execResultService;
+ @Resource
+ private WorkflowExecStepService execStepService;
+
+ /**
+ * 获取工作流的对话运行描述和输入表单。
+ *
+ * @param workflowId 工作流 ID
+ * @param request HTTP 请求
+ * @return 对话运行描述
+ */
+ @GetMapping("/descriptor")
+ public Result