Commit 81a572e65ebc5788208e462747e91f797fea2536
1 parent
dcf98766
1111
Showing
9 changed files
with
231 additions
and
78 deletions
pom.xml
src/main/java/com/xly/agent/DynamicTableNl2SqlAiAgent.java
| ... | ... | @@ -23,10 +23,11 @@ public interface DynamicTableNl2SqlAiAgent { |
| 23 | 23 | 2. 输出格式:仅返回SQL语句本身,无任何解释、换行、```sql/```包裹、备注、多余空格,直接输出可执行SQL; |
| 24 | 24 | 3. 编写规范: |
| 25 | 25 | 3.1 多表关联必须使用 表名+字段名(如表名.字段名),严格按下面[涉及表名]中的表次序关联,聚合函数(SUM/COUNT/AVG/MIN/MAX)必须加业务化别名,日期过滤使用标准DATE格式(yyyy-MM-dd); |
| 26 | - 3.2 SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件 | |
| 27 | - 3.3 SQL所有字段涉及的所有表名,都要**严格**按下面[涉及表名]中的表次序关联,没有关联不允许使用 | |
| 28 | - 3.4 SQL所有的查询条件,如果是字符类型的字段,均需要加不为空判断,用示例格式判断,示例:ifnull(customername,'')<>'' | |
| 29 | - 3.5 SQL所有的显示字段的别名中,不能出现空格,如: tCreateDate as earliest 订单日期,正确的应是 tCreateDate as earliest订单日期 | |
| 26 | + 3.2 SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件; | |
| 27 | + 3.3 SQL所有字段涉及的所有表名,都要**严格**按下面[涉及表名]中的表次序关联,没有关联不允许使用; | |
| 28 | + 3.4 SQL所有的查询条件,如果是字符类型的字段,均需要加不为空判断,用示例格式判断,示例:ifnull(customername,'')<>''; | |
| 29 | + 3.5 SQL所有的查询条件,如果是日期类型的字段,均需要加不为空判断,用示例格式判断,示例:tmakedate is not Null; | |
| 30 | + 3.6 SQL所有的显示字段的别名中,不能出现空格,如: tCreateDate as earliest 订单日期,正确的应是 tCreateDate as earliest订单日期 | |
| 30 | 31 | 4. 安全约束:禁止生成任何DDL/DML语句(DROP/ALTER/INSERT/UPDATE/DELETE等),禁止使用子查询、存储过程、自定义函数、临时表; |
| 31 | 32 | 5. 精准性: |
| 32 | 33 | 5.1 严格按用户需求+传入的表结构生成,仅使用指定字段/表,无多余字段、无无效表关联、无冗余过滤条件; | ... | ... |
src/main/java/com/xly/entity/UserSceneSession.java
| ... | ... | @@ -41,6 +41,12 @@ public class UserSceneSession { |
| 41 | 41 | private SceneDto currentScene; |
| 42 | 42 | |
| 43 | 43 | private ToolMeta currentTool; |
| 44 | + | |
| 45 | + /*** | |
| 46 | + * 当前未清返回的数据集 | |
| 47 | + **/ | |
| 48 | + private Map<Integer,Map<String,Object>> currentRowData; | |
| 49 | + | |
| 44 | 50 | /*** |
| 45 | 51 | * @Author 钱豹 |
| 46 | 52 | * @Date 10:07 2026/1/31 | ... | ... |
src/main/java/com/xly/service/XlyErpService.java
| ... | ... | @@ -108,6 +108,7 @@ public class XlyErpService { |
| 108 | 108 | //5.执行工具方法后,清除记忆 |
| 109 | 109 | if(session.getBCleanMemory()){ |
| 110 | 110 | operableChatMemoryProvider.clearSpecifiedMemory(userId); |
| 111 | + session.setCurrentTool(null); | |
| 111 | 112 | session.setBCleanMemory(false); |
| 112 | 113 | } |
| 113 | 114 | // 6.找到方法并且本方法带表结构描述时,需要调用 自然语言转SQL智能体 |
| ... | ... | @@ -318,6 +319,7 @@ public class XlyErpService { |
| 318 | 319 | session.setBCleanMemory(false); |
| 319 | 320 | session.setCurrentTool(null); |
| 320 | 321 | session.setCurrentScene(null); |
| 322 | + session.setCurrentRowData(null); | |
| 321 | 323 | UserSceneSessionService.USER_SCENE_SESSION_CACHE.put(userId, session); |
| 322 | 324 | // 清空Agent缓存 |
| 323 | 325 | UserSceneSessionService.ERP_AGENT_CACHE.remove(userId); | ... | ... |
src/main/java/com/xly/tool/DynamicToolProvider.java
| ... | ... | @@ -20,10 +20,7 @@ import com.xly.mapper.ParamRuleMapper; |
| 20 | 20 | import com.xly.mapper.ToolMetaMapper; |
| 21 | 21 | import com.xly.service.DynamicExeDbService; |
| 22 | 22 | import com.xly.service.UserSceneSessionService; |
| 23 | -import com.xly.util.DeepCopyUtils; | |
| 24 | -import com.xly.util.HttpsRequestUtil; | |
| 25 | -import com.xly.util.JsonUtils; | |
| 26 | -import com.xly.util.OkHttpUtil; | |
| 23 | +import com.xly.util.*; | |
| 27 | 24 | import dev.langchain4j.agent.tool.*; |
| 28 | 25 | |
| 29 | 26 | import dev.langchain4j.data.message.ChatMessage; |
| ... | ... | @@ -233,7 +230,15 @@ public class DynamicToolProvider implements ToolProvider { |
| 233 | 230 | StringBuffer sl = new StringBuffer(); |
| 234 | 231 | |
| 235 | 232 | if(ObjectUtil.isNotEmpty(meta.getStoolDesc())){ |
| 236 | - stoolDesc.append("MethodNo:").append(meta.getSMethodNo()).append(",核心工作内容:【").append(meta.getSMethodName()).append("】").append(meta.getStoolDesc()); | |
| 233 | + stoolDesc.append("MethodNo:").append(meta.getSMethodNo()).append(",核心工作内容:【").append(meta.getSMethodName()); | |
| 234 | +// if (meta.getIBizType()==4){ | |
| 235 | +// stoolDesc.append(",").append("并选择数据后执行["+meta.getSControlName()+"]操作"); | |
| 236 | +// } | |
| 237 | + stoolDesc.append("】").append(meta.getStoolDesc()); | |
| 238 | + if (meta.getIBizType()==4){ | |
| 239 | + stoolDesc.append(",").append("并选择数据后执行 "+meta.getSControlName()+" 操作"); | |
| 240 | +// .append("1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"1,3行确认""); | |
| 241 | + } | |
| 237 | 242 | } |
| 238 | 243 | if("boxQuote".equals(meta.getSMethodNo())){ |
| 239 | 244 | log.info(meta.getSParamRules()); |
| ... | ... | @@ -558,6 +563,14 @@ public class DynamicToolProvider implements ToolProvider { |
| 558 | 563 | |
| 559 | 564 | // {"0":"查询","1":"执行"} 查询不需要确认 |
| 560 | 565 | Boolean isConfirmed = isConfirmed(input) || input.contains("生成") || input.contains("确认"); |
| 566 | + //判断是否生成数据 | |
| 567 | + List<Map<String,Object>> sRowData = new ArrayList<>(); | |
| 568 | + String sHandleType = "merge"; | |
| 569 | + if(4== meta.getIBizType() && ObjectUtil.isNotEmpty(session.getCurrentRowData())){ | |
| 570 | + Map<String,Object> sRowDataMap = UserChoseIntentParser.getSelectedRows( input, session.getCurrentRowData()); | |
| 571 | + sRowData = (List<Map<String, Object>>) sRowDataMap.get("sRowData"); | |
| 572 | + sHandleType = sRowDataMap.get("sHandleType").toString(); | |
| 573 | + } | |
| 561 | 574 | if((isConfirmed || 0== meta.getIActionType()) && 5!= meta.getIBizType()){ |
| 562 | 575 | // 确认后必填项校验 |
| 563 | 576 | List<String> missingAfter = checkConfirmAfterParam(args, paramRuleData); |
| ... | ... | @@ -575,9 +588,9 @@ public class DynamicToolProvider implements ToolProvider { |
| 575 | 588 | askconfirmMsg = buildConfirmUserMessage(meta, args); |
| 576 | 589 | }else if(4== meta.getIBizType() || meta.getIBizType()==5){ |
| 577 | 590 | askconfirmMsg = doGetFromData( meta,args,session); |
| 578 | - session.setSFunPrompts(askconfirmMsg); | |
| 579 | - operableChatMemoryProvider.get(memoryId).add(UserMessage.from("SYSTEM: 等待用户确认或选择部分数据操作")); | |
| 580 | - return String.valueOf(successResult(toolExecutionRequest,askconfirmMsg)); | |
| 591 | +// session.setSFunPrompts(askconfirmMsg); | |
| 592 | +// operableChatMemoryProvider.get(memoryId).add(UserMessage.from("SYSTEM: 等待用户确认或选择部分数据操作")); | |
| 593 | + return executeWithConfirmation(toolExecutionRequest,askconfirmMsg,operableChatMemoryProvider.get(memoryId), session, meta).text(); | |
| 581 | 594 | }else{ |
| 582 | 595 | askconfirmMsg =getDefMessage(args,meta.getSControlName(),meta); |
| 583 | 596 | } |
| ... | ... | @@ -863,64 +876,64 @@ public class DynamicToolProvider implements ToolProvider { |
| 863 | 876 | **/ |
| 864 | 877 | private String executeToolAfter(ToolMeta meta, Map<String, Object> args,ToolExecutionRequest toolExecutionRequest,List<ParamRule> paramDefs,UserSceneSession session) { |
| 865 | 878 | // {"1":"存储过程","2":"SQL查询","3":"第三方API","4":"窗体查询","5":"按钮执行","6":"其它"} |
| 866 | - String sBizContent = meta.getSBizContent(); | |
| 867 | - Integer iBizType = meta.getIBizType(); | |
| 868 | - args.put("sUserId",session.getUserId()); | |
| 869 | - args.put("sLoginId",session.getUserName()); | |
| 870 | - args.put("sMakePerson",session.getUserName()); | |
| 871 | - args.put("sBrId",session.getSBrandsId()); | |
| 872 | - args.put("sBrandsId",session.getSBrandsId()); | |
| 873 | - args.put("sSuId",session.getSSubsidiaryId()); | |
| 874 | - args.put("sSubsidiaryId",session.getSSubsidiaryId()); | |
| 875 | - if(iBizType==1 || iBizType==4){ | |
| 876 | - Map<String,Object> data = new HashMap<>(args); | |
| 877 | - data.put("sData", JSONObject.toJSONString(data)); | |
| 878 | - Map<String, Object> searMap = this.dynamicExeDbService.getDoProMap(sBizContent, data); | |
| 879 | - Map<String,Object> sReturn = this.dynamicExeDbService.getCallPro(searMap,sBizContent); | |
| 880 | - Integer sCode = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SCODE))? Integer.valueOf(sReturn.get(ProcedureConstant.SCODE).toString()):0; | |
| 881 | - String sMsgText = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SRETURN))? sReturn.get(ProcedureConstant.SRETURN).toString():"操作成功"; | |
| 882 | - if(sCode< 0){ | |
| 883 | - String msg = ObjectUtil.isEmpty(sMsgText) ?"调用过程sCode:"+Integer.valueOf(searMap.get(ProcedureConstant.SCODE).toString()):sMsgText; | |
| 884 | - return String.valueOf(askUserResult(toolExecutionRequest, msg)); | |
| 885 | - } | |
| 886 | - session.setSFunPrompts(sMsgText); | |
| 887 | - return sMsgText; | |
| 888 | - }else if(iBizType==2 && ObjectUtil.isNotEmpty(sBizContent)){ | |
| 889 | - //SQL查询 | |
| 890 | - if(sBizContent.toLowerCase().startsWith("update")){ | |
| 891 | - this.dynamicExeDbService.updateSql(args,sBizContent); | |
| 892 | - }else if(sBizContent.toLowerCase().startsWith("delete")){ | |
| 893 | - this.dynamicExeDbService.delSql(args,sBizContent); | |
| 894 | - }else if(sBizContent.toLowerCase().startsWith("insert")){ | |
| 895 | - this.dynamicExeDbService.addSql(args,sBizContent); | |
| 896 | - }else{ | |
| 897 | - List<Map<String,Object>> retData = this.dynamicExeDbService.findSql(args,sBizContent); | |
| 898 | - if(ObjectUtil.isNotEmpty(retData)){ | |
| 899 | - StringBuffer sb = new StringBuffer(); | |
| 900 | - retData.forEach(one->{ | |
| 901 | - one.forEach((k,v)->{ | |
| 902 | - sb.append(v).append(" "); | |
| 903 | - }); | |
| 904 | - sb.append("<br/>"); | |
| 905 | - }); | |
| 906 | - if(ObjectUtil.isNotEmpty(retData)){ | |
| 907 | - sb.append("请根据这些信息安排今天的工作吧!如果有具体任务需要进一步处理,请告诉我"); | |
| 908 | - } | |
| 909 | - session.setSFunPrompts(sb.toString()); | |
| 910 | - if("queryTodayTask".equals(meta.getSMethodNo())){ | |
| 911 | - session.setBCleanMemory(true); | |
| 912 | - } | |
| 913 | - return String.valueOf(successResult(toolExecutionRequest, sb.toString())); | |
| 914 | - }else{ | |
| 915 | - session.setSFunPrompts("未找到对应的数据"); | |
| 916 | - return "未找到对应的数据"; | |
| 917 | - } | |
| 918 | - } | |
| 919 | - }else if(iBizType==3){ | |
| 920 | - return HttpsRequestUtil.me().doRequestHttp(sBizContent,JSONUtil.toJsonStr(args), | |
| 921 | - new HashMap<>(),"POST","JSON"); | |
| 922 | - } | |
| 923 | - return String.valueOf(successResult(toolExecutionRequest, "操作成功")); | |
| 879 | + String sBizContent = meta.getSBizContent(); | |
| 880 | + Integer iBizType = meta.getIBizType(); | |
| 881 | + args.put("sUserId", session.getUserId()); | |
| 882 | + args.put("sLoginId", session.getUserName()); | |
| 883 | + args.put("sMakePerson", session.getUserName()); | |
| 884 | + args.put("sBrId", session.getSBrandsId()); | |
| 885 | + args.put("sBrandsId", session.getSBrandsId()); | |
| 886 | + args.put("sSuId", session.getSSubsidiaryId()); | |
| 887 | + args.put("sSubsidiaryId", session.getSSubsidiaryId()); | |
| 888 | + if (iBizType == 1 || iBizType == 4) { | |
| 889 | + Map<String, Object> data = new HashMap<>(args); | |
| 890 | + data.put("sData", JSONObject.toJSONString(data)); | |
| 891 | + Map<String, Object> searMap = this.dynamicExeDbService.getDoProMap(sBizContent, data); | |
| 892 | + Map<String, Object> sReturn = this.dynamicExeDbService.getCallPro(searMap, sBizContent); | |
| 893 | + Integer sCode = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SCODE)) ? Integer.valueOf(sReturn.get(ProcedureConstant.SCODE).toString()) : 0; | |
| 894 | + String sMsgText = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SRETURN)) ? sReturn.get(ProcedureConstant.SRETURN).toString() : "操作成功"; | |
| 895 | + if (sCode < 0) { | |
| 896 | + String msg = ObjectUtil.isEmpty(sMsgText) ? "调用过程sCode:" + Integer.valueOf(searMap.get(ProcedureConstant.SCODE).toString()) : sMsgText; | |
| 897 | + return String.valueOf(askUserResult(toolExecutionRequest, msg)); | |
| 898 | + } | |
| 899 | + session.setSFunPrompts(sMsgText); | |
| 900 | + return sMsgText; | |
| 901 | + } else if (iBizType == 2 && ObjectUtil.isNotEmpty(sBizContent)) { | |
| 902 | + //SQL查询 | |
| 903 | + if (sBizContent.toLowerCase().startsWith("update")) { | |
| 904 | + this.dynamicExeDbService.updateSql(args, sBizContent); | |
| 905 | + } else if (sBizContent.toLowerCase().startsWith("delete")) { | |
| 906 | + this.dynamicExeDbService.delSql(args, sBizContent); | |
| 907 | + } else if (sBizContent.toLowerCase().startsWith("insert")) { | |
| 908 | + this.dynamicExeDbService.addSql(args, sBizContent); | |
| 909 | + } else { | |
| 910 | + List<Map<String, Object>> retData = this.dynamicExeDbService.findSql(args, sBizContent); | |
| 911 | + if (ObjectUtil.isNotEmpty(retData)) { | |
| 912 | + StringBuffer sb = new StringBuffer(); | |
| 913 | + retData.forEach(one -> { | |
| 914 | + one.forEach((k, v) -> { | |
| 915 | + sb.append(v).append(" "); | |
| 916 | + }); | |
| 917 | + sb.append("<br/>"); | |
| 918 | + }); | |
| 919 | + if (ObjectUtil.isNotEmpty(retData)) { | |
| 920 | + sb.append("请根据这些信息安排今天的工作吧!如果有具体任务需要进一步处理,请告诉我"); | |
| 921 | + } | |
| 922 | + session.setSFunPrompts(sb.toString()); | |
| 923 | + if ("queryTodayTask".equals(meta.getSMethodNo())) { | |
| 924 | + session.setBCleanMemory(true); | |
| 925 | + } | |
| 926 | + return String.valueOf(successResult(toolExecutionRequest, sb.toString())); | |
| 927 | + } else { | |
| 928 | + session.setSFunPrompts("未找到对应的数据"); | |
| 929 | + return "未找到对应的数据"; | |
| 930 | + } | |
| 931 | + } | |
| 932 | + } else if (iBizType == 3) { | |
| 933 | + return HttpsRequestUtil.me().doRequestHttp(sBizContent, JSONUtil.toJsonStr(args), | |
| 934 | + new HashMap<>(), "POST", "JSON"); | |
| 935 | + } | |
| 936 | + return String.valueOf(successResult(toolExecutionRequest, "操作成功")); | |
| 924 | 937 | } |
| 925 | 938 | |
| 926 | 939 | |
| ... | ... | @@ -983,7 +996,7 @@ public class DynamicToolProvider implements ToolProvider { |
| 983 | 996 | log.info("headers=============================={}", JSONObject.toJSONString(headers)); |
| 984 | 997 | log.info("请求URL,JSON,headers=={},{},{}",sUrl,JSONObject.toJSONString(sBody),JSONObject.toJSONString(headers)); |
| 985 | 998 | ErpResult erpResult = JsonUtils.toObject(result,ErpResult.class); |
| 986 | - result = buildResultMessageWithTable( meta, erpResult); | |
| 999 | + result = buildResultMessageWithTable( meta, erpResult, session); | |
| 987 | 1000 | }catch (Exception e){ |
| 988 | 1001 | result ="执行异常:"+e.getMessage(); |
| 989 | 1002 | } |
| ... | ... | @@ -993,8 +1006,8 @@ public class DynamicToolProvider implements ToolProvider { |
| 993 | 1006 | /** |
| 994 | 1007 | * 构建 窗体获取数据方法 未清或者明细 |
| 995 | 1008 | */ |
| 996 | - public String buildResultMessageWithTable(ToolMeta meta,ErpResult erpResult){ | |
| 997 | - | |
| 1009 | + public String buildResultMessageWithTable(ToolMeta meta,ErpResult erpResult,UserSceneSession session){ | |
| 1010 | + Map<Integer,Map<String,Object>> currentRowData = new HashMap<>(); | |
| 998 | 1011 | ErpDataset dataset = erpResult.getDataset(); |
| 999 | 1012 | //返回错误信息 |
| 1000 | 1013 | if(erpResult.getCode()<0 && ObjectUtil.isNotEmpty(erpResult.getMsg())){ |
| ... | ... | @@ -1037,24 +1050,36 @@ public class DynamicToolProvider implements ToolProvider { |
| 1037 | 1050 | headers.forEach(header -> markdown.append(header).append(" | ")); |
| 1038 | 1051 | markdown.append("\n|").append("---|".repeat(headers.size() + 1)).append("\n"); |
| 1039 | 1052 | // 填充表格数据 |
| 1053 | + List<Map<String,Object>> machineData = new LinkedList<>(); | |
| 1040 | 1054 | for (int i = 0; i < recordData.size(); i++) { |
| 1041 | 1055 | // 保存隐藏列的值(如"唯一"字段) |
| 1042 | 1056 | String uniqueValue = recordData.get(i).get("sSlaveId") != null ? recordData.get(i).get("sSlaveId").toString() : ""; |
| 1043 | 1057 | markdown.append("| ").append(i + 1).append(" | "); |
| 1058 | + Map<String,Object> rMap = new HashMap<>(); | |
| 1044 | 1059 | for (String header : headers) { |
| 1045 | 1060 | // 这里需要根据你的数据结构来获取对应的值 |
| 1046 | 1061 | Object value = recordData.get(i)!= null ? recordData.get(i).get(header) : null; |
| 1047 | 1062 | markdown.append(value != null ? value : "—").append(" | "); |
| 1063 | + rMap.put(header,value); | |
| 1048 | 1064 | } |
| 1065 | + rMap.put("sSlaveId",uniqueValue); | |
| 1066 | + rMap.put("唯一",uniqueValue); | |
| 1049 | 1067 | // 在行末添加隐藏数据的特殊标记(AI可以解析) |
| 1050 | - markdown.append(" <!-- HIDDEN_DATA:{\"sSlaveId\":\"").append(uniqueValue).append("\"} -->"); | |
| 1068 | + markdown.append(" <!-- HIDDEN_DATA:").append(JSONUtil.toJsonStr(rMap)).append("-->"); | |
| 1051 | 1069 | markdown.append("\n"); |
| 1070 | + machineData.add(rMap); | |
| 1071 | + currentRowData.put(i + 1,recordData.get(i)); | |
| 1052 | 1072 | } |
| 1053 | 1073 | markdown.append(">"); |
| 1074 | +// // 4. 机器可读的结构化数据(只出现一次!) | |
| 1075 | +// markdown.append("<!-- MACHINE_DATA_START -->\n"); | |
| 1076 | +// markdown.append(JSONUtil.toJsonStr(machineData)); | |
| 1077 | +// markdown.append("\n<!-- MACHINE_DATA_END -->\n\n"); | |
| 1054 | 1078 | if(meta.getIBizType()==4){ |
| 1055 | 1079 | markdown.append("\n---\n"); |
| 1056 | 1080 | appendConfirmAll(markdown,meta.getSControlName()); |
| 1057 | 1081 | } |
| 1082 | + session.setCurrentRowData(currentRowData); | |
| 1058 | 1083 | return markdown.toString(); |
| 1059 | 1084 | } |
| 1060 | 1085 | ... | ... |
src/main/java/com/xly/tts/service/PythonTtsProxyService.java
| ... | ... | @@ -77,7 +77,6 @@ public class PythonTtsProxyService { |
| 77 | 77 | String sUserType = request.getUsertype(); |
| 78 | 78 | String authorization = request.getAuthorization(); |
| 79 | 79 | //校验登录token 是否有效 |
| 80 | - | |
| 81 | 80 | AiResponseDTO voiceText = xlyErpService.erpUserInput(userInput,sUserId,sUserName,sBrandsId,sSubsidiaryId,sUserType, authorization); |
| 82 | 81 | return synthesizeStreamAi(request,voiceText); |
| 83 | 82 | } | ... | ... |
src/main/java/com/xly/util/AIModelDataFormatter.java
0 → 100644
| 1 | +package com.xly.util; | |
| 2 | +import cn.hutool.json.JSONUtil; | |
| 3 | +import com.xly.entity.ToolMeta; | |
| 4 | + | |
| 5 | +import java.util.*; | |
| 6 | + | |
| 7 | +public class AIModelDataFormatter { | |
| 8 | + public static String formatDataForAI(ToolMeta meta, Integer TotalCount, | |
| 9 | + List<Map<String, Object>> rows, | |
| 10 | + Set<String> headers) { | |
| 11 | + StringBuilder response = new StringBuilder(); | |
| 12 | + // 3. **关键:先显示用户友好的表格** | |
| 13 | + response.append("## 查询结果(共").append(TotalCount).append("条)\n\n"); | |
| 14 | + response.append(buildMarkdownTable(rows, headers)); | |
| 15 | + response.append("\n"); | |
| 16 | + // 4. 机器可读的结构化数据(只出现一次!) | |
| 17 | + response.append("<!-- MACHINE_DATA_START -->\n"); | |
| 18 | + response.append(buildMachineReadableData(rows,headers)); | |
| 19 | + response.append("\n<!-- MACHINE_DATA_END -->\n\n"); | |
| 20 | + return response.toString(); | |
| 21 | + } | |
| 22 | + | |
| 23 | + /** | |
| 24 | + * 构建Markdown表格 | |
| 25 | + */ | |
| 26 | + public static String buildMarkdownTable(List<Map<String, Object>> rows, Set<String> headers) { | |
| 27 | + StringBuilder table = new StringBuilder(); | |
| 28 | + // 表头 | |
| 29 | + table.append("| 序号 | "); | |
| 30 | + for (String header : headers) { | |
| 31 | + table.append(header).append(" | "); | |
| 32 | + } | |
| 33 | + table.append("\n|"); | |
| 34 | + table.append("---|".repeat(headers.size() + 1)); | |
| 35 | + table.append("\n"); | |
| 36 | + // 表格数据 | |
| 37 | + for (int i = 0; i < rows.size(); i++) { | |
| 38 | + Map<String, Object> row = rows.get(i); | |
| 39 | + table.append("| ").append(i + 1).append(" | "); | |
| 40 | + for (String header : headers) { | |
| 41 | + Object value = row.get(header); | |
| 42 | + table.append(value != null ? value : "—").append(" | "); | |
| 43 | + } | |
| 44 | + table.append("\n"); | |
| 45 | + } | |
| 46 | + | |
| 47 | + return table.toString(); | |
| 48 | + } | |
| 49 | + | |
| 50 | + /** | |
| 51 | + * 构建机器可读数据 | |
| 52 | + */ | |
| 53 | + public static String buildMachineReadableData(List<Map<String, Object>> rows, Set<String> headers) { | |
| 54 | + List<Map<String, Object>> dataList = new ArrayList<>(); | |
| 55 | + for (int i = 0; i < rows.size(); i++) { | |
| 56 | + Map<String, Object> row = rows.get(i); | |
| 57 | + Map<String, Object> item = new LinkedHashMap<>(); | |
| 58 | + item.put("序号", i + 1); | |
| 59 | + for (String header : headers) { | |
| 60 | + Object value = row.get(header); | |
| 61 | + item.put(header,value); | |
| 62 | + } | |
| 63 | + dataList.add(item); | |
| 64 | + } | |
| 65 | + return JSONUtil.toJsonStr(dataList); | |
| 66 | + } | |
| 67 | + | |
| 68 | +} | |
| 0 | 69 | \ No newline at end of file | ... | ... |
src/main/java/com/xly/util/UserChoseIntentParser.java
0 → 100644
| 1 | +package com.xly.util; | |
| 2 | + | |
| 3 | +import java.util.*; | |
| 4 | +import java.util.stream.Collectors; | |
| 5 | + | |
| 6 | +public class UserChoseIntentParser { | |
| 7 | + | |
| 8 | + /** | |
| 9 | + * @param input 用户输入:全部确认 / 合并确认 / 1,3行确认 | |
| 10 | + * @param rowMap 上面parse出来的全量数据 | |
| 11 | + * @return 最终要生成订单的明细列表 | |
| 12 | + */ | |
| 13 | + public static Map<String,Object> getSelectedRows(String input, Map<Integer, Map<String,Object>> rowMap) { | |
| 14 | + List<Map<String,Object>> sRowData = new ArrayList<>(); | |
| 15 | + Map<String,Object> rMap = new HashMap<>(); | |
| 16 | + String sHandleType = "merge";//单个的 就是全部确认 否则就是合并确认 | |
| 17 | + if (input.contains("全部确认") || input.contains("合并确认")) { | |
| 18 | + if(input.contains("全部确认")){ | |
| 19 | + sHandleType = "single"; | |
| 20 | + } | |
| 21 | + sRowData = new ArrayList<>(rowMap.values()); | |
| 22 | + rMap.put("sRowData",sRowData); | |
| 23 | + rMap.put("sHandleType",sHandleType); | |
| 24 | + return rMap; | |
| 25 | + } | |
| 26 | + | |
| 27 | + // 解析 1,3,5-7 这类行号 | |
| 28 | + Set<Integer> selected = new HashSet<>(); | |
| 29 | + String[] parts = input.replaceAll("[^0-9,-]", "").split("[,,]"); | |
| 30 | + for (String part : parts) { | |
| 31 | + try { | |
| 32 | + if (part.contains("-")) { | |
| 33 | + String[] range = part.split("-"); | |
| 34 | + int start = Integer.parseInt(range[0]); | |
| 35 | + int end = Integer.parseInt(range[1]); | |
| 36 | + for (int i = start; i <= end; i++) { | |
| 37 | + selected.add(i); | |
| 38 | + } | |
| 39 | + } else { | |
| 40 | + selected.add(Integer.parseInt(part)); | |
| 41 | + } | |
| 42 | + } catch (Exception ignored) {} | |
| 43 | + } | |
| 44 | + rMap.put("sRowData",selected.stream() | |
| 45 | + .filter(rowMap::containsKey) | |
| 46 | + .map(rowMap::get) | |
| 47 | + .collect(Collectors.toList())); | |
| 48 | + rMap.put("sHandleType",sHandleType); | |
| 49 | + return rMap; | |
| 50 | + } | |
| 51 | +} | ... | ... |
src/main/resources/templates/chat.html
| ... | ... | @@ -462,13 +462,13 @@ |
| 462 | 462 | <script> |
| 463 | 463 | let sessionId =""; |
| 464 | 464 | // let userid= "17706006510007934913359242990000"; |
| 465 | - let userid= "17502321750004978169421209637000"; | |
| 466 | - let username= "admin"; | |
| 465 | + let userid= "17522967560005776104370282597000"; | |
| 466 | + let username= "钱豹"; | |
| 467 | 467 | let brandsid= "1111111111"; |
| 468 | 468 | let subsidiaryid= "1111111111"; |
| 469 | 469 | let usertype= "sysadmin"; |
| 470 | 470 | // let usertype= "General"; |
| 471 | - let authorization="1EDB99C9BF070115F7A57AC43D8CB09F0B8C49F979DAB63A2AEA84B372B2B42BF3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1D2D86C92E4DCE571A8ECF0767494BBDE2495FD8E662F2065F9430347C7E4472B5538155B7ADAEE71E899235DC1122F426"; | |
| 471 | + let authorization="CE444885A9BCFDDE1FD793F8A0931301E9D7DE6CEDD9DE4B83ECE2219C7829A8F3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1DD20808D80550B390B6BE6ECDCFF597F4D8668502224FF2D5675E74B8CDD2ABFB538155B7ADAEE71E899235DC1122F426"; | |
| 472 | 472 | //"1EDB99C9BF070115F7A57AC43D8CB09F0B8C49F979DAB63A2AEA84B372B2B42BF3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1DC16A7526DEC4395CF09285C2BD330B9FD8668502224FF2D5675E74B8CDD2ABFB538155B7ADAEE71E899235DC1122F426"; |
| 473 | 473 | let hrefLock = window.location.origin+"/xlyAi"; |
| 474 | 474 | // ==================== 配置部分 ==================== | ... | ... |