package com.ruoyi.ai.assistant; import com.alibaba.fastjson2.JSON; import com.ruoyi.ai.tools.ApproveTodoTools; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @Component public class ApproveTodoIntentExecutor { private static final Pattern APPROVE_ID_BY_LABEL_PATTERN = Pattern.compile("(流程编号|流程号|流程ID|审批编号|编号)\\s*[::]?\\s*([A-Za-z0-9_-]{2,64})"); private static final Pattern APPROVE_ID_PATTERN = Pattern.compile("\\b[A-Za-z]*\\d{6,}[A-Za-z0-9_-]*\\b"); private static final Pattern LIMIT_PATTERN = Pattern.compile("(前|最近)?\\s*(\\d{1,2})\\s*条"); private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})"); private static final Pattern NUMBER_PATTERN = Pattern.compile("(\\d+(?:\\.\\d+)?)"); private static final Pattern RECENT_RANGE_PATTERN = Pattern.compile("近\\d+(天|周|个月|月|年)"); private static final Pattern HALF_RANGE_PATTERN = Pattern.compile("(最近|近)?半(个)?(月|年)"); private static final Pattern EXPLICIT_RANGE_PATTERN = Pattern.compile(".*(到|至).*"); private final ApproveTodoTools approveTodoTools; public ApproveTodoIntentExecutor(ApproveTodoTools approveTodoTools) { this.approveTodoTools = approveTodoTools; } public String tryExecute(String memoryId, String message) { if (!StringUtils.hasText(message)) { return null; } String text = message.trim(); String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text); if (StringUtils.hasText(quickPromptResponse)) { return quickPromptResponse; } String approveId = extractApproveId(text); boolean hasApproveId = StringUtils.hasText(approveId) && !isPlaceholderApproveId(approveId); String startDate = extractStartDate(text); String endDate = extractEndDate(text); String timeRange = extractTimeRange(text); if (isStatsIntent(text)) { return approveTodoTools.getTodoStats( memoryId, startDate, endDate, timeRange ); } if (containsAny(text, "流转", "进度", "节点", "日志", "卡在", "卡到", "当前审批人", "处理记录")) { return hasApproveId ? approveTodoTools.getTodoProgress(memoryId, approveId) : missingApproveId("todo_progress", "查询审批进度需要提供流程编号。"); } if (containsAny(text, "详情", "明细") && !containsAny(text, "列表")) { return hasApproveId ? approveTodoTools.getTodoDetail(memoryId, approveId) : missingApproveId("todo_detail", "查询审批详情需要提供流程编号。"); } if (containsAny(text, "取消审核", "撤销审核", "回退审核", "撤销审批", "撤回审批") || (containsAny(text, "撤销", "撤回") && containsAny(text, "审批操作", "审核操作"))) { return hasApproveId ? approveTodoTools.cancelReviewTodo(memoryId, approveId, extractRemark(text)) : missingApproveId("cancel_review_action", "取消审核需要提供流程编号。"); } if (containsAny(text, "删除", "移除")) { return hasApproveId ? approveTodoTools.deleteTodo(memoryId, approveId) : missingApproveId("delete_action", "删除审批单需要提供流程编号。"); } if (containsAny(text, "驳回", "拒绝")) { return hasApproveId ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", extractRemark(text)) : missingApproveId("review_action", "驳回审批需要提供流程编号。"); } if (containsAny(text, "审核通过", "审批通过", "通过审批", "同意审批", "审批同意")) { return hasApproveId ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text)) : missingApproveId("review_action", "审批通过需要提供流程编号。"); } if (hasApproveId && containsAny(text, "通过", "同意") && !containsAny(text, "未通过", "通过率", "审批通过率", "审核通过率")) { return approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text)); } if (containsAny(text, "修改", "更新", "变更")) { return hasApproveId ? approveTodoTools.updateTodo( memoryId, approveId, extractValue(text, "标题"), extractDateValue(text, "开始日期"), extractDateValue(text, "结束日期"), extractBigDecimalValue(text, "金额"), extractValue(text, "地点"), extractIntegerValue(text, "类型"), extractValue(text, "备注")) : missingApproveId("update_action", "修改审批单需要提供流程编号。"); } if (containsAny(text, "列表", "待办", "查询审批", "单据", "流程", "审批批")) { return approveTodoTools.listTodos( memoryId, extractStatus(text), extractApproveType(text), extractKeyword(text), extractLimit(text), extractScope(text), startDate, endDate, timeRange); } return null; } private String tryExecuteQuickPrompt(String memoryId, String text) { String normalized = normalizeForMatch(text); String approveId = extractApproveId(text); boolean hasApproveId = StringUtils.hasText(approveId) && !isPlaceholderApproveId(approveId); if ("我当前有哪些审批待办需要处理".equals(normalized)) { return approveTodoTools.listTodos(memoryId, "pending", null, null, 10, "approver", null, null, null); } if ("帮我列出今天新增的审批待办".equals(normalized)) { return approveTodoTools.listTodos(memoryId, "all", null, null, 10, "related", null, null, "今天"); } if ("当前待我审批的单据按时间倒序列出来".equals(normalized)) { return approveTodoTools.listTodos(memoryId, "pending", null, null, 10, "approver", null, null, null); } if ("我发起的审批里哪些还在处理中".equals(normalized)) { return approveTodoTools.listTodos(memoryId, "processing", null, null, 10, "applicant", null, null, null); } if ("近7天我的审批待办统计情况怎么样".equals(normalized)) { return approveTodoTools.getTodoStats(memoryId, null, null, "近7天"); } if ("本月我的审批中通过驳回处理中各有多少".equals(normalized)) { return approveTodoTools.getTodoStats(memoryId, null, null, "本月"); } if ("近30天各类型审批数量分布是什么".equals(normalized)) { return approveTodoTools.getTodoStats(memoryId, null, null, "近30天"); } if (normalized.startsWith("查询流程编号") && normalized.contains("审批详情")) { return hasApproveId ? approveTodoTools.getTodoDetail(memoryId, approveId) : missingApproveId("todo_detail", "查询审批详情需要提供真实流程编号。"); } if (normalized.startsWith("流程编号") && normalized.contains("卡在哪个审批节点") && normalized.contains("当前审批人是谁")) { return hasApproveId ? approveTodoTools.getTodoProgress(memoryId, approveId) : missingApproveId("todo_progress", "查询审批进度需要提供真实流程编号。"); } if (normalized.startsWith("帮我查看流程编号") && normalized.contains("审批流转记录")) { return hasApproveId ? approveTodoTools.getTodoProgress(memoryId, approveId) : missingApproveId("todo_progress", "查询审批流转记录需要提供真实流程编号。"); } if (normalized.startsWith("帮我审批通过流程编号")) { return hasApproveId ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text)) : missingApproveId("review_action", "审批通过需要提供真实流程编号。"); } if (normalized.startsWith("帮我驳回流程编号")) { return hasApproveId ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", extractRemark(text)) : missingApproveId("review_action", "驳回审批需要提供真实流程编号。"); } if (normalized.startsWith("撤销我刚刚对流程编号") && normalized.contains("审批操作")) { return hasApproveId ? approveTodoTools.cancelReviewTodo(memoryId, approveId, extractRemark(text)) : missingApproveId("cancel_review_action", "撤销审批操作需要提供真实流程编号。"); } if (normalized.startsWith("帮我修改流程编号") && normalized.contains("备注为")) { return hasApproveId ? approveTodoTools.updateTodo(memoryId, approveId, null, null, null, null, null, null, extractRemark(text)) : missingApproveId("update_action", "修改审批单需要提供真实流程编号。"); } if (normalized.startsWith("删除我发起的流程编号")) { return hasApproveId ? approveTodoTools.deleteTodo(memoryId, approveId) : missingApproveId("delete_action", "删除审批单需要提供真实流程编号。"); } return null; } private boolean isStatsIntent(String text) { if (containsAny(text, "统计", "分析", "图表", "趋势", "占比", "汇总", "总量", "分布", "各有多少", "有多少")) { return true; } boolean hasQueryWord = containsAny(text, "查询", "查看", "看下", "看看", "获取"); boolean hasDataWord = containsAny(text, "数据", "报表", "情况", "数量", "金额"); boolean hasTimeWord = containsAny(text, "今天", "昨日", "昨天", "本周", "上周", "本月", "上月", "本年", "今年", "去年") || DATE_PATTERN.matcher(text).find() || RECENT_RANGE_PATTERN.matcher(text).find() || HALF_RANGE_PATTERN.matcher(text).find() || EXPLICIT_RANGE_PATTERN.matcher(text).matches(); return hasQueryWord && hasDataWord && hasTimeWord; } private boolean containsAny(String text, String... keywords) { for (String keyword : keywords) { if (text.contains(keyword)) { return true; } } return false; } private String extractApproveId(String text) { Matcher keywordMatcher = APPROVE_ID_BY_LABEL_PATTERN.matcher(text); if (keywordMatcher.find()) { return keywordMatcher.group(2); } Matcher matcher = APPROVE_ID_PATTERN.matcher(text); return matcher.find() ? matcher.group() : null; } private Integer extractLimit(String text) { Matcher matcher = LIMIT_PATTERN.matcher(text); return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10; } private String extractStatus(String text) { if (containsAny(text, "待审核", "待审批")) { return "pending"; } if (containsAny(text, "审核中", "处理中", "处理中的", "办理中")) { return "processing"; } if (containsAny(text, "已通过", "通过", "审核完成", "审批完成")) { return "approved"; } if (containsAny(text, "未通过", "驳回", "已驳回", "拒绝")) { return "rejected"; } if (containsAny(text, "重新提交")) { return "resubmitted"; } return "all"; } private Integer extractApproveType(String text) { if (text.contains("公出")) { return 1; } if (text.contains("请假")) { return 2; } if (text.contains("出差")) { return 3; } if (text.contains("报销")) { return 4; } if (text.contains("采购")) { return 5; } if (text.contains("报价")) { return 6; } if (text.contains("发货")) { return 7; } if (text.contains("危险作业")) { return 8; } return null; } private String extractKeyword(String text) { String cleaned = text .replace("查询", "") .replace("查看", "") .replace("列出", "") .replace("帮我", "") .replace("审批", "") .replace("单据", "") .replace("待办", "") .replace("列表", "") .replace("流程编号", "") .replace("流程号", "") .replace("前10条", "") .replace("最近10条", "") .trim(); return cleaned.length() >= 2 ? cleaned : null; } private String extractValue(String text, String fieldName) { Pattern pattern = Pattern.compile(fieldName + "(改为|修改为|为|是)[::]?[\\s]*([^,,。;;\\s]+)"); Matcher matcher = pattern.matcher(text); return matcher.find() ? matcher.group(2) : null; } private String extractDateValue(String text, String fieldName) { int index = text.indexOf(fieldName); if (index < 0) { return null; } Matcher matcher = DATE_PATTERN.matcher(text.substring(index)); return matcher.find() ? matcher.group(1) : null; } private String extractStartDate(String text) { Matcher matcher = DATE_PATTERN.matcher(text); return matcher.find() ? matcher.group(1) : null; } private String extractEndDate(String text) { Matcher matcher = DATE_PATTERN.matcher(text); if (!matcher.find()) { return null; } return matcher.find() ? matcher.group(1) : null; } private String extractTimeRange(String text) { if (containsAny(text, "今天", "昨日", "昨天", "本周", "上周", "本月", "上月", "本年", "今年", "去年")) { return text; } if (RECENT_RANGE_PATTERN.matcher(text).find()) { return text; } if (HALF_RANGE_PATTERN.matcher(text).find()) { return text; } if (EXPLICIT_RANGE_PATTERN.matcher(text).matches()) { return text; } return null; } private Integer extractIntegerValue(String text, String fieldName) { if (!text.contains(fieldName)) { return null; } Matcher matcher = Pattern.compile(fieldName + "(改为|修改为|为|是)[::]?[\\s]*(\\d{1,2})").matcher(text); return matcher.find() ? Integer.parseInt(matcher.group(2)) : null; } private BigDecimal extractBigDecimalValue(String text, String fieldName) { int index = text.indexOf(fieldName); if (index < 0) { return null; } Matcher matcher = NUMBER_PATTERN.matcher(text.substring(index)); return matcher.find() ? new BigDecimal(matcher.group(1)) : null; } private String extractTail(String text, String key) { Pattern quotedPattern = Pattern.compile(key + "(是|为)?[::]?[\\s]*[“\"]([^”\"]+)[”\"]"); Matcher quotedMatcher = quotedPattern.matcher(text); if (quotedMatcher.find()) { return cleanContent(quotedMatcher.group(2)); } Pattern pattern = Pattern.compile(key + "(是|为)?[::]?[\\s]*(.+)"); Matcher matcher = pattern.matcher(text); return matcher.find() ? cleanContent(matcher.group(2)) : null; } private String extractScope(String text) { if (containsAny(text, "我发起", "我提交", "我申请", "申请人是我")) { return "applicant"; } if (containsAny(text, "待我审批", "待我审核", "我处理", "我审批", "当前待我", "需要我处理", "需要处理")) { return "approver"; } return "related"; } private String extractRemark(String text) { return firstNonBlank(firstNonBlank(extractTail(text, "备注"), extractTail(text, "原因")), extractQuotedContent(text)); } private String extractQuotedContent(String text) { Matcher matcher = Pattern.compile("[“\"]([^”\"]+)[”\"]").matcher(text); return matcher.find() ? cleanContent(matcher.group(1)) : null; } private String normalizeForMatch(String text) { if (!StringUtils.hasText(text)) { return ""; } return text.replace(",", "") .replace(",", "") .replace("。", "") .replace(".", "") .replace("!", "") .replace("!", "") .replace("?", "") .replace("?", "") .replace(":", "") .replace(":", "") .replace(";", "") .replace(";", "") .replace("“", "") .replace("”", "") .replace("\"", "") .replace(" ", "") .trim(); } private boolean isPlaceholderApproveId(String approveId) { if (!StringUtils.hasText(approveId)) { return true; } String value = approveId.trim(); return "xxx".equalsIgnoreCase(value) || value.matches("[xX]{2,}") || "流程编号".equals(value) || "编号".equals(value) || value.contains("示例") || value.contains("请输入"); } private String cleanContent(String text) { if (!StringUtils.hasText(text)) { return null; } return text.trim() .replace("“", "") .replace("”", "") .replace("\"", "") .replace("。", "") .replace(";", "") .replace(";", "") .trim(); } private String firstNonBlank(String first, String second) { return StringUtils.hasText(first) ? first : second; } private String missingApproveId(String type, String description) { Map result = new LinkedHashMap<>(); result.put("success", false); result.put("type", type); result.put("description", description); result.put("summary", Map.of()); result.put("data", Map.of()); result.put("charts", Map.of()); return JSON.toJSONString(result); } }