From 51877df13075ad10ef51107f15bcd21f1661febe Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期二, 17 三月 2026 09:48:01 +0800
Subject: [PATCH] #AI
---
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FieldsController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/HostService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Menu.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ServerBoot.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/CheckDiffDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiSessionRenameRequest.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/ReportMsgController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/constant/Constants.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/IpTools.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateNodeMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MenuServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncTransferItems.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiPromptController.java | 233 +
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BusinessRes.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JwtUtil.java | 1
rsf-admin/src/page/system/aiDiagnosis/index.jsx | 13
rsf-admin/src/page/system/aiPrompt/AiPromptCreate.jsx | 87
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/utils/SerialRuleUtils.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ChangeLocParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Fields.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/SerialRuleService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Role.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncTransferParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/enums/LoginSystemType.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/config/SysStockProperties.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/FileServerUtil.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMergeMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/DictDataService.java | 1
rsf-admin/src/page/system/aiParam/AiParamList.jsx | 165
docs/AI_DEVELOPMENT_GUIDE.md | 186
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/CallBackEvent.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/BaseController.java | 22
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/DataFieldSortFunc.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepLogMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepInstance.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiPromptPublishLogService.java | 15
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MatnrRoleMenuServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/CommonUtil.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisRuntimeService.java | 113
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/RoleMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ManualShelvingParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/MobileController.java | 1
rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanList.jsx | 175
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FieldsServiceImpl.java | 1
rsf-admin/src/page/system/aiDiagnosis/AiDiagnosisList.jsx | 134
rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatMessageMapper.java | 13
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiParamMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskInstanceServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncLocReviseParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/InBoundController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/enums/StatusType.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/config/RedisProperties.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskInstanceNodeService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiProperties.java | 33
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateMergeServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/CheckObjDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DictTypeController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOtherService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/DeptService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiChatSession.java | 16
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/WarehouseParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskInstanceNodeMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOutStockService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiMcpMount.java | 118
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateNode.java | 1
rsf-admin/src/page/system/aiMcpMount/AiMcpMountCreate.jsx | 92
rsf-admin/src/page/system/aiMcpMount/AiMcpMountEdit.jsx | 74
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OtherReceiptParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/utils/TimeConverterUtils.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateMergeService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisReportService.java | 182
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepTemplate.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/CheckObjParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepLogService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiCallLogController.java | 103
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiRuntimeConfigService.java | 15
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/LocTypeDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/ConfigController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ExMsgParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepTemplateController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FieldsItem.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mes/MesController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/TaskQueueDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiParamService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OrderParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskInstanceNodeController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosisPlanMapper.java | 11
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/WcsMsgTypeEvent.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/constant/SerialRuleCode.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosisPlanServiceImpl.java | 90
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/OpenOfficeUtil.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/OperationRecordController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowInstanceMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncReviseItems.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiParamController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncCheckDiffParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/UserLogin.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiDeviceSiteSummaryService.java | 159
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiModelRouteRuntimeService.java | 145
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DictDataController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiPromptPublishLogMapper.java | 10
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiMcpMountMapper.java | 11
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiCallLogMapper.java | 13
rsf-server/src/main/java/com/vincent/rsf/server/system/service/SerialRuleItemService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/security/JwtSubject.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Config.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiModelRouteServiceImpl.java | 38
rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/WarehouseRoleMenuService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DeptServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/LocAreasParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosisRecordServiceImpl.java | 13
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReportDataParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SaveCheckDiffParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/BaseInfoController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Host.java | 1
rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/service/AiGatewayService.java | 226
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserLoginMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AuthController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateMerge.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/MatnrRoleMenuService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserLoginServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowInstanceService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/SyncLocsDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mcp/McpController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/utils/SystemAuthUtils.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosisRecordService.java | 9
rsf-server/src/main/java/com/vincent/rsf/server/api/config/RemotesInfoProperties.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/DictTypeService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/service/EmailService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskInstanceNodeServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/ContainerWaveDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/config/SchedulerConfig.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosisPlanController.java | 169
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/ConfigMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/LoginResult.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiModelRouteMapper.java | 13
rsf-server/src/main/java/com/vincent/rsf/server/common/handler/global/GlobalDictService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/MonitorController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/constant/DictTypeCode.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosticToolConfig.java | 85
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiTaskSummaryService.java | 85
rsf-server/src/main/java/com/vincent/rsf/server/api/service/ReportMsgService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/Http.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosticToolConfigService.java | 16
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Dept.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/TenantService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosticToolConfigController.java | 156
rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptContextService.java | 5
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/WcsTaskParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiDiagnosticToolResult.java | 28
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SerialRuleItem.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/DictType.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/MatnrDefectType.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserRoleServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TenantMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiWarehouseSummaryService.java | 91
rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiChatMessage.java | 16
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaCheckOrderServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CommonRequest.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/PdaRoleMenuMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SubsystemFlowTemplateController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/UserLoginController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/SubsystemFlowTemplateService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiTextCompletionService.java | 40
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOtherController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/config/BeanConfig.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/RoleMenu.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/ErpQueryController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InspectDetlDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiSessionService.java | 37
rsf-server/src/main/java/com/vincent/rsf/server/system/service/OperationRecordService.java | 1
rsf-admin/src/page/system/aiDiagnosis/AiDiagnosisEdit.jsx | 152
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowInstanceServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosisPlanService.java | 23
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JChardetFacadeUtil.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReportMsgServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/handler/ExcelDictHandlerImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/utils/LocUtils.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/MonitorService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SubsystemFlowTemplateMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiOperationRecordSummaryService.java | 101
rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanEdit.jsx | 69
rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepTemplateService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepLogController.java | 1
rsf-admin/src/i18n/zh.js | 7
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OrderItem.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/DateUtils.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MenuMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisPlanRunnerService.java | 355 +
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java | 3
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MonitorServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiModelRoute.java | 140
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OpStockParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DeptMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateNodeServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/PageResult.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/SystemInfoVo.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiGatewayClient.java | 18
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/QueryOrderParam.java | 1
rsf-admin/src/page/system/aiPrompt/AiPromptEdit.jsx | 64
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/NodeUtils.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/validator/SyncOrderValidator.java | 1
rsf-admin/src/ai/AiChatWidget.jsx | 61
rsf-server/src/main/java/com/vincent/rsf/server/api/config/RestTemplateConfig.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/RegisterParam.java | 1
rsf-admin/src/page/system/aiRoute/AiRouteEdit.jsx | 60
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/TaskInParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SubsystemFlowTemplate.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/ReceiptDetlsDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DictDataMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/RoleMenuService.java | 1
rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/controller/AiGatewayController.java | 100
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosisRecord.java | 173
rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/config/AiGatewayProperties.java | 2
rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatStreamRequest.java | 3
rsf-admin/src/page/system/aiToolConfig/index.jsx | 13
rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/service/GatewayStreamEvent.java | 8
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TenantServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java | 1
rsf-admin/src/i18n/en.js | 7
rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiCallLogService.java | 9
rsf-admin/src/page/system/aiCallLog/index.jsx | 13
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepLog.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/User.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ErpInspectParams.java | 1
rsf-admin/src/page/system/aiRoute/AiRouteList.jsx | 190
rsf-server/src/main/java/com/vincent/rsf/server/system/service/PdaRoleMenuService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepInstanceService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MatnrRoleMenuController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReportParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiDiagnosticDataProvider.java | 38
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/TransferInfoDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SerialRuleServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiSessionCreateRequest.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InTaskMsgDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplate.java | 1
rsf-admin/src/page/components/AiConsoleLayout.jsx | 133
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiMcpMountController.java | 394 +
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/InTaskWcsReportParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/config/ConfigProperties.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/MatnrRoleMenu.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FieldsMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SubsystemFlowTemplateServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateNodeController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstance.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FieldsItemServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/RoleServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpSseClient.java | 434 +
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncMatGroupsParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/WcsController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiCallLog.java | 124
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskReportParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowInstanceController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/UpdatePasswordParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/RoleMenuMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/MissionTaskIssueParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/enums/ConfigType.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/annotation/OperationLog.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/UserController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiPromptTemplateServiceImpl.java | 131
rsf-admin/src/page/system/aiCallLog/AiCallLogEdit.jsx | 38
rsf-server/src/main/java/com/vincent/rsf/server/common/aspect/OperationLogAspect.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepInstanceServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/WarehouseRoleMenuServiceImpl.java | 1
rsf-admin/src/page/system/aiToolConfig/AiToolConfigCreate.jsx | 92
rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiController.java | 217
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpPayloadMapper.java | 204
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/MenuVo.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowInstance.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DictTypeServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/WkOrderDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/utils/ExtendFieldsUtils.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpRegistryService.java | 567 ++
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/PdaRoleMenuController.java | 1
rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx | 383 +
rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatSessionMapper.java | 13
rsf-server/src/main/java/com/vincent/rsf/server/system/enums/SerialRuleType.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepTemplateServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/WarehouseRoleMenuController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DictDataServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaCheckOrderService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepTemplateMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiParamServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/WarehouseRoleMenuMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/utils/SlaveProperties.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/BaseMatParms.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/enums/CompanyType.java | 1
rsf-admin/src/page/system/aiPrompt/index.jsx | 13
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FieldsItemController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/FieldsService.java | 1
rsf-admin/src/page/system/aiToolConfig/AiToolConfigList.jsx | 134
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosticToolConfigServiceImpl.java | 42
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SerialRuleMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskInstanceMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/PublicToStockParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/KeyValVo.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/constant/AiMcpConstants.java | 24
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiPromptTemplate.java | 159
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mcp/AiMcpProtocolController.java | 43
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/TenantInitParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ErpInspectItem.java | 1
rsf-admin/src/page/system/aiRoute/AiRouteCreate.jsx | 84
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstanceNode.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/enums/SerialRuleReset.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MenuController.java | 1
rsf-admin/src/page/system/aiDiagnosisPlan/index.jsx | 13
version/db/20260317_ai_all_in_one.sql | 1194 +++++
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskInstanceController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiPromptTemplateMapper.java | 13
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/SysInfoController.java | 1
rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/config/WebAsyncConfig.java | 15
rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiPromptContext.java | 3
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosticToolConfigMapper.java | 10
rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserLoginService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/AgvService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosisRecordMapper.java | 13
rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/GatewayChatMessage.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpProtocolService.java | 163
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskItemParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SerialRuleItemServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/OrderOutGeneralParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/RoleService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ContainerWaveParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisPlanScheduler.java | 48
rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/GatewayChatRequest.java | 5
rsf-server/src/main/java/com/vincent/rsf/server/common/handler/AggregationDataHandler.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/RoleScopeParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiPromptPublishLogServiceImpl.java | 42
rsf-ai-gateway/src/main/resources/application.yml | 17
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FieldsItemMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CallForEmptyContainersParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/InventoryQueryConditionParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Tenant.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosisPlan.java | 144
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/PdaRoleMenu.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/config/WebMvcConfig.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/ConfigServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/ConfigService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosisController.java | 97
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/PdaRoleMenuServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/DictData.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/OperateStockDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/security/JwtAuthenticationFilter.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserRoleService.java | 1
rsf-admin/src/page/system/aiCallLog/AiCallLogList.jsx | 144
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SerialRuleItemMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/FieldsItemService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiMcpToolDescriptor.java | 39
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/CommonResponse.java | 3
rsf-server/src/main/resources/application.yml | 17
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateMergeController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/config/WebSocketConfig.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SerialRuleController.java | 1
rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/dto/GatewayChatRequest.java | 4
rsf-server/src/main/java/com/vincent/rsf/server/api/service/MobileService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskInstanceService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/WcsService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/SyncOrderController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/enums/EmailType.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiSchemaGuard.java | 64
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ReassignLocParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/PoItemsDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOutStockController.java | 1
rsf-admin/src/page/system/aiRoute/index.jsx | 13
rsf-admin/src/page/system/aiMcpMount/index.jsx | 13
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/QueueTask.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TenantController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/RoleMenuServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CreateInTaskParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/TaskLocAreaDto.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/OperationRecordMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/HostServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/BaseSyncParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncLocsParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/HostController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/ReceiveMsgService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepLogServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptRuntimeService.java | 87
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/HostMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiCallLogServiceImpl.java | 13
rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanCreate.jsx | 88
rsf-admin/src/page/system/aiToolConfig/AiToolConfigEdit.jsx | 67
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DictTypeMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepInstanceMapper.java | 1
rsf-admin/src/page/ResourceContent.js | 21
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiApiFailureSummaryService.java | 101
rsf-server/src/main/java/com/vincent/rsf/server/system/constant/GlobalConfigCode.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/RoleController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/UserRole.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiMcpMountService.java | 16
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DeptController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/LoginParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/exception/BusinessException.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiModelRouteService.java | 15
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MatnrRoleMenuMapper.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/constant/AiSceneCode.java | 12
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SerialRule.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiSessionServiceImpl.java | 189
rsf-server/src/main/java/com/vincent/rsf/server/system/constant/CodeRes.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaCheckOrderController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiMcpMountServiceImpl.java | 44
rsf-server/src/main/java/com/vincent/rsf/server/common/enums/WarehouseAreaType.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateNodeService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/CompaniesParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiRouteController.java | 183
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/AgvController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepInstanceController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SerialRuleItemController.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiPromptPublishLog.java | 50
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/OperationRecord.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/security/SecurityConfig.java | 2
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JSONUtil.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/config/SwaggerConfig.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/service/MenuService.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisMcpRuntimeService.java | 426 +
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpHttpClient.java | 161
rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiPromptTemplateService.java | 24
version/db/init.sql | 5
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReceiptParams.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiChatStreamOrchestrator.java | 467 ++
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptContextProvider.java | 7
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/OperationRecordServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/PageParam.java | 1
/dev/null | 224
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOtherServiceImpl.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BaseParam.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/WarehouseRoleMenu.java | 1
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosticToolService.java | 181
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserRoleMapper.java | 1
rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx | 618 ++
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InspectItemDto.java | 1
450 files changed, 13,002 insertions(+), 487 deletions(-)
diff --git a/docs/AI_DEVELOPMENT_GUIDE.md b/docs/AI_DEVELOPMENT_GUIDE.md
new file mode 100644
index 0000000..f37a006
--- /dev/null
+++ b/docs/AI_DEVELOPMENT_GUIDE.md
@@ -0,0 +1,186 @@
+# WMS AI鍔熻兘寮�鍙戞枃妗�
+
+## 1. 鐩爣璇存槑
+
+鏈枃妗e熀浜� `zy-wcs-master` 鐨� AI 宸℃鎬濊矾锛屽褰撳墠 `wms-master` 椤圭洰杩涜浜嗗姛鑳芥槧灏勫拰瀹炵幇璇存槑銆�
+鏈瀹炵幇鐨勭洰鏍囦笉鏄収鎼� Spring AI + MCP 鐨勫畬鏁翠綋绯伙紝鑰屾槸澶嶇敤褰撳墠椤圭洰宸叉湁鐨� `rsf-server + rsf-ai-gateway + rsf-admin` 鏋舵瀯锛屼互鏈�灏忔敼鍔ㄨˉ榻愪互涓嬭兘鍔涳細
+
+- 閫氱敤鑱婂ぉ鍦烘櫙涓庣郴缁熻瘖鏂満鏅苟瀛�
+- 涓�閿瘖鏂叆鍙�
+- 鍩轰簬搴撳瓨銆佷换鍔°�佽澶囩珯鐐圭殑瀹炴椂璇婃柇涓婁笅鏂�
+- AI浼氳瘽涓庢秷鎭寔涔呭寲
+- 淇濇寔鐜版湁妯″瀷閰嶇疆涓績鍜岀綉鍏宠皟鐢ㄩ摼涓嶅彉
+
+## 2. 涓� `zy-wcs-master` 鐨勬槧灏勫叧绯�
+
+| `zy-wcs-master` 鑳藉姏 | 褰撳墠椤圭洰鏄犲皠鏂规 |
+| --- | --- |
+| 涓�閿贰妫� / 杩炵画杩介棶 | `AiController` 鏂板璇婃柇鍦烘櫙锛屽墠绔鍔犫�滀竴閿瘖鏂�� |
+| Prompt 鍦烘櫙涓績 | 浣跨敤 `sceneCode + AiProperties + AiPromptContextProvider` 缁勫悎瀹炵幇 |
+| MCP 宸ュ叿鑱氬悎 | 浣跨敤鐜版湁涓婁笅鏂囨彁渚涘櫒鐩存帴鏌ヨ涓氬姟琛紝鍏堝疄鐜拌交閲忕骇鏁版嵁宸ュ叿鑳藉姏 |
+| 浼氳瘽钀藉簱 | 鏂板 `sys_ai_chat_session`銆乣sys_ai_chat_message` |
+| LLM 缃戝叧 | 缁х画澶嶇敤 `rsf-ai-gateway` |
+
+璇存槑锛氬綋鍓嶇増鏈病鏈夌洿鎺ュ紩鍏� `zy-wcs-master` 鐨� Prompt 绠$悊鍚庡彴銆丮CP 鎸傝浇涓績銆佹ā鍨嬭矾鐢卞鐏句腑蹇冦�傝繖浜涜兘鍔涗繚鐣欎负涓嬩竴闃舵澧炲己椤广��
+
+## 3. 褰撳墠瀹炵幇鏋舵瀯
+
+### 3.1 妯″潡鍒嗗伐
+
+- `rsf-admin`
+ - AI 瀵硅瘽鎶藉眽缁勪欢
+ - 鏂板涓�閿瘖鏂寜閽�
+- `rsf-server`
+ - AI鎺у埗鍣ㄣ�佸満鏅矾鐢便�佷笂涓嬫枃鎷艰銆佷細璇濆瓨鍌�
+ - 鐩存帴浠� WMS 涓氬姟琛ㄨ鍙栬瘖鏂憳瑕�
+- `rsf-ai-gateway`
+ - 缁熶竴灏佽 OpenAI 鍏煎娴佸紡鎺ュ彛
+
+### 3.2 璋冪敤閾捐矾
+
+```mermaid
+flowchart LR
+ A["rsf-admin AI瀵硅瘽缁勪欢"] --> B["rsf-server /ai/chat/stream"]
+ A --> C["rsf-server /ai/diagnose/stream"]
+ B --> D["AiPromptContextService"]
+ C --> D
+ D --> E["搴撳瓨鎽樿"]
+ D --> F["浠诲姟鎽樿"]
+ D --> G["璁惧绔欑偣鎽樿"]
+ B --> H["AiSessionService"]
+ C --> H
+ B --> I["rsf-ai-gateway"]
+ C --> I
+ I --> J["OpenAI鍏煎妯″瀷鎺ュ彛"]
+```
+
+## 4. 鍚庣瀹炵幇璇存槑
+
+### 4.1 鍦烘櫙瀹氫箟
+
+鏂板鍦烘櫙缂栫爜锛�
+
+- `general_chat`锛氭櫘閫氬璇�
+- `system_diagnose`锛氱郴缁熷贰妫�璇婃柇
+
+鐩稿叧浣嶇疆锛�
+
+- `rsf-server/src/main/java/com/vincent/rsf/server/ai/constant/AiSceneCode.java`
+- `rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatStreamRequest.java`
+- `rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiPromptContext.java`
+
+### 4.2 鎺у埗鍣ㄨ兘鍔�
+
+`AiController` 褰撳墠鎻愪緵涓ゆ潯 SSE 鑳藉姏锛�
+
+- `/ai/chat/stream`
+ - 閫氱敤鑱婂ぉ
+ - 鏀寔鏄惧紡浼犲叆 `sceneCode`
+- `/ai/diagnose/stream`
+ - 璇婃柇蹇嵎鍏ュ彛
+ - 鑷姩浣跨敤 `system_diagnose`
+ - 濡傛灉鍓嶇鏈紶娑堟伅锛屽垯浣跨敤榛樿宸℃鎻愮ず璇�
+
+### 4.3 Prompt 涓庝笂涓嬫枃绛栫暐
+
+褰撳墠瀹炵幇閲囩敤鈥滃熀纭�鎻愮ず璇� + 鍦烘櫙鎻愮ず璇� + 鏁版嵁鎽樿鎻愮ず璇嶁�濈殑缁勫悎鏂瑰紡锛�
+
+1. 鍩虹鎻愮ず璇嶆潵鑷ā鍨嬮厤缃垨 `application.yml`
+2. 璇婃柇鍦烘櫙杩藉姞 `diagnosis-system-prompt`
+3. `AiPromptContextService` 鑷姩鎷兼帴涓婁笅鏂囨彁渚涘櫒鍐呭
+
+涓婁笅鏂囨彁渚涘櫒濡備笅锛�
+
+- `AiDiagnosisPromptProvider`
+ - 杈撳嚭璇婃柇娴佺▼鍜屽洖绛旂粨鏋勭害鏉�
+- `AiWarehouseSummaryService`
+ - 姹囨�� `man_loc`銆乣man_loc_item`
+- `AiTaskSummaryService`
+ - 姹囨�� `man_task`
+- `AiDeviceSiteSummaryService`
+ - 姹囨�� `man_device_site`
+
+### 4.4 璇婃柇鏁版嵁鏉ユ簮
+
+鏈瀹炵幇浼樺厛鎺ュ叆瀹炴椂涓氬姟琛紝涓嶉澶栧紩鍏ョ嫭绔嬬煡璇嗗簱锛�
+
+- 搴撳瓨涓庡簱浣嶏細`man_loc`銆乣man_loc_item`
+- 浠诲姟锛歚man_task`
+- 璁惧绔欑偣锛歚man_device_site`
+
+杩欎娇寰� AI 鏇村亸鍚戔�滆繍琛屾�佽瘖鏂姪鎵嬧�濓紝鑰屼笉鏄�氱敤鐭ヨ瘑闂瓟鏈哄櫒浜恒��
+
+## 5. 浼氳瘽鎸佷箙鍖栬璁�
+
+### 5.1 琛ㄧ粨鏋�
+
+鏂板鑴氭湰锛歚version/db/20260316_ai_chat_storage.sql`
+
+娑夊強涓ゅ紶琛細
+
+- `sys_ai_chat_session`
+- `sys_ai_chat_message`
+
+### 5.2 瀛樺偍绛栫暐
+
+`AiSessionServiceImpl` 閲囩敤鈥滄暟鎹簱浼樺厛锛屽唴瀛樺厹搴曗�濇ā寮忥細
+
+- 濡傛灉妫�娴嬪埌涓ゅ紶琛ㄥ瓨鍦紝鍒欒嚜鍔ㄥ惎鐢ㄦ暟鎹簱鎸佷箙鍖�
+- 濡傛灉鏈墽琛岃縼绉昏剼鏈紝鍒欑户缁娇鐢ㄥ師鏈夊唴瀛樼紦瀛橀�昏緫
+
+杩欐牱鍙互闄嶄綆鍙戝竷椋庨櫓锛岄�傚悎鍒嗛樁娈典笂绾裤��
+
+### 5.3 涓婄嚎寤鸿
+
+寤鸿鍦ㄦ寮忓惎鐢ㄥ墠鍏堟墽琛屼互涓嬭剼鏈細
+
+- `version/db/20260311_ai_param.sql`
+- `version/db/20260316_ai_chat_storage.sql`
+
+鑴氭湰鎵ц瀹屾垚鍚庨噸鍚� `rsf-server`锛屼娇鍏跺湪鍚姩闃舵妫�娴嬪埌浼氳瘽琛ㄥ苟鍒囨崲鍒版寔涔呭寲妯″紡銆�
+
+## 6. 鍓嶇浜や簰璇存槑
+
+`rsf-admin/src/ai/AiChatWidget.jsx` 宸插鍔犫�滀竴閿瘖鏂�濆叆鍙o紝鍏ュ彛浣嶇疆鍖呮嫭锛�
+
+- 浼氳瘽椤堕儴宸ュ叿鍖�
+- 绌虹櫧鎬佸紩瀵煎尯
+- 搴曢儴杈撳叆鍖虹姸鎬佹爮
+
+鐐瑰嚮鍚庝細鍚� `/ai/diagnose/stream` 鍙戣捣娴佸紡璇锋眰锛屽苟鑷姩甯︿笂锛�
+
+- `sessionId`
+- `modelCode`
+- `sceneCode=system_diagnose`
+
+## 7. 閰嶇疆椤硅鏄�
+
+`rsf-server/src/main/resources/application.yml`
+
+鍏抽敭閰嶇疆锛�
+
+- `ai.system-prompt`
+- `ai.diagnosis-system-prompt`
+- `ai.default-model-code`
+- `ai.max-context-messages`
+
+妯″瀷鎺ュ叆閰嶇疆浠嶇劧浼樺厛浠� AI 鍙傛暟绠$悊璇诲彇锛宍application.yml` 涓昏鎵挎媴榛樿鍏滃簳閰嶇疆銆�
+
+## 8. 涓� `zy-wcs-master` 鐨勫樊璺�
+
+褰撳墠鐗堟湰宸茬粡鍏峰鈥滃彲鐢ㄢ�濈殑璇婃柇鑳藉姏锛屼絾涓� `zy-wcs-master` 鐩告瘮浠嶆湁浠ヤ笅宸窛锛�
+
+- 灏氭湭瀹炵幇鐙珛鐨� Prompt 閰嶇疆鍚庡彴
+- 灏氭湭瀹炵幇鏍囧噯 Spring AI MCP Server / MCP Mount 鑱氬悎
+- 灏氭湭瀹炵幇澶氭ā鍨嬭矾鐢便�佸け璐ュ垏鎹€�佽皟鐢ㄦ棩蹇椾腑蹇�
+- 褰撳墠璇婃柇渚濊禆鏁版嵁搴撴憳瑕侊紝灏氭湭绾冲叆鏃ュ織娴併�佽澶囧疄鏃剁嚎绋嬬姸鎬佺瓑鏇存繁灞傛暟鎹�
+
+## 9. 涓嬩竴闃舵寤鸿
+
+濡傛灉缁х画鍚� `zy-wcs-master` 闈犺繎锛屽缓璁寜浠ヤ笅椤哄簭婕旇繘锛�
+
+1. 鎶借薄缁熶竴鐨勨�淎I鏁版嵁宸ュ叿灞傗�濓紝鏇夸唬闆舵暎鐨勪笂涓嬫枃鎻愪緵鍣�
+2. 鏂板 Prompt 妯℃澘琛ㄥ拰鍙戝竷鏈哄埗
+3. 缁� `rsf-ai-gateway` 澧炲姞璋冪敤鏃ュ織銆佸け璐ュ垏鎹€�佹ā鍨嬭矾鐢�
+4. 灏嗘棩蹇椼�佷换鍔″紓甯搞�佹帴鍙eけ璐ョ巼绾冲叆璇婃柇涓婁笅鏂�
+5. 澧炲姞鐙珛鐨� AI 绠$悊椤碉紝鐢ㄤ簬璇婃柇璁板綍銆丳rompt 璋冧紭鍜屾ā鍨嬪垏鎹�
diff --git a/rsf-admin/src/ai/AiChatWidget.jsx b/rsf-admin/src/ai/AiChatWidget.jsx
index 9c713e7..9a5bcf0 100644
--- a/rsf-admin/src/ai/AiChatWidget.jsx
+++ b/rsf-admin/src/ai/AiChatWidget.jsx
@@ -32,6 +32,7 @@
const DRAWER_WIDTH = 720;
const SESSION_WIDTH = 220;
+const DIAGNOSIS_MESSAGE = '璇峰褰撳墠WMS绯荤粺杩涜涓�娆″贰妫�璇婃柇锛岀粨鍚堝簱瀛樸�佷换鍔°�佽澶囩珯鐐规暟鎹瘑鍒紓甯稿苟缁欏嚭澶勭悊寤鸿銆�';
const parseSseChunk = (chunk, onEvent) => {
const blocks = chunk.split('\n\n');
@@ -269,14 +270,12 @@
}
};
- const handleSend = async () => {
- if (!draft.trim() || !activeSessionId || sending) {
+ const streamChat = async ({ userContent, endpoint, sceneCode }) => {
+ if (!userContent || !activeSessionId || sending) {
return;
}
- const userContent = draft.trim();
const sessionId = activeSessionId;
const modelCode = activeSession?.modelCode || models[0]?.code;
- setDraft('');
setError('');
setSending(true);
setMessagesBySession((prev) => {
@@ -304,7 +303,7 @@
let receivedDelta = false;
try {
- const response = await fetch(`${PREFIX_BASE_URL}ai/chat/stream`, {
+ const response = await fetch(`${PREFIX_BASE_URL}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -314,7 +313,8 @@
body: JSON.stringify({
sessionId,
message: userContent,
- modelCode
+ modelCode,
+ sceneCode
}),
signal: controller.signal
});
@@ -392,6 +392,30 @@
setSending(false);
streamControllerRef.current = null;
}
+ };
+
+ const handleSend = async () => {
+ if (!draft.trim() || !activeSessionId || sending) {
+ return;
+ }
+ const userContent = draft.trim();
+ setDraft('');
+ await streamChat({
+ userContent,
+ endpoint: 'ai/chat/stream',
+ sceneCode: 'general_chat'
+ });
+ };
+
+ const handleDiagnose = async () => {
+ if (!activeSessionId || sending) {
+ return;
+ }
+ await streamChat({
+ userContent: DIAGNOSIS_MESSAGE,
+ endpoint: 'ai/diagnose/stream',
+ sceneCode: 'system_diagnose'
+ });
};
const assistantReplyText = (messageList) => {
@@ -575,6 +599,15 @@
</MenuItem>
))}
</Select>
+ <Button
+ variant="outlined"
+ size="small"
+ disabled={sending || !activeSessionId}
+ onClick={handleDiagnose}
+ sx={{ borderRadius: 2, whiteSpace: 'nowrap' }}
+ >
+ 涓�閿瘖鏂�
+ </Button>
</Stack>
<Divider />
<Box
@@ -609,8 +642,11 @@
寮�濮嬫柊鐨勬櫤鑳藉璇�
</Typography>
<Typography variant="body2">
- 鍙互鐩存帴鎻愰棶浠撳偍涓氬姟闂锛屾垨鍒囨崲妯″瀷寮�濮嬫柊鐨勪細璇濄��
+ 鍙互鐩存帴鎻愰棶浠撳偍涓氬姟闂锛屾垨鐐瑰嚮涓�閿瘖鏂揩閫熷贰妫�褰撳墠WMS鐘舵�併��
</Typography>
+ <Button variant="outlined" onClick={handleDiagnose} disabled={sending || !activeSessionId}>
+ 涓�閿瘖鏂�
+ </Button>
</Stack>
) : (
<Stack spacing={2}>
@@ -704,6 +740,15 @@
label={activeSession?.modelCode || '鏈�夋嫨妯″瀷'}
sx={{ bgcolor: 'rgba(25,118,210,0.08)', color: 'primary.main' }}
/>
+ <Button
+ variant="text"
+ size="small"
+ disabled={sending || !activeSessionId}
+ onClick={handleDiagnose}
+ sx={{ minWidth: 'auto', px: 1 }}
+ >
+ 涓�閿瘖鏂�
+ </Button>
<Typography variant="caption" color="text.secondary">
`Enter` 鍙戦�侊紝`Shift + Enter` 鎹㈣
</Typography>
@@ -715,7 +760,7 @@
maxRows={6}
value={draft}
onChange={(event) => setDraft(event.target.value)}
- placeholder="杈撳叆闂锛屾敮鎸佸浼氳瘽鍜屾ā鍨嬪垏鎹�"
+ placeholder="杈撳叆闂锛屾敮鎸佸浼氳瘽銆佸妯″瀷鍜屼竴閿瘖鏂�"
onKeyDown={(event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
diff --git a/rsf-admin/src/i18n/en.js b/rsf-admin/src/i18n/en.js
index 9c32fd3..ab6ed0e 100644
--- a/rsf-admin/src/i18n/en.js
+++ b/rsf-admin/src/i18n/en.js
@@ -151,6 +151,13 @@
operation: 'Operation',
config: 'Config',
aiParam: 'AI Params',
+ aiPrompt: 'AI Prompt',
+ aiDiagnosis: 'AI Diagnosis',
+ aiDiagnosisPlan: 'AI Diagnosis Plan',
+ aiCallLog: 'AI Call Log',
+ aiRoute: 'AI Route',
+ aiToolConfig: 'AI Diagnostic Tool',
+ aiMcpMount: 'AI MCP Mount',
tenant: 'Tenant',
userLogin: 'Token',
customer: 'Customer',
diff --git a/rsf-admin/src/i18n/zh.js b/rsf-admin/src/i18n/zh.js
index a351480..ddf6a87 100644
--- a/rsf-admin/src/i18n/zh.js
+++ b/rsf-admin/src/i18n/zh.js
@@ -152,6 +152,13 @@
operation: '鎿嶄綔鏃ュ織',
config: '閰嶇疆鍙傛暟',
aiParam: 'AI鍙傛暟',
+ aiPrompt: 'AI鎻愮ず璇�',
+ aiDiagnosis: 'AI璇婃柇璁板綍',
+ aiDiagnosisPlan: 'AI宸℃璁″垝',
+ aiCallLog: 'AI璋冪敤鏃ュ織',
+ aiRoute: 'AI妯″瀷璺敱',
+ aiToolConfig: 'AI璇婃柇宸ュ叿',
+ aiMcpMount: 'AI MCP鎸傝浇',
tenant: '绉熸埛绠$悊',
userLogin: '鐧诲綍鏃ュ織',
customer: '瀹㈡埛琛�',
diff --git a/rsf-admin/src/page/ResourceContent.js b/rsf-admin/src/page/ResourceContent.js
index a4f7965..9f7c34d 100644
--- a/rsf-admin/src/page/ResourceContent.js
+++ b/rsf-admin/src/page/ResourceContent.js
@@ -7,6 +7,13 @@
import host from "./system/host";
import config from "./system/config";
import aiParam from "./system/aiParam";
+import aiPrompt from "./system/aiPrompt";
+import aiDiagnosis from "./system/aiDiagnosis";
+import aiDiagnosisPlan from "./system/aiDiagnosisPlan";
+import aiCallLog from "./system/aiCallLog";
+import aiRoute from "./system/aiRoute";
+import aiToolConfig from "./system/aiToolConfig";
+import aiMcpMount from "./system/aiMcpMount";
import tenant from "./system/tenant";
import role from "./system/role";
import userLogin from "./system/userLogin";
@@ -80,6 +87,20 @@
return config;
case "aiParam":
return aiParam;
+ case "aiPrompt":
+ return aiPrompt;
+ case "aiDiagnosis":
+ return aiDiagnosis;
+ case "aiDiagnosisPlan":
+ return aiDiagnosisPlan;
+ case "aiCallLog":
+ return aiCallLog;
+ case "aiRoute":
+ return aiRoute;
+ case "aiToolConfig":
+ return aiToolConfig;
+ case "aiMcpMount":
+ return aiMcpMount;
case "tenant":
return tenant;
case "role":
diff --git a/rsf-admin/src/page/components/AiConsoleLayout.jsx b/rsf-admin/src/page/components/AiConsoleLayout.jsx
new file mode 100644
index 0000000..0393683
--- /dev/null
+++ b/rsf-admin/src/page/components/AiConsoleLayout.jsx
@@ -0,0 +1,133 @@
+import React from "react";
+import { alpha } from '@mui/material/styles';
+import { Box, Grid, Paper, Stack, Typography } from '@mui/material';
+
+const pageShellSx = {
+ mt: 0.5,
+ width: '100%',
+};
+
+export const AiConsoleLayout = ({ title, subtitle, actions, stats, children }) => (
+ <Box sx={pageShellSx}>
+ <Paper
+ elevation={0}
+ sx={{
+ borderRadius: 3,
+ px: { xs: 2, md: 2.5 },
+ py: 2,
+ overflow: 'hidden',
+ backgroundColor: '#fff',
+ border: '1px solid #e6ebf2',
+ boxShadow: '0 1px 3px rgba(15, 23, 42, 0.06)',
+ }}
+ >
+ <Stack direction={{ xs: 'column', md: 'row' }} justifyContent="space-between" spacing={2}>
+ <Box>
+ <Typography variant="h6" sx={{ fontWeight: 700, letterSpacing: 0.1 }}>
+ {title}
+ </Typography>
+ {subtitle ? (
+ <Typography variant="body2" color="text.secondary" sx={{ mt: 0.5, maxWidth: 760 }}>
+ {subtitle}
+ </Typography>
+ ) : null}
+ </Box>
+ {actions ? (
+ <Stack direction="row" spacing={1} flexWrap="wrap" justifyContent="flex-end">
+ {actions}
+ </Stack>
+ ) : null}
+ </Stack>
+ {stats?.length ? (
+ <Grid container spacing={1.5} sx={{ mt: 1.5 }}>
+ {stats.map((item) => (
+ <Grid item xs={12} sm={6} md={12 / Math.min(stats.length, 4)} key={item.label}>
+ <Box
+ sx={{
+ minHeight: 76,
+ px: 1.5,
+ py: 1.25,
+ borderRadius: 2,
+ border: '1px solid #e7ecf3',
+ backgroundColor: '#fafbfd',
+ }}
+ >
+ <Typography variant="caption" color="text.secondary">
+ {item.label}
+ </Typography>
+ <Typography variant="h5" sx={{ mt: 0.5, fontWeight: 700, lineHeight: 1.1 }}>
+ {item.value}
+ </Typography>
+ {item.helper ? (
+ <Typography variant="caption" color="text.secondary">
+ {item.helper}
+ </Typography>
+ ) : null}
+ </Box>
+ </Grid>
+ ))}
+ </Grid>
+ ) : null}
+ </Paper>
+ <Box sx={{ mt: 1.5 }}>
+ {children}
+ </Box>
+ </Box>
+);
+
+export const AiConsolePanel = ({ title, subtitle, action, children, minHeight }) => (
+ <Paper
+ elevation={0}
+ sx={{
+ height: '100%',
+ minHeight: minHeight || 240,
+ borderRadius: 3,
+ border: '1px solid #e6ebf2',
+ backgroundColor: '#fff',
+ boxShadow: '0 1px 3px rgba(15, 23, 42, 0.06)',
+ overflow: 'hidden',
+ }}
+ >
+ <Stack
+ direction="row"
+ justifyContent="space-between"
+ alignItems="flex-start"
+ spacing={1}
+ sx={{
+ px: 2,
+ py: 1.5,
+ borderBottom: '1px solid #e6edf5',
+ backgroundColor: alpha('#fafbfc', 1),
+ }}
+ >
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
+ {title}
+ </Typography>
+ {subtitle ? (
+ <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5 }}>
+ {subtitle}
+ </Typography>
+ ) : null}
+ </Box>
+ {action}
+ </Stack>
+ <Box sx={{ p: 1.5 }}>
+ {children}
+ </Box>
+ </Paper>
+);
+
+export const aiCardSx = (active) => ({
+ p: 1.5,
+ borderRadius: 2.5,
+ border: active ? '1px solid #d7e2ef' : '1px solid #e5eaf0',
+ backgroundColor: '#fff',
+ boxShadow: active
+ ? '0 2px 8px rgba(15, 23, 42, 0.08)'
+ : '0 1px 4px rgba(15, 23, 42, 0.04)',
+ transition: 'box-shadow 0.18s ease, border-color 0.18s ease',
+ '&:hover': {
+ boxShadow: '0 3px 10px rgba(15, 23, 42, 0.08)',
+ },
+});
diff --git a/rsf-admin/src/page/system/aiCallLog/AiCallLogEdit.jsx b/rsf-admin/src/page/system/aiCallLog/AiCallLogEdit.jsx
new file mode 100644
index 0000000..dacb4b4
--- /dev/null
+++ b/rsf-admin/src/page/system/aiCallLog/AiCallLogEdit.jsx
@@ -0,0 +1,38 @@
+import React from "react";
+import {
+ Edit,
+ SimpleForm,
+ TextInput,
+ NumberInput,
+} from 'react-admin';
+import { Stack, Grid, Typography } from '@mui/material';
+import EditBaseAside from "@/page/components/EditBaseAside";
+import CustomerTopToolBar from "@/page/components/EditTopToolBar";
+
+const AiCallLogEdit = () => (
+ <Edit actions={<CustomerTopToolBar />} aside={<EditBaseAside />}>
+ <SimpleForm toolbar={false}>
+ <Grid container width={{ xs: '100%', xl: '80%' }} rowSpacing={3} columnSpacing={3}>
+ <Grid item xs={12}>
+ <Typography variant="h6" gutterBottom>璋冪敤璇︽儏</Typography>
+ <Stack direction='row' gap={2}>
+ <TextInput source="routeCode" label="璺敱缂栫爜" disabled />
+ <TextInput source="modelCode" label="妯″瀷缂栫爜" disabled />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <NumberInput source="attemptNo" label="灏濊瘯搴忓彿" disabled />
+ <TextInput source="result$" label="缁撴灉" disabled />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <NumberInput source="spendTime" label="鑰楁椂(ms)" disabled />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="err" label="閿欒淇℃伅" fullWidth multiline minRows={4} disabled />
+ </Stack>
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ </Edit>
+);
+
+export default AiCallLogEdit;
diff --git a/rsf-admin/src/page/system/aiCallLog/AiCallLogList.jsx b/rsf-admin/src/page/system/aiCallLog/AiCallLogList.jsx
new file mode 100644
index 0000000..4bf7195
--- /dev/null
+++ b/rsf-admin/src/page/system/aiCallLog/AiCallLogList.jsx
@@ -0,0 +1,144 @@
+import React, { useEffect, useState } from "react";
+import {
+ List,
+ SearchInput,
+ TopToolbar,
+ SelectColumnsButton,
+ EditButton,
+ FilterButton,
+ TextInput,
+ DateInput,
+ SelectInput,
+ useListContext,
+ Pagination,
+} from 'react-admin';
+import { Box, Chip, Grid, Typography } from '@mui/material';
+import EmptyData from "@/page/components/EmptyData";
+import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
+import MyExportButton from '@/page/components/MyExportButton';
+import { DEFAULT_PAGE_SIZE } from '@/config/setting';
+import request from "@/utils/request";
+
+const filters = [
+ <SearchInput source="condition" alwaysOn />,
+ <DateInput label='common.time.after' source="timeStart" alwaysOn />,
+ <DateInput label='common.time.before' source="timeEnd" alwaysOn />,
+ <TextInput source="modelCode" label="妯″瀷缂栫爜" />,
+ <TextInput source="routeCode" label="璺敱缂栫爜" />,
+ <SelectInput source="result" label="缁撴灉" choices={[
+ { id: '1', name: '鎴愬姛' },
+ { id: '0', name: '澶辫触' },
+ ]} />,
+];
+
+const resultColor = (result) => Number(result) === 1 ? 'success' : 'error';
+
+const CallLogBoard = () => {
+ const { data, isLoading } = useListContext();
+ const records = data || [];
+ const [stats, setStats] = useState({});
+
+ useEffect(() => {
+ let mounted = true;
+ const fetchStats = async () => {
+ try {
+ const { data: res } = await request.get('/ai/call-log/stats');
+ if (mounted && res?.code === 200) {
+ setStats(res.data || {});
+ }
+ } catch (error) {
+ }
+ };
+ fetchStats();
+ return () => {
+ mounted = false;
+ };
+ }, []);
+
+ if (!isLoading && !records.length) {
+ return <EmptyData />;
+ }
+
+ return (
+ <AiConsoleLayout
+ title="AI璋冪敤鏃ュ織"
+ subtitle="鎸夊綋鍓嶇郴缁熷悗鍙伴鏍煎睍绀烘ā鍨嬭皟鐢ㄨ娴嬶紝绐佸嚭鎴愬姛鐜囥�佹渶杩� 24 灏忔椂璋冪敤閲忓拰骞冲潎鑰楁椂锛屼究浜庡揩閫熸帓鏌ヨ矾鐢变笌涓婃父妯″瀷闂銆�"
+ stats={[
+ { label: '璋冪敤鎬绘暟', value: stats.total || 0 },
+ { label: '鎴愬姛鐜�', value: `${Number(stats.successRate || 0).toFixed(1)}%` },
+ { label: '骞冲潎鑰楁椂', value: `${stats.avgSpendTime || 0} ms` },
+ { label: '杩�24灏忔椂', value: stats.last24hCount || 0 },
+ ]}
+ >
+ <AiConsolePanel
+ title="璋冪敤鏄庣粏"
+ subtitle={`宸茶娴嬫ā鍨� ${stats.modelCount || 0} 涓紝璺敱 ${stats.routeCount || 0} 鏉°�俙}
+ minHeight={420}
+ >
+ <Grid container spacing={2}>
+ {records.map((record) => (
+ <Grid item xs={12} md={6} xl={4} key={record.id}>
+ <Box sx={aiCardSx(record.result === 1)}>
+ <Box display="flex" justifyContent="space-between" gap={1}>
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#284059' }}>
+ {record.modelCode || '鏈褰曟ā鍨�'}
+ </Typography>
+ <Typography variant="caption" sx={{ color: '#8093a8' }}>
+ {record.routeCode || '鏈褰曡矾鐢�'} 路 绗� {record.attemptNo || 1} 娆�
+ </Typography>
+ </Box>
+ <Chip size="small" color={resultColor(record.result)} label={record.result$ || '鏈煡'} />
+ </Box>
+ <Box sx={{ mt: 1.25, display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 1.5 }}>
+ <Box>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>鑰楁椂</Typography>
+ <Typography variant="body2" sx={{ color: '#31465d' }}>{record.spendTime || 0} ms</Typography>
+ </Box>
+ <Box>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>璇锋眰鏃堕棿</Typography>
+ <Typography variant="body2" sx={{ color: '#31465d' }}>{record.requestTime || '-'}</Typography>
+ </Box>
+ </Box>
+ <Box sx={{ mt: 1.25 }}>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>閿欒淇℃伅</Typography>
+ <Typography variant="body2" sx={{ mt: 0.5, minHeight: 72, color: '#31465d' }}>
+ {record.err || '鏃�'}
+ </Typography>
+ </Box>
+ <Box sx={{ mt: 1.5 }}>
+ <EditButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} />
+ </Box>
+ </Box>
+ </Grid>
+ ))}
+ </Grid>
+ <Box sx={{ mt: 2 }}>
+ <Pagination rowsPerPageOptions={[DEFAULT_PAGE_SIZE, 25, 50]} />
+ </Box>
+ </AiConsolePanel>
+ </AiConsoleLayout>
+ );
+};
+
+const AiCallLogList = () => (
+ <List
+ sx={{ width: '100%', flexGrow: 1 }}
+ title={"menu.aiCallLog"}
+ filters={filters}
+ sort={{ field: "createTime", order: "desc" }}
+ actions={(
+ <TopToolbar>
+ <FilterButton />
+ <SelectColumnsButton preferenceKey='aiCallLog' />
+ <MyExportButton />
+ </TopToolbar>
+ )}
+ perPage={DEFAULT_PAGE_SIZE}
+ pagination={false}
+ >
+ <CallLogBoard />
+ </List>
+);
+
+export default AiCallLogList;
diff --git a/rsf-admin/src/page/system/aiCallLog/index.jsx b/rsf-admin/src/page/system/aiCallLog/index.jsx
new file mode 100644
index 0000000..59ebe86
--- /dev/null
+++ b/rsf-admin/src/page/system/aiCallLog/index.jsx
@@ -0,0 +1,13 @@
+import {
+ ShowGuesser,
+} from "react-admin";
+
+import AiCallLogList from "./AiCallLogList";
+import AiCallLogEdit from "./AiCallLogEdit";
+
+export default {
+ list: AiCallLogList,
+ edit: AiCallLogEdit,
+ show: ShowGuesser,
+ recordRepresentation: (record) => `${record.modelCode || record.id || ''}`
+};
diff --git a/rsf-admin/src/page/system/aiDiagnosis/AiDiagnosisEdit.jsx b/rsf-admin/src/page/system/aiDiagnosis/AiDiagnosisEdit.jsx
new file mode 100644
index 0000000..8a4a61d
--- /dev/null
+++ b/rsf-admin/src/page/system/aiDiagnosis/AiDiagnosisEdit.jsx
@@ -0,0 +1,152 @@
+import React from "react";
+import {
+ Edit,
+ SimpleForm,
+ TextInput,
+ NumberInput,
+ useNotify,
+ useRecordContext,
+} from 'react-admin';
+import { Box, Button, Chip, Grid, Paper, Stack, Typography } from '@mui/material';
+import EditBaseAside from "@/page/components/EditBaseAside";
+import CustomerTopToolBar from "@/page/components/EditTopToolBar";
+import request from "@/utils/request";
+
+const ReportExportButton = () => {
+ const record = useRecordContext();
+ const notify = useNotify();
+
+ if (!record?.id) {
+ return null;
+ }
+
+ const handleExport = async () => {
+ try {
+ const res = await request.get(`/ai/diagnosis/report/export/${record.id}`, {
+ responseType: 'blob',
+ });
+ const blob = new Blob([res.data], { type: 'text/markdown;charset=utf-8' });
+ const link = document.createElement('a');
+ link.href = window.URL.createObjectURL(blob);
+ link.setAttribute('download', `${record.diagnosisNo || 'ai-diagnosis-report'}.md`);
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+ } catch (error) {
+ notify(error.message || '瀵煎嚭澶辫触', { type: 'error' });
+ }
+ };
+
+ return <Button variant="outlined" onClick={handleExport}>瀵煎嚭鎶ュ憡</Button>;
+};
+
+const ToolTracePanel = () => {
+ const record = useRecordContext();
+
+ if (!record?.toolSummary) {
+ return (
+ <Typography variant="body2" color="text.secondary">
+ 鏆傛棤宸ュ叿杞ㄨ抗鏁版嵁
+ </Typography>
+ );
+ }
+
+ let tools = [];
+ try {
+ const parsed = JSON.parse(record.toolSummary);
+ if (Array.isArray(parsed)) {
+ tools = parsed;
+ }
+ } catch (error) {
+ }
+
+ if (!tools.length) {
+ return (
+ <TextInput source="toolSummary" label="宸ュ叿鎽樿(JSON)" fullWidth multiline minRows={6} disabled />
+ );
+ }
+
+ return (
+ <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 2 }}>
+ {tools.map((item, index) => (
+ <Paper key={`${item.toolCode || 'tool'}-${index}`} variant="outlined" sx={{ p: 2, borderRadius: 2 }}>
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+ <Box>
+ <Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
+ {item.toolName || item.toolCode || `宸ュ叿${index + 1}`}
+ </Typography>
+ <Typography variant="caption" color="text.secondary">
+ {(item.mcpToolName || item.toolCode || '-') + (item.mountCode ? ` 路 ${item.mountCode}` : '')}
+ </Typography>
+ </Box>
+ <Chip size="small" label={item.severity || 'INFO'} color={item.severity === 'ERROR' ? 'error' : (item.severity === 'WARN' ? 'warning' : 'primary')} />
+ </Stack>
+ <Typography variant="body2" sx={{ mt: 1.25, whiteSpace: 'pre-wrap' }}>
+ {item.summaryText || '鏃犳憳瑕�'}
+ </Typography>
+ </Paper>
+ ))}
+ </Box>
+ );
+};
+
+const AiDiagnosisEdit = () => (
+ <Edit actions={<CustomerTopToolBar />} aside={<EditBaseAside />}>
+ <SimpleForm toolbar={false}>
+ <Grid container width={{ xs: '100%', xl: '85%' }} rowSpacing={3} columnSpacing={3}>
+ <Grid item xs={12}>
+ <Typography variant="h6" gutterBottom>璇婃柇姒傝</Typography>
+ <Stack direction='row' gap={2} flexWrap="wrap">
+ <TextInput source="diagnosisNo" label="璇婃柇缂栧彿" disabled />
+ <TextInput source="sceneCode$" label="鍦烘櫙" disabled />
+ <TextInput source="modelCode" label="妯″瀷缂栫爜" disabled />
+ <TextInput source="result$" label="缁撴灉" disabled />
+ <NumberInput source="spendTime" label="鑰楁椂(ms)" disabled />
+ </Stack>
+ <Stack direction='row' gap={2} sx={{ mt: 1 }}>
+ <ReportExportButton />
+ </Stack>
+ </Grid>
+ <Grid item xs={12}>
+ <Typography variant="h6" gutterBottom>鎶ュ憡鎽樿</Typography>
+ <Stack direction='row' gap={2}>
+ <TextInput source="reportTitle" label="鎶ュ憡鏍囬" fullWidth disabled />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="question" label="璇婃柇闂" fullWidth multiline minRows={3} disabled />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="executiveSummary" label="闂姒傝堪" fullWidth multiline minRows={4} disabled />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="evidenceSummary" label="鍏抽敭璇佹嵁" fullWidth multiline minRows={4} disabled />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="actionSummary" label="寤鸿鍔ㄤ綔" fullWidth multiline minRows={4} disabled />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="riskSummary" label="椋庨櫓璇勪及" fullWidth multiline minRows={4} disabled />
+ </Stack>
+ </Grid>
+ <Grid item xs={12}>
+ <Typography variant="h6" gutterBottom>鍘熷鏁版嵁</Typography>
+ <Stack direction='row' gap={2}>
+ <TextInput source="conclusion" label="AI缁撹" fullWidth multiline minRows={6} disabled />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="reportMarkdown" label="鎶ュ憡Markdown" fullWidth multiline minRows={8} disabled />
+ </Stack>
+ <Box sx={{ mt: 2 }}>
+ <Typography variant="subtitle1" gutterBottom>宸ュ叿鎵ц杞ㄨ抗</Typography>
+ <ToolTracePanel />
+ </Box>
+ <Stack direction='row' gap={2}>
+ <TextInput source="err" label="閿欒淇℃伅" fullWidth multiline minRows={3} disabled />
+ </Stack>
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ </Edit>
+);
+
+export default AiDiagnosisEdit;
diff --git a/rsf-admin/src/page/system/aiDiagnosis/AiDiagnosisList.jsx b/rsf-admin/src/page/system/aiDiagnosis/AiDiagnosisList.jsx
new file mode 100644
index 0000000..40845df
--- /dev/null
+++ b/rsf-admin/src/page/system/aiDiagnosis/AiDiagnosisList.jsx
@@ -0,0 +1,134 @@
+import React from "react";
+import {
+ List,
+ SearchInput,
+ TopToolbar,
+ SelectColumnsButton,
+ EditButton,
+ FilterButton,
+ TextInput,
+ DateInput,
+ SelectInput,
+ useListContext,
+ Pagination,
+} from 'react-admin';
+import { Box, Chip, Grid, Stack, Typography } from '@mui/material';
+import MyExportButton from '@/page/components/MyExportButton';
+import { DEFAULT_PAGE_SIZE } from '@/config/setting';
+import EmptyData from "@/page/components/EmptyData";
+import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
+
+const filters = [
+ <SearchInput source="condition" alwaysOn />,
+ <DateInput label='common.time.after' source="timeStart" alwaysOn />,
+ <DateInput label='common.time.before' source="timeEnd" alwaysOn />,
+ <TextInput source="diagnosisNo" label="璇婃柇缂栧彿" />,
+ <TextInput source="modelCode" label="妯″瀷缂栫爜" />,
+ <SelectInput source="result" label="缁撴灉" choices={[
+ { id: '2', name: '杩愯涓�' },
+ { id: '1', name: '鎴愬姛' },
+ { id: '0', name: '澶辫触' },
+ ]} />,
+];
+
+const resultColor = (result) => {
+ if (result === 1) {
+ return 'success';
+ }
+ if (result === 0) {
+ return 'error';
+ }
+ return 'warning';
+};
+
+const DiagnosisBoard = () => {
+ const { data, isLoading } = useListContext();
+ const records = data || [];
+ const successCount = records.filter((item) => item.result === 1).length;
+ const failedCount = records.filter((item) => item.result === 0).length;
+ const runningCount = records.filter((item) => item.result === 2).length;
+
+ if (!isLoading && !records.length) {
+ return <EmptyData />;
+ }
+
+ return (
+ <AiConsoleLayout
+ title="AI璇婃柇鎶ュ憡"
+ subtitle="璇婃柇鍒楄〃鏀规垚鎶ュ憡鍗$墖瑙嗗浘锛岀獊鍑虹粨璁烘爣棰樸�佸鐞嗙粨鏋滃拰鑰楁椂锛涜鎯呴〉缁х画鎵挎帴瀹屾暣鎶ュ憡涓� Markdown 瀵煎嚭銆�"
+ stats={[
+ { label: '鎶ュ憡鎬绘暟', value: records.length },
+ { label: '鎴愬姛', value: successCount },
+ { label: '澶辫触', value: failedCount },
+ { label: '杩愯涓�', value: runningCount },
+ ]}
+ >
+ <AiConsolePanel
+ title="鎶ュ憡鍒楄〃"
+ subtitle="鐐瑰嚮缂栬緫杩涘叆鎶ュ憡璇︽儏椤碉紝鍙煡鐪嬬粨鏋勫寲鎶ュ憡銆佸師濮嬬粨璁哄拰瀵煎嚭 Markdown銆�"
+ minHeight={460}
+ >
+ <Grid container spacing={2}>
+ {records.map((record) => (
+ <Grid item xs={12} md={6} xl={4} key={record.id}>
+ <Box sx={aiCardSx(record.result === 1)}>
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#284059' }}>
+ {record.reportTitle || record.diagnosisNo}
+ </Typography>
+ <Typography variant="caption" sx={{ color: '#8093a8' }}>
+ {record.sceneCode$} 路 {record.modelCode || '鏈褰曟ā鍨�'}
+ </Typography>
+ </Box>
+ <Chip size="small" color={resultColor(record.result)} label={record.result$ || '鏈煡'} />
+ </Stack>
+ <Typography variant="body2" sx={{ mt: 1.25, minHeight: 72, color: '#31465d' }}>
+ {record.executiveSummary || record.question || '鏆傛棤璇婃柇鎽樿'}
+ </Typography>
+ <Stack direction="row" spacing={2} sx={{ mt: 1.25 }}>
+ <Box>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>鑰楁椂</Typography>
+ <Typography variant="body2" sx={{ color: '#31465d' }}>{record.spendTime || 0} ms</Typography>
+ </Box>
+ <Box>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>寮�濮嬫椂闂�</Typography>
+ <Typography variant="body2" sx={{ color: '#31465d' }}>{record.startTime || '-'}</Typography>
+ </Box>
+ </Stack>
+ <Box sx={{ mt: 1.5 }}>
+ <EditButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} />
+ </Box>
+ </Box>
+ </Grid>
+ ))}
+ </Grid>
+ <Box sx={{ mt: 2 }}>
+ <Pagination rowsPerPageOptions={[DEFAULT_PAGE_SIZE, 25, 50]} />
+ </Box>
+ </AiConsolePanel>
+ </AiConsoleLayout>
+ );
+};
+
+const AiDiagnosisList = () => (
+ <List
+ sx={{ width: '100%', flexGrow: 1 }}
+ title={"menu.aiDiagnosis"}
+ filters={filters}
+ sort={{ field: "createTime", order: "desc" }}
+ actions={(
+ <TopToolbar>
+ <FilterButton />
+ <SelectColumnsButton preferenceKey='aiDiagnosis' />
+ <MyExportButton />
+ </TopToolbar>
+ )}
+ perPage={DEFAULT_PAGE_SIZE}
+ pagination={false}
+ >
+ <DiagnosisBoard />
+ </List>
+);
+
+export default AiDiagnosisList;
diff --git a/rsf-admin/src/page/system/aiDiagnosis/index.jsx b/rsf-admin/src/page/system/aiDiagnosis/index.jsx
new file mode 100644
index 0000000..d045a36
--- /dev/null
+++ b/rsf-admin/src/page/system/aiDiagnosis/index.jsx
@@ -0,0 +1,13 @@
+import {
+ ShowGuesser,
+} from "react-admin";
+
+import AiDiagnosisList from "./AiDiagnosisList";
+import AiDiagnosisEdit from "./AiDiagnosisEdit";
+
+export default {
+ list: AiDiagnosisList,
+ edit: AiDiagnosisEdit,
+ show: ShowGuesser,
+ recordRepresentation: (record) => `${record.diagnosisNo || record.id || ''}`
+};
diff --git a/rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanCreate.jsx b/rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanCreate.jsx
new file mode 100644
index 0000000..668d67d
--- /dev/null
+++ b/rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanCreate.jsx
@@ -0,0 +1,88 @@
+import React from "react";
+import {
+ CreateBase,
+ useTranslate,
+ TextInput,
+ SaveButton,
+ SelectInput,
+ Toolbar,
+ useNotify,
+ Form,
+} from 'react-admin';
+import {
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Grid,
+ Box,
+} from '@mui/material';
+import DialogCloseButton from "@/page/components/DialogCloseButton";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+import MemoInput from "@/page/components/MemoInput";
+
+const sceneChoices = [
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+];
+
+const AiDiagnosisPlanCreate = (props) => {
+ const { open, setOpen } = props;
+ const translate = useTranslate();
+ const notify = useNotify();
+
+ const handleClose = (event, reason) => {
+ if (reason !== "backdropClick") {
+ setOpen(false);
+ }
+ };
+
+ const handleSuccess = async () => {
+ setOpen(false);
+ notify('common.response.success');
+ };
+
+ const handleError = async (error) => {
+ notify(error.message || 'common.response.fail', { type: 'error', messageArgs: { _: error.message } });
+ };
+
+ return (
+ <CreateBase
+ record={{
+ sceneCode: 'system_diagnose',
+ cronExpr: '0 0/30 * * * ?',
+ status: 1,
+ }}
+ mutationOptions={{ onSuccess: handleSuccess, onError: handleError }}
+ >
+ <Dialog open={open} onClose={handleClose} fullWidth disableRestoreFocus maxWidth="md">
+ <Form>
+ <DialogTitle sx={{ position: 'sticky', top: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+ {translate('create.title')}
+ <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
+ <DialogCloseButton onClose={handleClose} />
+ </Box>
+ </DialogTitle>
+ <DialogContent sx={{ mt: 2 }}>
+ <Grid container rowSpacing={2} columnSpacing={2}>
+ <Grid item xs={6}><TextInput source="planName" label="璁″垝鍚嶇О" fullWidth /></Grid>
+ <Grid item xs={6}><SelectInput source="sceneCode" label="鍦烘櫙" choices={sceneChoices} fullWidth /></Grid>
+ <Grid item xs={6}><TextInput source="cronExpr" label="Cron琛ㄨ揪寮�" fullWidth helperText="渚嬪: 0 0/30 * * * ?" /></Grid>
+ <Grid item xs={6}><TextInput source="preferredModelCode" label="浼樺厛妯″瀷缂栫爜" fullWidth /></Grid>
+ <Grid item xs={12}><TextInput source="prompt" label="宸℃鎻愮ず璇�" fullWidth multiline minRows={5} /></Grid>
+ <Grid item xs={6}><StatusSelectInput fullWidth /></Grid>
+ <Grid item xs={12}><MemoInput /></Grid>
+ </Grid>
+ </DialogContent>
+ <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+ <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}>
+ <SaveButton />
+ </Toolbar>
+ </DialogActions>
+ </Form>
+ </Dialog>
+ </CreateBase>
+ )
+}
+
+export default AiDiagnosisPlanCreate;
diff --git a/rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanEdit.jsx b/rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanEdit.jsx
new file mode 100644
index 0000000..534c29f
--- /dev/null
+++ b/rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanEdit.jsx
@@ -0,0 +1,69 @@
+import React from "react";
+import {
+ Edit,
+ SimpleForm,
+ TextInput,
+ SaveButton,
+ SelectInput,
+ Toolbar,
+ DeleteButton,
+} from 'react-admin';
+import { Stack, Grid, Typography } from '@mui/material';
+import { EDIT_MODE } from '@/config/setting';
+import EditBaseAside from "@/page/components/EditBaseAside";
+import CustomerTopToolBar from "@/page/components/EditTopToolBar";
+import MemoInput from "@/page/components/MemoInput";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+
+const sceneChoices = [
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+];
+
+const FormToolbar = () => (
+ <Toolbar sx={{ justifyContent: 'space-between' }}>
+ <SaveButton />
+ <DeleteButton mutationMode="optimistic" />
+ </Toolbar>
+);
+
+const AiDiagnosisPlanEdit = () => (
+ <Edit redirect="list" mutationMode={EDIT_MODE} actions={<CustomerTopToolBar />} aside={<EditBaseAside />}>
+ <SimpleForm shouldUnregister warnWhenUnsavedChanges toolbar={<FormToolbar />} mode="onTouched">
+ <Grid container width={{ xs: '100%', xl: '80%' }} rowSpacing={3} columnSpacing={3}>
+ <Grid item xs={12} md={8}>
+ <Typography variant="h6" gutterBottom>涓昏</Typography>
+ <Stack direction='row' gap={2}>
+ <TextInput source="planName" label="璁″垝鍚嶇О" fullWidth />
+ <SelectInput source="sceneCode" label="鍦烘櫙" choices={sceneChoices} fullWidth />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="cronExpr" label="Cron琛ㄨ揪寮�" fullWidth />
+ <TextInput source="preferredModelCode" label="浼樺厛妯″瀷缂栫爜" fullWidth />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="nextRunTime$" label="涓嬫杩愯鏃堕棿" disabled fullWidth />
+ <TextInput source="lastRunTime$" label="涓婃杩愯鏃堕棿" disabled fullWidth />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="lastResult$" label="鏈�杩戠粨鏋�" disabled fullWidth />
+ <TextInput source="lastDiagnosisId" label="鏈�杩戣瘖鏂璉D" disabled fullWidth />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="lastMessage" label="鏈�杩戞秷鎭�" fullWidth multiline minRows={3} disabled />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="prompt" label="宸℃鎻愮ず璇�" fullWidth multiline minRows={6} />
+ </Stack>
+ </Grid>
+ <Grid item xs={12} md={4}>
+ <Typography variant="h6" gutterBottom>閫氱敤</Typography>
+ <StatusSelectInput />
+ <MemoInput />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ </Edit>
+)
+
+export default AiDiagnosisPlanEdit;
diff --git a/rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanList.jsx b/rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanList.jsx
new file mode 100644
index 0000000..0a685bb
--- /dev/null
+++ b/rsf-admin/src/page/system/aiDiagnosisPlan/AiDiagnosisPlanList.jsx
@@ -0,0 +1,175 @@
+import React, { useState } from "react";
+import {
+ List,
+ SearchInput,
+ TopToolbar,
+ SelectColumnsButton,
+ EditButton,
+ FilterButton,
+ TextInput,
+ DateInput,
+ SelectInput,
+ useListContext,
+ Pagination,
+ useNotify,
+ useRefresh,
+ DeleteButton,
+} from 'react-admin';
+import { Box, Button, Chip, Grid, Stack, Typography } from '@mui/material';
+import EmptyData from "@/page/components/EmptyData";
+import MyCreateButton from "@/page/components/MyCreateButton";
+import MyExportButton from '@/page/components/MyExportButton';
+import { DEFAULT_PAGE_SIZE, OPERATE_MODE } from '@/config/setting';
+import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
+import AiDiagnosisPlanCreate from "./AiDiagnosisPlanCreate";
+import request from "@/utils/request";
+
+const sceneChoices = [
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+];
+
+const filters = [
+ <SearchInput source="condition" alwaysOn />,
+ <DateInput label='common.time.after' source="timeStart" alwaysOn />,
+ <DateInput label='common.time.before' source="timeEnd" alwaysOn />,
+ <SelectInput source="sceneCode" label="鍦烘櫙" choices={sceneChoices} />,
+ <TextInput source="planName" label="璁″垝鍚嶇О" />,
+ <SelectInput source="status" label="鐘舵��" choices={[
+ { id: '1', name: '鍚敤' },
+ { id: '0', name: '鍋滅敤' },
+ ]} />,
+];
+
+const RunPlanButton = ({ record }) => {
+ const notify = useNotify();
+ const refresh = useRefresh();
+
+ const handleRun = async () => {
+ try {
+ const { data } = await request.post('/ai/diagnosis-plan/run', { id: record.id });
+ if (data?.code !== 200) {
+ throw new Error(data?.msg || '鎵ц澶辫触');
+ }
+ notify('宸叉彁浜ゆ墽琛�');
+ refresh();
+ } catch (error) {
+ notify(error.message || '鎵ц澶辫触', { type: 'error' });
+ }
+ };
+
+ return (
+ <Button size="small" variant="outlined" onClick={handleRun} disabled={record.runningFlag === 1}>
+ 绔嬪嵆鎵ц
+ </Button>
+ );
+};
+
+const PlanBoard = () => {
+ const { data, isLoading } = useListContext();
+ const records = data || [];
+ const enabledCount = records.filter((item) => item.status === 1).length;
+ const runningCount = records.filter((item) => item.runningFlag === 1).length;
+ const successCount = records.filter((item) => item.lastResult === 1).length;
+
+ if (!isLoading && !records.length) {
+ return <EmptyData />;
+ }
+
+ return (
+ <AiConsoleLayout
+ title="AI宸℃璁″垝"
+ subtitle="鎶婁竴閿瘖鏂墿鎴愬彲璁″垝鎵ц鐨勫贰妫�浠诲姟锛屾寜 Cron 瀹氭椂瑙﹀彂锛岃嚜鍔ㄧ敓鎴愭柊鐨勮瘖鏂褰曞拰鎶ュ憡銆�"
+ stats={[
+ { label: '璁″垝鎬绘暟', value: records.length },
+ { label: '鍚敤', value: enabledCount },
+ { label: '杩愯涓�', value: runningCount },
+ { label: '鏈�杩戞垚鍔�', value: successCount },
+ ]}
+ >
+ <AiConsolePanel
+ title="璁″垝鍒楄〃"
+ subtitle="璁″垝鎵ц鏃朵細鑷姩鍐欏叆璇婃柇璁板綍鍜岃皟鐢ㄦ棩蹇楋紱鍋滅敤鍚庝笉浼氱户缁Е鍙戯紝浣嗕粛鍙墜鍔ㄦ墽琛屻��"
+ minHeight={420}
+ >
+ <Grid container spacing={2}>
+ {records.map((record) => (
+ <Grid item xs={12} md={6} xl={4} key={record.id}>
+ <Box sx={aiCardSx(record.status === 1)}>
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#284059' }}>
+ {record.planName || '鏈懡鍚嶈鍒�'}
+ </Typography>
+ <Typography variant="caption" sx={{ color: '#8093a8' }}>
+ {record.sceneCode$} 路 {record.cronExpr}
+ </Typography>
+ </Box>
+ <Stack direction="row" spacing={0.75} flexWrap="wrap" justifyContent="flex-end">
+ <Chip size="small" color={record.status === 1 ? 'success' : 'default'} label={record.status === 1 ? '鍚敤' : '鍋滅敤'} />
+ <Chip size="small" color={record.lastResult === 1 ? 'success' : (record.lastResult === 0 ? 'error' : 'warning')} label={record.lastResult$} />
+ </Stack>
+ </Stack>
+ <Stack direction="row" spacing={2} sx={{ mt: 1.25 }}>
+ <Box>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>浼樺厛妯″瀷</Typography>
+ <Typography variant="body2" sx={{ color: '#31465d' }}>{record.preferredModelCode || '鑷姩璺敱'}</Typography>
+ </Box>
+ <Box>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>涓嬫杩愯</Typography>
+ <Typography variant="body2" sx={{ color: '#31465d' }}>{record.nextRunTime$ || '-'}</Typography>
+ </Box>
+ </Stack>
+ <Box sx={{ mt: 1.25 }}>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>鏈�杩戞秷鎭�</Typography>
+ <Typography variant="body2" sx={{ mt: 0.5, minHeight: 72, color: '#31465d' }}>
+ {record.lastMessage || record.prompt || '鏆傛棤鎵ц璁板綍'}
+ </Typography>
+ </Box>
+ <Stack direction="row" spacing={1} sx={{ mt: 1.5 }} flexWrap="wrap">
+ <RunPlanButton record={record} />
+ <EditButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} />
+ <DeleteButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} mutationMode={OPERATE_MODE} />
+ </Stack>
+ </Box>
+ </Grid>
+ ))}
+ </Grid>
+ <Box sx={{ mt: 2 }}>
+ <Pagination rowsPerPageOptions={[DEFAULT_PAGE_SIZE, 25, 50]} />
+ </Box>
+ </AiConsolePanel>
+ </AiConsoleLayout>
+ );
+};
+
+const AiDiagnosisPlanList = () => {
+ const [createDialog, setCreateDialog] = useState(false);
+
+ return (
+ <Box display="flex" sx={{ width: '100%' }}>
+ <List
+ sx={{ width: '100%', flexGrow: 1 }}
+ title={"menu.aiDiagnosisPlan"}
+ empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
+ filters={filters}
+ sort={{ field: "nextRunTime", order: "asc" }}
+ actions={(
+ <TopToolbar>
+ <FilterButton />
+ <MyCreateButton onClick={() => { setCreateDialog(true) }} />
+ <SelectColumnsButton preferenceKey='aiDiagnosisPlan' />
+ <MyExportButton />
+ </TopToolbar>
+ )}
+ perPage={DEFAULT_PAGE_SIZE}
+ pagination={false}
+ >
+ <PlanBoard />
+ </List>
+ <AiDiagnosisPlanCreate open={createDialog} setOpen={setCreateDialog} />
+ </Box>
+ )
+}
+
+export default AiDiagnosisPlanList;
diff --git a/rsf-admin/src/page/system/aiDiagnosisPlan/index.jsx b/rsf-admin/src/page/system/aiDiagnosisPlan/index.jsx
new file mode 100644
index 0000000..1ede8e8
--- /dev/null
+++ b/rsf-admin/src/page/system/aiDiagnosisPlan/index.jsx
@@ -0,0 +1,13 @@
+import {
+ ShowGuesser,
+} from "react-admin";
+
+import AiDiagnosisPlanList from "./AiDiagnosisPlanList";
+import AiDiagnosisPlanEdit from "./AiDiagnosisPlanEdit";
+
+export default {
+ list: AiDiagnosisPlanList,
+ edit: AiDiagnosisPlanEdit,
+ show: ShowGuesser,
+ recordRepresentation: (record) => `${record.planName || record.cronExpr || ''}`
+};
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountCreate.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountCreate.jsx
new file mode 100644
index 0000000..e00dc17
--- /dev/null
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountCreate.jsx
@@ -0,0 +1,92 @@
+import React from "react";
+import {
+ CreateBase,
+ useTranslate,
+ TextInput,
+ NumberInput,
+ SaveButton,
+ SelectInput,
+ Toolbar,
+ useNotify,
+ Form,
+} from 'react-admin';
+import {
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Grid,
+ Box,
+} from '@mui/material';
+import DialogCloseButton from "@/page/components/DialogCloseButton";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+import MemoInput from "@/page/components/MemoInput";
+
+const transportChoices = [
+ { id: 'INTERNAL', name: '鍐呴儴宸ュ叿闆�' },
+ { id: 'HTTP', name: 'Streamable HTTP' },
+ { id: 'SSE', name: 'SSE MCP' },
+];
+
+const enabledChoices = [
+ { id: 1, name: '鍚敤' },
+ { id: 0, name: '鍋滅敤' },
+];
+
+const AiMcpMountCreate = (props) => {
+ const { open, setOpen } = props;
+ const translate = useTranslate();
+ const notify = useNotify();
+
+ const handleClose = (event, reason) => {
+ if (reason !== "backdropClick") {
+ setOpen(false);
+ }
+ };
+
+ const handleSuccess = async () => {
+ setOpen(false);
+ notify('common.response.success');
+ };
+
+ const handleError = async (error) => {
+ notify(error.message || 'common.response.fail', { type: 'error', messageArgs: { _: error.message } });
+ };
+
+ return (
+ <CreateBase
+ record={{ transportType: 'INTERNAL', enabledFlag: 1, timeoutMs: 10000, status: 1 }}
+ mutationOptions={{ onSuccess: handleSuccess, onError: handleError }}
+ >
+ <Dialog open={open} onClose={handleClose} fullWidth disableRestoreFocus maxWidth="md">
+ <Form>
+ <DialogTitle sx={{ position: 'sticky', top: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+ {translate('create.title')}
+ <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
+ <DialogCloseButton onClose={handleClose} />
+ </Box>
+ </DialogTitle>
+ <DialogContent sx={{ mt: 2 }}>
+ <Grid container rowSpacing={2} columnSpacing={2}>
+ <Grid item xs={6}><TextInput source="name" label="鎸傝浇鍚嶇О" fullWidth /></Grid>
+ <Grid item xs={6}><TextInput source="mountCode" label="鎸傝浇缂栫爜" fullWidth /></Grid>
+ <Grid item xs={6}><SelectInput source="transportType" label="浼犺緭绫诲瀷" choices={transportChoices} fullWidth /></Grid>
+ <Grid item xs={6}><SelectInput source="enabledFlag" label="鍚敤" choices={enabledChoices} fullWidth /></Grid>
+ <Grid item xs={6}><NumberInput source="timeoutMs" label="瓒呮椂姣" fullWidth /></Grid>
+ <Grid item xs={6}><StatusSelectInput fullWidth /></Grid>
+ <Grid item xs={12}><TextInput source="url" label="鍦板潃" fullWidth helperText="鍐呴儴宸ュ叿闆嗕細鑷姩鍐欏叆 /ai/mcp锛涘閮ㄦ寕杞藉彲濉啓杩滅▼ Streamable HTTP 鎴� SSE MCP 鍦板潃" /></Grid>
+ <Grid item xs={12}><MemoInput /></Grid>
+ </Grid>
+ </DialogContent>
+ <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+ <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}>
+ <SaveButton />
+ </Toolbar>
+ </DialogActions>
+ </Form>
+ </Dialog>
+ </CreateBase>
+ )
+}
+
+export default AiMcpMountCreate;
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountEdit.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountEdit.jsx
new file mode 100644
index 0000000..f9337b6
--- /dev/null
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountEdit.jsx
@@ -0,0 +1,74 @@
+import React from "react";
+import {
+ Edit,
+ SimpleForm,
+ TextInput,
+ NumberInput,
+ SaveButton,
+ SelectInput,
+ Toolbar,
+ DeleteButton,
+} from 'react-admin';
+import { Stack, Grid, Typography } from '@mui/material';
+import { EDIT_MODE } from '@/config/setting';
+import EditBaseAside from "@/page/components/EditBaseAside";
+import CustomerTopToolBar from "@/page/components/EditTopToolBar";
+import MemoInput from "@/page/components/MemoInput";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+
+const transportChoices = [
+ { id: 'INTERNAL', name: '鍐呴儴宸ュ叿闆�' },
+ { id: 'HTTP', name: 'Streamable HTTP' },
+ { id: 'SSE', name: 'SSE MCP' },
+];
+
+const enabledChoices = [
+ { id: 1, name: '鍚敤' },
+ { id: 0, name: '鍋滅敤' },
+];
+
+const FormToolbar = () => (
+ <Toolbar sx={{ justifyContent: 'space-between' }}>
+ <SaveButton />
+ <DeleteButton mutationMode="optimistic" />
+ </Toolbar>
+);
+
+const AiMcpMountEdit = () => (
+ <Edit redirect="list" mutationMode={EDIT_MODE} actions={<CustomerTopToolBar />} aside={<EditBaseAside />}>
+ <SimpleForm shouldUnregister warnWhenUnsavedChanges toolbar={<FormToolbar />} mode="onTouched">
+ <Grid container width={{ xs: '100%', xl: '80%' }} rowSpacing={3} columnSpacing={3}>
+ <Grid item xs={12} md={8}>
+ <Typography variant="h6" gutterBottom>涓昏</Typography>
+ <Stack direction='row' gap={2}>
+ <TextInput source="name" label="鎸傝浇鍚嶇О" fullWidth />
+ <TextInput source="mountCode" label="鎸傝浇缂栫爜" fullWidth />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <SelectInput source="transportType" label="浼犺緭绫诲瀷" choices={transportChoices} fullWidth />
+ <SelectInput source="enabledFlag" label="鍚敤" choices={enabledChoices} fullWidth />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="url" label="鍦板潃" fullWidth />
+ <NumberInput source="timeoutMs" label="瓒呮椂姣" fullWidth />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="lastTestResult$" label="鏈�杩戞祴璇曠粨鏋�" disabled fullWidth />
+ <TextInput source="lastTestTime$" label="鏈�杩戞祴璇曟椂闂�" disabled fullWidth />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="lastToolCount" label="鏈�杩戝伐鍏锋暟" disabled fullWidth />
+ <TextInput source="lastTestMessage" label="鏈�杩戞祴璇曟秷鎭�" disabled fullWidth multiline minRows={2} />
+ </Stack>
+ </Grid>
+ <Grid item xs={12} md={4}>
+ <Typography variant="h6" gutterBottom>閫氱敤</Typography>
+ <StatusSelectInput />
+ <MemoInput />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ </Edit>
+)
+
+export default AiMcpMountEdit;
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx
new file mode 100644
index 0000000..aa55328
--- /dev/null
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx
@@ -0,0 +1,618 @@
+import React, { useEffect, useMemo, useState } from "react";
+import {
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
+ Alert,
+ Box,
+ Button,
+ Chip,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ FormControl,
+ Grid,
+ InputLabel,
+ MenuItem,
+ Paper,
+ Select,
+ Stack,
+ Switch,
+ TextField,
+ Typography,
+} from "@mui/material";
+import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
+import AddIcon from "@mui/icons-material/Add";
+import EditIcon from "@mui/icons-material/Edit";
+import StorageIcon from "@mui/icons-material/Storage";
+import HubIcon from "@mui/icons-material/Hub";
+import BuildIcon from "@mui/icons-material/Build";
+import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
+import { useNotify } from "react-admin";
+import request from "@/utils/request";
+import EmptyData from "@/page/components/EmptyData";
+import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
+
+const usageChoices = [
+ { id: "DIAGNOSE_ONLY", name: "浠呰瘖鏂娇鐢�" },
+ { id: "CHAT_AND_DIAGNOSE", name: "鑱婂ぉ涓庤瘖鏂兘鍙敤" },
+ { id: "DISABLED", name: "绂佺敤" },
+];
+
+const transportChoices = [
+ { id: "AUTO", name: "鑷姩璇嗗埆" },
+ { id: "HTTP", name: "Streamable HTTP" },
+ { id: "SSE", name: "SSE" },
+];
+
+const authChoices = [
+ { id: "NONE", name: "鏃犺璇�" },
+ { id: "BEARER", name: "Bearer Token" },
+ { id: "API_KEY", name: "X-API-Key" },
+];
+
+const defaultServiceForm = {
+ id: null,
+ name: "",
+ url: "",
+ transportType: "AUTO",
+ authType: "NONE",
+ authValue: "",
+ usageScope: "DIAGNOSE_ONLY",
+ timeoutMs: 10000,
+ enabledFlag: 1,
+ memo: "",
+};
+
+const usageLabel = (usageScope) => {
+ const matched = usageChoices.find((item) => item.id === usageScope);
+ return matched ? matched.name : "浠呰瘖鏂娇鐢�";
+};
+
+const transportLabel = (transportType) => {
+ const matched = transportChoices.find((item) => item.id === transportType);
+ return matched ? matched.name : transportType || "鑷姩璇嗗埆";
+};
+
+const BuiltInToolCard = ({ tool, onSave }) => {
+ const notify = useNotify();
+ const [usageScope, setUsageScope] = useState(tool.usageScope || "DIAGNOSE_ONLY");
+ const [priority, setPriority] = useState(tool.priority || 10);
+ const [toolPrompt, setToolPrompt] = useState(tool.toolPrompt || "");
+ const [saving, setSaving] = useState(false);
+
+ useEffect(() => {
+ setUsageScope(tool.usageScope || "DIAGNOSE_ONLY");
+ setPriority(tool.priority || 10);
+ setToolPrompt(tool.toolPrompt || "");
+ }, [tool]);
+
+ const handleSave = async () => {
+ try {
+ setSaving(true);
+ const { data: res } = await request.post("/ai/mcp/console/builtin-tool/save", {
+ toolCode: tool.toolCode,
+ toolName: tool.toolName,
+ priority: priority || 10,
+ toolPrompt,
+ usageScope,
+ });
+ if (res?.code !== 200) {
+ throw new Error(res?.msg || "淇濆瓨澶辫触");
+ }
+ notify("鍐呯疆宸ュ叿绛栫暐宸叉洿鏂�");
+ onSave?.(res?.data || []);
+ } catch (error) {
+ notify(error.message || "淇濆瓨澶辫触", { type: "error" });
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const enabled = usageScope !== "DISABLED";
+
+ return (
+ <Box sx={aiCardSx(enabled)}>
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: "#284059" }}>
+ {tool.toolName}
+ </Typography>
+ <Typography variant="caption" sx={{ color: "#8093a8" }}>
+ {tool.toolCode}
+ </Typography>
+ </Box>
+ <Stack direction="row" spacing={0.75} flexWrap="wrap" justifyContent="flex-end">
+ <Chip size="small" color={enabled ? "success" : "default"} label={enabled ? "鍚敤" : "鍋滅敤"} />
+ <Chip size="small" color="primary" label={usageLabel(usageScope)} />
+ </Stack>
+ </Stack>
+ <Typography variant="body2" sx={{ mt: 1.5, minHeight: 44, color: "#31465d" }}>
+ {tool.description || "绯荤粺鍐呯疆宸ュ叿锛岄粯璁ょ敱骞冲彴鎵樼銆�"}
+ </Typography>
+ <FormControl size="small" fullWidth sx={{ mt: 1.5 }}>
+ <InputLabel>鐢ㄩ�旈璁�</InputLabel>
+ <Select value={usageScope} label="鐢ㄩ�旈璁�" onChange={(event) => setUsageScope(event.target.value)}>
+ {usageChoices.map((item) => (
+ <MenuItem key={item.id} value={item.id}>{item.name}</MenuItem>
+ ))}
+ </Select>
+ </FormControl>
+ <Accordion elevation={0} disableGutters sx={{ mt: 1.5, borderRadius: 2, border: "1px solid #dbe5f1" }}>
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ <Typography variant="body2" sx={{ fontWeight: 600 }}>楂樼骇璁剧疆</Typography>
+ </AccordionSummary>
+ <AccordionDetails>
+ <Stack spacing={1.5}>
+ <TextField
+ label="鎵ц浼樺厛绾�"
+ size="small"
+ type="number"
+ value={priority}
+ onChange={(event) => setPriority(Number(event.target.value || 10))}
+ />
+ <TextField
+ label="闄勫姞瑙勫垯"
+ size="small"
+ value={toolPrompt}
+ multiline
+ minRows={3}
+ onChange={(event) => setToolPrompt(event.target.value)}
+ helperText="杩欓噷鍙湪闇�瑕佽鐩栭粯璁ゅ伐鍏疯鏄庢椂濉啓銆�"
+ />
+ </Stack>
+ </AccordionDetails>
+ </Accordion>
+ <Stack direction="row" justifyContent="flex-end" sx={{ mt: 1.5 }}>
+ <Button variant="contained" size="small" onClick={handleSave} disabled={saving}>
+ {saving ? "淇濆瓨涓�..." : "淇濆瓨"}
+ </Button>
+ </Stack>
+ </Box>
+ );
+};
+
+const ServiceDialog = ({ open, record, onClose, onSaved }) => {
+ const notify = useNotify();
+ const [form, setForm] = useState(defaultServiceForm);
+ const [saving, setSaving] = useState(false);
+
+ useEffect(() => {
+ if (open) {
+ setForm(record ? {
+ ...defaultServiceForm,
+ ...record,
+ authType: record.authType || "NONE",
+ transportType: record.transportType || "AUTO",
+ usageScope: record.usageScope || "DIAGNOSE_ONLY",
+ timeoutMs: record.timeoutMs || 10000,
+ } : defaultServiceForm);
+ }
+ }, [open, record]);
+
+ const handleChange = (field, value) => {
+ setForm((prev) => ({ ...prev, [field]: value }));
+ };
+
+ const handleSave = async () => {
+ try {
+ setSaving(true);
+ const payload = { ...form };
+ if (payload.authType === "NONE") {
+ payload.authValue = "";
+ }
+ const { data: res } = await request.post("/ai/mcp/console/service/save", payload);
+ if (res?.code !== 200) {
+ throw new Error(res?.msg || "淇濆瓨澶辫触");
+ }
+ notify("澶栭儴 MCP 鏈嶅姟宸蹭繚瀛�");
+ onSaved?.(res?.data);
+ } catch (error) {
+ notify(error.message || "淇濆瓨澶辫触", { type: "error" });
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ return (
+ <Dialog open={open} onClose={onClose} fullWidth maxWidth="md">
+ <DialogTitle>{form.id ? "缂栬緫澶栭儴 MCP 鏈嶅姟" : "鏂板澶栭儴 MCP 鏈嶅姟"}</DialogTitle>
+ <DialogContent dividers>
+ <Grid container spacing={2} sx={{ mt: 0.25 }}>
+ <Grid item xs={12} md={6}>
+ <TextField label="鏈嶅姟鍚嶇О" fullWidth size="small" value={form.name} onChange={(event) => handleChange("name", event.target.value)} />
+ </Grid>
+ <Grid item xs={12} md={6}>
+ <FormControl fullWidth size="small">
+ <InputLabel>杩炴帴鏂瑰紡</InputLabel>
+ <Select value={form.transportType} label="杩炴帴鏂瑰紡" onChange={(event) => handleChange("transportType", event.target.value)}>
+ {transportChoices.map((item) => (
+ <MenuItem key={item.id} value={item.id}>{item.name}</MenuItem>
+ ))}
+ </Select>
+ </FormControl>
+ </Grid>
+ <Grid item xs={12}>
+ <TextField
+ label="鏈嶅姟鍦板潃"
+ fullWidth
+ size="small"
+ value={form.url}
+ onChange={(event) => handleChange("url", event.target.value)}
+ helperText="鐩存帴濉啓杩滅▼ MCP 鏈嶅姟鍦板潃锛涚郴缁熶細浼樺厛鎸夎嚜鍔ㄨ瘑鍒崗璁繛鎺ャ��"
+ />
+ </Grid>
+ <Grid item xs={12} md={4}>
+ <FormControl fullWidth size="small">
+ <InputLabel>璁よ瘉鏂瑰紡</InputLabel>
+ <Select value={form.authType} label="璁よ瘉鏂瑰紡" onChange={(event) => handleChange("authType", event.target.value)}>
+ {authChoices.map((item) => (
+ <MenuItem key={item.id} value={item.id}>{item.name}</MenuItem>
+ ))}
+ </Select>
+ </FormControl>
+ </Grid>
+ <Grid item xs={12} md={8}>
+ <TextField
+ label="璁よ瘉淇℃伅"
+ fullWidth
+ size="small"
+ type={form.authType === "NONE" ? "text" : "password"}
+ value={form.authValue}
+ disabled={form.authType === "NONE"}
+ onChange={(event) => handleChange("authValue", event.target.value)}
+ />
+ </Grid>
+ <Grid item xs={12} md={6}>
+ <FormControl fullWidth size="small">
+ <InputLabel>鐢ㄩ�旈璁�</InputLabel>
+ <Select value={form.usageScope} label="鐢ㄩ�旈璁�" onChange={(event) => handleChange("usageScope", event.target.value)}>
+ {usageChoices.filter((item) => item.id !== "DISABLED").map((item) => (
+ <MenuItem key={item.id} value={item.id}>{item.name}</MenuItem>
+ ))}
+ </Select>
+ </FormControl>
+ </Grid>
+ <Grid item xs={12} md={3}>
+ <TextField
+ label="瓒呮椂姣"
+ type="number"
+ size="small"
+ fullWidth
+ value={form.timeoutMs}
+ onChange={(event) => handleChange("timeoutMs", Number(event.target.value || 10000))}
+ />
+ </Grid>
+ <Grid item xs={12} md={3}>
+ <Stack direction="row" alignItems="center" spacing={1} sx={{ height: "100%" }}>
+ <Switch checked={Number(form.enabledFlag) === 1} onChange={(event) => handleChange("enabledFlag", event.target.checked ? 1 : 0)} />
+ <Typography variant="body2">鍚敤鏈嶅姟</Typography>
+ </Stack>
+ </Grid>
+ <Grid item xs={12}>
+ <TextField
+ label="澶囨敞"
+ fullWidth
+ size="small"
+ value={form.memo}
+ multiline
+ minRows={2}
+ onChange={(event) => handleChange("memo", event.target.value)}
+ />
+ </Grid>
+ </Grid>
+ </DialogContent>
+ <DialogActions>
+ <Button onClick={onClose}>鍙栨秷</Button>
+ <Button variant="contained" onClick={handleSave} disabled={saving}>{saving ? "淇濆瓨涓�..." : "淇濆瓨"}</Button>
+ </DialogActions>
+ </Dialog>
+ );
+};
+
+const ExternalServiceCard = ({ service, onEdit, onTest, onInspect, onRemove }) => (
+ <Box sx={aiCardSx(service.enabledFlag === 1)}>
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: "#284059" }}>
+ {service.name}
+ </Typography>
+ <Typography variant="caption" sx={{ color: "#8093a8" }}>
+ {transportLabel(service.transportType)} 路 {usageLabel(service.usageScope)}
+ </Typography>
+ </Box>
+ <Stack direction="row" spacing={0.75} flexWrap="wrap" justifyContent="flex-end">
+ <Chip size="small" color={service.enabledFlag === 1 ? "success" : "default"} label={service.enabledFlag === 1 ? "鍚敤" : "鍋滅敤"} />
+ <Chip size="small" color={service.lastTestResult === 1 ? "success" : "default"} label={service.lastTestResult$ || "鏈祴璇�"} />
+ </Stack>
+ </Stack>
+ <Typography variant="body2" sx={{ mt: 1.25, color: "#31465d", minHeight: 42 }}>
+ {service.url}
+ </Typography>
+ {service.lastTestMessage ? (
+ <Alert severity={service.lastTestResult === 1 ? "success" : "warning"} sx={{ mt: 1.25 }}>
+ {service.lastTestMessage}
+ </Alert>
+ ) : null}
+ <Typography variant="caption" display="block" sx={{ mt: 1.25, color: "#70839a" }}>
+ {service.lastTestTime ? `鏈�杩戞祴璇�: ${service.lastTestTime}` : "鏈�杩戞祴璇�: -"}
+ </Typography>
+ <Typography variant="caption" display="block" sx={{ color: "#70839a" }}>
+ {service.lastToolCount !== null && service.lastToolCount !== undefined ? `宸插彂鐜板伐鍏�: ${service.lastToolCount}` : "宸插彂鐜板伐鍏�: -"}
+ </Typography>
+ <Stack direction="row" spacing={1} flexWrap="wrap" sx={{ mt: 1.5 }}>
+ <Button size="small" variant="outlined" onClick={() => onInspect(service)}>鏌ョ湅宸ュ叿</Button>
+ <Button size="small" variant="outlined" onClick={() => onTest(service)}>杩炴帴娴嬭瘯</Button>
+ <Button size="small" startIcon={<EditIcon />} onClick={() => onEdit(service)}>缂栬緫</Button>
+ <Button size="small" color="error" startIcon={<DeleteOutlineIcon />} onClick={() => onRemove(service)}>鍒犻櫎</Button>
+ </Stack>
+ </Box>
+);
+
+const ExternalToolsPanel = ({ selectedService, tools, preview, onPreview }) => (
+ <Grid container spacing={2}>
+ <Grid item xs={12} lg={7}>
+ <AiConsolePanel
+ title="澶栭儴鏈嶅姟宸ュ叿鐩綍"
+ subtitle={selectedService ? `褰撳墠鏈嶅姟锛�${selectedService.name}` : "閫変腑浠讳竴澶栭儴鏈嶅姟鍚庯紝浼氬睍绀烘渶鏂板彂鐜扮殑宸ュ叿鐩綍銆�"}
+ minHeight={320}
+ >
+ <Box sx={{ display: "grid", gap: 1.5 }}>
+ {tools.map((tool) => (
+ <Paper key={tool.mcpToolName} variant="outlined" sx={{ p: 1.5, borderRadius: 2 }}>
+ <Stack direction="row" justifyContent="space-between" spacing={1}>
+ <Box>
+ <Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
+ {tool.toolName || tool.mcpToolName}
+ </Typography>
+ <Typography variant="caption" color="text.secondary">
+ {tool.mcpToolName}
+ </Typography>
+ </Box>
+ <Button size="small" variant="text" onClick={() => onPreview(tool)}>鎵ц棰勮</Button>
+ </Stack>
+ <Typography variant="body2" sx={{ mt: 1 }}>
+ {tool.description || tool.toolPrompt || "鏆傛棤璇存槑"}
+ </Typography>
+ </Paper>
+ ))}
+ {!tools.length ? <EmptyData /> : null}
+ </Box>
+ </AiConsolePanel>
+ </Grid>
+ <Grid item xs={12} lg={5}>
+ <AiConsolePanel
+ title="宸ュ叿棰勮"
+ subtitle="鐢ㄤ簬蹇�熺‘璁よ繙绋嬪伐鍏锋槸鍚︾湡鐨勮兘杩斿洖缁撴灉銆�"
+ minHeight={320}
+ >
+ {preview ? (
+ <Box>
+ <Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
+ {preview.toolName || preview.mcpToolName || preview.toolCode}
+ </Typography>
+ <Typography variant="caption" color="text.secondary">
+ {preview.severity || "INFO"}
+ </Typography>
+ <Typography variant="body2" sx={{ mt: 1.5, whiteSpace: "pre-wrap" }}>
+ {preview.summaryText || "鏆傛棤鎽樿"}
+ </Typography>
+ </Box>
+ ) : (
+ <EmptyData />
+ )}
+ </AiConsolePanel>
+ </Grid>
+ </Grid>
+);
+
+const AiMcpMountList = () => {
+ const notify = useNotify();
+ const [overview, setOverview] = useState({ builtInMount: null, builtInTools: [], externalServices: [] });
+ const [serviceDialogOpen, setServiceDialogOpen] = useState(false);
+ const [editingService, setEditingService] = useState(null);
+ const [selectedService, setSelectedService] = useState(null);
+ const [serviceTools, setServiceTools] = useState([]);
+ const [preview, setPreview] = useState(null);
+
+ const loadOverview = async () => {
+ try {
+ const { data: res } = await request.get("/ai/mcp/console/overview");
+ if (res?.code !== 200) {
+ throw new Error(res?.msg || "鍔犺浇澶辫触");
+ }
+ setOverview(res.data || { builtInMount: null, builtInTools: [], externalServices: [] });
+ } catch (error) {
+ notify(error.message || "鍔犺浇澶辫触", { type: "error" });
+ }
+ };
+
+ useEffect(() => {
+ loadOverview();
+ }, []);
+
+ const stats = useMemo(() => {
+ const builtInEnabled = (overview.builtInTools || []).filter((item) => item.usageScope !== "DISABLED").length;
+ const externalEnabled = (overview.externalServices || []).filter((item) => item.enabledFlag === 1).length;
+ const successCount = (overview.externalServices || []).filter((item) => item.lastTestResult === 1).length;
+ const discoveredTools = (overview.externalServices || []).reduce((sum, item) => sum + (item.lastToolCount || 0), 0);
+ return [
+ { label: "鍐呯疆宸ュ叿鍚敤", value: builtInEnabled },
+ { label: "澶栭儴鏈嶅姟", value: (overview.externalServices || []).length },
+ { label: "鏈�杩戞祴璇曟垚鍔�", value: successCount },
+ { label: "鍙戠幇澶栭儴宸ュ叿", value: discoveredTools },
+ ];
+ }, [overview]);
+
+ const handleBuiltInSaved = (tools) => {
+ setOverview((prev) => ({ ...prev, builtInTools: tools }));
+ };
+
+ const handleServiceSaved = async () => {
+ setServiceDialogOpen(false);
+ setEditingService(null);
+ await loadOverview();
+ };
+
+ const handleTestService = async (service) => {
+ try {
+ const { data: res } = await request.post("/ai/mcp/console/service/test", { id: service.id }, { timeout: (service.timeoutMs || 10000) + 5000 });
+ if (res?.code !== 200) {
+ throw new Error(res?.msg || "娴嬭瘯澶辫触");
+ }
+ notify(res?.data?.message || "杩炴帴娴嬭瘯瀹屾垚");
+ await loadOverview();
+ setSelectedService(res?.data?.service || service);
+ setServiceTools(res?.data?.tools || []);
+ setPreview(null);
+ } catch (error) {
+ notify(error.message || "娴嬭瘯澶辫触", { type: "error" });
+ }
+ };
+
+ const handleInspectService = async (service) => {
+ try {
+ const { data: res } = await request.get("/ai/mcp/mount/toolList", { params: { mountId: service.id } });
+ if (res?.code !== 200) {
+ throw new Error(res?.msg || "鍔犺浇宸ュ叿澶辫触");
+ }
+ setSelectedService(service);
+ setServiceTools(res.data || []);
+ setPreview(null);
+ } catch (error) {
+ notify(error.message || "鍔犺浇宸ュ叿澶辫触", { type: "error" });
+ }
+ };
+
+ const handlePreviewTool = async (tool) => {
+ try {
+ const { data: res } = await request.post("/ai/mcp/mount/toolPreview", {
+ mountCode: tool.mountCode,
+ toolCode: tool.toolCode,
+ sceneCode: tool.sceneCode,
+ question: "璇疯繑鍥炶宸ュ叿褰撳墠鐨勬憳瑕侀瑙堢粨鏋�",
+ });
+ if (res?.code !== 200) {
+ throw new Error(res?.msg || "棰勮澶辫触");
+ }
+ setPreview(res.data);
+ } catch (error) {
+ notify(error.message || "棰勮澶辫触", { type: "error" });
+ }
+ };
+
+ const handleRemoveService = async (service) => {
+ try {
+ const { data: res } = await request.post(`/ai/mcp/console/service/remove/${service.id}`);
+ if (res?.code !== 200) {
+ throw new Error(res?.msg || "鍒犻櫎澶辫触");
+ }
+ notify("鏈嶅姟宸插垹闄�");
+ if (selectedService?.id === service.id) {
+ setSelectedService(null);
+ setServiceTools([]);
+ setPreview(null);
+ }
+ await loadOverview();
+ } catch (error) {
+ notify(error.message || "鍒犻櫎澶辫触", { type: "error" });
+ }
+ };
+
+ return (
+ <Box sx={{ width: "100%" }}>
+ <AiConsoleLayout
+ title="MCP涓績"
+ subtitle="鍐呯疆宸ュ叿鐢辩郴缁熻嚜鍔ㄦ墭绠★紱澶栭儴 MCP 鏈嶅姟鍙繚鐣欐帴鍏ュ湴鍧�銆佽璇佸拰鐢ㄩ�旈璁撅紝澶嶆潅鍗忚缁嗚妭鐢辩郴缁熻嚜鍔ㄥ鐞嗐��"
+ stats={stats}
+ >
+ <AiConsolePanel
+ title="鍐呯疆宸ュ叿"
+ subtitle="杩欎簺宸ュ叿鐩存帴杩炴帴褰撳墠 WMS 鍐呴儴鏁版嵁锛岄粯璁ゅ紑绠卞嵆鐢紝涓嶉渶瑕佹墜鍔ㄩ厤缃寕杞藉湴鍧�鎴栧崗璁��"
+ action={(
+ <Chip
+ icon={<StorageIcon />}
+ label={overview.builtInMount ? "绯荤粺鎵樼" : "寰呭垵濮嬪寲"}
+ color={overview.builtInMount ? "success" : "default"}
+ variant="outlined"
+ />
+ )}
+ minHeight={260}
+ >
+ <Grid container spacing={2}>
+ {(overview.builtInTools || []).map((tool) => (
+ <Grid item xs={12} md={6} xl={4} key={tool.toolCode}>
+ <BuiltInToolCard tool={tool} onSave={handleBuiltInSaved} />
+ </Grid>
+ ))}
+ {!overview?.builtInTools?.length ? <EmptyData /> : null}
+ </Grid>
+ </AiConsolePanel>
+
+ <Box sx={{ mt: 1.5 }}>
+ <AiConsolePanel
+ title="澶栭儴 MCP 鏈嶅姟"
+ subtitle="榛樿鍙渶瑕佸綍鍏ユ湇鍔″悕绉般�佸湴鍧�鍜岀敤閫斻�傜郴缁熶細浼樺厛鑷姩璇嗗埆鍗忚锛岃繛鎺ユ垚鍔熷悗鍐嶅睍绀哄彂鐜板埌鐨勫伐鍏枫��"
+ action={(
+ <Button variant="contained" startIcon={<AddIcon />} onClick={() => { setEditingService(null); setServiceDialogOpen(true); }}>
+ 鏂板鏈嶅姟
+ </Button>
+ )}
+ minHeight={280}
+ >
+ <Grid container spacing={2}>
+ {(overview.externalServices || []).map((service) => (
+ <Grid item xs={12} md={6} xl={4} key={service.id}>
+ <ExternalServiceCard
+ service={service}
+ onEdit={(item) => { setEditingService(item); setServiceDialogOpen(true); }}
+ onTest={handleTestService}
+ onInspect={handleInspectService}
+ onRemove={handleRemoveService}
+ />
+ </Grid>
+ ))}
+ {!overview?.externalServices?.length ? <EmptyData /> : null}
+ </Grid>
+ </AiConsolePanel>
+ </Box>
+
+ <Box sx={{ mt: 1.5 }}>
+ <AiConsolePanel
+ title="宸ュ叿浣跨敤绛栫暐"
+ subtitle="鍐呯疆宸ュ叿鎸夌敤閫旈璁捐嚜鍔ㄥ弬涓庤亰澶╂垨璇婃柇锛涘閮ㄦ湇鍔″湪杩炴帴鎴愬姛鍚庡彲浠ユ煡鐪嬪叾瀹為檯宸ュ叿鐩綍骞跺仛璋冪敤棰勮銆�"
+ minHeight={120}
+ >
+ <Stack direction="row" spacing={1} flexWrap="wrap">
+ <Chip icon={<BuildIcon />} label="鍐呯疆宸ュ叿榛樿鑷姩鍙備笌璇婃柇" color="primary" variant="outlined" />
+ <Chip icon={<HubIcon />} label="澶栭儴鏈嶅姟榛樿鎸夌敤閫旈璁惧弬涓庤繍琛屾椂宸ュ叿閫夋嫨" color="primary" variant="outlined" />
+ <Chip label="楂樼骇瑙勫垯宸叉姌鍙犲埌鍚勫崱鐗囧唴閮�" variant="outlined" />
+ </Stack>
+ </AiConsolePanel>
+ </Box>
+
+ <Box sx={{ mt: 1.5 }}>
+ <ExternalToolsPanel
+ selectedService={selectedService}
+ tools={serviceTools}
+ preview={preview}
+ onPreview={handlePreviewTool}
+ />
+ </Box>
+ </AiConsoleLayout>
+
+ <ServiceDialog
+ open={serviceDialogOpen}
+ record={editingService}
+ onClose={() => {
+ setServiceDialogOpen(false);
+ setEditingService(null);
+ }}
+ onSaved={handleServiceSaved}
+ />
+ </Box>
+ );
+};
+
+export default AiMcpMountList;
diff --git a/rsf-admin/src/page/system/aiMcpMount/index.jsx b/rsf-admin/src/page/system/aiMcpMount/index.jsx
new file mode 100644
index 0000000..5a75c39
--- /dev/null
+++ b/rsf-admin/src/page/system/aiMcpMount/index.jsx
@@ -0,0 +1,13 @@
+import {
+ ShowGuesser,
+} from "react-admin";
+
+import AiMcpMountList from "./AiMcpMountList";
+import AiMcpMountEdit from "./AiMcpMountEdit";
+
+export default {
+ list: AiMcpMountList,
+ edit: AiMcpMountEdit,
+ show: ShowGuesser,
+ recordRepresentation: (record) => `${record.name || record.mountCode || ''}`
+};
diff --git a/rsf-admin/src/page/system/aiParam/AiParamList.jsx b/rsf-admin/src/page/system/aiParam/AiParamList.jsx
index 8f12399..41fb75c 100644
--- a/rsf-admin/src/page/system/aiParam/AiParamList.jsx
+++ b/rsf-admin/src/page/system/aiParam/AiParamList.jsx
@@ -1,42 +1,25 @@
import React, { useState } from "react";
import {
List,
- DatagridConfigurable,
SearchInput,
TopToolbar,
SelectColumnsButton,
- EditButton,
FilterButton,
- BulkDeleteButton,
- WrapperField,
- TextField,
- NumberField,
- DateField,
- BooleanField,
TextInput,
DateInput,
SelectInput,
+ useListContext,
+ Pagination,
+ EditButton,
DeleteButton,
} from 'react-admin';
-import { Box } from '@mui/material';
-import { styled } from '@mui/material/styles';
+import { Box, Chip, Grid, Stack, Typography } from '@mui/material';
import EmptyData from "@/page/components/EmptyData";
import MyCreateButton from "@/page/components/MyCreateButton";
import MyExportButton from '@/page/components/MyExportButton';
import { OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
import AiParamCreate from "./AiParamCreate";
-
-const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
- '& .css-1vooibu-MuiSvgIcon-root': {
- height: '.9em'
- },
- '& .RaDatagrid-row': {
- cursor: 'auto'
- },
- '& .opt': {
- width: 200
- },
-}));
+import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
const filters = [
<SearchInput source="condition" alwaysOn />,
@@ -63,14 +46,123 @@
{ id: '0', name: 'common.enums.statusFalse' },
]}
/>,
-]
+];
+
+const providerLabel = (provider) => {
+ if (provider === 'openai') {
+ return 'OpenAI Compatible';
+ }
+ if (provider === 'mock') {
+ return 'Mock';
+ }
+ return provider || '鏈厤缃�';
+};
+
+const AiParamBoard = () => {
+ const { data, isLoading } = useListContext();
+ const records = data || [];
+ const enabledCount = records.filter((item) => item.status === 1).length;
+ const defaultCount = records.filter((item) => item.defaultFlag === 1).length;
+ const openaiCount = records.filter((item) => item.provider === 'openai').length;
+ const mockCount = records.filter((item) => item.provider === 'mock').length;
+
+ if (!isLoading && !records.length) {
+ return <EmptyData />;
+ }
+
+ return (
+ <AiConsoleLayout
+ title="AI鍙傛暟"
+ subtitle="淇濇寔褰撳墠绯荤粺鍚庡彴鐨勭櫧搴曞崱鐗囧拰杞昏竟妗嗛鏍硷紝鐢ㄥ崱鐗囨柟寮忓睍绀烘ā鍨嬮厤缃鍐碉紝鏂逛究鍜屽叾浠� AI 椤甸潰缁熶竴鏌ョ湅銆�"
+ stats={[
+ { label: '鍙傛暟鎬绘暟', value: records.length },
+ { label: '鍚敤', value: enabledCount },
+ { label: '榛樿妯″瀷', value: defaultCount },
+ { label: 'OpenAI / Mock', value: `${openaiCount} / ${mockCount}` },
+ ]}
+ >
+ <AiConsolePanel
+ title="妯″瀷閰嶇疆"
+ subtitle="灞曠ず妯″瀷缂栫爜銆佷緵搴斿晢銆佷笂涓嬫枃杞暟鍜岄粯璁ょ姸鎬侊紱鍒涘缓銆佺紪杈戙�佸垹闄や粛娌跨敤鍘熺郴缁熺殑寮圭獥涓庣紪杈戦〉銆�"
+ minHeight={460}
+ >
+ <Box
+ sx={{
+ display: 'grid',
+ gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
+ gap: 2,
+ }}
+ >
+ {records.map((record) => (
+ <Box key={record.id}>
+ <Box sx={aiCardSx(record.defaultFlag === 1)}>
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
+ {record.name || record.modelCode || record.uuid}
+ </Typography>
+ <Typography variant="caption" color="text.secondary">
+ {record.modelCode || '鏈~鍐欐ā鍨嬬紪鐮�'}
+ </Typography>
+ </Box>
+ <Stack direction="row" spacing={0.75} flexWrap="wrap" justifyContent="flex-end">
+ <Chip size="small" color={record.status === 1 ? 'success' : 'default'} label={record.status === 1 ? '鍚敤' : '鍋滅敤'} />
+ {record.defaultFlag === 1 ? <Chip size="small" color="primary" label="榛樿" /> : null}
+ </Stack>
+ </Stack>
+ <Grid container spacing={1.25} sx={{ mt: 1 }}>
+ <Grid item xs={6}>
+ <Typography variant="caption" color="text.secondary">渚涘簲鍟�</Typography>
+ <Typography variant="body2" sx={{ mt: 0.25 }}>{providerLabel(record.provider)}</Typography>
+ </Grid>
+ <Grid item xs={6}>
+ <Typography variant="caption" color="text.secondary">妯″瀷鍚�</Typography>
+ <Typography variant="body2" sx={{ mt: 0.25 }}>{record.modelName || '-'}</Typography>
+ </Grid>
+ <Grid item xs={6}>
+ <Typography variant="caption" color="text.secondary">涓婁笅鏂囪疆鏁�</Typography>
+ <Typography variant="body2" sx={{ mt: 0.25 }}>{record.maxContextMessages || 0}</Typography>
+ </Grid>
+ <Grid item xs={6}>
+ <Typography variant="caption" color="text.secondary">鎺掑簭</Typography>
+ <Typography variant="body2" sx={{ mt: 0.25 }}>{record.sort || 0}</Typography>
+ </Grid>
+ <Grid item xs={12}>
+ <Typography variant="caption" color="text.secondary">鑱婂ぉ鍦板潃</Typography>
+ <Typography variant="body2" sx={{ mt: 0.25, wordBreak: 'break-all' }}>
+ {record.chatUrl || '鏈厤缃�'}
+ </Typography>
+ </Grid>
+ <Grid item xs={12}>
+ <Typography variant="caption" color="text.secondary">澶囨敞</Typography>
+ <Typography variant="body2" sx={{ mt: 0.25, minHeight: 42 }}>
+ {record.memo || '鏈~鍐欏娉�'}
+ </Typography>
+ </Grid>
+ </Grid>
+ <Stack direction="row" spacing={1} sx={{ mt: 1.5 }}>
+ <EditButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} />
+ <DeleteButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} mutationMode={OPERATE_MODE} />
+ </Stack>
+ </Box>
+ </Box>
+ ))}
+ </Box>
+ <Box sx={{ mt: 2 }}>
+ <Pagination rowsPerPageOptions={[DEFAULT_PAGE_SIZE, 25, 50]} />
+ </Box>
+ </AiConsolePanel>
+ </AiConsoleLayout>
+ );
+};
const AiParamList = () => {
const [createDialog, setCreateDialog] = useState(false);
return (
- <Box display="flex">
+ <Box display="flex" sx={{ width: '100%' }}>
<List
+ sx={{ width: '100%', flexGrow: 1 }}
title={"menu.aiParam"}
empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
filters={filters}
@@ -84,30 +176,9 @@
</TopToolbar>
)}
perPage={DEFAULT_PAGE_SIZE}
+ pagination={false}
>
- <StyledDatagrid
- preferenceKey='aiParam'
- bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
- rowClick={false}
- omit={['id', 'createTime', 'memo', 'statusBool', 'defaultFlagBool']}
- >
- <NumberField source="id" />
- <TextField source="uuid" label="table.field.aiParam.uuid" />
- <TextField source="name" label="table.field.aiParam.name" />
- <TextField source="modelCode" label="table.field.aiParam.modelCode" />
- <TextField source="provider" label="table.field.aiParam.provider" />
- <TextField source="modelName" label="table.field.aiParam.modelName" />
- <NumberField source="maxContextMessages" label="table.field.aiParam.maxContextMessages" />
- <NumberField source="sort" label="table.field.aiParam.sort" />
- <BooleanField source="defaultFlagBool" label="table.field.aiParam.defaultFlag" sortable={false} />
- <BooleanField source="statusBool" label="common.field.status" sortable={false} />
- <DateField source="updateTime" label="common.field.updateTime" showTime />
- <TextField source="memo" label="common.field.memo" sortable={false} />
- <WrapperField cellClassName="opt" label="common.field.opt">
- <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
- <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
- </WrapperField>
- </StyledDatagrid>
+ <AiParamBoard />
</List>
<AiParamCreate
open={createDialog}
diff --git a/rsf-admin/src/page/system/aiPrompt/AiPromptCreate.jsx b/rsf-admin/src/page/system/aiPrompt/AiPromptCreate.jsx
new file mode 100644
index 0000000..1a4d34e
--- /dev/null
+++ b/rsf-admin/src/page/system/aiPrompt/AiPromptCreate.jsx
@@ -0,0 +1,87 @@
+import React from "react";
+import {
+ CreateBase,
+ useTranslate,
+ TextInput,
+ NumberInput,
+ SaveButton,
+ SelectInput,
+ Toolbar,
+ useNotify,
+ Form,
+} from 'react-admin';
+import {
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Stack,
+ Grid,
+ Box,
+} from '@mui/material';
+import DialogCloseButton from "@/page/components/DialogCloseButton";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+import MemoInput from "@/page/components/MemoInput";
+
+const sceneChoices = [
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+];
+
+const AiPromptCreate = (props) => {
+ const { open, setOpen } = props;
+ const translate = useTranslate();
+ const notify = useNotify();
+
+ const handleClose = (event, reason) => {
+ if (reason !== "backdropClick") {
+ setOpen(false);
+ }
+ };
+
+ const handleSuccess = async () => {
+ setOpen(false);
+ notify('common.response.success');
+ };
+
+ const handleError = async (error) => {
+ notify(error.message || 'common.response.fail', { type: 'error', messageArgs: { _: error.message } });
+ };
+
+ return (
+ <CreateBase
+ record={{ sceneCode: 'system_diagnose', status: 1, publishedFlag: 0 }}
+ mutationOptions={{ onSuccess: handleSuccess, onError: handleError }}
+ >
+ <Dialog open={open} onClose={handleClose} fullWidth disableRestoreFocus maxWidth="md">
+ <Form>
+ <DialogTitle sx={{ position: 'sticky', top: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+ {translate('create.title')}
+ <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
+ <DialogCloseButton onClose={handleClose} />
+ </Box>
+ </DialogTitle>
+ <DialogContent sx={{ mt: 2 }}>
+ <Grid container rowSpacing={2} columnSpacing={2}>
+ <Grid item xs={6}><SelectInput source="sceneCode" label="鍦烘櫙" choices={sceneChoices} fullWidth /></Grid>
+ <Grid item xs={6}><TextInput source="templateName" label="妯℃澘鍚嶇О" fullWidth /></Grid>
+ <Grid item xs={12}><TextInput source="basePrompt" label="鍩虹鎻愮ず璇�" fullWidth multiline minRows={4} /></Grid>
+ <Grid item xs={12}><TextInput source="toolPrompt" label="宸ュ叿鎻愮ず璇�" fullWidth multiline minRows={4} /></Grid>
+ <Grid item xs={12}><TextInput source="outputPrompt" label="杈撳嚭鎻愮ず璇�" fullWidth multiline minRows={4} /></Grid>
+ <Grid item xs={6}><NumberInput source="versionNo" label="鐗堟湰鍙�" fullWidth /></Grid>
+ <Grid item xs={6}><StatusSelectInput fullWidth /></Grid>
+ <Grid item xs={12}><Stack direction="column" spacing={1} width={'100%'}><MemoInput /></Stack></Grid>
+ </Grid>
+ </DialogContent>
+ <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+ <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}>
+ <SaveButton />
+ </Toolbar>
+ </DialogActions>
+ </Form>
+ </Dialog>
+ </CreateBase>
+ )
+}
+
+export default AiPromptCreate;
diff --git a/rsf-admin/src/page/system/aiPrompt/AiPromptEdit.jsx b/rsf-admin/src/page/system/aiPrompt/AiPromptEdit.jsx
new file mode 100644
index 0000000..3493b5f
--- /dev/null
+++ b/rsf-admin/src/page/system/aiPrompt/AiPromptEdit.jsx
@@ -0,0 +1,64 @@
+import React from "react";
+import {
+ Edit,
+ SimpleForm,
+ TextInput,
+ NumberInput,
+ SaveButton,
+ SelectInput,
+ Toolbar,
+ DeleteButton,
+} from 'react-admin';
+import { Stack, Grid, Typography } from '@mui/material';
+import { EDIT_MODE } from '@/config/setting';
+import EditBaseAside from "@/page/components/EditBaseAside";
+import CustomerTopToolBar from "@/page/components/EditTopToolBar";
+import MemoInput from "@/page/components/MemoInput";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+
+const sceneChoices = [
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+];
+
+const FormToolbar = () => (
+ <Toolbar sx={{ justifyContent: 'space-between' }}>
+ <SaveButton />
+ <DeleteButton mutationMode="optimistic" />
+ </Toolbar>
+);
+
+const AiPromptEdit = () => (
+ <Edit redirect="list" mutationMode={EDIT_MODE} actions={<CustomerTopToolBar />} aside={<EditBaseAside />}>
+ <SimpleForm shouldUnregister warnWhenUnsavedChanges toolbar={<FormToolbar />} mode="onTouched">
+ <Grid container width={{ xs: '100%', xl: '80%' }} rowSpacing={3} columnSpacing={3}>
+ <Grid item xs={12} md={8}>
+ <Typography variant="h6" gutterBottom>涓昏</Typography>
+ <Stack direction='row' gap={2}>
+ <SelectInput source="sceneCode" label="鍦烘櫙" choices={sceneChoices} />
+ <TextInput source="templateName" label="妯℃澘鍚嶇О" />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <NumberInput source="versionNo" label="鐗堟湰鍙�" />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="basePrompt" label="鍩虹鎻愮ず璇�" fullWidth multiline minRows={4} />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="toolPrompt" label="宸ュ叿鎻愮ず璇�" fullWidth multiline minRows={4} />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="outputPrompt" label="杈撳嚭鎻愮ず璇�" fullWidth multiline minRows={4} />
+ </Stack>
+ </Grid>
+ <Grid item xs={12} md={4}>
+ <Typography variant="h6" gutterBottom>閫氱敤</Typography>
+ <StatusSelectInput />
+ <MemoInput />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ </Edit>
+)
+
+export default AiPromptEdit;
diff --git a/rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx b/rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx
new file mode 100644
index 0000000..df57447
--- /dev/null
+++ b/rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx
@@ -0,0 +1,383 @@
+import React, { useEffect, useMemo, useState } from "react";
+import {
+ List,
+ SearchInput,
+ TopToolbar,
+ SelectColumnsButton,
+ FilterButton,
+ TextInput,
+ DateInput,
+ SelectInput,
+ useNotify,
+ useRefresh,
+ useListContext,
+ Pagination,
+ EditButton,
+} from 'react-admin';
+import { Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, Grid, InputLabel, MenuItem, Select, Stack, TextField as MuiTextField, Typography } from '@mui/material';
+import EmptyData from "@/page/components/EmptyData";
+import MyCreateButton from "@/page/components/MyCreateButton";
+import MyExportButton from '@/page/components/MyExportButton';
+import { DEFAULT_PAGE_SIZE } from '@/config/setting';
+import AiPromptCreate from "./AiPromptCreate";
+import request from "@/utils/request";
+import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
+
+const sceneChoices = [
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+];
+
+const sceneLabels = {
+ general_chat: '閫氱敤瀵硅瘽',
+ system_diagnose: '绯荤粺璇婃柇',
+};
+
+const filters = [
+ <SearchInput source="condition" alwaysOn />,
+ <DateInput label='common.time.after' source="timeStart" alwaysOn />,
+ <DateInput label='common.time.before' source="timeEnd" alwaysOn />,
+ <SelectInput source="sceneCode" label="鍦烘櫙" choices={sceneChoices} />,
+ <TextInput source="templateName" label="妯℃澘鍚嶇О" />,
+ <SelectInput
+ source="publishedFlag"
+ label="鍙戝竷鐘舵��"
+ choices={[
+ { id: '1', name: '宸插彂甯�' },
+ { id: '0', name: '鑽夌' },
+ ]}
+ />,
+ <SelectInput
+ label="common.field.status"
+ source="status"
+ choices={[
+ { id: '1', name: 'common.enums.statusTrue' },
+ { id: '0', name: 'common.enums.statusFalse' },
+ ]}
+ />,
+];
+
+const PromptBoard = ({ onCreateDraft }) => {
+ const { data, isLoading } = useListContext();
+ const records = data || [];
+ const refresh = useRefresh();
+ const notify = useNotify();
+ const [selectedScene, setSelectedScene] = useState('');
+ const [versionDialog, setVersionDialog] = useState(false);
+ const [activeRecord, setActiveRecord] = useState(null);
+ const [logs, setLogs] = useState([]);
+ const [compareId, setCompareId] = useState('');
+ const [compareData, setCompareData] = useState(null);
+
+ const groupedScenes = useMemo(() => {
+ const map = {};
+ records.forEach((item) => {
+ const code = item.sceneCode || 'unknown';
+ if (!map[code]) {
+ map[code] = [];
+ }
+ map[code].push(item);
+ });
+ return map;
+ }, [records]);
+
+ const sceneCodes = Object.keys(groupedScenes).sort();
+ const selectedRecords = groupedScenes[selectedScene] || [];
+ const publishedCount = records.filter((item) => item.publishedFlag === 1).length;
+ const draftCount = records.filter((item) => item.publishedFlag !== 1).length;
+
+ useEffect(() => {
+ if (!selectedScene && sceneCodes.length) {
+ setSelectedScene(sceneCodes[0]);
+ }
+ if (selectedScene && !groupedScenes[selectedScene] && sceneCodes.length) {
+ setSelectedScene(sceneCodes[0]);
+ }
+ }, [selectedScene, sceneCodes, groupedScenes]);
+
+ const openVersionDialog = async (record) => {
+ try {
+ const logRes = await request.get(`/ai/prompt/publish-log/list?sceneCode=${record.sceneCode}`);
+ setActiveRecord(record);
+ setLogs(logRes.data?.data || []);
+ setCompareId('');
+ setCompareData(null);
+ setVersionDialog(true);
+ } catch (error) {
+ notify(error.message || '鍔犺浇鐗堟湰鏃ュ織澶辫触', { type: 'error' });
+ }
+ };
+
+ const handlePublish = async (record) => {
+ try {
+ await request.post('/ai/prompt/publish', { id: record.id });
+ notify('common.response.success');
+ refresh();
+ } catch (error) {
+ notify(error.message || '鎿嶄綔澶辫触', { type: 'error' });
+ }
+ };
+
+ const handleRollback = async (record) => {
+ try {
+ await request.post('/ai/prompt/rollback', { id: record.id });
+ notify('common.response.success');
+ refresh();
+ } catch (error) {
+ notify(error.message || '鎿嶄綔澶辫触', { type: 'error' });
+ }
+ };
+
+ const handleCopy = async (record) => {
+ try {
+ await request.post('/ai/prompt/copy', { id: record.id });
+ notify('common.response.success');
+ refresh();
+ } catch (error) {
+ notify(error.message || '鎿嶄綔澶辫触', { type: 'error' });
+ }
+ };
+
+ const handleCompare = async () => {
+ if (!activeRecord || !compareId) {
+ return;
+ }
+ try {
+ const res = await request.get(`/ai/prompt/compare?leftId=${activeRecord.id}&rightId=${compareId}`);
+ setCompareData(res.data?.data || null);
+ } catch (error) {
+ notify(error.message || '鍔犺浇瀵规瘮澶辫触', { type: 'error' });
+ }
+ };
+
+ if (!isLoading && !records.length) {
+ return <EmptyData onClick={onCreateDraft} />;
+ }
+
+ return (
+ <>
+ <AiConsoleLayout
+ title="Prompt 閰嶇疆涓績"
+ subtitle="椤甸潰缁撴瀯鍙傝�� zy-wcs-master 鐨� Prompt 涓績锛屼繚鐣欏師绯荤粺鐨勬寜閽�佺紪杈戦〉鍜屾潈闄愭ā鍨嬶紝閲嶇偣澧炲己鍦烘櫙鎰熺煡銆佺増鏈槄璇诲拰杩愯惀鍔ㄤ綔灞曠ず銆�"
+ actions={[
+ <Button key="draft" variant="contained" onClick={onCreateDraft}>鏂板缓鑽夌</Button>,
+ ]}
+ stats={[
+ { label: '鍦烘櫙鏁�', value: sceneCodes.length },
+ { label: '鐗堟湰鎬绘暟', value: records.length },
+ { label: '宸插彂甯�', value: publishedCount },
+ { label: '鑽夌', value: draftCount },
+ ]}
+ >
+ <Grid container spacing={2}>
+ <Grid item xs={12} md={3}>
+ <AiConsolePanel
+ title="鍦烘櫙"
+ subtitle="姣忎釜鍦烘櫙鍙細鏈変竴涓凡鍙戝竷鐗堟湰锛岃瘖鏂拰鑱婂ぉ杩愯鏃朵細鐩存帴璇诲彇瀹冦��"
+ minHeight={520}
+ >
+ <Stack spacing={1.5}>
+ {sceneCodes.map((code) => {
+ const list = groupedScenes[code] || [];
+ const published = list.find((item) => item.publishedFlag === 1);
+ const drafts = list.filter((item) => item.publishedFlag !== 1).length;
+ return (
+ <Box key={code} sx={{ ...aiCardSx(selectedScene === code), cursor: 'pointer' }} onClick={() => setSelectedScene(code)}>
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#284059' }}>
+ {sceneLabels[code] || code}
+ </Typography>
+ <Typography variant="caption" sx={{ color: '#8aa0b7' }}>{code}</Typography>
+ </Box>
+ <Chip size="small" color={published ? 'success' : 'default'} label={published ? `v${published.versionNo}` : '鏈彂甯�'} />
+ </Stack>
+ <Grid container spacing={1} sx={{ mt: 1 }}>
+ <Grid item xs={6}>
+ <Box sx={{ p: 1, borderRadius: 2, border: '1px solid #e7eef7', backgroundColor: '#fff' }}>
+ <Typography variant="caption" sx={{ color: '#7f92a8' }}>鐗堟湰鏁�</Typography>
+ <Typography variant="h6" sx={{ mt: 0.25, color: '#2a3e55' }}>{list.length}</Typography>
+ </Box>
+ </Grid>
+ <Grid item xs={6}>
+ <Box sx={{ p: 1, borderRadius: 2, border: '1px solid #e7eef7', backgroundColor: '#fff' }}>
+ <Typography variant="caption" sx={{ color: '#7f92a8' }}>鑽夌鏁�</Typography>
+ <Typography variant="h6" sx={{ mt: 0.25, color: '#2a3e55' }}>{drafts}</Typography>
+ </Box>
+ </Grid>
+ </Grid>
+ </Box>
+ );
+ })}
+ </Stack>
+ </AiConsolePanel>
+ </Grid>
+ <Grid item xs={12} md={4}>
+ <AiConsolePanel
+ title="鐗堟湰鍒楄〃"
+ subtitle={`褰撳墠鍦烘櫙锛�${sceneLabels[selectedScene] || selectedScene || '鏈�夋嫨鍦烘櫙'}`}
+ action={<Button size="small" variant="outlined" onClick={onCreateDraft}>鏂板缓鑽夌</Button>}
+ minHeight={520}
+ >
+ <Stack spacing={1.5}>
+ {selectedRecords.map((record) => (
+ <Box key={record.id} sx={aiCardSx(record.publishedFlag === 1)}>
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#284059' }}>
+ {record.templateName || `${sceneLabels[record.sceneCode] || record.sceneCode} v${record.versionNo}`}
+ </Typography>
+ <Typography variant="caption" sx={{ color: '#8093a8' }}>
+ 鐗堟湰 v{record.versionNo} 路 鏇存柊鏃堕棿 {record.updateTime || '-'}
+ </Typography>
+ </Box>
+ <Stack direction="row" spacing={0.75}>
+ <Chip size="small" color={record.publishedFlag === 1 ? 'warning' : 'default'} label={record.publishedFlag === 1 ? '宸插彂甯�' : '鑽夌'} />
+ <Chip size="small" color={record.status === 1 ? 'success' : 'default'} label={record.status === 1 ? '鍚敤' : '鍋滅敤'} />
+ </Stack>
+ </Stack>
+ <Typography variant="body2" sx={{ mt: 1.25, minHeight: 48, color: '#31465d' }}>
+ {record.memo || '鏈~鍐欑増鏈娉�'}
+ </Typography>
+ <Stack direction="row" spacing={1} flexWrap="wrap" sx={{ mt: 1.5 }}>
+ <EditButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} />
+ <Button size="small" variant="outlined" onClick={() => openVersionDialog(record)}>鐗堟湰</Button>
+ <Button size="small" variant="outlined" onClick={() => handleCopy(record)}>澶嶅埗</Button>
+ <Button size="small" variant="outlined" onClick={() => handlePublish(record)}>鍙戝竷</Button>
+ <Button size="small" variant="outlined" onClick={() => handleRollback(record)}>鍥炴粴</Button>
+ </Stack>
+ </Box>
+ ))}
+ </Stack>
+ <Box sx={{ mt: 2 }}>
+ <Pagination rowsPerPageOptions={[DEFAULT_PAGE_SIZE, 25, 50]} />
+ </Box>
+ </AiConsolePanel>
+ </Grid>
+ <Grid item xs={12} md={5}>
+ <AiConsolePanel
+ title="杩愯惀鎻愮ず"
+ subtitle="褰撳墠鐗堟湰缂栬緫浠嶈繘鍏ュ師鏈夌紪杈戦〉锛岃繖閲屼富瑕佹壙鎺ョ増鏈槄璇汇�佸姣斿拰鍙戝竷鏃ュ織棰勮銆�"
+ minHeight={520}
+ >
+ <Box sx={{ ...aiCardSx(false), minHeight: 140 }}>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#284059' }}>浣跨敤寤鸿</Typography>
+ <Typography variant="body2" sx={{ mt: 1, color: '#31465d', lineHeight: 1.8 }}>
+ 鍏堝湪宸︿晶鍒囨崲鍦烘櫙锛屽啀鍦ㄤ腑闂存寫閫夎崏绋挎垨绾夸笂鐗堟湰銆傞渶瑕佹繁搴︽煡鐪嬪彂甯冭建杩广�佸仛涓ょ増 Prompt 鏂囨湰瀵规瘮鏃讹紝鐐瑰嚮鈥滅増鏈�濊繘鍏ヨ繍钀ュ脊绐椼��
+ </Typography>
+ </Box>
+ <Box sx={{ ...aiCardSx(true), mt: 1.5, minHeight: 230 }}>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#284059' }}>褰撳墠鍦烘櫙鎽樿</Typography>
+ <Stack spacing={1} sx={{ mt: 1.25 }}>
+ <Typography variant="body2" sx={{ color: '#31465d' }}>
+ 鍦烘櫙缂栫爜锛歿selectedScene || '-'}
+ </Typography>
+ <Typography variant="body2" sx={{ color: '#31465d' }}>
+ 鍙戝竷鐗堟湰锛歿(selectedRecords.find((item) => item.publishedFlag === 1)?.versionNo) ? `v${selectedRecords.find((item) => item.publishedFlag === 1)?.versionNo}` : '鏆傛棤'}
+ </Typography>
+ <Typography variant="body2" sx={{ color: '#31465d' }}>
+ 鑽夌鏁伴噺锛歿selectedRecords.filter((item) => item.publishedFlag !== 1).length}
+ </Typography>
+ <Typography variant="body2" sx={{ color: '#31465d' }}>
+ 鍚敤鐗堟湰锛歿selectedRecords.filter((item) => item.status === 1).length}
+ </Typography>
+ </Stack>
+ </Box>
+ </AiConsolePanel>
+ </Grid>
+ </Grid>
+ </AiConsoleLayout>
+
+ <Dialog open={versionDialog} onClose={() => setVersionDialog(false)} fullWidth maxWidth="lg">
+ <DialogTitle>Prompt 鐗堟湰杩愯惀</DialogTitle>
+ <DialogContent>
+ <Stack spacing={3}>
+ <Typography variant="body2">
+ 褰撳墠妯℃澘锛歿activeRecord?.templateName || '-'} / 鐗堟湰 {activeRecord?.versionNo || '-'}
+ </Typography>
+ <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>
+ <Box flex={1}>
+ <Typography variant="subtitle1" gutterBottom>鍙戝竷鏃ュ織</Typography>
+ {logs.map((item) => (
+ <Box key={item.id} sx={{ borderBottom: '1px solid #eee', pb: 1, mb: 1 }}>
+ <Typography variant="body2">{item.actionType} / v{item.versionNo} / {item.templateName}</Typography>
+ <Typography variant="caption" color="text.secondary">{item.actionDesc} / {item.createTime}</Typography>
+ </Box>
+ ))}
+ </Box>
+ <Box flex={1}>
+ <Typography variant="subtitle1" gutterBottom>鏂囨湰瀵规瘮</Typography>
+ <Stack spacing={1.5}>
+ <FormControl sx={{ minWidth: 220 }}>
+ <InputLabel id="prompt-compare-label">瀵规瘮鐗堟湰</InputLabel>
+ <Select
+ labelId="prompt-compare-label"
+ label="瀵规瘮鐗堟湰"
+ value={compareId}
+ onChange={(event) => setCompareId(event.target.value)}
+ >
+ {selectedRecords.filter((item) => item.id !== activeRecord?.id).map((item) => (
+ <MenuItem key={item.id} value={item.id}>v{item.versionNo} / {item.templateName}</MenuItem>
+ ))}
+ </Select>
+ </FormControl>
+ <Button variant="outlined" onClick={handleCompare}>鍔犺浇瀵规瘮</Button>
+ </Stack>
+ </Box>
+ </Stack>
+ {compareData ? (
+ <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>
+ <Box flex={1}>
+ <Typography variant="subtitle1" gutterBottom>褰撳墠鐗堟湰</Typography>
+ <MuiTextField label="鍩虹鎻愮ず璇�" fullWidth multiline minRows={5} value={compareData.left?.basePrompt || ''} disabled margin="dense" />
+ <MuiTextField label="宸ュ叿鎻愮ず璇�" fullWidth multiline minRows={5} value={compareData.left?.toolPrompt || ''} disabled margin="dense" />
+ <MuiTextField label="杈撳嚭鎻愮ず璇�" fullWidth multiline minRows={5} value={compareData.left?.outputPrompt || ''} disabled margin="dense" />
+ </Box>
+ <Box flex={1}>
+ <Typography variant="subtitle1" gutterBottom>瀵规瘮鐗堟湰</Typography>
+ <MuiTextField label="鍩虹鎻愮ず璇�" fullWidth multiline minRows={5} value={compareData.right?.basePrompt || ''} disabled margin="dense" />
+ <MuiTextField label="宸ュ叿鎻愮ず璇�" fullWidth multiline minRows={5} value={compareData.right?.toolPrompt || ''} disabled margin="dense" />
+ <MuiTextField label="杈撳嚭鎻愮ず璇�" fullWidth multiline minRows={5} value={compareData.right?.outputPrompt || ''} disabled margin="dense" />
+ </Box>
+ </Stack>
+ ) : null}
+ </Stack>
+ </DialogContent>
+ <DialogActions>
+ <Button onClick={() => setVersionDialog(false)}>鍏抽棴</Button>
+ </DialogActions>
+ </Dialog>
+ </>
+ );
+};
+
+const AiPromptList = () => {
+ const [createDialog, setCreateDialog] = useState(false);
+
+ return (
+ <Box display="flex" sx={{ width: '100%' }}>
+ <List
+ sx={{ width: '100%', flexGrow: 1 }}
+ title={"menu.aiPrompt"}
+ empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
+ filters={filters}
+ sort={{ field: "updateTime", order: "desc" }}
+ actions={(
+ <TopToolbar>
+ <FilterButton />
+ <MyCreateButton onClick={() => { setCreateDialog(true) }} />
+ <SelectColumnsButton preferenceKey='aiPrompt' />
+ <MyExportButton />
+ </TopToolbar>
+ )}
+ perPage={DEFAULT_PAGE_SIZE}
+ pagination={false}
+ >
+ <PromptBoard onCreateDraft={() => setCreateDialog(true)} />
+ </List>
+ <AiPromptCreate open={createDialog} setOpen={setCreateDialog} />
+ </Box>
+ )
+}
+
+export default AiPromptList;
diff --git a/rsf-admin/src/page/system/aiPrompt/index.jsx b/rsf-admin/src/page/system/aiPrompt/index.jsx
new file mode 100644
index 0000000..fd45e59
--- /dev/null
+++ b/rsf-admin/src/page/system/aiPrompt/index.jsx
@@ -0,0 +1,13 @@
+import {
+ ShowGuesser,
+} from "react-admin";
+
+import AiPromptList from "./AiPromptList";
+import AiPromptEdit from "./AiPromptEdit";
+
+export default {
+ list: AiPromptList,
+ edit: AiPromptEdit,
+ show: ShowGuesser,
+ recordRepresentation: (record) => `${record.templateName || record.sceneCode || ''}`
+};
diff --git a/rsf-admin/src/page/system/aiRoute/AiRouteCreate.jsx b/rsf-admin/src/page/system/aiRoute/AiRouteCreate.jsx
new file mode 100644
index 0000000..408d86e
--- /dev/null
+++ b/rsf-admin/src/page/system/aiRoute/AiRouteCreate.jsx
@@ -0,0 +1,84 @@
+import React from "react";
+import {
+ CreateBase,
+ useTranslate,
+ TextInput,
+ NumberInput,
+ SaveButton,
+ SelectInput,
+ Toolbar,
+ useNotify,
+ Form,
+} from 'react-admin';
+import {
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Stack,
+ Grid,
+ Box,
+} from '@mui/material';
+import DialogCloseButton from "@/page/components/DialogCloseButton";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+import MemoInput from "@/page/components/MemoInput";
+
+const routeChoices = [
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+];
+
+const AiRouteCreate = (props) => {
+ const { open, setOpen } = props;
+ const translate = useTranslate();
+ const notify = useNotify();
+
+ const handleClose = (event, reason) => {
+ if (reason !== "backdropClick") {
+ setOpen(false);
+ }
+ };
+
+ const handleSuccess = async () => {
+ setOpen(false);
+ notify('common.response.success');
+ };
+
+ const handleError = async (error) => {
+ notify(error.message || 'common.response.fail', { type: 'error', messageArgs: { _: error.message } });
+ };
+
+ return (
+ <CreateBase
+ record={{ routeCode: 'general_chat', priority: 1, failCount: 0, successCount: 0, status: 1 }}
+ mutationOptions={{ onSuccess: handleSuccess, onError: handleError }}
+ >
+ <Dialog open={open} onClose={handleClose} fullWidth disableRestoreFocus maxWidth="md">
+ <Form>
+ <DialogTitle sx={{ position: 'sticky', top: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+ {translate('create.title')}
+ <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
+ <DialogCloseButton onClose={handleClose} />
+ </Box>
+ </DialogTitle>
+ <DialogContent sx={{ mt: 2 }}>
+ <Grid container rowSpacing={2} columnSpacing={2}>
+ <Grid item xs={6}><SelectInput source="routeCode" label="璺敱缂栫爜" choices={routeChoices} fullWidth /></Grid>
+ <Grid item xs={6}><TextInput source="modelCode" label="妯″瀷缂栫爜" fullWidth /></Grid>
+ <Grid item xs={6}><NumberInput source="priority" label="浼樺厛绾�" fullWidth /></Grid>
+ <Grid item xs={6}><StatusSelectInput fullWidth /></Grid>
+ <Grid item xs={12}><Stack direction="column" spacing={1} width={'100%'}><MemoInput /></Stack></Grid>
+ </Grid>
+ </DialogContent>
+ <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+ <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}>
+ <SaveButton />
+ </Toolbar>
+ </DialogActions>
+ </Form>
+ </Dialog>
+ </CreateBase>
+ )
+}
+
+export default AiRouteCreate;
diff --git a/rsf-admin/src/page/system/aiRoute/AiRouteEdit.jsx b/rsf-admin/src/page/system/aiRoute/AiRouteEdit.jsx
new file mode 100644
index 0000000..8bd5dc5
--- /dev/null
+++ b/rsf-admin/src/page/system/aiRoute/AiRouteEdit.jsx
@@ -0,0 +1,60 @@
+import React from "react";
+import {
+ Edit,
+ SimpleForm,
+ TextInput,
+ NumberInput,
+ SaveButton,
+ SelectInput,
+ Toolbar,
+ DeleteButton,
+} from 'react-admin';
+import { Stack, Grid, Typography } from '@mui/material';
+import { EDIT_MODE } from '@/config/setting';
+import EditBaseAside from "@/page/components/EditBaseAside";
+import CustomerTopToolBar from "@/page/components/EditTopToolBar";
+import MemoInput from "@/page/components/MemoInput";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+
+const routeChoices = [
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+];
+
+const FormToolbar = () => (
+ <Toolbar sx={{ justifyContent: 'space-between' }}>
+ <SaveButton />
+ <DeleteButton mutationMode="optimistic" />
+ </Toolbar>
+);
+
+const AiRouteEdit = () => (
+ <Edit redirect="list" mutationMode={EDIT_MODE} actions={<CustomerTopToolBar />} aside={<EditBaseAside />}>
+ <SimpleForm shouldUnregister warnWhenUnsavedChanges toolbar={<FormToolbar />} mode="onTouched">
+ <Grid container width={{ xs: '100%', xl: '80%' }} rowSpacing={3} columnSpacing={3}>
+ <Grid item xs={12} md={8}>
+ <Typography variant="h6" gutterBottom>涓昏</Typography>
+ <Stack direction='row' gap={2}>
+ <SelectInput source="routeCode" label="璺敱缂栫爜" choices={routeChoices} />
+ <TextInput source="modelCode" label="妯″瀷缂栫爜" />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <NumberInput source="priority" label="浼樺厛绾�" />
+ <NumberInput source="failCount" label="澶辫触娆℃暟" />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <NumberInput source="successCount" label="鎴愬姛娆℃暟" />
+ <TextInput source="cooldownUntil$" label="鍐峰嵈鎴" disabled />
+ </Stack>
+ </Grid>
+ <Grid item xs={12} md={4}>
+ <Typography variant="h6" gutterBottom>閫氱敤</Typography>
+ <StatusSelectInput />
+ <MemoInput />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ </Edit>
+)
+
+export default AiRouteEdit;
diff --git a/rsf-admin/src/page/system/aiRoute/AiRouteList.jsx b/rsf-admin/src/page/system/aiRoute/AiRouteList.jsx
new file mode 100644
index 0000000..044f87c
--- /dev/null
+++ b/rsf-admin/src/page/system/aiRoute/AiRouteList.jsx
@@ -0,0 +1,190 @@
+import React, { useState } from "react";
+import {
+ List,
+ SearchInput,
+ TopToolbar,
+ SelectColumnsButton,
+ EditButton,
+ FilterButton,
+ TextInput,
+ DateInput,
+ SelectInput,
+ useNotify,
+ useRefresh,
+ useListContext,
+ Pagination,
+ DeleteButton,
+} from 'react-admin';
+import { Box, Button, Chip, Grid, Stack, Typography } from '@mui/material';
+import EmptyData from "@/page/components/EmptyData";
+import MyCreateButton from "@/page/components/MyCreateButton";
+import MyExportButton from '@/page/components/MyExportButton';
+import { OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
+import AiRouteCreate from "./AiRouteCreate";
+import request from "@/utils/request";
+import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
+
+const routeChoices = [
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+];
+
+const filters = [
+ <SearchInput source="condition" alwaysOn />,
+ <DateInput label='common.time.after' source="timeStart" alwaysOn />,
+ <DateInput label='common.time.before' source="timeEnd" alwaysOn />,
+ <SelectInput source="routeCode" label="璺敱缂栫爜" choices={routeChoices} />,
+ <TextInput source="modelCode" label="妯″瀷缂栫爜" />,
+ <SelectInput label="common.field.status" source="status" choices={[
+ { id: '1', name: 'common.enums.statusTrue' },
+ { id: '0', name: 'common.enums.statusFalse' },
+ ]} />,
+];
+
+const RouteCardActions = ({ record }) => {
+ const refresh = useRefresh();
+ const notify = useNotify();
+
+ const handleToggle = async () => {
+ try {
+ await request.post('/ai/route/toggle', { id: record.id, status: record.status === 1 ? 0 : 1 });
+ notify('common.response.success');
+ refresh();
+ } catch (error) {
+ notify(error.message || '鎿嶄綔澶辫触', { type: 'error' });
+ }
+ };
+
+ const handleReset = async () => {
+ try {
+ await request.post('/ai/route/reset', { id: record.id });
+ notify('common.response.success');
+ refresh();
+ } catch (error) {
+ notify(error.message || '鎿嶄綔澶辫触', { type: 'error' });
+ }
+ };
+
+ return (
+ <Stack direction="row" spacing={1} flexWrap="wrap">
+ <EditButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} />
+ <DeleteButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} mutationMode={OPERATE_MODE} />
+ <Button size="small" variant="outlined" onClick={handleToggle}>{record.status === 1 ? '鍋滅敤' : '鍚敤'}</Button>
+ <Button size="small" variant="outlined" onClick={handleReset}>閲嶇疆</Button>
+ </Stack>
+ );
+};
+
+const RouteBoard = () => {
+ const { data, isLoading } = useListContext();
+ const records = data || [];
+ const enabledCount = records.filter((item) => item.status === 1).length;
+ const coolingCount = records.filter((item) => item.cooldownUntil).length;
+ const failSwitchCount = records.filter((item) => (item.failCount || 0) > 0).length;
+ const successCount = records.reduce((sum, item) => sum + (item.successCount || 0), 0);
+
+ if (!isLoading && !records.length) {
+ return <EmptyData />;
+ }
+
+ return (
+ <AiConsoleLayout
+ title="AI妯″瀷璺敱"
+ subtitle="鍙傝�� zy-wcs-master 鐨� LLM 鎺у埗鍙颁俊鎭眰娆★紝绐佸嚭璺敱鐘舵�併�佸喎鍗翠笌鎴愬姛澶辫触璁℃暟锛屼絾淇濈暀鐜版湁绯荤粺鐨勬寜閽�佺瓫閫夊拰琛ㄥ崟椋庢牸銆�"
+ stats={[
+ { label: '鎬昏矾鐢�', value: records.length },
+ { label: '鍚敤', value: enabledCount },
+ { label: '鍐峰嵈涓�', value: coolingCount },
+ { label: '绱鎴愬姛', value: successCount, helper: `宸叉湁澶辫触璁板綍 ${failSwitchCount} 鏉 },
+ ]}
+ >
+ <AiConsolePanel
+ title="璺敱鍗$墖"
+ subtitle="姣忓紶鍗$墖浠h〃涓�鏉℃ā鍨嬪�欓�夛紝浼樺厛绾ц秺灏忚秺鍏堝懡涓紱鍚仠銆侀噸缃細绔嬪嵆浣滅敤浜庡悗缁姹傘��"
+ minHeight={460}
+ >
+ <Grid container spacing={2}>
+ {records.map((record) => (
+ <Grid item xs={12} md={6} xl={4} key={record.id}>
+ <Box sx={aiCardSx(record.status === 1)}>
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#284059' }}>
+ {record.modelCode}
+ </Typography>
+ <Typography variant="caption" sx={{ color: '#8093a8' }}>
+ {record.routeCode} 路 浼樺厛绾� {record.priority}
+ </Typography>
+ </Box>
+ <Stack direction="row" spacing={0.75} flexWrap="wrap" justifyContent="flex-end">
+ <Chip size="small" color={record.status === 1 ? 'success' : 'default'} label={record.status === 1 ? '鍚敤' : '鍋滅敤'} />
+ {record.cooldownUntil ? <Chip size="small" color="warning" label="鍐峰嵈涓�" /> : null}
+ </Stack>
+ </Stack>
+ <Grid container spacing={1.25} sx={{ mt: 1 }}>
+ <Grid item xs={6}>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>澶辫触娆℃暟</Typography>
+ <Typography variant="h6" sx={{ color: '#2f455c', mt: 0.25 }}>{record.failCount || 0}</Typography>
+ </Grid>
+ <Grid item xs={6}>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>鎴愬姛娆℃暟</Typography>
+ <Typography variant="h6" sx={{ color: '#2f455c', mt: 0.25 }}>{record.successCount || 0}</Typography>
+ </Grid>
+ <Grid item xs={12}>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>鍐峰嵈鎴</Typography>
+ <Typography variant="body2" sx={{ mt: 0.25, color: '#31465d' }}>
+ {record.cooldownUntil$ || '鏈繘鍏ュ喎鍗�'}
+ </Typography>
+ </Grid>
+ <Grid item xs={12}>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>澶囨敞</Typography>
+ <Typography variant="body2" sx={{ mt: 0.25, color: '#31465d', minHeight: 42 }}>
+ {record.memo || '鏈~鍐欏娉�'}
+ </Typography>
+ </Grid>
+ </Grid>
+ <Box sx={{ mt: 1.5 }}>
+ <RouteCardActions record={record} />
+ </Box>
+ </Box>
+ </Grid>
+ ))}
+ </Grid>
+ <Box sx={{ mt: 2 }}>
+ <Pagination rowsPerPageOptions={[DEFAULT_PAGE_SIZE, 25, 50]} />
+ </Box>
+ </AiConsolePanel>
+ </AiConsoleLayout>
+ );
+};
+
+const AiRouteList = () => {
+ const [createDialog, setCreateDialog] = useState(false);
+
+ return (
+ <Box display="flex" sx={{ width: '100%' }}>
+ <List
+ sx={{ width: '100%', flexGrow: 1 }}
+ title={"menu.aiRoute"}
+ empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
+ filters={filters}
+ sort={{ field: "priority", order: "asc" }}
+ actions={(
+ <TopToolbar>
+ <FilterButton />
+ <MyCreateButton onClick={() => { setCreateDialog(true) }} />
+ <SelectColumnsButton preferenceKey='aiRoute' />
+ <MyExportButton />
+ </TopToolbar>
+ )}
+ perPage={DEFAULT_PAGE_SIZE}
+ pagination={false}
+ >
+ <RouteBoard />
+ </List>
+ <AiRouteCreate open={createDialog} setOpen={setCreateDialog} />
+ </Box>
+ );
+}
+
+export default AiRouteList;
diff --git a/rsf-admin/src/page/system/aiRoute/index.jsx b/rsf-admin/src/page/system/aiRoute/index.jsx
new file mode 100644
index 0000000..223ed58
--- /dev/null
+++ b/rsf-admin/src/page/system/aiRoute/index.jsx
@@ -0,0 +1,13 @@
+import {
+ ShowGuesser,
+} from "react-admin";
+
+import AiRouteList from "./AiRouteList";
+import AiRouteEdit from "./AiRouteEdit";
+
+export default {
+ list: AiRouteList,
+ edit: AiRouteEdit,
+ show: ShowGuesser,
+ recordRepresentation: (record) => `${record.routeCode || record.modelCode || ''}`
+};
diff --git a/rsf-admin/src/page/system/aiToolConfig/AiToolConfigCreate.jsx b/rsf-admin/src/page/system/aiToolConfig/AiToolConfigCreate.jsx
new file mode 100644
index 0000000..6295a4c
--- /dev/null
+++ b/rsf-admin/src/page/system/aiToolConfig/AiToolConfigCreate.jsx
@@ -0,0 +1,92 @@
+import React from "react";
+import {
+ CreateBase,
+ useTranslate,
+ TextInput,
+ NumberInput,
+ SaveButton,
+ SelectInput,
+ Toolbar,
+ useNotify,
+ Form,
+} from 'react-admin';
+import {
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Stack,
+ Grid,
+ Box,
+} from '@mui/material';
+import DialogCloseButton from "@/page/components/DialogCloseButton";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+import MemoInput from "@/page/components/MemoInput";
+
+const sceneChoices = [
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+];
+
+const enabledChoices = [
+ { id: 1, name: '鍚敤' },
+ { id: 0, name: '鍋滅敤' },
+];
+
+const AiToolConfigCreate = (props) => {
+ const { open, setOpen } = props;
+ const translate = useTranslate();
+ const notify = useNotify();
+
+ const handleClose = (event, reason) => {
+ if (reason !== "backdropClick") {
+ setOpen(false);
+ }
+ };
+
+ const handleSuccess = async () => {
+ setOpen(false);
+ notify('common.response.success');
+ };
+
+ const handleError = async (error) => {
+ notify(error.message || 'common.response.fail', { type: 'error', messageArgs: { _: error.message } });
+ };
+
+ return (
+ <CreateBase
+ record={{ sceneCode: 'system_diagnose', priority: 10, enabledFlag: 1, status: 1 }}
+ mutationOptions={{ onSuccess: handleSuccess, onError: handleError }}
+ >
+ <Dialog open={open} onClose={handleClose} fullWidth disableRestoreFocus maxWidth="md">
+ <Form>
+ <DialogTitle sx={{ position: 'sticky', top: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+ {translate('create.title')}
+ <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
+ <DialogCloseButton onClose={handleClose} />
+ </Box>
+ </DialogTitle>
+ <DialogContent sx={{ mt: 2 }}>
+ <Grid container rowSpacing={2} columnSpacing={2}>
+ <Grid item xs={6}><SelectInput source="sceneCode" label="鍦烘櫙" choices={sceneChoices} fullWidth /></Grid>
+ <Grid item xs={6}><TextInput source="toolCode" label="宸ュ叿缂栫爜" fullWidth /></Grid>
+ <Grid item xs={6}><TextInput source="toolName" label="宸ュ叿鍚嶇О" fullWidth /></Grid>
+ <Grid item xs={6}><NumberInput source="priority" label="浼樺厛绾�" fullWidth /></Grid>
+ <Grid item xs={6}><SelectInput source="enabledFlag" label="鍚敤" choices={enabledChoices} fullWidth /></Grid>
+ <Grid item xs={6}><StatusSelectInput fullWidth /></Grid>
+ <Grid item xs={12}><TextInput source="toolPrompt" label="宸ュ叿鎻愮ず璇�" fullWidth multiline minRows={4} /></Grid>
+ <Grid item xs={12}><Stack direction="column" spacing={1} width={'100%'}><MemoInput /></Stack></Grid>
+ </Grid>
+ </DialogContent>
+ <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+ <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}>
+ <SaveButton />
+ </Toolbar>
+ </DialogActions>
+ </Form>
+ </Dialog>
+ </CreateBase>
+ )
+}
+
+export default AiToolConfigCreate;
diff --git a/rsf-admin/src/page/system/aiToolConfig/AiToolConfigEdit.jsx b/rsf-admin/src/page/system/aiToolConfig/AiToolConfigEdit.jsx
new file mode 100644
index 0000000..fb23e7a
--- /dev/null
+++ b/rsf-admin/src/page/system/aiToolConfig/AiToolConfigEdit.jsx
@@ -0,0 +1,67 @@
+import React from "react";
+import {
+ Edit,
+ SimpleForm,
+ TextInput,
+ NumberInput,
+ SaveButton,
+ SelectInput,
+ Toolbar,
+ DeleteButton,
+} from 'react-admin';
+import { Stack, Grid, Typography } from '@mui/material';
+import { EDIT_MODE } from '@/config/setting';
+import EditBaseAside from "@/page/components/EditBaseAside";
+import CustomerTopToolBar from "@/page/components/EditTopToolBar";
+import MemoInput from "@/page/components/MemoInput";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+
+const sceneChoices = [
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+];
+
+const enabledChoices = [
+ { id: 1, name: '鍚敤' },
+ { id: 0, name: '鍋滅敤' },
+];
+
+const FormToolbar = () => (
+ <Toolbar sx={{ justifyContent: 'space-between' }}>
+ <SaveButton />
+ <DeleteButton mutationMode="optimistic" />
+ </Toolbar>
+);
+
+const AiToolConfigEdit = () => (
+ <Edit redirect="list" mutationMode={EDIT_MODE} actions={<CustomerTopToolBar />} aside={<EditBaseAside />}>
+ <SimpleForm shouldUnregister warnWhenUnsavedChanges toolbar={<FormToolbar />} mode="onTouched">
+ <Grid container width={{ xs: '100%', xl: '80%' }} rowSpacing={3} columnSpacing={3}>
+ <Grid item xs={12} md={8}>
+ <Typography variant="h6" gutterBottom>涓昏</Typography>
+ <Stack direction='row' gap={2}>
+ <SelectInput source="sceneCode" label="鍦烘櫙" choices={sceneChoices} />
+ <TextInput source="toolCode" label="宸ュ叿缂栫爜" />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="toolName" label="宸ュ叿鍚嶇О" />
+ <NumberInput source="priority" label="浼樺厛绾�" />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <SelectInput source="enabledFlag" label="鍚敤" choices={enabledChoices} />
+ </Stack>
+ <Stack direction='row' gap={2}>
+ <TextInput source="toolPrompt" label="宸ュ叿鎻愮ず璇�" fullWidth multiline minRows={4} />
+ </Stack>
+ </Grid>
+ <Grid item xs={12} md={4}>
+ <Typography variant="h6" gutterBottom>閫氱敤</Typography>
+ <StatusSelectInput />
+ <MemoInput />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ </Edit>
+)
+
+export default AiToolConfigEdit;
diff --git a/rsf-admin/src/page/system/aiToolConfig/AiToolConfigList.jsx b/rsf-admin/src/page/system/aiToolConfig/AiToolConfigList.jsx
new file mode 100644
index 0000000..ab8f376
--- /dev/null
+++ b/rsf-admin/src/page/system/aiToolConfig/AiToolConfigList.jsx
@@ -0,0 +1,134 @@
+import React, { useState } from "react";
+import {
+ List,
+ SearchInput,
+ TopToolbar,
+ SelectColumnsButton,
+ EditButton,
+ FilterButton,
+ TextInput,
+ DateInput,
+ SelectInput,
+ useListContext,
+ Pagination,
+ DeleteButton,
+} from 'react-admin';
+import { Box, Button, Chip, Grid, Stack, Typography } from '@mui/material';
+import EmptyData from "@/page/components/EmptyData";
+import MyCreateButton from "@/page/components/MyCreateButton";
+import MyExportButton from '@/page/components/MyExportButton';
+import { OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
+import AiToolConfigCreate from "./AiToolConfigCreate";
+import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
+
+const sceneChoices = [
+ { id: 'general_chat', name: '閫氱敤瀵硅瘽' },
+ { id: 'system_diagnose', name: '绯荤粺璇婃柇' },
+];
+
+const filters = [
+ <SearchInput source="condition" alwaysOn />,
+ <DateInput label='common.time.after' source="timeStart" alwaysOn />,
+ <DateInput label='common.time.before' source="timeEnd" alwaysOn />,
+ <SelectInput source="sceneCode" label="鍦烘櫙" choices={sceneChoices} />,
+ <TextInput source="toolCode" label="宸ュ叿缂栫爜" />,
+ <TextInput source="toolName" label="宸ュ叿鍚嶇О" />,
+];
+
+const ToolConfigBoard = () => {
+ const { data, isLoading } = useListContext();
+ const records = data || [];
+ const enabledCount = records.filter((item) => item.enabledFlag === 1).length;
+ const diagnoseCount = records.filter((item) => item.sceneCode === 'system_diagnose').length;
+ const chatCount = records.filter((item) => item.sceneCode === 'general_chat').length;
+
+ if (!isLoading && !records.length) {
+ return <EmptyData />;
+ }
+
+ return (
+ <AiConsoleLayout
+ title="AI璇婃柇宸ュ叿涓績"
+ subtitle="鍙傝�� zy-wcs-master 鐨勫伐浣滃尯甯冨眬锛屾彁渚涘満鏅寲宸ュ叿缂栨帓銆佸惎鍋滃拰鎻愮ず璇嶅寮猴紝浣嗘帶浠朵笌浜や簰浠嶄繚鎸佸綋鍓嶇郴缁熺殑 react-admin 椋庢牸銆�"
+ stats={[
+ { label: '宸ュ叿鎬绘暟', value: records.length },
+ { label: '宸插惎鐢�', value: enabledCount },
+ { label: '璇婃柇鍦烘櫙', value: diagnoseCount },
+ { label: '閫氱敤瀵硅瘽', value: chatCount },
+ ]}
+ >
+ <AiConsolePanel
+ title="宸ュ叿缂栨帓"
+ subtitle="鍚屼竴鍦烘櫙浼氭寜浼樺厛绾ф墽琛岋紝鍚敤鐘舵�佸拰宸ュ叿鎻愮ず璇嶄細鐩存帴杩涘叆璇婃柇杩愯鏃躲��"
+ minHeight={420}
+ >
+ <Grid container spacing={2}>
+ {records.map((record) => (
+ <Grid item xs={12} md={6} xl={4} key={record.id}>
+ <Box sx={aiCardSx(record.enabledFlag === 1)}>
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+ <Box>
+ <Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#284059' }}>
+ {record.toolName || record.toolCode}
+ </Typography>
+ <Typography variant="caption" sx={{ color: '#8093a8' }}>
+ {record.sceneCode} 路 {record.toolCode}
+ </Typography>
+ </Box>
+ <Stack direction="row" spacing={0.75} flexWrap="wrap" justifyContent="flex-end">
+ <Chip size="small" color={record.enabledFlag === 1 ? 'success' : 'default'} label={record.enabledFlag === 1 ? '鍚敤' : '鍋滅敤'} />
+ <Chip size="small" color={record.status === 1 ? 'primary' : 'default'} label={`P${record.priority || 0}`} />
+ </Stack>
+ </Stack>
+ <Box sx={{ mt: 1.5 }}>
+ <Typography variant="caption" sx={{ color: '#70839a' }}>宸ュ叿鎻愮ず璇�</Typography>
+ <Typography variant="body2" sx={{ mt: 0.5, color: '#31465d', minHeight: 72 }}>
+ {record.toolPrompt || '鏈厤缃檮鍔犳彁绀鸿瘝锛屽皢鍙娇鐢ㄩ粯璁ゅ伐鍏锋憳瑕併��'}
+ </Typography>
+ </Box>
+ <Stack direction="row" spacing={1} sx={{ mt: 1.5 }}>
+ <EditButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} />
+ <DeleteButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} mutationMode={OPERATE_MODE} />
+ </Stack>
+ </Box>
+ </Grid>
+ ))}
+ </Grid>
+ <Box sx={{ mt: 2 }}>
+ <Pagination rowsPerPageOptions={[DEFAULT_PAGE_SIZE, 25, 50]} />
+ </Box>
+ </AiConsolePanel>
+ </AiConsoleLayout>
+ );
+};
+
+const AiToolConfigList = () => {
+ const [createDialog, setCreateDialog] = useState(false);
+
+ return (
+ <Box display="flex" sx={{ width: '100%' }}>
+ <List
+ sx={{ width: '100%', flexGrow: 1 }}
+ title={"menu.aiToolConfig"}
+ empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
+ filters={filters}
+ sort={{ field: "priority", order: "asc" }}
+ actions={(
+ <TopToolbar>
+ <FilterButton />
+ <MyCreateButton onClick={() => { setCreateDialog(true) }} />
+ <SelectColumnsButton preferenceKey='aiToolConfig' />
+ <MyExportButton />
+ </TopToolbar>
+ )}
+ perPage={DEFAULT_PAGE_SIZE}
+ pagination={false}
+ >
+ <ToolConfigBoard />
+ </List>
+ <AiToolConfigCreate open={createDialog} setOpen={setCreateDialog} />
+ </Box>
+ )
+}
+
+export default AiToolConfigList;
diff --git a/rsf-admin/src/page/system/aiToolConfig/index.jsx b/rsf-admin/src/page/system/aiToolConfig/index.jsx
new file mode 100644
index 0000000..9532f87
--- /dev/null
+++ b/rsf-admin/src/page/system/aiToolConfig/index.jsx
@@ -0,0 +1,13 @@
+import {
+ ShowGuesser,
+} from "react-admin";
+
+import AiToolConfigList from "./AiToolConfigList";
+import AiToolConfigEdit from "./AiToolConfigEdit";
+
+export default {
+ list: AiToolConfigList,
+ edit: AiToolConfigEdit,
+ show: ShowGuesser,
+ recordRepresentation: (record) => `${record.toolName || record.toolCode || ''}`
+};
diff --git a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/config/AiGatewayProperties.java b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/config/AiGatewayProperties.java
index 0b7fcd4..edc5d5f 100644
--- a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/config/AiGatewayProperties.java
+++ b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/config/AiGatewayProperties.java
@@ -12,7 +12,7 @@
@ConfigurationProperties(prefix = "gateway.ai")
public class AiGatewayProperties {
- private String defaultModelCode = "mock-general";
+ private String defaultModelCode = "deepseek-ai/DeepSeek-V3.2";
private Integer connectTimeoutMillis = 10000;
diff --git a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/config/WebAsyncConfig.java b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/config/WebAsyncConfig.java
new file mode 100644
index 0000000..0c07bc3
--- /dev/null
+++ b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/config/WebAsyncConfig.java
@@ -0,0 +1,15 @@
+package com.vincent.rsf.ai.gateway.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebAsyncConfig implements WebMvcConfigurer {
+
+ @Override
+ public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
+ // StreamingResponseBody is used for long-lived model streams; disable the MVC async timeout.
+ configurer.setDefaultTimeout(0L);
+ }
+}
diff --git a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/controller/AiGatewayController.java b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/controller/AiGatewayController.java
index f8842d9..f065ad0 100644
--- a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/controller/AiGatewayController.java
+++ b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/controller/AiGatewayController.java
@@ -4,6 +4,8 @@
import com.vincent.rsf.ai.gateway.service.AiGatewayService;
import com.vincent.rsf.ai.gateway.service.GatewayStreamEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -13,11 +15,15 @@
import javax.annotation.Resource;
import java.io.IOException;
+import java.io.InterruptedIOException;
import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicBoolean;
@RestController
@RequestMapping("/internal/chat")
public class AiGatewayController {
+
+ private static final Logger logger = LoggerFactory.getLogger(AiGatewayController.class);
@Resource
private AiGatewayService aiGatewayService;
@@ -27,16 +33,106 @@
@PostMapping(value = "/stream", produces = "application/x-ndjson")
public StreamingResponseBody stream(@RequestBody GatewayChatRequest request) {
return outputStream -> {
+ logger.info("AI gateway controller stream opened: sessionId={}, routeCode={}, attemptNo={}, modelCode={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ request.getModelCode());
+ AtomicBoolean streaming = new AtomicBoolean(true);
+ Object writeLock = new Object();
+ Thread heartbeatThread = new Thread(() -> {
+ while (streaming.get()) {
+ try {
+ Thread.sleep(10000L);
+ if (!streaming.get()) {
+ break;
+ }
+ String json = objectMapper.writeValueAsString(new GatewayStreamEvent()
+ .setType("ping")
+ .setModelCode(request.getModelCode())
+ .setResponseTime(System.currentTimeMillis())) + "\n";
+ synchronized (writeLock) {
+ outputStream.write(json.getBytes(StandardCharsets.UTF_8));
+ outputStream.flush();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logger.info("AI gateway heartbeat interrupted: sessionId={}, routeCode={}, attemptNo={}, modelCode={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ request.getModelCode());
+ break;
+ } catch (Exception e) {
+ logger.warn("AI gateway heartbeat write failed: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, message={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ request.getModelCode(),
+ e.getMessage());
+ break;
+ }
+ }
+ }, "ai-gateway-heartbeat-" + (request.getSessionId() == null ? "unknown" : request.getSessionId()));
+ heartbeatThread.setDaemon(true);
+ heartbeatThread.start();
try {
aiGatewayService.stream(request, event -> {
String json = objectMapper.writeValueAsString(event) + "\n";
- outputStream.write(json.getBytes(StandardCharsets.UTF_8));
- outputStream.flush();
+ synchronized (writeLock) {
+ outputStream.write(json.getBytes(StandardCharsets.UTF_8));
+ outputStream.flush();
+ }
});
} catch (Exception e) {
+ if (isInterruptedError(e)) {
+ logger.warn("AI gateway controller stream interrupted: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, message={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ request.getModelCode(),
+ e.getMessage());
+ return;
+ }
+ logger.error("AI gateway controller stream failed: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, message={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ request.getModelCode(),
+ e.getMessage(),
+ e);
throw new IOException(e);
+ } finally {
+ streaming.set(false);
+ heartbeatThread.interrupt();
+ logger.info("AI gateway controller stream closed: sessionId={}, routeCode={}, attemptNo={}, modelCode={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ request.getModelCode());
}
};
}
+ private boolean isInterruptedError(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (current instanceof InterruptedException || current instanceof InterruptedIOException) {
+ return true;
+ }
+ String message = current.getMessage();
+ if (message != null) {
+ String normalized = message.toLowerCase();
+ if (normalized.contains("interrupted")
+ || normalized.contains("broken pipe")
+ || normalized.contains("connection reset")
+ || normalized.contains("forcibly closed")) {
+ return true;
+ }
+ }
+ current = current.getCause();
+ }
+ return false;
+ }
+
}
diff --git a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/dto/GatewayChatRequest.java b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/dto/GatewayChatRequest.java
index cf105b9..668d45b 100644
--- a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/dto/GatewayChatRequest.java
+++ b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/dto/GatewayChatRequest.java
@@ -13,6 +13,10 @@
private String modelCode;
+ private String routeCode;
+
+ private Integer attemptNo;
+
private String systemPrompt;
private String chatUrl;
diff --git a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/service/AiGatewayService.java b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/service/AiGatewayService.java
index da04faa..070bb04 100644
--- a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/service/AiGatewayService.java
+++ b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/service/AiGatewayService.java
@@ -5,12 +5,15 @@
import com.vincent.rsf.ai.gateway.dto.GatewayChatRequest;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
@@ -23,6 +26,8 @@
@Service
public class AiGatewayService {
+ private static final Logger logger = LoggerFactory.getLogger(AiGatewayService.class);
+
@Resource
private AiGatewayProperties aiGatewayProperties;
@Resource
@@ -34,7 +39,20 @@
public void stream(GatewayChatRequest request, EventConsumer consumer) throws Exception {
AiGatewayProperties.ModelConfig modelConfig = resolveModel(request);
+ logger.info("AI gateway stream start: sessionId={}, routeCode={}, attemptNo={}, requestModelCode={}, resolvedModelCode={}, provider={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ request.getModelCode(),
+ modelConfig == null ? null : modelConfig.getCode(),
+ modelConfig == null ? null : modelConfig.getProvider());
if (modelConfig == null || modelConfig.getChatUrl() == null || modelConfig.getChatUrl().trim().isEmpty()) {
+ logger.info("AI gateway use mock stream: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, provider={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig == null ? request.getModelCode() : modelConfig.getCode(),
+ modelConfig == null ? "mock" : modelConfig.getProvider());
mockStream(request, modelConfig, consumer);
return;
}
@@ -87,6 +105,7 @@
private void mockStream(GatewayChatRequest request, AiGatewayProperties.ModelConfig modelConfig,
EventConsumer consumer) throws Exception {
+ long requestTime = System.currentTimeMillis();
String modelCode = modelConfig == null ? aiGatewayProperties.getDefaultModelCode() : modelConfig.getCode();
String lastQuestion = "";
List<GatewayChatMessage> messages = request.getMessages();
@@ -98,22 +117,58 @@
}
}
String answer = "褰撳墠涓烘紨绀烘ā寮忥紝妯″瀷[" + modelCode + "]宸叉敹鍒颁綘鐨勯棶棰橈細" + lastQuestion;
+ logger.info("AI gateway mock stream emitting response: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, answerLength={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelCode,
+ answer.length());
for (char c : answer.toCharArray()) {
consumer.accept(new GatewayStreamEvent()
.setType("delta")
.setModelCode(modelCode)
.setContent(String.valueOf(c)));
- Thread.sleep(20L);
+ try {
+ Thread.sleep(20L);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
+ }
}
consumer.accept(new GatewayStreamEvent()
.setType("done")
- .setModelCode(modelCode));
+ .setModelCode(modelCode)
+ .setSuccess(true)
+ .setRequestTime(requestTime)
+ .setResponseTime(System.currentTimeMillis())
+ .setDurationMs(System.currentTimeMillis() - requestTime));
+ logger.info("AI gateway mock stream completed: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, durationMs={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelCode,
+ System.currentTimeMillis() - requestTime);
}
private void openAiCompatibleStream(GatewayChatRequest request, AiGatewayProperties.ModelConfig modelConfig,
EventConsumer consumer) throws Exception {
HttpURLConnection connection = null;
+ long requestTime = System.currentTimeMillis();
+ boolean terminalEventSent = false;
+ int eventLineCount = 0;
+ int deltaCount = 0;
+ int contentChars = 0;
+ boolean firstDeltaLogged = false;
+ String normalizedUrl = modelConfig == null ? null : modelConfig.getChatUrl();
try {
+ logger.info("AI gateway opening upstream stream: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, provider={}, url={}, modelName={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig == null ? null : modelConfig.getCode(),
+ modelConfig == null ? null : modelConfig.getProvider(),
+ normalizedUrl,
+ modelConfig == null ? null : modelConfig.getModelName());
connection = (HttpURLConnection) new URL(modelConfig.getChatUrl()).openConnection();
connection.setConnectTimeout(aiGatewayProperties.getConnectTimeoutMillis());
connection.setReadTimeout(aiGatewayProperties.getReadTimeoutMillis());
@@ -136,19 +191,49 @@
}
int statusCode = connection.getResponseCode();
+ logger.info("AI gateway upstream response received: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, statusCode={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig.getCode(),
+ statusCode);
InputStream inputStream = statusCode >= 400 ? connection.getErrorStream() : connection.getInputStream();
if (inputStream == null) {
+ logger.warn("AI gateway upstream returned empty stream: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, url={}, statusCode={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig.getCode(),
+ normalizedUrl,
+ statusCode);
consumer.accept(new GatewayStreamEvent()
.setType("error")
.setModelCode(modelConfig.getCode())
- .setMessage("妯″瀷鏈嶅姟鏃犲搷搴�"));
+ .setMessage("妯″瀷鏈嶅姟鏃犲搷搴�")
+ .setSuccess(false)
+ .setRequestTime(requestTime)
+ .setResponseTime(System.currentTimeMillis())
+ .setDurationMs(System.currentTimeMillis() - requestTime));
+ terminalEventSent = true;
return;
}
if (statusCode >= 400) {
+ logger.warn("AI gateway upstream http error: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, url={}, statusCode={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig.getCode(),
+ normalizedUrl,
+ statusCode);
consumer.accept(new GatewayStreamEvent()
.setType("error")
.setModelCode(modelConfig.getCode())
- .setMessage(readErrorMessage(inputStream, statusCode)));
+ .setMessage(readErrorMessage(inputStream, statusCode))
+ .setSuccess(false)
+ .setRequestTime(requestTime)
+ .setResponseTime(System.currentTimeMillis())
+ .setDurationMs(System.currentTimeMillis() - requestTime));
+ terminalEventSent = true;
return;
}
@@ -158,11 +243,27 @@
if (line.trim().isEmpty() || !line.startsWith("data:")) {
continue;
}
+ eventLineCount++;
String payload = line.substring(5).trim();
if ("[DONE]".equals(payload)) {
+ long responseTime = System.currentTimeMillis();
+ logger.info("AI gateway upstream done marker received: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, eventLines={}, deltaCount={}, contentChars={}, durationMs={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig.getCode(),
+ eventLineCount,
+ deltaCount,
+ contentChars,
+ responseTime - requestTime);
consumer.accept(new GatewayStreamEvent()
.setType("done")
- .setModelCode(modelConfig.getCode()));
+ .setModelCode(modelConfig.getCode())
+ .setSuccess(true)
+ .setRequestTime(requestTime)
+ .setResponseTime(responseTime)
+ .setDurationMs(responseTime - requestTime));
+ terminalEventSent = true;
break;
}
JsonNode root = objectMapper.readTree(payload);
@@ -170,29 +271,117 @@
JsonNode delta = choice.path("delta");
JsonNode contentNode = delta.path("content");
if (!contentNode.isMissingNode() && !contentNode.isNull()) {
+ String content = contentNode.asText();
+ deltaCount++;
+ contentChars += content.length();
+ if (!firstDeltaLogged) {
+ logger.info("AI gateway upstream first delta received: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, afterMs={}, sampleLength={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig.getCode(),
+ System.currentTimeMillis() - requestTime,
+ content.length());
+ firstDeltaLogged = true;
+ }
consumer.accept(new GatewayStreamEvent()
.setType("delta")
.setModelCode(modelConfig.getCode())
- .setContent(contentNode.asText()));
+ .setContent(content));
}
JsonNode finishReason = choice.path("finish_reason");
if (!finishReason.isMissingNode() && !finishReason.isNull()) {
+ long responseTime = System.currentTimeMillis();
+ logger.info("AI gateway upstream finish_reason received: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, finishReason={}, eventLines={}, deltaCount={}, contentChars={}, durationMs={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig.getCode(),
+ finishReason.asText(),
+ eventLineCount,
+ deltaCount,
+ contentChars,
+ responseTime - requestTime);
consumer.accept(new GatewayStreamEvent()
.setType("done")
- .setModelCode(modelConfig.getCode()));
+ .setModelCode(modelConfig.getCode())
+ .setSuccess(true)
+ .setRequestTime(requestTime)
+ .setResponseTime(responseTime)
+ .setDurationMs(responseTime - requestTime));
+ terminalEventSent = true;
break;
}
}
}
+ if (!terminalEventSent) {
+ long responseTime = System.currentTimeMillis();
+ logger.warn("AI gateway upstream ended without terminal event: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, url={}, eventLines={}, deltaCount={}, contentChars={}, durationMs={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig.getCode(),
+ normalizedUrl,
+ eventLineCount,
+ deltaCount,
+ contentChars,
+ responseTime - requestTime);
+ consumer.accept(new GatewayStreamEvent()
+ .setType("error")
+ .setModelCode(modelConfig.getCode())
+ .setMessage("妯″瀷娴佸紓甯镐腑鏂�")
+ .setSuccess(false)
+ .setRequestTime(requestTime)
+ .setResponseTime(responseTime)
+ .setDurationMs(responseTime - requestTime));
+ }
} catch (Exception e) {
+ if (isInterruptedError(e)) {
+ logger.warn("AI gateway upstream interrupted: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, url={}, stage={}, message={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig == null ? null : modelConfig.getCode(),
+ normalizedUrl,
+ terminalEventSent ? "after_terminal" : "streaming",
+ e.getMessage());
+ if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ return;
+ }
+ logger.error("AI gateway upstream exception: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, url={}, eventLines={}, deltaCount={}, contentChars={}, message={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig == null ? null : modelConfig.getCode(),
+ normalizedUrl,
+ eventLineCount,
+ deltaCount,
+ contentChars,
+ e.getMessage(),
+ e);
consumer.accept(new GatewayStreamEvent()
.setType("error")
.setModelCode(modelConfig.getCode())
- .setMessage(e.getMessage()));
+ .setMessage(e.getMessage())
+ .setSuccess(false)
+ .setRequestTime(requestTime)
+ .setResponseTime(System.currentTimeMillis())
+ .setDurationMs(System.currentTimeMillis() - requestTime));
} finally {
if (connection != null) {
connection.disconnect();
}
+ logger.info("AI gateway upstream stream closed: sessionId={}, routeCode={}, attemptNo={}, modelCode={}, terminalEventSent={}, eventLines={}, deltaCount={}, contentChars={}",
+ request.getSessionId(),
+ request.getRouteCode(),
+ request.getAttemptNo(),
+ modelConfig == null ? null : modelConfig.getCode(),
+ terminalEventSent,
+ eventLineCount,
+ deltaCount,
+ contentChars);
}
}
@@ -264,4 +453,25 @@
}
}
+ private boolean isInterruptedError(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (current instanceof InterruptedException || current instanceof InterruptedIOException) {
+ return true;
+ }
+ String message = current.getMessage();
+ if (message != null) {
+ String normalized = message.toLowerCase();
+ if (normalized.contains("interrupted")
+ || normalized.contains("broken pipe")
+ || normalized.contains("connection reset")
+ || normalized.contains("forcibly closed")) {
+ return true;
+ }
+ }
+ current = current.getCause();
+ }
+ return false;
+ }
+
}
diff --git a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/service/GatewayStreamEvent.java b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/service/GatewayStreamEvent.java
index 7ea9d37..2f71f3e 100644
--- a/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/service/GatewayStreamEvent.java
+++ b/rsf-ai-gateway/src/main/java/com/vincent/rsf/ai/gateway/service/GatewayStreamEvent.java
@@ -17,4 +17,12 @@
private String modelCode;
+ private Boolean success;
+
+ private Long requestTime;
+
+ private Long responseTime;
+
+ private Long durationMs;
+
}
diff --git a/rsf-ai-gateway/src/main/resources/application.yml b/rsf-ai-gateway/src/main/resources/application.yml
index 78797bf..68c956a 100644
--- a/rsf-ai-gateway/src/main/resources/application.yml
+++ b/rsf-ai-gateway/src/main/resources/application.yml
@@ -7,17 +7,14 @@
gateway:
ai:
- default-model-code: mock-general
+ default-model-code: deepseek-ai/DeepSeek-V3.2
connect-timeout-millis: 10000
read-timeout-millis: 0
models:
- - code: mock-general
- name: Mock General
- provider: mock
- model-name: mock-general
- enabled: true
- - code: mock-creative
- name: Mock Creative
- provider: mock
- model-name: mock-creative
+ - code: deepseek-ai/DeepSeek-V3.2
+ name: DEEPSEEK
+ provider: openai
+ chat-url: https://api.siliconflow.cn
+ api-key:
+ model-name: deepseek-ai/DeepSeek-V3.2
enabled: true
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ServerBoot.java b/rsf-server/src/main/java/com/vincent/rsf/server/ServerBoot.java
index 1104ea3..ff12589 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ServerBoot.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ServerBoot.java
@@ -11,3 +11,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiProperties.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiProperties.java
index 751d469..4d85462 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiProperties.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiProperties.java
@@ -1,5 +1,6 @@
package com.vincent.rsf.server.ai.config;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@@ -21,7 +22,23 @@
private String systemPrompt = "浣犳槸WMS绯荤粺鍐呯殑鏅鸿兘鍔╂墜锛屽洖绛旀椂浼樺厛淇濇寔鍑嗙‘銆佺畝娲侊紝骞剁粨鍚堜笂涓嬫枃甯姪鐢ㄦ埛鐞嗚В浠撳偍涓氬姟銆�";
- private String defaultModelCode = "mock-general";
+ private String diagnosisSystemPrompt = "浣犳槸涓�鍚嶈祫娣盬MS鏅鸿兘璇婃柇鍔╂墜锛岀洰鏍囨槸缁撳悎褰撳墠绯荤粺涓婁笅鏂囧浠撳簱杩愯鎯呭喌鍋氬贰妫�鍒嗘瀽銆�"
+ + "鍥炵瓟鏃剁姝㈠嚟绌虹寽娴嬶紝蹇呴』浼樺厛渚濇嵁鎻愪緵鐨勫疄鏃舵憳瑕佽繘琛屽垽鏂��"
+ + "璇蜂紭鍏堟寜浠ヤ笅椤哄簭鍒嗘瀽锛氬厛鎬荤粨搴撳瓨銆佷换鍔°�佽澶囩珯鐐圭殑瀹炴椂鐘舵�侊紝鎸囧嚭鏄惁瀛樺湪鏄庢樉寮傚父锛�"
+ + "濡傛灉鍙戠幇寮傚父锛岃缁欏嚭寮傚父鐜拌薄銆佸彲鑳藉師鍥犮�佸奖鍝嶈寖鍥淬�佸缓璁鐞嗘楠わ紱"
+ + "濡傛灉鏁版嵁姝e父锛岃鏄庣‘璇存槑褰撳墠鏈彂鐜版槑鏄惧紓甯革紝骞舵彁閱掍粛闇�浜哄伐缁撳悎鐜板満鐘舵�佸鏍搞��"
+ + "鍥炵瓟灏介噺寮曠敤浣犳嬁鍒扮殑瀹炴椂鏁版嵁锛屼笉瑕佺紪閫犳湭鏌ヨ鍒扮殑璁惧鐘舵�佹垨涓氬姟浜嬪疄銆�"
+ + "璇锋寜鈥滈棶棰樻杩般�佸叧閿瘉鎹�佸彲鑳藉師鍥犮�佸缓璁姩浣溿�侀闄╄瘎浼扳�濈殑缁撴瀯杈撳嚭锛屽苟浼樺厛缁欏嚭鍙墽琛屽缓璁��";
+
+ private Integer routeFailThreshold = 3;
+
+ private Integer routeCooldownMinutes = 10;
+
+ private Integer diagnosticLogWindowHours = 24;
+
+ private Integer apiFailureWindowHours = 24;
+
+ private String defaultModelCode = "deepseek-ai/DeepSeek-V3.2";
private List<ModelConfig> models = new ArrayList<>();
@@ -33,7 +50,18 @@
if (defaultModelCode != null && !defaultModelCode.trim().isEmpty()) {
return defaultModelCode;
}
- return getEnabledModels().isEmpty() ? "mock-general" : getEnabledModels().get(0).getCode();
+ return getEnabledModels().isEmpty() ? "deepseek-ai/DeepSeek-V3.2" : getEnabledModels().get(0).getCode();
+ }
+
+ public String buildScenePrompt(String sceneCode, String basePrompt) {
+ String prompt = basePrompt == null ? null : basePrompt.trim();
+ if (AiSceneCode.SYSTEM_DIAGNOSE.equals(sceneCode)) {
+ if (prompt == null || prompt.isEmpty()) {
+ return diagnosisSystemPrompt;
+ }
+ return prompt + "\n\n" + diagnosisSystemPrompt;
+ }
+ return prompt;
}
@Data
@@ -45,3 +73,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiSchemaGuard.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiSchemaGuard.java
new file mode 100644
index 0000000..89cc622
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiSchemaGuard.java
@@ -0,0 +1,64 @@
+package com.vincent.rsf.server.ai.config;
+
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.util.Arrays;
+import java.util.List;
+
+@Component
+public class AiSchemaGuard {
+
+ private static final List<String> REQUIRED_TABLES = Arrays.asList(
+ "sys_ai_chat_session",
+ "sys_ai_chat_message",
+ "sys_ai_prompt_template",
+ "sys_ai_prompt_publish_log",
+ "sys_ai_diagnosis_record",
+ "sys_ai_diagnosis_plan",
+ "sys_ai_call_log",
+ "sys_ai_mcp_mount",
+ "sys_ai_model_route",
+ "sys_ai_diagnostic_tool_config"
+ );
+
+ @Resource
+ private DataSource dataSource;
+
+ @PostConstruct
+ public void validate() {
+ try (Connection connection = dataSource.getConnection()) {
+ for (String table : REQUIRED_TABLES) {
+ if (!tableExists(connection, table)) {
+ throw new IllegalStateException("AI feature table missing: " + table + "锛岃鍏堟墽琛� AI 杩佺Щ鑴氭湰");
+ }
+ }
+ } catch (IllegalStateException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException("AI feature schema validation failed: " + e.getMessage(), e);
+ }
+ }
+
+ private boolean tableExists(Connection connection, String tableName) throws Exception {
+ try (ResultSet resultSet = connection.getMetaData().getTables(connection.getCatalog(), null, tableName, null)) {
+ if (resultSet.next()) {
+ return true;
+ }
+ }
+ try (ResultSet resultSet = connection.getMetaData().getTables(connection.getCatalog(), null, tableName.toUpperCase(), null)) {
+ if (resultSet.next()) {
+ return true;
+ }
+ }
+ try (ResultSet resultSet = connection.getMetaData().getTables(connection.getCatalog(), null, tableName.toLowerCase(), null)) {
+ return resultSet.next();
+ }
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/constant/AiMcpConstants.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/constant/AiMcpConstants.java
new file mode 100644
index 0000000..4d71d3b
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/constant/AiMcpConstants.java
@@ -0,0 +1,24 @@
+package com.vincent.rsf.server.ai.constant;
+
+public class AiMcpConstants {
+
+ public static final String DEFAULT_LOCAL_MOUNT_CODE = "wms_local";
+ public static final String DEFAULT_LOCAL_MOUNT_NAME = "WMS鏈湴MCP";
+ public static final String TRANSPORT_AUTO = "AUTO";
+ public static final String TRANSPORT_INTERNAL = "INTERNAL";
+ public static final String TRANSPORT_HTTP = "HTTP";
+ public static final String TRANSPORT_SSE = "SSE";
+ public static final String USAGE_SCOPE_DIAGNOSE_ONLY = "DIAGNOSE_ONLY";
+ public static final String USAGE_SCOPE_CHAT_AND_DIAGNOSE = "CHAT_AND_DIAGNOSE";
+ public static final String USAGE_SCOPE_DISABLED = "DISABLED";
+ public static final String AUTH_TYPE_NONE = "NONE";
+ public static final String AUTH_TYPE_BEARER = "BEARER";
+ public static final String AUTH_TYPE_API_KEY = "API_KEY";
+ public static final String PROTOCOL_VERSION = "2025-03-26";
+ public static final String SERVER_NAME = "wms-rsf-mcp";
+ public static final String SERVER_VERSION = "1.0.0";
+
+ private AiMcpConstants() {
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/constant/AiSceneCode.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/constant/AiSceneCode.java
new file mode 100644
index 0000000..e3e4f21
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/constant/AiSceneCode.java
@@ -0,0 +1,12 @@
+package com.vincent.rsf.server.ai.constant;
+
+public final class AiSceneCode {
+
+ public static final String GENERAL_CHAT = "general_chat";
+
+ public static final String SYSTEM_DIAGNOSE = "system_diagnose";
+
+ private AiSceneCode() {
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiController.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiController.java
index 3768be3..49322ec 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiController.java
@@ -2,27 +2,32 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.vincent.rsf.framework.common.R;
-import com.vincent.rsf.server.ai.config.AiProperties;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
import com.vincent.rsf.server.ai.dto.AiChatStreamRequest;
import com.vincent.rsf.server.ai.dto.AiSessionCreateRequest;
import com.vincent.rsf.server.ai.dto.AiSessionRenameRequest;
-import com.vincent.rsf.server.ai.dto.GatewayChatMessage;
-import com.vincent.rsf.server.ai.dto.GatewayChatRequest;
import com.vincent.rsf.server.ai.model.AiChatMessage;
import com.vincent.rsf.server.ai.model.AiChatSession;
import com.vincent.rsf.server.ai.model.AiPromptContext;
-import com.vincent.rsf.server.ai.service.AiGatewayClient;
-import com.vincent.rsf.server.ai.service.AiPromptContextService;
+import com.vincent.rsf.server.ai.service.diagnosis.AiChatStreamOrchestrator;
+import com.vincent.rsf.server.ai.service.diagnosis.AiDiagnosisRuntimeService;
+import com.vincent.rsf.server.ai.service.AiModelRouteRuntimeService;
import com.vincent.rsf.server.ai.service.AiRuntimeConfigService;
import com.vincent.rsf.server.ai.service.AiSessionService;
import com.vincent.rsf.server.system.controller.BaseController;
+import com.vincent.rsf.server.system.entity.AiDiagnosisRecord;
import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.*;
+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 org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.annotation.Resource;
import java.io.IOException;
-import java.util.Date;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -34,17 +39,17 @@
@Resource
private AiSessionService aiSessionService;
@Resource
- private AiProperties aiProperties;
- @Resource
- private AiGatewayClient aiGatewayClient;
- @Resource
private AiRuntimeConfigService aiRuntimeConfigService;
@Resource
- private AiPromptContextService aiPromptContextService;
+ private AiModelRouteRuntimeService aiModelRouteRuntimeService;
+ @Resource
+ private AiDiagnosisRuntimeService aiDiagnosisRuntimeService;
+ @Resource
+ private AiChatStreamOrchestrator aiChatStreamOrchestrator;
@GetMapping("/model/list")
public R modelList() {
- List<Map<String, Object>> models = new java.util.ArrayList<>();
+ List<Map<String, Object>> models = new ArrayList<>();
for (AiRuntimeConfigService.ModelRuntimeConfig model : aiRuntimeConfigService.listEnabledModels()) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("code", model.getCode());
@@ -99,6 +104,20 @@
@PostMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chatStream(@RequestBody AiChatStreamRequest request) {
+ return doChatStream(normalizeRequest(request));
+ }
+
+ @PostMapping(value = "/diagnose/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+ public SseEmitter diagnoseStream(@RequestBody(required = false) AiChatStreamRequest request) {
+ AiChatStreamRequest diagnosisRequest = normalizeRequest(request);
+ diagnosisRequest.setSceneCode(AiSceneCode.SYSTEM_DIAGNOSE);
+ if (diagnosisRequest.getMessage() == null || diagnosisRequest.getMessage().trim().isEmpty()) {
+ diagnosisRequest.setMessage("璇峰褰撳墠WMS绯荤粺杩涜涓�娆″贰妫�璇婃柇锛岀粨鍚堝簱瀛樸�佷换鍔°�佽澶囩珯鐐规暟鎹瘑鍒紓甯稿苟缁欏嚭澶勭悊寤鸿銆�");
+ }
+ return doChatStream(diagnosisRequest);
+ }
+
+ private SseEmitter doChatStream(AiChatStreamRequest request) {
SseEmitter emitter = new SseEmitter(0L);
Long tenantId = getTenantId();
Long userId = getLoginUserId();
@@ -110,137 +129,79 @@
completeWithError(emitter, "娑堟伅鍐呭涓嶈兘涓虹┖");
return emitter;
}
+
AiChatSession session = aiSessionService.ensureSession(tenantId, userId, request.getSessionId(), request.getModelCode());
aiSessionService.clearStopFlag(session.getId());
aiSessionService.appendMessage(tenantId, userId, session.getId(), "user", request.getMessage(), session.getModelCode());
- AiRuntimeConfigService.ModelRuntimeConfig modelRuntimeConfig = aiRuntimeConfigService.resolveModel(session.getModelCode());
+
+ AiPromptContext promptContext = new AiPromptContext()
+ .setTenantId(tenantId)
+ .setUserId(userId)
+ .setSessionId(session.getId())
+ .setModelCode(session.getModelCode())
+ .setQuestion(request.getMessage())
+ .setSceneCode(request.getSceneCode());
+
+ List<AiModelRouteRuntimeService.RouteCandidate> candidates = aiModelRouteRuntimeService.resolveCandidates(
+ tenantId,
+ request.getSceneCode(),
+ session.getModelCode()
+ );
+ if (candidates.isEmpty()) {
+ completeWithError(emitter, "鏈壘鍒板彲鐢ㄧ殑AI妯″瀷閰嶇疆");
+ return emitter;
+ }
+ int maxContextMessages = resolveContextSize(candidates);
List<AiChatMessage> contextMessages = aiSessionService.listContextMessages(
tenantId,
userId,
session.getId(),
- modelRuntimeConfig.getMaxContextMessages()
+ maxContextMessages
);
+ AiDiagnosisRecord diagnosisRecord = AiSceneCode.SYSTEM_DIAGNOSE.equals(request.getSceneCode())
+ ? aiDiagnosisRuntimeService.startDiagnosis(tenantId, userId, session.getId(), request.getSceneCode(), request.getMessage())
+ : null;
- Thread thread = new Thread(() -> {
- StringBuilder assistantReply = new StringBuilder();
- boolean doneSent = false;
- try {
- emitter.send(SseEmitter.event().name("session").data(buildSessionPayload(session), MediaType.APPLICATION_JSON));
- GatewayChatRequest gatewayChatRequest = buildGatewayRequest(
- tenantId,
- userId,
- session,
- contextMessages,
- modelRuntimeConfig,
- request.getMessage()
- );
- aiGatewayClient.stream(gatewayChatRequest, event -> handleGatewayEvent(
- emitter,
- event,
- session,
- assistantReply
- ));
- if (aiSessionService.isStopRequested(session.getId())) {
- if (assistantReply.length() > 0) {
- aiSessionService.appendMessage(tenantId, userId, session.getId(), "assistant", assistantReply.toString(), session.getModelCode());
- }
- emitter.send(SseEmitter.event().name("done").data(buildDonePayload(session, true), MediaType.APPLICATION_JSON));
- doneSent = true;
- }
- } catch (Exception e) {
- try {
- emitter.send(SseEmitter.event().name("error").data(buildErrorPayload(e.getMessage()), MediaType.APPLICATION_JSON));
- } catch (IOException ignore) {
- }
- } finally {
- if (!doneSent && assistantReply.length() > 0) {
- aiSessionService.appendMessage(tenantId, userId, session.getId(), "assistant", assistantReply.toString(), session.getModelCode());
- try {
- emitter.send(SseEmitter.event().name("done").data(buildDonePayload(session, false), MediaType.APPLICATION_JSON));
- } catch (IOException ignore) {
- }
- }
- emitter.complete();
- aiSessionService.clearStopFlag(session.getId());
- }
- }, "ai-chat-stream-" + session.getId());
+ Thread thread = new Thread(() -> executeStream(
+ emitter, tenantId, userId, session, request, promptContext, contextMessages, diagnosisRecord, candidates
+ ), "ai-chat-stream-" + session.getId());
thread.setDaemon(true);
thread.start();
return emitter;
}
- private boolean handleGatewayEvent(SseEmitter emitter, JsonNode event, AiChatSession session,
- StringBuilder assistantReply) throws Exception {
- if (aiSessionService.isStopRequested(session.getId())) {
- return false;
- }
- String type = event.path("type").asText();
- if ("delta".equals(type)) {
- String content = event.path("content").asText("");
- assistantReply.append(content);
- emitter.send(SseEmitter.event().name("delta").data(buildDeltaPayload(session, content), MediaType.APPLICATION_JSON));
- return true;
- }
- if ("error".equals(type)) {
- emitter.send(SseEmitter.event().name("error").data(buildErrorPayload(event.path("message").asText("妯″瀷璋冪敤澶辫触")), MediaType.APPLICATION_JSON));
- return false;
- }
- if ("done".equals(type)) {
- return false;
- }
- return true;
+ private void executeStream(SseEmitter emitter,
+ Long tenantId,
+ Long userId,
+ AiChatSession session,
+ AiChatStreamRequest request,
+ AiPromptContext promptContext,
+ List<AiChatMessage> contextMessages,
+ AiDiagnosisRecord diagnosisRecord,
+ List<AiModelRouteRuntimeService.RouteCandidate> candidates) {
+ aiChatStreamOrchestrator.executeStream(
+ emitter,
+ tenantId,
+ userId,
+ session,
+ request,
+ promptContext,
+ contextMessages,
+ diagnosisRecord,
+ candidates
+ );
}
- private GatewayChatRequest buildGatewayRequest(Long tenantId, Long userId, AiChatSession session, List<AiChatMessage> contextMessages,
- AiRuntimeConfigService.ModelRuntimeConfig modelRuntimeConfig,
- String latestQuestion) {
- GatewayChatRequest request = new GatewayChatRequest();
- request.setSessionId(session.getId());
- request.setModelCode(session.getModelCode());
- request.setSystemPrompt(aiPromptContextService.buildSystemPrompt(
- modelRuntimeConfig.getSystemPrompt(),
- new AiPromptContext()
- .setTenantId(tenantId)
- .setUserId(userId)
- .setSessionId(session.getId())
- .setModelCode(session.getModelCode())
- .setQuestion(latestQuestion)
- ));
- request.setChatUrl(modelRuntimeConfig.getChatUrl());
- request.setApiKey(modelRuntimeConfig.getApiKey());
- request.setModelName(modelRuntimeConfig.getModelName());
- for (AiChatMessage contextMessage : contextMessages) {
- GatewayChatMessage item = new GatewayChatMessage();
- item.setRole(contextMessage.getRole());
- item.setContent(contextMessage.getContent());
- request.getMessages().add(item);
+ private int resolveContextSize(List<AiModelRouteRuntimeService.RouteCandidate> candidates) {
+ return aiChatStreamOrchestrator.resolveContextSize(candidates);
+ }
+
+ private AiChatStreamRequest normalizeRequest(AiChatStreamRequest request) {
+ AiChatStreamRequest normalized = request == null ? new AiChatStreamRequest() : request;
+ if (normalized.getSceneCode() == null || normalized.getSceneCode().trim().isEmpty()) {
+ normalized.setSceneCode(AiSceneCode.GENERAL_CHAT);
}
- return request;
- }
-
- private Map<String, Object> buildSessionPayload(AiChatSession session) {
- Map<String, Object> payload = new LinkedHashMap<>();
- payload.put("sessionId", session.getId());
- payload.put("title", session.getTitle());
- payload.put("modelCode", session.getModelCode());
- return payload;
- }
-
- private Map<String, Object> buildDeltaPayload(AiChatSession session, String content) {
- Map<String, Object> payload = new LinkedHashMap<>();
- payload.put("sessionId", session.getId());
- payload.put("modelCode", session.getModelCode());
- payload.put("content", content);
- payload.put("timestamp", new Date().getTime());
- return payload;
- }
-
- private Map<String, Object> buildDonePayload(AiChatSession session, boolean stopped) {
- Map<String, Object> payload = new LinkedHashMap<>();
- payload.put("sessionId", session.getId());
- payload.put("modelCode", session.getModelCode());
- payload.put("stopped", stopped);
- return payload;
+ return normalized;
}
private Map<String, Object> buildErrorPayload(String message) {
@@ -256,5 +217,5 @@
}
emitter.complete();
}
-
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatStreamRequest.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatStreamRequest.java
index fca5bbf..5a6c93c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatStreamRequest.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatStreamRequest.java
@@ -13,4 +13,7 @@
private String modelCode;
+ private String sceneCode;
+
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiSessionCreateRequest.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiSessionCreateRequest.java
index 1bf463c..83f4d1c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiSessionCreateRequest.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiSessionCreateRequest.java
@@ -12,3 +12,4 @@
private String modelCode;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiSessionRenameRequest.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiSessionRenameRequest.java
index b5eab2d..caba8e8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiSessionRenameRequest.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiSessionRenameRequest.java
@@ -10,3 +10,4 @@
private String title;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/GatewayChatMessage.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/GatewayChatMessage.java
index c50659a..fb4144f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/GatewayChatMessage.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/GatewayChatMessage.java
@@ -12,3 +12,4 @@
private String content;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/GatewayChatRequest.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/GatewayChatRequest.java
index 439092a..8d0cbbd 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/GatewayChatRequest.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/GatewayChatRequest.java
@@ -13,6 +13,10 @@
private String modelCode;
+ private String routeCode;
+
+ private Integer attemptNo;
+
private String systemPrompt;
private String chatUrl;
@@ -24,3 +28,4 @@
private List<GatewayChatMessage> messages = new ArrayList<>();
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatMessageMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatMessageMapper.java
new file mode 100644
index 0000000..8142a25
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatMessageMapper.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.ai.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.ai.model.AiChatMessage;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiChatMessageMapper extends BaseMapper<AiChatMessage> {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatSessionMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatSessionMapper.java
new file mode 100644
index 0000000..635fb5a
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatSessionMapper.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.ai.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.ai.model.AiChatSession;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiChatSessionMapper extends BaseMapper<AiChatSession> {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiChatMessage.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiChatMessage.java
index f124919..ea1c384 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiChatMessage.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiChatMessage.java
@@ -1,5 +1,9 @@
package com.vincent.rsf.server.ai.model;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -8,9 +12,15 @@
@Data
@Accessors(chain = true)
+@TableName("sys_ai_chat_message")
public class AiChatMessage implements Serializable {
+ @TableId(value = "id", type = IdType.INPUT)
private String id;
+
+ private Long tenantId;
+
+ private Long userId;
private String sessionId;
@@ -22,4 +32,10 @@
private Date createTime;
+ private Integer status;
+
+ @TableLogic
+ private Integer deleted;
+
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiChatSession.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiChatSession.java
index 725efc8..097d95e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiChatSession.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiChatSession.java
@@ -1,5 +1,9 @@
package com.vincent.rsf.server.ai.model;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -8,9 +12,15 @@
@Data
@Accessors(chain = true)
+@TableName("sys_ai_chat_session")
public class AiChatSession implements Serializable {
+ @TableId(value = "id", type = IdType.INPUT)
private String id;
+
+ private Long tenantId;
+
+ private Long userId;
private String title;
@@ -24,4 +34,10 @@
private Date updateTime;
+ private Integer status;
+
+ @TableLogic
+ private Integer deleted;
+
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiDiagnosticToolResult.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiDiagnosticToolResult.java
new file mode 100644
index 0000000..91d0e0f
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiDiagnosticToolResult.java
@@ -0,0 +1,28 @@
+package com.vincent.rsf.server.ai.model;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Map;
+
+@Data
+@Accessors(chain = true)
+public class AiDiagnosticToolResult implements Serializable {
+
+ private String toolCode;
+
+ private String mountCode;
+
+ private String mcpToolName;
+
+ private String toolName;
+
+ private String severity;
+
+ private String summaryText;
+
+ private Map<String, Object> rawMeta;
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiMcpToolDescriptor.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiMcpToolDescriptor.java
new file mode 100644
index 0000000..ccf8b30
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiMcpToolDescriptor.java
@@ -0,0 +1,39 @@
+package com.vincent.rsf.server.ai.model;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Map;
+
+@Data
+@Accessors(chain = true)
+public class AiMcpToolDescriptor implements Serializable {
+
+ private String mountCode;
+
+ private String mountName;
+
+ private String toolCode;
+
+ private String mcpToolName;
+
+ private String toolName;
+
+ private String sceneCode;
+
+ private String description;
+
+ private Integer enabledFlag;
+
+ private Integer priority;
+
+ private String toolPrompt;
+
+ private String usageScope;
+
+ private String transportType;
+
+ private Map<String, Object> inputSchema;
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiPromptContext.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiPromptContext.java
index 5179597..ffbc0e7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiPromptContext.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/model/AiPromptContext.java
@@ -18,4 +18,7 @@
private String modelCode;
private String question;
+
+ private String sceneCode;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiGatewayClient.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiGatewayClient.java
index b41bb2d..53c9066 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiGatewayClient.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiGatewayClient.java
@@ -8,6 +8,7 @@
import javax.annotation.Resource;
import java.io.BufferedReader;
+import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
@@ -24,11 +25,20 @@
private ObjectMapper objectMapper;
public interface StreamCallback {
+ /**
+ * 澶勭悊缃戝叧杩斿洖鐨勪竴鏉� NDJSON 浜嬩欢銆�
+ * 杩斿洖 true 琛ㄧず缁х画娑堣垂鍚庣画浜嬩欢锛岃繑鍥� false 琛ㄧず涓诲姩鍋滄鏈娴佽鍙栥��
+ */
boolean handle(JsonNode event) throws Exception;
}
+ /**
+ * 璋冪敤 AI 缃戝叧鐨勫唴閮ㄦ祦寮忔帴鍙o紝骞跺皢缃戝叧杩斿洖鐨勪簨浠堕�愭潯鍥炶皟缁欎笂灞傜紪鎺掗�昏緫銆�
+ * 杩欓噷灞忚斀浜� HTTP 缁嗚妭锛岃皟鐢ㄦ柟鍙渶瑕佸叧娉� delta / done / error 浜嬩欢鏈韩銆�
+ */
public void stream(GatewayChatRequest request, StreamCallback callback) throws Exception {
HttpURLConnection connection = null;
+ boolean terminalEventReceived = false;
try {
String url = aiProperties.getGatewayBaseUrl() + "/internal/chat/stream";
connection = (HttpURLConnection) new URL(url).openConnection();
@@ -53,10 +63,17 @@
continue;
}
JsonNode event = objectMapper.readTree(line);
+ String type = event.path("type").asText();
+ if ("done".equals(type) || "error".equals(type)) {
+ terminalEventReceived = true;
+ }
if (!callback.handle(event)) {
break;
}
}
+ }
+ if (!terminalEventReceived) {
+ throw new IOException("AI缃戝叧娴佸紓甯镐腑鏂�");
}
} finally {
if (connection != null) {
@@ -66,3 +83,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiModelRouteRuntimeService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiModelRouteRuntimeService.java
new file mode 100644
index 0000000..b57fe3d
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiModelRouteRuntimeService.java
@@ -0,0 +1,145 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
+import com.vincent.rsf.server.system.entity.AiModelRoute;
+import com.vincent.rsf.server.system.service.AiModelRouteService;
+import lombok.Data;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+@Service
+public class AiModelRouteRuntimeService {
+
+ @Resource
+ private AiRuntimeConfigService aiRuntimeConfigService;
+ @Resource
+ private AiModelRouteService aiModelRouteService;
+ @Resource
+ private com.vincent.rsf.server.ai.config.AiProperties aiProperties;
+
+ /**
+ * 涓轰竴娆¤亰澶�/璇婃柇璇锋眰瑙f瀽鍊欓�夋ā鍨嬪垪琛ㄣ��
+ * 椤哄簭鏄細浼氳瘽棣栭�夋ā鍨� -> 褰撳墠鍦烘櫙鍙敤璺敱 -> 榛樿妯″瀷銆�
+ */
+ public List<RouteCandidate> resolveCandidates(Long tenantId, String sceneCode, String preferredModelCode) {
+ List<RouteCandidate> output = new ArrayList<>();
+ Set<String> seen = new LinkedHashSet<>();
+ if (preferredModelCode != null && !preferredModelCode.trim().isEmpty()) {
+ RouteCandidate preferredCandidate = toCandidate(null, preferredModelCode, seen);
+ if (preferredCandidate != null) {
+ output.add(preferredCandidate);
+ }
+ }
+ String routeCode = resolveRouteCode(sceneCode);
+ for (AiModelRoute route : aiModelRouteService.listAvailableRoutes(tenantId, routeCode)) {
+ RouteCandidate candidate = toCandidate(route, route.getModelCode(), seen);
+ if (candidate != null) {
+ output.add(candidate);
+ }
+ }
+ if (output.isEmpty()) {
+ RouteCandidate defaultCandidate = toCandidate(null, aiRuntimeConfigService.resolveDefaultModelCode(), seen);
+ if (defaultCandidate != null) {
+ output.add(defaultCandidate);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * 鏍囪鏌愭潯妯″瀷璺敱鏈璋冪敤鎴愬姛锛屽苟娓呯┖澶辫触璁℃暟鍜屽喎鍗寸姸鎬併��
+ */
+ public void markSuccess(Long routeId) {
+ if (routeId == null) {
+ return;
+ }
+ aiModelRouteService.update(new LambdaUpdateWrapper<AiModelRoute>()
+ .setSql("success_count = IFNULL(success_count, 0) + 1")
+ .set(AiModelRoute::getFailCount, 0)
+ .set(AiModelRoute::getCooldownUntil, null)
+ .eq(AiModelRoute::getId, routeId));
+ }
+
+ /**
+ * 鏍囪鏌愭潯妯″瀷璺敱鏈璋冪敤澶辫触銆�
+ * 杩炵画澶辫触杈惧埌闃堝�煎悗浼氳繘鍏ュ喎鍗存湡锛岄伩鍏嶇煭鏃堕棿鍐呮寔缁懡涓晠闅滄ā鍨嬨��
+ */
+ public void markFailure(Long routeId) {
+ if (routeId == null) {
+ return;
+ }
+ AiModelRoute route = aiModelRouteService.getById(routeId);
+ if (route == null) {
+ return;
+ }
+ int nextFailCount = (route.getFailCount() == null ? 0 : route.getFailCount()) + 1;
+ Date cooldownUntil = route.getCooldownUntil();
+ if (nextFailCount >= aiProperties.getRouteFailThreshold()) {
+ cooldownUntil = new Date(System.currentTimeMillis() + aiProperties.getRouteCooldownMinutes() * 60_000L);
+ }
+ route.setFailCount(nextFailCount);
+ route.setCooldownUntil(cooldownUntil);
+ aiModelRouteService.updateById(route);
+ }
+
+ /**
+ * 鎵嬪姩閲嶇疆涓�鏉¤矾鐢辩殑鎴愬姛/澶辫触缁熻鍜屽喎鍗存椂闂淬��
+ */
+ public void resetRoute(Long routeId) {
+ if (routeId == null) {
+ return;
+ }
+ aiModelRouteService.update(new LambdaUpdateWrapper<AiModelRoute>()
+ .set(AiModelRoute::getFailCount, 0)
+ .set(AiModelRoute::getSuccessCount, 0)
+ .set(AiModelRoute::getCooldownUntil, null)
+ .eq(AiModelRoute::getId, routeId));
+ }
+
+ /**
+ * 灏嗚亰澶╁満鏅粺涓�鏄犲皠鎴愯矾鐢辩粍缂栫爜銆�
+ */
+ private String resolveRouteCode(String sceneCode) {
+ if (AiSceneCode.SYSTEM_DIAGNOSE.equals(sceneCode)) {
+ return AiSceneCode.SYSTEM_DIAGNOSE;
+ }
+ return AiSceneCode.GENERAL_CHAT;
+ }
+
+ /**
+ * 鍩轰簬妯″瀷缂栫爜鍜岃矾鐢辫褰曠粍瑁呰繍琛屾椂鍊欓�夐」锛屽苟璐熻矗鍘婚噸涓庡彲鐢ㄦ�ф牎楠屻��
+ */
+ private RouteCandidate toCandidate(AiModelRoute route, String modelCode, Set<String> seen) {
+ if (modelCode == null || modelCode.trim().isEmpty() || seen.contains(modelCode)) {
+ return null;
+ }
+ AiRuntimeConfigService.ModelRuntimeConfig runtimeConfig = aiRuntimeConfigService.resolveModel(modelCode);
+ if (runtimeConfig == null || !Boolean.TRUE.equals(runtimeConfig.getEnabled())) {
+ return null;
+ }
+ seen.add(modelCode);
+ RouteCandidate candidate = new RouteCandidate();
+ candidate.setRouteId(route == null ? null : route.getId());
+ candidate.setRouteCode(route == null ? null : route.getRouteCode());
+ candidate.setAttemptModelCode(runtimeConfig.getCode());
+ candidate.setRuntimeConfig(runtimeConfig);
+ return candidate;
+ }
+
+ @Data
+ public static class RouteCandidate {
+ private Long routeId;
+ private String routeCode;
+ private String attemptModelCode;
+ private AiRuntimeConfigService.ModelRuntimeConfig runtimeConfig;
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptContextProvider.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptContextProvider.java
index 2592d9f..3e3ddee 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptContextProvider.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptContextProvider.java
@@ -4,7 +4,14 @@
public interface AiPromptContextProvider {
+ /**
+ * 鍒ゆ柇褰撳墠涓婁笅鏂囨槸鍚﹂渶瑕佺敱璇ユ彁渚涘櫒琛ュ厖绯荤粺鎻愮ず璇嶃��
+ */
boolean supports(AiPromptContext context);
+ /**
+ * 鏍规嵁褰撳墠璇锋眰涓婁笅鏂囩敓鎴愪竴娈靛彲闄勫姞鍒扮郴缁� Prompt 鐨勪笟鍔¤儗鏅弿杩般��
+ */
String buildContext(AiPromptContext context);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptContextService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptContextService.java
index 3a91504..00a81f0 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptContextService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptContextService.java
@@ -15,6 +15,10 @@
this.providers = providers == null ? new ArrayList<>() : providers;
}
+ /**
+ * 灏嗗熀纭� Prompt 涓庢墍鏈夊懡涓殑涓婁笅鏂囨彁渚涘櫒缁撴灉鎷艰鎴愭渶缁堢郴缁熸彁绀鸿瘝銆�
+ * 鏅�氳亰澶╁満鏅富瑕佷緷璧栬繖鏉¢摼琛ュ厖涓氬姟鑳屾櫙锛岃瘖鏂満鏅垯鍦ㄦ鍩虹涓婂彔鍔犲伐鍏锋憳瑕併��
+ */
public String buildSystemPrompt(String basePrompt, AiPromptContext context) {
List<String> promptParts = new ArrayList<>();
if (basePrompt != null && !basePrompt.trim().isEmpty()) {
@@ -35,3 +39,4 @@
return String.join("\n\n", promptParts);
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptRuntimeService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptRuntimeService.java
new file mode 100644
index 0000000..3b9ae62
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptRuntimeService.java
@@ -0,0 +1,87 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.vincent.rsf.server.ai.config.AiProperties;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+import com.vincent.rsf.server.ai.service.diagnosis.AiDiagnosticToolService;
+import com.vincent.rsf.server.system.entity.AiPromptTemplate;
+import com.vincent.rsf.server.system.service.AiPromptTemplateService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class AiPromptRuntimeService {
+
+ @Resource
+ private AiProperties aiProperties;
+ @Resource
+ private AiPromptContextService aiPromptContextService;
+ @Resource
+ private AiPromptTemplateService aiPromptTemplateService;
+ @Resource
+ private AiDiagnosticToolService aiDiagnosticToolService;
+
+ /**
+ * 鏋勯�犳寚瀹氬満鏅殑绯荤粺 Prompt銆�
+ * 褰撴湭鏄惧紡浼犲叆璇婃柇宸ュ叿缁撴灉鏃讹紝璇婃柇鍦烘櫙浼氬湪鍐呴儴涓诲姩鏀堕泦涓�娆″伐鍏锋憳瑕併��
+ */
+ public String buildSystemPrompt(String sceneCode, String fallbackBasePrompt, AiPromptContext context) {
+ return buildSystemPrompt(sceneCode, fallbackBasePrompt, context, null);
+ }
+
+ /**
+ * 鏋勯�犳渶缁堢郴缁� Prompt锛屾寜鈥滃凡鍙戝竷妯℃澘/榛樿妯℃澘 + 涓婁笅鏂� + 宸ュ叿鎽樿鈥濈殑椤哄簭鎷艰銆�
+ */
+ public String buildSystemPrompt(String sceneCode, String fallbackBasePrompt, AiPromptContext context,
+ List<AiDiagnosticToolResult> diagnosticResults) {
+ String basePrompt = resolveBasePrompt(sceneCode, fallbackBasePrompt, context.getTenantId());
+ List<AiDiagnosticToolResult> results = diagnosticResults;
+ if (results == null && AiSceneCode.SYSTEM_DIAGNOSE.equals(sceneCode)) {
+ results = aiDiagnosticToolService.collect(context);
+ }
+ if (results != null && !results.isEmpty()) {
+ List<String> parts = new ArrayList<>();
+ String contextPrompt = aiPromptContextService.buildSystemPrompt(basePrompt, context);
+ if (contextPrompt != null && !contextPrompt.trim().isEmpty()) {
+ parts.add(contextPrompt.trim());
+ }
+ String diagnosticPrompt = aiDiagnosticToolService.buildPrompt(context.getTenantId(), sceneCode, results);
+ if (!diagnosticPrompt.trim().isEmpty()) {
+ parts.add(diagnosticPrompt);
+ }
+ return String.join("\n\n", parts);
+ }
+ return aiPromptContextService.buildSystemPrompt(basePrompt, context);
+ }
+
+ /**
+ * 瑙f瀽褰撳墠鍦烘櫙鐨勫熀纭� Prompt銆�
+ * 浼樺厛浣跨敤宸插彂甯冪殑 Prompt 妯℃澘锛涜嫢妯℃澘涓嶅瓨鍦ㄦ垨涓虹┖锛屽垯鍥為��鍒伴厤缃枃浠朵腑鐨勯粯璁ゆ彁绀鸿瘝銆�
+ */
+ private String resolveBasePrompt(String sceneCode, String fallbackBasePrompt, Long tenantId) {
+ AiPromptTemplate publishedTemplate = aiPromptTemplateService.getPublishedTemplate(tenantId, sceneCode);
+ if (publishedTemplate == null) {
+ return aiProperties.buildScenePrompt(sceneCode, fallbackBasePrompt);
+ }
+ List<String> parts = new ArrayList<>();
+ if (publishedTemplate.getBasePrompt() != null && !publishedTemplate.getBasePrompt().trim().isEmpty()) {
+ parts.add(publishedTemplate.getBasePrompt().trim());
+ }
+ if (publishedTemplate.getToolPrompt() != null && !publishedTemplate.getToolPrompt().trim().isEmpty()) {
+ parts.add(publishedTemplate.getToolPrompt().trim());
+ }
+ if (publishedTemplate.getOutputPrompt() != null && !publishedTemplate.getOutputPrompt().trim().isEmpty()) {
+ parts.add(publishedTemplate.getOutputPrompt().trim());
+ }
+ if (parts.isEmpty()) {
+ return aiProperties.buildScenePrompt(sceneCode, fallbackBasePrompt);
+ }
+ return String.join("\n\n", parts);
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiRuntimeConfigService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiRuntimeConfigService.java
index 9790dda..bf3ce00 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiRuntimeConfigService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiRuntimeConfigService.java
@@ -18,6 +18,10 @@
@Resource
private AiParamService aiParamService;
+ /**
+ * 鏋氫妇褰撳墠鍙敤鐨勬ā鍨嬭繍琛屾椂閰嶇疆銆�
+ * 浼樺厛浠庢暟鎹簱璇诲彇绉熸埛鍙繍钀ョ殑妯″瀷鍙傛暟锛涙暟鎹簱涓嶅彲鐢ㄦ椂鍥為��鍒� application 閰嶇疆銆�
+ */
public List<ModelRuntimeConfig> listEnabledModels() {
List<ModelRuntimeConfig> output = new ArrayList<>();
try {
@@ -43,6 +47,10 @@
return output;
}
+ /**
+ * 瑙f瀽鎸囧畾妯″瀷缂栫爜瀵瑰簲鐨勮繍琛屾椂閰嶇疆銆�
+ * 濡傛灉鏈寚瀹氭ā鍨嬬紪鐮侊紝鍒欒繑鍥炲綋鍓嶉粯璁ゆā鍨嬶紱濡傛灉鏁版嵁搴撴棤璁板綍锛屽垯鍥為��鍒伴潤鎬侀厤缃��
+ */
public ModelRuntimeConfig resolveModel(String modelCode) {
try {
AiParam aiParam;
@@ -82,10 +90,16 @@
return config;
}
+ /**
+ * 鑾峰彇绯荤粺褰撳墠榛樿妯″瀷缂栫爜銆�
+ */
public String resolveDefaultModelCode() {
return resolveModel(null).getCode();
}
+ /**
+ * 灏嗘暟鎹簱涓殑 AI 鍙傛暟瀹炰綋杞崲涓鸿繍琛屾椂缁熶竴浣跨敤鐨勬ā鍨嬮厤缃璞°��
+ */
private ModelRuntimeConfig toRuntimeConfig(AiParam aiParam) {
ModelRuntimeConfig config = new ModelRuntimeConfig();
config.setCode(aiParam.getModelCode());
@@ -117,3 +131,4 @@
private Boolean enabled;
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiSessionService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiSessionService.java
index 189fcab..0eea538 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiSessionService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiSessionService.java
@@ -7,28 +7,65 @@
public interface AiSessionService {
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鍙鐨� AI 浼氳瘽鍒楄〃锛屾寜鏈�杩戞洿鏂版椂闂村�掑簭杩斿洖銆�
+ */
List<AiChatSession> listSessions(Long tenantId, Long userId);
+ /**
+ * 鏄惧紡鍒涘缓涓�涓柊浼氳瘽锛屽苟鏍规嵁浼犲叆妯″瀷鎴栭粯璁ゆā鍨嬪垵濮嬪寲浼氳瘽鍏冩暟鎹��
+ */
AiChatSession createSession(Long tenantId, Long userId, String title, String modelCode);
+ /**
+ * 纭繚鎸囧畾浼氳瘽瀛樺湪锛涘鏋滀細璇濅笉瀛樺湪鍒欒嚜鍔ㄥ垱寤猴紝瀛樺湪鏃跺彲椤哄甫鏇存柊妯″瀷鍋忓ソ銆�
+ */
AiChatSession ensureSession(Long tenantId, Long userId, String sessionId, String modelCode);
+ /**
+ * 鎸夌鎴枫�佺敤鎴峰拰浼氳瘽 ID 绮剧‘璇诲彇浼氳瘽锛岄伩鍏嶈法绉熸埛/璺ㄧ敤鎴蜂覆浼氳瘽銆�
+ */
AiChatSession getSession(Long tenantId, Long userId, String sessionId);
+ /**
+ * 閲嶅懡鍚嶄細璇濇爣棰樸��
+ */
AiChatSession renameSession(Long tenantId, Long userId, String sessionId, String title);
+ /**
+ * 鍒犻櫎浼氳瘽鍙婂叾鑱婂ぉ娑堟伅銆�
+ */
void removeSession(Long tenantId, Long userId, String sessionId);
+ /**
+ * 鏌ヨ浼氳瘽涓嬬殑瀹屾暣娑堟伅鍒楄〃銆�
+ */
List<AiChatMessage> listMessages(Long tenantId, Long userId, String sessionId);
+ /**
+ * 鏌ヨ鏋勯�犱笂涓嬫枃鎵�闇�鐨勬渶杩戣嫢骞叉潯娑堟伅銆�
+ */
List<AiChatMessage> listContextMessages(Long tenantId, Long userId, String sessionId, int maxCount);
+ /**
+ * 杩藉姞涓�鏉¤亰澶╂秷鎭紝骞跺悓姝ュ埛鏂颁細璇濇渶鍚庢秷鎭�佹渶鍚庢椿璺冩椂闂村拰妯″瀷淇℃伅銆�
+ */
AiChatMessage appendMessage(Long tenantId, Long userId, String sessionId, String role, String content, String modelCode);
+ /**
+ * 娓呴櫎浼氳瘽鐨勨�滃仠姝㈢敓鎴愨�濇爣璁帮紝閫氬父鍦ㄤ竴娆℃祦寮忓璇濇敹灏炬椂璋冪敤銆�
+ */
void clearStopFlag(String sessionId);
+ /**
+ * 鏍囪浼氳瘽闇�瑕佸仠姝㈢敓鎴愶紝渚涙祦寮忕紪鎺掔嚎绋嬭疆璇㈡秷璐广��
+ */
void requestStop(String sessionId);
+ /**
+ * 鍒ゆ柇褰撳墠浼氳瘽鏄惁宸叉敹鍒板仠姝㈢敓鎴愯姹傘��
+ */
boolean isStopRequested(String sessionId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiTextCompletionService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiTextCompletionService.java
new file mode 100644
index 0000000..4a54c31
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiTextCompletionService.java
@@ -0,0 +1,40 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.vincent.rsf.server.ai.dto.GatewayChatRequest;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+@Service
+public class AiTextCompletionService {
+
+ @Resource
+ private AiGatewayClient aiGatewayClient;
+
+ /**
+ * 鐢ㄦ祦寮忕綉鍏虫帴鍙fā鎷熶竴娆♀�滃悓姝ヨˉ鍏ㄦ枃鏈�濊皟鐢ㄣ��
+ * 閫傚悎宸ュ叿瑙勫垝銆佺粨鏋勫寲 JSON 閫夋嫨杩欑被鐭枃鏈换鍔★紝鍐呴儴浠嶇劧澶嶇敤缁熶竴鐨勬祦寮忕綉鍏宠兘鍔涖��
+ */
+ public String complete(GatewayChatRequest request) throws Exception {
+ final StringBuilder output = new StringBuilder();
+ aiGatewayClient.stream(request, event -> handleEvent(event, output));
+ return output.toString().trim();
+ }
+
+ /**
+ * 鍙敹闆� delta 鏂囨湰锛屽苟鎶� error / done 浜嬩欢杞崲鎴愬悓姝ヨ皟鐢ㄨ涔夈��
+ */
+ private boolean handleEvent(JsonNode event, StringBuilder output) {
+ String type = event.path("type").asText("");
+ if ("delta".equals(type)) {
+ output.append(event.path("content").asText(""));
+ return true;
+ }
+ if ("error".equals(type)) {
+ throw new IllegalStateException(event.path("message").asText("妯″瀷璋冪敤澶辫触"));
+ }
+ return !"done".equals(type);
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiChatStreamOrchestrator.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiChatStreamOrchestrator.java
new file mode 100644
index 0000000..fd6386f
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiChatStreamOrchestrator.java
@@ -0,0 +1,467 @@
+package com.vincent.rsf.server.ai.service.diagnosis;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
+import com.vincent.rsf.server.ai.dto.AiChatStreamRequest;
+import com.vincent.rsf.server.ai.dto.GatewayChatMessage;
+import com.vincent.rsf.server.ai.dto.GatewayChatRequest;
+import com.vincent.rsf.server.ai.model.AiChatMessage;
+import com.vincent.rsf.server.ai.model.AiChatSession;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+import com.vincent.rsf.server.ai.service.AiGatewayClient;
+import com.vincent.rsf.server.ai.service.AiModelRouteRuntimeService;
+import com.vincent.rsf.server.ai.service.AiPromptRuntimeService;
+import com.vincent.rsf.server.ai.service.AiSessionService;
+import com.vincent.rsf.server.system.entity.AiDiagnosisRecord;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class AiChatStreamOrchestrator {
+
+ @Resource
+ private AiSessionService aiSessionService;
+ @Resource
+ private AiGatewayClient aiGatewayClient;
+ @Resource
+ private AiPromptRuntimeService aiPromptRuntimeService;
+ @Resource
+ private AiDiagnosticToolService aiDiagnosticToolService;
+ @Resource
+ private AiModelRouteRuntimeService aiModelRouteRuntimeService;
+ @Resource
+ private AiDiagnosisRuntimeService aiDiagnosisRuntimeService;
+ @Resource
+ private AiDiagnosisMcpRuntimeService aiDiagnosisMcpRuntimeService;
+
+ /**
+ * 浠庡�欓�夋ā鍨嬪垪琛ㄤ腑鎸戝嚭鏈�澶х殑涓婁笅鏂囩獥鍙o紝鐢ㄤ簬鎻愬墠鎴柇浼氳瘽鍘嗗彶銆�
+ */
+ public int resolveContextSize(List<AiModelRouteRuntimeService.RouteCandidate> candidates) {
+ int max = 12;
+ for (AiModelRouteRuntimeService.RouteCandidate item : candidates) {
+ if (item != null && item.getRuntimeConfig() != null
+ && item.getRuntimeConfig().getMaxContextMessages() != null
+ && item.getRuntimeConfig().getMaxContextMessages() > max) {
+ max = item.getRuntimeConfig().getMaxContextMessages();
+ }
+ }
+ return max;
+ }
+
+ /**
+ * 鎵ц涓�娆″畬鏁寸殑娴佸紡鑱婂ぉ/璇婃柇缂栨帓銆�
+ * 杩欓噷缁熶竴璐熻矗 MCP 缁撴灉鍑嗗銆佹ā鍨嬮噸璇曘�佷簨浠惰浆鍙戙�佽皟鐢ㄦ棩蹇楀拰璇婃柇鏀跺熬銆�
+ */
+ public void executeStream(SseEmitter emitter,
+ Long tenantId,
+ Long userId,
+ AiChatSession session,
+ AiChatStreamRequest request,
+ AiPromptContext promptContext,
+ List<AiChatMessage> contextMessages,
+ AiDiagnosisRecord diagnosisRecord,
+ List<AiModelRouteRuntimeService.RouteCandidate> candidates) {
+ StringBuilder assistantReply = new StringBuilder();
+ String finalModelCode = session.getModelCode();
+ String finalErrorMessage = null;
+ boolean stopped = false;
+ boolean success = false;
+ boolean assistantSaved = false;
+ boolean errorSent = false;
+ List<AiDiagnosticToolResult> runtimeDiagnosticResults = new ArrayList<>();
+ String toolSummary = "[]";
+ try {
+ emitter.send(SseEmitter.event().name("session").data(buildSessionPayload(session), MediaType.APPLICATION_JSON));
+ if (aiDiagnosisMcpRuntimeService.shouldUseMcp(promptContext)) {
+ runtimeDiagnosticResults = aiDiagnosisMcpRuntimeService.resolveToolResults(
+ tenantId,
+ promptContext,
+ contextMessages,
+ candidates.isEmpty() ? null : candidates.get(0)
+ );
+ toolSummary = aiDiagnosticToolService.serializeResults(runtimeDiagnosticResults);
+ }
+ int attemptNo = 1;
+ for (AiModelRouteRuntimeService.RouteCandidate candidate : candidates) {
+ AttemptState attemptState = new AttemptState();
+ Date requestTime = new Date();
+ try {
+ GatewayChatRequest gatewayChatRequest = buildGatewayRequest(
+ session,
+ contextMessages,
+ candidate,
+ request,
+ promptContext,
+ runtimeDiagnosticResults,
+ attemptNo
+ );
+ aiGatewayClient.stream(gatewayChatRequest, event -> handleGatewayEvent(
+ emitter,
+ event,
+ session,
+ assistantReply,
+ attemptState
+ ));
+ } catch (Exception e) {
+ attemptState.setSuccess(false);
+ attemptState.setErrorMessage(e.getMessage());
+ attemptState.setInterrupted(isInterruptedError(e));
+ attemptState.setResponseTime(new Date());
+ }
+ if (attemptState.getResponseTime() == null) {
+ attemptState.setResponseTime(new Date());
+ }
+ String actualModelCode = attemptState.getActualModelCode() == null
+ ? candidate.getAttemptModelCode()
+ : attemptState.getActualModelCode();
+ finalModelCode = actualModelCode;
+ aiDiagnosisRuntimeService.saveCallLog(
+ tenantId,
+ userId,
+ session.getId(),
+ diagnosisRecord == null ? null : diagnosisRecord.getId(),
+ resolveRouteCode(candidate, request),
+ actualModelCode,
+ attemptNo,
+ requestTime,
+ attemptState.getResponseTime(),
+ Boolean.TRUE.equals(attemptState.getSuccess()) ? 1 : 0,
+ attemptState.getErrorMessage()
+ );
+ if (Boolean.TRUE.equals(attemptState.getSuccess())) {
+ aiModelRouteRuntimeService.markSuccess(candidate.getRouteId());
+ success = assistantReply.length() > 0;
+ if (!success) {
+ finalErrorMessage = "妯″瀷鏈繑鍥炴湁鏁堝唴瀹�";
+ }
+ break;
+ }
+ if (attemptState.isStopped() || aiSessionService.isStopRequested(session.getId())) {
+ stopped = true;
+ break;
+ }
+ if (!attemptState.isInterrupted()) {
+ aiModelRouteRuntimeService.markFailure(candidate.getRouteId());
+ }
+ finalErrorMessage = attemptState.getErrorMessage();
+ if (attemptState.isReceivedDelta() || attemptNo >= candidates.size()) {
+ if (!attemptState.isInterrupted() && finalErrorMessage != null && !finalErrorMessage.trim().isEmpty()) {
+ emitter.send(SseEmitter.event().name("error").data(buildErrorPayload(finalErrorMessage), MediaType.APPLICATION_JSON));
+ errorSent = true;
+ }
+ break;
+ }
+ attemptNo++;
+ }
+
+ if (aiSessionService.isStopRequested(session.getId())) {
+ stopped = true;
+ }
+
+ if (stopped) {
+ if (assistantReply.length() > 0) {
+ aiSessionService.appendMessage(tenantId, userId, session.getId(), "assistant", assistantReply.toString(), finalModelCode);
+ assistantSaved = true;
+ }
+ emitter.send(SseEmitter.event().name("done").data(buildDonePayload(session, finalModelCode, true), MediaType.APPLICATION_JSON));
+ if (diagnosisRecord != null) {
+ aiDiagnosisRuntimeService.finishDiagnosisFailure(diagnosisRecord, assistantReply.toString(), "鐢ㄦ埛宸插仠姝㈢敓鎴�", toolSummary);
+ }
+ return;
+ }
+
+ if (success) {
+ aiSessionService.appendMessage(tenantId, userId, session.getId(), "assistant", assistantReply.toString(), finalModelCode);
+ assistantSaved = true;
+ emitter.send(SseEmitter.event().name("done").data(buildDonePayload(session, finalModelCode, false), MediaType.APPLICATION_JSON));
+ if (diagnosisRecord != null) {
+ aiDiagnosisRuntimeService.finishDiagnosisSuccess(diagnosisRecord, assistantReply.toString(), finalModelCode, toolSummary);
+ }
+ return;
+ }
+
+ if (assistantReply.length() > 0 && !assistantSaved) {
+ aiSessionService.appendMessage(tenantId, userId, session.getId(), "assistant", assistantReply.toString(), finalModelCode);
+ assistantSaved = true;
+ }
+ if (diagnosisRecord != null) {
+ aiDiagnosisRuntimeService.finishDiagnosisFailure(diagnosisRecord, assistantReply.toString(), finalErrorMessage, toolSummary);
+ }
+ if (!errorSent && finalErrorMessage != null && !finalErrorMessage.trim().isEmpty()) {
+ emitter.send(SseEmitter.event().name("error").data(buildErrorPayload(finalErrorMessage), MediaType.APPLICATION_JSON));
+ }
+ } catch (Exception e) {
+ if (diagnosisRecord != null) {
+ aiDiagnosisRuntimeService.finishDiagnosisFailure(diagnosisRecord, assistantReply.toString(), e.getMessage(), toolSummary);
+ }
+ if (!isInterruptedError(e)) {
+ try {
+ emitter.send(SseEmitter.event().name("error").data(buildErrorPayload(e.getMessage()), MediaType.APPLICATION_JSON));
+ } catch (IOException ignore) {
+ }
+ } else {
+ Thread.currentThread().interrupt();
+ }
+ } finally {
+ emitter.complete();
+ aiSessionService.clearStopFlag(session.getId());
+ }
+ }
+
+ /**
+ * 娑堣垂缃戝叧杩斿洖鐨勫崟鏉℃祦寮忎簨浠讹紝骞舵妸鐘舵�佸啓鍥炴湰娆″皾璇曚笂涓嬫枃銆�
+ */
+ private boolean handleGatewayEvent(SseEmitter emitter, JsonNode event, AiChatSession session,
+ StringBuilder assistantReply, AttemptState attemptState) throws Exception {
+ if (aiSessionService.isStopRequested(session.getId())) {
+ attemptState.setStopped(true);
+ attemptState.setResponseTime(new Date());
+ return false;
+ }
+ String type = event.path("type").asText();
+ String modelCode = event.path("modelCode").asText(session.getModelCode());
+ if ("delta".equals(type)) {
+ String content = event.path("content").asText("");
+ assistantReply.append(content);
+ attemptState.setReceivedDelta(true);
+ attemptState.setActualModelCode(modelCode);
+ emitter.send(SseEmitter.event().name("delta").data(buildDeltaPayload(session, modelCode, content), MediaType.APPLICATION_JSON));
+ return true;
+ }
+ if ("error".equals(type)) {
+ String message = event.path("message").asText("妯″瀷璋冪敤澶辫触");
+ attemptState.setSuccess(false);
+ attemptState.setErrorMessage(message);
+ attemptState.setActualModelCode(modelCode);
+ attemptState.setResponseTime(parseResponseTime(event));
+ attemptState.setInterrupted(isInterruptedMessage(message));
+ return false;
+ }
+ if ("done".equals(type)) {
+ attemptState.setSuccess(true);
+ attemptState.setActualModelCode(modelCode);
+ attemptState.setResponseTime(parseResponseTime(event));
+ return false;
+ }
+ if ("ping".equals(type)) {
+ emitter.send(SseEmitter.event().name("ping").data(buildPingPayload(modelCode), MediaType.APPLICATION_JSON));
+ return true;
+ }
+ return true;
+ }
+
+ /**
+ * 灏嗗綋鍓嶅皾璇曢渶瑕佺殑涓婁笅鏂囥�佺郴缁� Prompt 鍜屾ā鍨嬩俊鎭粍瑁呮垚缃戝叧璇锋眰銆�
+ */
+ private GatewayChatRequest buildGatewayRequest(AiChatSession session,
+ List<AiChatMessage> contextMessages,
+ AiModelRouteRuntimeService.RouteCandidate candidate,
+ AiChatStreamRequest chatRequest,
+ AiPromptContext promptContext,
+ List<AiDiagnosticToolResult> diagnosticResults,
+ Integer attemptNo) {
+ GatewayChatRequest request = new GatewayChatRequest();
+ request.setSessionId(session.getId());
+ request.setModelCode(candidate.getAttemptModelCode());
+ request.setRouteCode(resolveRouteCode(candidate, chatRequest));
+ request.setAttemptNo(attemptNo);
+ request.setSystemPrompt(aiPromptRuntimeService.buildSystemPrompt(
+ chatRequest.getSceneCode(),
+ candidate.getRuntimeConfig().getSystemPrompt(),
+ promptContext,
+ diagnosticResults
+ ));
+ request.setChatUrl(candidate.getRuntimeConfig().getChatUrl());
+ request.setApiKey(candidate.getRuntimeConfig().getApiKey());
+ request.setModelName(candidate.getRuntimeConfig().getModelName());
+ for (AiChatMessage contextMessage : contextMessages) {
+ GatewayChatMessage item = new GatewayChatMessage();
+ item.setRole(contextMessage.getRole());
+ item.setContent(contextMessage.getContent());
+ request.getMessages().add(item);
+ }
+ return request;
+ }
+
+ /**
+ * 瑙f瀽褰撳墠灏濊瘯搴旇惤鍒板摢涓矾鐢辩紪鐮侊紝浼樺厛浣跨敤璺敱鍊欓�夎嚜甯︾殑 routeCode銆�
+ */
+ private String resolveRouteCode(AiModelRouteRuntimeService.RouteCandidate candidate, AiChatStreamRequest request) {
+ if (candidate != null && candidate.getRouteCode() != null && !candidate.getRouteCode().trim().isEmpty()) {
+ return candidate.getRouteCode();
+ }
+ return AiSceneCode.SYSTEM_DIAGNOSE.equals(request.getSceneCode())
+ ? AiSceneCode.SYSTEM_DIAGNOSE
+ : AiSceneCode.GENERAL_CHAT;
+ }
+
+ /**
+ * 浠庣綉鍏充簨浠朵腑瑙f瀽鍝嶅簲鏃堕棿锛岀己鐪佹椂鍥為��涓哄綋鍓嶆椂闂淬��
+ */
+ private Date parseResponseTime(JsonNode event) {
+ long millis = event.path("responseTime").asLong(0L);
+ return millis <= 0L ? new Date() : new Date(millis);
+ }
+
+ /**
+ * 鏋勯�犱細璇濆垵濮嬪寲浜嬩欢缁欏墠绔��
+ */
+ private Map<String, Object> buildSessionPayload(AiChatSession session) {
+ Map<String, Object> payload = new LinkedHashMap<>();
+ payload.put("sessionId", session.getId());
+ payload.put("title", session.getTitle());
+ payload.put("modelCode", session.getModelCode());
+ return payload;
+ }
+
+ /**
+ * 鏋勯�犲閲忚緭鍑轰簨浠剁粰鍓嶇銆�
+ */
+ private Map<String, Object> buildDeltaPayload(AiChatSession session, String modelCode, String content) {
+ Map<String, Object> payload = new LinkedHashMap<>();
+ payload.put("sessionId", session.getId());
+ payload.put("modelCode", modelCode);
+ payload.put("content", content);
+ payload.put("timestamp", new Date().getTime());
+ return payload;
+ }
+
+ /**
+ * 鏋勯�犳祦寮忓畬鎴愪簨浠剁粰鍓嶇銆�
+ */
+ private Map<String, Object> buildDonePayload(AiChatSession session, String modelCode, boolean stopped) {
+ Map<String, Object> payload = new LinkedHashMap<>();
+ payload.put("sessionId", session.getId());
+ payload.put("modelCode", modelCode);
+ payload.put("stopped", stopped);
+ return payload;
+ }
+
+ /**
+ * 鏋勯�犻敊璇簨浠剁粰鍓嶇銆�
+ */
+ private Map<String, Object> buildErrorPayload(String message) {
+ Map<String, Object> payload = new LinkedHashMap<>();
+ payload.put("message", message == null ? "AI鏈嶅姟寮傚父" : message);
+ return payload;
+ }
+
+ /**
+ * 鏋勯�犲績璺充簨浠剁粰鍓嶇锛岀敤浜庣淮鎸侀暱杩炴帴娲绘�с��
+ */
+ private Map<String, Object> buildPingPayload(String modelCode) {
+ Map<String, Object> payload = new LinkedHashMap<>();
+ payload.put("modelCode", modelCode);
+ payload.put("timestamp", new Date().getTime());
+ return payload;
+ }
+
+ /**
+ * 鍒ゆ柇寮傚父閾句腑鏄惁鍖呭惈绾跨▼涓柇绫婚敊璇��
+ */
+ private boolean isInterruptedError(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (current instanceof InterruptedException || current instanceof InterruptedIOException) {
+ return true;
+ }
+ if (isInterruptedMessage(current.getMessage())) {
+ return true;
+ }
+ current = current.getCause();
+ }
+ return false;
+ }
+
+ private boolean isInterruptedMessage(String message) {
+ if (message == null || message.trim().isEmpty()) {
+ return false;
+ }
+ String normalized = message.toLowerCase();
+ return normalized.contains("interrupted")
+ || normalized.contains("broken pipe")
+ || normalized.contains("connection reset")
+ || normalized.contains("forcibly closed");
+ }
+
+ private static class AttemptState {
+ private Boolean success;
+ private String actualModelCode;
+ private String errorMessage;
+ private boolean receivedDelta;
+ private boolean interrupted;
+ private boolean stopped;
+ private Date responseTime;
+
+ public Boolean getSuccess() {
+ return success;
+ }
+
+ public void setSuccess(Boolean success) {
+ this.success = success;
+ }
+
+ public String getActualModelCode() {
+ return actualModelCode;
+ }
+
+ public void setActualModelCode(String actualModelCode) {
+ this.actualModelCode = actualModelCode;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public void setErrorMessage(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+ public boolean isReceivedDelta() {
+ return receivedDelta;
+ }
+
+ public void setReceivedDelta(boolean receivedDelta) {
+ this.receivedDelta = receivedDelta;
+ }
+
+ public boolean isInterrupted() {
+ return interrupted;
+ }
+
+ public void setInterrupted(boolean interrupted) {
+ this.interrupted = interrupted;
+ }
+
+ public boolean isStopped() {
+ return stopped;
+ }
+
+ public void setStopped(boolean stopped) {
+ this.stopped = stopped;
+ }
+
+ public Date getResponseTime() {
+ return responseTime;
+ }
+
+ public void setResponseTime(Date responseTime) {
+ this.responseTime = responseTime;
+ }
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisMcpRuntimeService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisMcpRuntimeService.java
new file mode 100644
index 0000000..618ccaf
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisMcpRuntimeService.java
@@ -0,0 +1,426 @@
+package com.vincent.rsf.server.ai.service.diagnosis;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vincent.rsf.server.ai.constant.AiMcpConstants;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
+import com.vincent.rsf.server.ai.dto.GatewayChatMessage;
+import com.vincent.rsf.server.ai.dto.GatewayChatRequest;
+import com.vincent.rsf.server.ai.model.AiChatMessage;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiMcpToolDescriptor;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+import com.vincent.rsf.server.ai.service.AiModelRouteRuntimeService;
+import com.vincent.rsf.server.ai.service.AiTextCompletionService;
+import com.vincent.rsf.server.ai.service.mcp.AiMcpRegistryService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Service
+public class AiDiagnosisMcpRuntimeService {
+
+ @Resource
+ private AiMcpRegistryService aiMcpRegistryService;
+ @Resource
+ private AiDiagnosticToolService aiDiagnosticToolService;
+ @Resource
+ private AiTextCompletionService aiTextCompletionService;
+ @Resource
+ private ObjectMapper objectMapper;
+
+ /**
+ * 鏍规嵁褰撳墠闂銆佷笂涓嬫枃娑堟伅鍜屽彲鐢ㄦā鍨嬶紝瑙f瀽鍑烘湰杞湡姝h鎵ц鐨� MCP 宸ュ叿缁撴灉銆�
+ * 鍏堝仛宸ュ叿杩囨护锛屽啀鍋氬惎鍙戝紡鎴栨ā鍨嬭鍒掗�夋嫨锛屾渶鍚庨『搴忔墽琛屽伐鍏枫��
+ */
+ public List<AiDiagnosticToolResult> resolveToolResults(Long tenantId,
+ AiPromptContext context,
+ List<AiChatMessage> contextMessages,
+ AiModelRouteRuntimeService.RouteCandidate plannerCandidate) {
+ List<AiMcpToolDescriptor> tools = filterTools(aiMcpRegistryService.listTools(tenantId, null), context.getSceneCode());
+ if (tools.isEmpty()) {
+ return fallbackResults(context);
+ }
+ List<AiMcpToolDescriptor> selectedTools = selectTools(context, contextMessages, plannerCandidate, tools);
+ if (selectedTools.isEmpty()) {
+ return fallbackResults(context);
+ }
+ List<AiDiagnosticToolResult> output = new ArrayList<>();
+ for (AiMcpToolDescriptor tool : selectedTools) {
+ try {
+ AiDiagnosticToolResult result = aiMcpRegistryService.executeTool(tenantId, tool, context);
+ if (result != null && result.getSummaryText() != null && !result.getSummaryText().trim().isEmpty()) {
+ output.add(result);
+ }
+ } catch (Exception e) {
+ output.add(new AiDiagnosticToolResult()
+ .setToolCode(tool.getToolCode())
+ .setMountCode(tool.getMountCode())
+ .setMcpToolName(tool.getMcpToolName())
+ .setToolName(tool.getToolName())
+ .setSeverity("WARN")
+ .setSummaryText("MCP宸ュ叿鎵ц澶辫触锛�" + e.getMessage()));
+ }
+ }
+ if (output.isEmpty()) {
+ return fallbackResults(context);
+ }
+ return output;
+ }
+
+ /**
+ * 鍒ゆ柇褰撳墠璇锋眰鏄惁鍊煎緱鍚敤 MCP銆�
+ * 璇婃柇鍦烘櫙榛樿鍚敤锛屾櫘閫氳亰澶╁垯鎸夊叧閿瘝鍜屼笟鍔¢棶棰樼壒寰佽Е鍙戙��
+ */
+ public boolean shouldUseMcp(AiPromptContext context) {
+ if (context == null) {
+ return false;
+ }
+ if (AiSceneCode.SYSTEM_DIAGNOSE.equals(context.getSceneCode())) {
+ return true;
+ }
+ String question = context.getQuestion();
+ if (question == null || question.trim().isEmpty()) {
+ return false;
+ }
+ String normalized = question.toLowerCase();
+ return normalized.contains("mcp")
+ || normalized.contains("宸ュ叿")
+ || normalized.contains("license")
+ || question.contains("璁稿彲璇�")
+ || normalized.contains("task")
+ || normalized.contains("device")
+ || normalized.contains("site")
+ || normalized.contains("loc")
+ || question.contains("搴撳瓨")
+ || question.contains("搴撲綅")
+ || question.contains("璐т綅")
+ || question.contains("鐗╂枡")
+ || question.contains("浠诲姟")
+ || question.contains("璁惧")
+ || question.contains("绔欑偣")
+ || question.contains("宸烽亾");
+ }
+
+ /**
+ * 璇婃柇鍦烘櫙鍦ㄦ病鏈夐�夊嚭鍏蜂綋宸ュ叿鏃讹紝閫�鍥炲埌鍐呯疆宸ュ叿鍏ㄩ噺鏀堕泦妯″紡锛�
+ * 鏅�氳亰澶╁垯鐩存帴杩斿洖绌虹粨鏋滐紝閬垮厤鏃犲叧宸ュ叿姹℃煋鍥炵瓟銆�
+ */
+ private List<AiDiagnosticToolResult> fallbackResults(AiPromptContext context) {
+ if (context != null && AiSceneCode.SYSTEM_DIAGNOSE.equals(context.getSceneCode())) {
+ return aiDiagnosticToolService.collect(context);
+ }
+ return new ArrayList<>();
+ }
+
+ /**
+ * 鎸夊満鏅拰鍚敤鐘舵�佽繃婊ゅ彲鐢ㄥ伐鍏风洰褰曘��
+ */
+ private List<AiMcpToolDescriptor> filterTools(List<AiMcpToolDescriptor> tools, String sceneCode) {
+ List<AiMcpToolDescriptor> output = new ArrayList<>();
+ for (AiMcpToolDescriptor tool : tools) {
+ if (tool == null || !Integer.valueOf(1).equals(tool.getEnabledFlag())) {
+ continue;
+ }
+ if (sceneCode != null && tool.getSceneCode() != null && !tool.getSceneCode().trim().isEmpty()
+ && !sceneCode.equals(tool.getSceneCode())) {
+ continue;
+ }
+ output.add(tool);
+ }
+ return output;
+ }
+
+ /**
+ * 鏅�氳亰澶╀紭鍏堜娇鐢ㄥ惎鍙戝紡宸ュ叿閫夋嫨锛岃瘖鏂満鏅紭鍏堝皾璇曟ā鍨嬭鍒掋��
+ */
+ private List<AiMcpToolDescriptor> selectTools(AiPromptContext context,
+ List<AiChatMessage> contextMessages,
+ AiModelRouteRuntimeService.RouteCandidate plannerCandidate,
+ List<AiMcpToolDescriptor> tools) {
+ List<AiMcpToolDescriptor> heuristic = heuristicSelect(tools, context == null ? null : context.getQuestion());
+ if (context != null && !AiSceneCode.SYSTEM_DIAGNOSE.equals(context.getSceneCode()) && !heuristic.isEmpty()) {
+ return heuristic;
+ }
+ List<AiMcpToolDescriptor> byModel = selectToolsByModel(context, contextMessages, plannerCandidate, tools);
+ if (!byModel.isEmpty()) {
+ return byModel;
+ }
+ return heuristic;
+ }
+
+ /**
+ * 浣跨敤涓�涓交閲忚鍒掕姹傦紝璁╂ā鍨嬪湪鐜版湁宸ュ叿鐩綍涓�夊嚭鏈�闇�瑕佽皟鐢ㄧ殑宸ュ叿銆�
+ */
+ private List<AiMcpToolDescriptor> selectToolsByModel(AiPromptContext context,
+ List<AiChatMessage> contextMessages,
+ AiModelRouteRuntimeService.RouteCandidate plannerCandidate,
+ List<AiMcpToolDescriptor> tools) {
+ if (plannerCandidate == null || plannerCandidate.getRuntimeConfig() == null) {
+ return new ArrayList<>();
+ }
+ try {
+ GatewayChatRequest request = new GatewayChatRequest();
+ request.setSessionId(context == null ? null : context.getSessionId());
+ request.setModelCode(plannerCandidate.getAttemptModelCode());
+ request.setRouteCode("system_diagnose_planner");
+ request.setAttemptNo(0);
+ request.setChatUrl(plannerCandidate.getRuntimeConfig().getChatUrl());
+ request.setApiKey(plannerCandidate.getRuntimeConfig().getApiKey());
+ request.setModelName(plannerCandidate.getRuntimeConfig().getModelName());
+ request.setSystemPrompt(buildPlannerSystemPrompt());
+ for (AiChatMessage item : contextMessages) {
+ GatewayChatMessage message = new GatewayChatMessage();
+ message.setRole(item.getRole());
+ message.setContent(item.getContent());
+ request.getMessages().add(message);
+ }
+ GatewayChatMessage plannerMessage = new GatewayChatMessage();
+ plannerMessage.setRole("user");
+ plannerMessage.setContent(buildPlannerUserPrompt(context, tools));
+ request.getMessages().add(plannerMessage);
+ String response = aiTextCompletionService.complete(request);
+ return mapSelection(parseToolNames(response), tools);
+ } catch (Exception ignore) {
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * 鐢熸垚涓撶敤浜庡伐鍏疯鍒掔殑 system prompt銆�
+ */
+ private String buildPlannerSystemPrompt() {
+ return "浣犳槸WMS璇婃柇宸ュ叿璋冨害鍣ㄣ�備綘鍙兘浠庢彁渚涚殑 MCP 宸ュ叿鐩綍涓�夋嫨鏈�闇�瑕佽皟鐢ㄧ殑宸ュ叿銆�"
+ + "鍙緭鍑� JSON锛屼笉瑕� markdown锛屼笉瑕佽В閲娿�傝緭鍑烘牸寮忓繀椤绘槸 "
+ + "{\"tools\":[{\"name\":\"宸ュ叿鍚峔",\"reason\":\"绠�鐭師鍥燶"}]}"
+ + "銆傚伐鍏峰悕蹇呴』鏉ヨ嚜鐩綍锛屾渶澶氶�夋嫨4涓紱濡傛灉鏃犻渶宸ュ叿锛岃繑鍥� {\"tools\":[]}銆�";
+ }
+
+ /**
+ * 灏嗛棶棰樺拰宸ュ叿鐩綍鏁寸悊鎴愯鍒掓ā鍨嬪彲鐩存帴娑堣垂鐨勮緭鍏ャ��
+ */
+ private String buildPlannerUserPrompt(AiPromptContext context, List<AiMcpToolDescriptor> tools) {
+ List<String> parts = new ArrayList<>();
+ parts.add("闂:");
+ parts.add(context == null || context.getQuestion() == null ? "" : context.getQuestion());
+ parts.add("");
+ parts.add("鍙敤宸ュ叿鐩綍:");
+ for (AiMcpToolDescriptor tool : tools) {
+ parts.add("- " + tool.getMcpToolName()
+ + " | " + safe(tool.getToolName())
+ + " | " + safe(tool.getDescription())
+ + " | " + safe(tool.getToolPrompt()));
+ }
+ return String.join("\n", parts);
+ }
+
+ /**
+ * 瑙f瀽瑙勫垝妯″瀷杩斿洖鐨� JSON锛屾彁鍙栬閫変腑鐨勫伐鍏峰悕鍒楄〃銆�
+ */
+ private List<String> parseToolNames(String response) {
+ List<String> output = new ArrayList<>();
+ if (response == null || response.trim().isEmpty()) {
+ return output;
+ }
+ String normalized = unwrapJson(response);
+ try {
+ JsonNode root = objectMapper.readTree(normalized);
+ JsonNode toolsNode = root.path("tools");
+ if (!toolsNode.isArray()) {
+ return output;
+ }
+ for (JsonNode item : toolsNode) {
+ String name = item.path("name").asText("");
+ if (!name.trim().isEmpty()) {
+ output.add(name.trim());
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ return output;
+ }
+
+ /**
+ * 灏嗘ā鍨嬭繑鍥炵殑宸ュ叿鍚嶆槧灏勫洖鏈湴宸ュ叿鎻忚堪绗︼紝骞舵寜杩斿洖椤哄簭鍘婚噸鎴柇銆�
+ */
+ private List<AiMcpToolDescriptor> mapSelection(List<String> names, List<AiMcpToolDescriptor> tools) {
+ Map<String, AiMcpToolDescriptor> byName = new LinkedHashMap<>();
+ for (AiMcpToolDescriptor tool : tools) {
+ byName.put(tool.getMcpToolName(), tool);
+ }
+ Set<String> seen = new LinkedHashSet<>();
+ List<AiMcpToolDescriptor> output = new ArrayList<>();
+ for (String name : names) {
+ if (seen.contains(name)) {
+ continue;
+ }
+ AiMcpToolDescriptor tool = byName.get(name);
+ if (tool != null) {
+ output.add(tool);
+ seen.add(name);
+ }
+ if (output.size() >= 4) {
+ break;
+ }
+ }
+ return output;
+ }
+
+ /**
+ * 鍩轰簬涓枃涓氬姟鍏抽敭璇嶄笌宸ュ叿鎻忚堪鎵撳垎锛岄�夊嚭鏈�鍙兘鍛戒腑鐨勫伐鍏枫��
+ */
+ private List<AiMcpToolDescriptor> heuristicSelect(List<AiMcpToolDescriptor> tools, String question) {
+ List<AiMcpToolDescriptor> output = new ArrayList<>();
+ if (tools.isEmpty()) {
+ return output;
+ }
+ String normalized = normalize(question);
+ List<ScoredTool> scoredTools = new ArrayList<>();
+ for (AiMcpToolDescriptor tool : tools) {
+ int score = matchScore(tool, normalized);
+ if (score > 0) {
+ scoredTools.add(new ScoredTool(tool, score));
+ }
+ }
+ scoredTools.sort(Comparator.comparingInt(ScoredTool::getScore).reversed());
+ for (ScoredTool scoredTool : scoredTools) {
+ output.add(scoredTool.getTool());
+ if (output.size() >= 4) {
+ return output;
+ }
+ }
+ for (AiMcpToolDescriptor tool : tools) {
+ if (!output.contains(tool)) {
+ output.add(tool);
+ }
+ if (output.size() >= 3) {
+ break;
+ }
+ }
+ return output;
+ }
+
+ /**
+ * 璁$畻鍗曚釜宸ュ叿涓庡綋鍓嶉棶棰樼殑鍛戒腑鍒嗘暟銆�
+ */
+ private int matchScore(AiMcpToolDescriptor tool, String question) {
+ if (question == null || question.trim().isEmpty()) {
+ return 0;
+ }
+ return countMatches(question, tool.getToolCode(), tool.getToolName(), tool.getDescription(), tool.getToolPrompt());
+ }
+
+ /**
+ * 缁熻闂鏂囨湰涓庝竴缁勫�欓�夊瓧娈电殑鐗囨鍛戒腑娆℃暟銆�
+ */
+ private int countMatches(String question, String... values) {
+ int score = 0;
+ for (String value : values) {
+ for (String piece : buildMatchFragments(value)) {
+ if (piece.length() >= 2 && question.contains(piece)) {
+ score++;
+ }
+ }
+ }
+ return score;
+ }
+
+ /**
+ * 灏嗗伐鍏峰悕銆佹弿杩扮瓑鏂囨湰鎷嗘垚閫傚悎涓枃涓氬姟鏌ヨ鍖归厤鐨勭墖娈甸泦鍚堛��
+ */
+ private List<String> buildMatchFragments(String value) {
+ List<String> fragments = new ArrayList<>();
+ if (value == null || value.trim().isEmpty()) {
+ return fragments;
+ }
+ String[] pieces = normalize(value).split("[^a-z0-9\\u4e00-\\u9fa5]+");
+ for (String piece : pieces) {
+ if (piece.length() < 2) {
+ continue;
+ }
+ fragments.add(piece);
+ if (piece.matches("[\\u4e00-\\u9fa5]+")) {
+ if (piece.endsWith("鎽樿") || piece.endsWith("姒傚喌") || piece.endsWith("姒傝") || piece.endsWith("鐘舵��")) {
+ fragments.add(piece.substring(0, piece.length() - 2));
+ }
+ for (int size = 2; size <= Math.min(4, piece.length()); size++) {
+ for (int i = 0; i <= piece.length() - size; i++) {
+ fragments.add(piece.substring(i, i + size));
+ }
+ }
+ }
+ }
+ return fragments;
+ }
+
+ /**
+ * 瀵规瘮瀵规枃鏈仛灏忓啓褰掍竴鍖栵紝渚夸簬缁熶竴鍖归厤銆�
+ */
+ private String normalize(String value) {
+ return value == null ? "" : value.toLowerCase();
+ }
+
+ /**
+ * 灏介噺浠庢ā鍨嬭緭鍑轰腑鍓ョ鍑哄彲瑙f瀽鐨� JSON 鐗囨銆�
+ */
+ private String unwrapJson(String response) {
+ String normalized = response.trim();
+ if (normalized.startsWith("```")) {
+ int firstBrace = normalized.indexOf('{');
+ int lastBrace = normalized.lastIndexOf('}');
+ if (firstBrace >= 0 && lastBrace > firstBrace) {
+ return normalized.substring(firstBrace, lastBrace + 1);
+ }
+ }
+ int firstBrace = normalized.indexOf('{');
+ int lastBrace = normalized.lastIndexOf('}');
+ if (firstBrace >= 0 && lastBrace > firstBrace) {
+ return normalized.substring(firstBrace, lastBrace + 1);
+ }
+ return normalized;
+ }
+
+ /**
+ * 涓� prompt 缁勮闃舵鎻愪緵绌哄�煎畨鍏ㄥ瓧绗︿覆銆�
+ */
+ private String safe(String value) {
+ return value == null ? "" : value;
+ }
+
+ private static class ScoredTool {
+ private final AiMcpToolDescriptor tool;
+ private final int score;
+
+ /**
+ * 淇濆瓨宸ュ叿鍙婂叾鍖归厤寰楀垎銆�
+ */
+ private ScoredTool(AiMcpToolDescriptor tool, int score) {
+ this.tool = tool;
+ this.score = score;
+ }
+
+ /**
+ * 杩斿洖琚墦鍒嗙殑宸ュ叿銆�
+ */
+ public AiMcpToolDescriptor getTool() {
+ return tool;
+ }
+
+ /**
+ * 杩斿洖宸ュ叿鍖归厤寰楀垎銆�
+ */
+ public int getScore() {
+ return score;
+ }
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisPlanRunnerService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisPlanRunnerService.java
new file mode 100644
index 0000000..5521c9e
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisPlanRunnerService.java
@@ -0,0 +1,355 @@
+package com.vincent.rsf.server.ai.service.diagnosis;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
+import com.vincent.rsf.server.ai.dto.GatewayChatMessage;
+import com.vincent.rsf.server.ai.dto.GatewayChatRequest;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+import com.vincent.rsf.server.ai.service.AiGatewayClient;
+import com.vincent.rsf.server.ai.service.AiModelRouteRuntimeService;
+import com.vincent.rsf.server.ai.service.AiPromptRuntimeService;
+import com.vincent.rsf.server.system.entity.AiDiagnosisPlan;
+import com.vincent.rsf.server.system.entity.AiDiagnosisRecord;
+import com.vincent.rsf.server.system.service.AiDiagnosisPlanService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.InterruptedIOException;
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class AiDiagnosisPlanRunnerService {
+
+ @Resource
+ private AiDiagnosisPlanService aiDiagnosisPlanService;
+ @Resource
+ private AiDiagnosticToolService aiDiagnosticToolService;
+ @Resource
+ private AiModelRouteRuntimeService aiModelRouteRuntimeService;
+ @Resource
+ private AiPromptRuntimeService aiPromptRuntimeService;
+ @Resource
+ private AiGatewayClient aiGatewayClient;
+ @Resource
+ private AiDiagnosisRuntimeService aiDiagnosisRuntimeService;
+
+ /**
+ * 鎵ц涓�娆″贰妫�璁″垝銆�
+ * 璁″垝鎵ц鏈川涓婂鐢ㄨ瘖鏂富閾捐矾锛屽彧鏄緭鍏ユ潵婧愪粠浜哄伐鎻愰棶鍙樻垚浜嗚鍒掗厤缃��
+ */
+ public void runPlan(Long planId, boolean manualTrigger) {
+ AiDiagnosisPlan plan = aiDiagnosisPlanService.getById(planId);
+ if (plan == null) {
+ return;
+ }
+ Date finishTime = new Date();
+ Date nextRunTime = Integer.valueOf(1).equals(plan.getStatus())
+ ? aiDiagnosisPlanService.calculateNextRunTime(plan.getCronExpr(), finishTime)
+ : null;
+ Long userId = plan.getUpdateBy() == null ? plan.getCreateBy() : plan.getUpdateBy();
+ String sessionId = "plan-" + plan.getId() + "-" + System.currentTimeMillis();
+ String question = plan.getPrompt();
+ if (question == null || question.trim().isEmpty()) {
+ question = "璇峰褰撳墠WMS绯荤粺杩涜涓�娆″贰妫�璇婃柇锛岀粨鍚堝簱瀛樸�佷换鍔°�佽澶囩珯鐐规暟鎹瘑鍒紓甯稿苟缁欏嚭澶勭悊寤鸿銆�";
+ }
+
+ AiPromptContext promptContext = new AiPromptContext()
+ .setTenantId(plan.getTenantId())
+ .setUserId(userId)
+ .setSessionId(sessionId)
+ .setModelCode(plan.getPreferredModelCode())
+ .setQuestion(question)
+ .setSceneCode(plan.getSceneCode() == null || plan.getSceneCode().trim().isEmpty()
+ ? AiSceneCode.SYSTEM_DIAGNOSE
+ : plan.getSceneCode());
+
+ List<AiDiagnosticToolResult> diagnosticResults = aiDiagnosticToolService.collect(promptContext);
+ String toolSummary = aiDiagnosticToolService.serializeResults(diagnosticResults);
+ AiDiagnosisRecord diagnosisRecord = aiDiagnosisRuntimeService.startDiagnosis(
+ plan.getTenantId(),
+ userId,
+ sessionId,
+ promptContext.getSceneCode(),
+ question
+ );
+
+ StringBuilder assistantReply = new StringBuilder();
+ String finalModelCode = plan.getPreferredModelCode();
+ String finalErrorMessage = null;
+ boolean success = false;
+
+ try {
+ List<AiModelRouteRuntimeService.RouteCandidate> candidates = aiModelRouteRuntimeService.resolveCandidates(
+ plan.getTenantId(),
+ promptContext.getSceneCode(),
+ plan.getPreferredModelCode()
+ );
+ if (candidates.isEmpty()) {
+ finalErrorMessage = "鏈壘鍒板彲鐢ㄧ殑AI妯″瀷閰嶇疆";
+ } else {
+ int attemptNo = 1;
+ for (AiModelRouteRuntimeService.RouteCandidate candidate : candidates) {
+ AttemptState attemptState = new AttemptState();
+ Date requestTime = new Date();
+ try {
+ GatewayChatRequest gatewayChatRequest = buildGatewayRequest(
+ sessionId,
+ question,
+ candidate,
+ promptContext,
+ diagnosticResults,
+ attemptNo
+ );
+ aiGatewayClient.stream(gatewayChatRequest, event -> handleGatewayEvent(event, assistantReply, attemptState));
+ } catch (Exception e) {
+ attemptState.setSuccess(false);
+ attemptState.setErrorMessage(e.getMessage());
+ attemptState.setInterrupted(isInterruptedError(e));
+ attemptState.setResponseTime(new Date());
+ }
+ if (attemptState.getResponseTime() == null) {
+ attemptState.setResponseTime(new Date());
+ }
+ String actualModelCode = attemptState.getActualModelCode() == null
+ ? candidate.getAttemptModelCode()
+ : attemptState.getActualModelCode();
+ finalModelCode = actualModelCode;
+ aiDiagnosisRuntimeService.saveCallLog(
+ plan.getTenantId(),
+ userId,
+ sessionId,
+ diagnosisRecord.getId(),
+ candidate.getRouteCode(),
+ actualModelCode,
+ attemptNo,
+ requestTime,
+ attemptState.getResponseTime(),
+ Boolean.TRUE.equals(attemptState.getSuccess()) ? 1 : 0,
+ attemptState.getErrorMessage()
+ );
+ if (Boolean.TRUE.equals(attemptState.getSuccess())) {
+ aiModelRouteRuntimeService.markSuccess(candidate.getRouteId());
+ success = assistantReply.length() > 0;
+ if (!success) {
+ finalErrorMessage = "妯″瀷鏈繑鍥炴湁鏁堝唴瀹�";
+ }
+ break;
+ }
+ if (!attemptState.isInterrupted()) {
+ aiModelRouteRuntimeService.markFailure(candidate.getRouteId());
+ }
+ finalErrorMessage = attemptState.getErrorMessage();
+ if (attemptState.isReceivedDelta() || attemptNo >= candidates.size()) {
+ break;
+ }
+ attemptNo++;
+ }
+ }
+
+ if (success) {
+ aiDiagnosisRuntimeService.finishDiagnosisSuccess(diagnosisRecord, assistantReply.toString(), finalModelCode, toolSummary);
+ aiDiagnosisPlanService.finishExecution(
+ plan.getId(),
+ 1,
+ diagnosisRecord.getId(),
+ buildPlanMessage(assistantReply.toString(), manualTrigger ? "鎵嬪姩鎵ц鎴愬姛" : "璁″垝鎵ц鎴愬姛"),
+ new Date(),
+ nextRunTime
+ );
+ return;
+ }
+ aiDiagnosisRuntimeService.finishDiagnosisFailure(diagnosisRecord, assistantReply.toString(), finalErrorMessage, toolSummary);
+ aiDiagnosisPlanService.finishExecution(
+ plan.getId(),
+ 0,
+ diagnosisRecord.getId(),
+ buildPlanMessage(finalErrorMessage, manualTrigger ? "鎵嬪姩鎵ц澶辫触" : "璁″垝鎵ц澶辫触"),
+ new Date(),
+ nextRunTime
+ );
+ } catch (Exception e) {
+ aiDiagnosisRuntimeService.finishDiagnosisFailure(diagnosisRecord, assistantReply.toString(), e.getMessage(), toolSummary);
+ aiDiagnosisPlanService.finishExecution(
+ plan.getId(),
+ 0,
+ diagnosisRecord.getId(),
+ buildPlanMessage(e.getMessage(), manualTrigger ? "鎵嬪姩鎵ц澶辫触" : "璁″垝鎵ц澶辫触"),
+ new Date(),
+ nextRunTime
+ );
+ }
+ }
+
+ /**
+ * 涓哄贰妫�璁″垝缁勮缃戝叧璇锋眰銆�
+ */
+ private GatewayChatRequest buildGatewayRequest(String sessionId,
+ String question,
+ AiModelRouteRuntimeService.RouteCandidate candidate,
+ AiPromptContext promptContext,
+ List<AiDiagnosticToolResult> diagnosticResults,
+ Integer attemptNo) {
+ GatewayChatRequest request = new GatewayChatRequest();
+ request.setSessionId(sessionId);
+ request.setModelCode(candidate.getAttemptModelCode());
+ request.setRouteCode(candidate.getRouteCode());
+ request.setAttemptNo(attemptNo);
+ request.setSystemPrompt(aiPromptRuntimeService.buildSystemPrompt(
+ promptContext.getSceneCode(),
+ candidate.getRuntimeConfig().getSystemPrompt(),
+ promptContext,
+ diagnosticResults
+ ));
+ request.setChatUrl(candidate.getRuntimeConfig().getChatUrl());
+ request.setApiKey(candidate.getRuntimeConfig().getApiKey());
+ request.setModelName(candidate.getRuntimeConfig().getModelName());
+
+ GatewayChatMessage message = new GatewayChatMessage();
+ message.setRole("user");
+ message.setContent(question);
+ request.getMessages().add(message);
+ return request;
+ }
+
+ /**
+ * 娑堣垂璁″垝鎵ц鏃剁殑娴佸紡缃戝叧浜嬩欢銆�
+ */
+ private boolean handleGatewayEvent(JsonNode event, StringBuilder assistantReply, AttemptState attemptState) {
+ String type = event.path("type").asText();
+ String modelCode = event.path("modelCode").asText();
+ if ("delta".equals(type)) {
+ String content = event.path("content").asText("");
+ assistantReply.append(content);
+ attemptState.setReceivedDelta(true);
+ attemptState.setActualModelCode(modelCode);
+ return true;
+ }
+ if ("error".equals(type)) {
+ attemptState.setSuccess(false);
+ attemptState.setActualModelCode(modelCode);
+ attemptState.setErrorMessage(event.path("message").asText("妯″瀷璋冪敤澶辫触"));
+ attemptState.setResponseTime(parseResponseTime(event));
+ attemptState.setInterrupted(isInterruptedMessage(attemptState.getErrorMessage()));
+ return false;
+ }
+ if ("done".equals(type)) {
+ attemptState.setSuccess(true);
+ attemptState.setActualModelCode(modelCode);
+ attemptState.setResponseTime(parseResponseTime(event));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 浠庣綉鍏充簨浠朵腑鎻愬彇鍝嶅簲鏃堕棿銆�
+ */
+ private Date parseResponseTime(JsonNode event) {
+ long millis = event.path("responseTime").asLong(0L);
+ return millis <= 0L ? new Date() : new Date(millis);
+ }
+
+ /**
+ * 灏嗚鍒掓墽琛岀粨鏋滃帇缂╂垚閫傚悎鍥炲啓鍒拌鍒掕褰曠殑鐭秷鎭��
+ */
+ private String buildPlanMessage(String text, String fallback) {
+ String source = text == null ? "" : text.trim();
+ if (source.isEmpty()) {
+ return fallback;
+ }
+ return source.length() > 120 ? source.substring(0, 120) : source;
+ }
+
+ /**
+ * 鍒ゆ柇寮傚父閾句腑鏄惁鍖呭惈涓柇绫婚敊璇��
+ */
+ private boolean isInterruptedError(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (current instanceof InterruptedException || current instanceof InterruptedIOException) {
+ return true;
+ }
+ if (isInterruptedMessage(current.getMessage())) {
+ return true;
+ }
+ current = current.getCause();
+ }
+ return false;
+ }
+
+ /**
+ * 鏍规嵁寮傚父娑堟伅鏂囨湰鍒ゆ柇鏄惁灞炰簬杩炴帴涓柇绫婚敊璇��
+ */
+ private boolean isInterruptedMessage(String message) {
+ if (message == null || message.trim().isEmpty()) {
+ return false;
+ }
+ String normalized = message.toLowerCase();
+ return normalized.contains("interrupted")
+ || normalized.contains("broken pipe")
+ || normalized.contains("connection reset")
+ || normalized.contains("forcibly closed");
+ }
+
+ private static class AttemptState {
+ private Boolean success;
+ private String actualModelCode;
+ private String errorMessage;
+ private boolean receivedDelta;
+ private boolean interrupted;
+ private Date responseTime;
+
+ public Boolean getSuccess() {
+ return success;
+ }
+
+ public void setSuccess(Boolean success) {
+ this.success = success;
+ }
+
+ public String getActualModelCode() {
+ return actualModelCode;
+ }
+
+ public void setActualModelCode(String actualModelCode) {
+ this.actualModelCode = actualModelCode;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public void setErrorMessage(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+ public boolean isReceivedDelta() {
+ return receivedDelta;
+ }
+
+ public void setReceivedDelta(boolean receivedDelta) {
+ this.receivedDelta = receivedDelta;
+ }
+
+ public boolean isInterrupted() {
+ return interrupted;
+ }
+
+ public void setInterrupted(boolean interrupted) {
+ this.interrupted = interrupted;
+ }
+
+ public Date getResponseTime() {
+ return responseTime;
+ }
+
+ public void setResponseTime(Date responseTime) {
+ this.responseTime = responseTime;
+ }
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisPlanScheduler.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisPlanScheduler.java
new file mode 100644
index 0000000..4c42617
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisPlanScheduler.java
@@ -0,0 +1,48 @@
+package com.vincent.rsf.server.ai.service.diagnosis;
+
+import com.vincent.rsf.server.system.entity.AiDiagnosisPlan;
+import com.vincent.rsf.server.system.service.AiDiagnosisPlanService;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+@Component
+public class AiDiagnosisPlanScheduler {
+
+ @Resource
+ private AiDiagnosisPlanService aiDiagnosisPlanService;
+ @Resource
+ private AiDiagnosisPlanRunnerService aiDiagnosisPlanRunnerService;
+ @Resource
+ private ThreadPoolTaskScheduler taskScheduler;
+
+ /**
+ * 姣� 30 绉掓壂鎻忎竴娆″埌鏈熷贰妫�璁″垝锛屽苟寮傛鍒嗗彂鎵ц銆�
+ */
+ @Scheduled(cron = "0/30 * * * * ?")
+ public void dispatchDuePlans() {
+ Date now = new Date();
+ List<AiDiagnosisPlan> plans = aiDiagnosisPlanService.listDuePlans(now);
+ for (AiDiagnosisPlan plan : plans) {
+ Long operatorId = plan.getUpdateBy() == null ? plan.getCreateBy() : plan.getUpdateBy();
+ Date nextRunTime = aiDiagnosisPlanService.calculateNextRunTime(plan.getCronExpr(), now);
+ boolean acquired = aiDiagnosisPlanService.acquireForExecution(
+ plan.getId(),
+ operatorId,
+ "璁″垝鎵ц涓�",
+ nextRunTime
+ );
+ if (!acquired) {
+ continue;
+ }
+ taskScheduler.execute(() -> aiDiagnosisPlanRunnerService.runPlan(plan.getId(), false));
+ }
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisReportService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisReportService.java
new file mode 100644
index 0000000..c8b627e
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisReportService.java
@@ -0,0 +1,182 @@
+package com.vincent.rsf.server.ai.service.diagnosis;
+
+import com.vincent.rsf.server.system.entity.AiDiagnosisRecord;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class AiDiagnosisReportService {
+
+ /**
+ * 鍩轰簬妯″瀷缁撹鍜屽伐鍏锋憳瑕佸洖濉瘖鏂姤鍛婄粨鏋勫寲瀛楁涓� Markdown 鍐呭銆�
+ */
+ public void fillReport(AiDiagnosisRecord record, String conclusion, String toolSummary) {
+ if (record == null) {
+ return;
+ }
+ Map<String, String> sections = extractSections(conclusion);
+ String executiveSummary = firstNonBlank(
+ sections.get("闂姒傝堪"),
+ sections.get("鎵ц鎽樿"),
+ safeText(conclusion)
+ );
+ String evidenceSummary = firstNonBlank(
+ sections.get("鍏抽敭璇佹嵁"),
+ sections.get("璇佹嵁鎽樿"),
+ safeText(toolSummary)
+ );
+ String actionSummary = firstNonBlank(
+ sections.get("寤鸿鍔ㄤ綔"),
+ sections.get("澶勭疆寤鸿"),
+ "璇风粨鍚堢幇鍦烘祦绋嬬户缁‘璁ゃ��"
+ );
+ String riskSummary = firstNonBlank(
+ sections.get("椋庨櫓璇勪及"),
+ sections.get("娼滃湪椋庨櫓"),
+ "濡傛湭鍙婃椂澶勭悊锛屽彲鑳界户缁奖鍝嶅綋鍓嶄笟鍔¤繍琛屻��"
+ );
+ record.setReportTitle(buildTitle(record));
+ record.setExecutiveSummary(executiveSummary);
+ record.setEvidenceSummary(evidenceSummary);
+ record.setActionSummary(actionSummary);
+ record.setRiskSummary(riskSummary);
+ record.setReportMarkdown(buildMarkdown(record, executiveSummary, evidenceSummary, actionSummary, riskSummary, conclusion));
+ }
+
+ /**
+ * 灏濊瘯浠庡師濮嬬粨璁烘枃鏈腑鎻愬彇鈥滈棶棰樻杩�/鍏抽敭璇佹嵁/寤鸿鍔ㄤ綔/椋庨櫓璇勪及鈥濈瓑绔犺妭銆�
+ */
+ private Map<String, String> extractSections(String conclusion) {
+ Map<String, String> sections = new LinkedHashMap<>();
+ if (conclusion == null || conclusion.trim().isEmpty()) {
+ return sections;
+ }
+ String[] lines = conclusion.replace("\r", "").split("\n");
+ String currentTitle = "闂姒傝堪";
+ StringBuilder currentBody = new StringBuilder();
+ for (String rawLine : lines) {
+ String line = rawLine == null ? "" : rawLine.trim();
+ String title = normalizeTitle(line);
+ if (title != null) {
+ saveSection(sections, currentTitle, currentBody);
+ currentTitle = title;
+ currentBody = new StringBuilder();
+ String remainder = line.substring(line.indexOf(title) + title.length()).trim();
+ if (remainder.startsWith("锛�") || remainder.startsWith(":")) {
+ remainder = remainder.substring(1).trim();
+ }
+ if (!remainder.isEmpty()) {
+ currentBody.append(remainder);
+ }
+ continue;
+ }
+ if (currentBody.length() > 0) {
+ currentBody.append("\n");
+ }
+ currentBody.append(rawLine == null ? "" : rawLine);
+ }
+ saveSection(sections, currentTitle, currentBody);
+ return sections;
+ }
+
+ /**
+ * 灏嗗綋鍓嶇珷鑺傚唴瀹瑰啓鍏� section map銆�
+ */
+ private void saveSection(Map<String, String> sections, String title, StringBuilder body) {
+ String content = body == null ? "" : body.toString().trim();
+ if (content.isEmpty()) {
+ return;
+ }
+ sections.put(title, content);
+ }
+
+ /**
+ * 鍒ゆ柇涓�琛屾枃鏈槸鍚﹀彲瑙嗕负鎶ュ憡绔犺妭鏍囬銆�
+ */
+ private String normalizeTitle(String line) {
+ if (line == null) {
+ return null;
+ }
+ String normalized = line.replace("#", "").replace("锛�", "").replace(":", "").trim();
+ List<String> titles = new ArrayList<>();
+ titles.add("闂姒傝堪");
+ titles.add("鎵ц鎽樿");
+ titles.add("鍏抽敭璇佹嵁");
+ titles.add("璇佹嵁鎽樿");
+ titles.add("鍙兘鍘熷洜");
+ titles.add("寤鸿鍔ㄤ綔");
+ titles.add("澶勭疆寤鸿");
+ titles.add("椋庨櫓璇勪及");
+ titles.add("娼滃湪椋庨櫓");
+ for (String title : titles) {
+ if (normalized.startsWith(title)) {
+ return title;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 鏋勯�犺瘖鏂姤鍛婃爣棰樸��
+ */
+ private String buildTitle(AiDiagnosisRecord record) {
+ String diagnosisNo = record.getDiagnosisNo() == null ? "-" : record.getDiagnosisNo();
+ return "WMS璇婃柇鎶ュ憡-" + diagnosisNo;
+ }
+
+ /**
+ * 鐢熸垚鏈�缁堟姤鍛� Markdown銆�
+ */
+ private String buildMarkdown(AiDiagnosisRecord record, String executiveSummary, String evidenceSummary,
+ String actionSummary, String riskSummary, String conclusion) {
+ List<String> parts = new ArrayList<>();
+ parts.add("# " + buildTitle(record));
+ parts.add("");
+ parts.add("## 闂姒傝堪");
+ parts.add(safeText(executiveSummary));
+ parts.add("");
+ parts.add("## 鍏抽敭璇佹嵁");
+ parts.add(safeText(evidenceSummary));
+ parts.add("");
+ parts.add("## 寤鸿鍔ㄤ綔");
+ parts.add(safeText(actionSummary));
+ parts.add("");
+ parts.add("## 椋庨櫓璇勪及");
+ parts.add(safeText(riskSummary));
+ if (conclusion != null && !conclusion.trim().isEmpty()) {
+ parts.add("");
+ parts.add("## 鍘熷缁撹");
+ parts.add(conclusion.trim());
+ }
+ return String.join("\n", parts);
+ }
+
+ /**
+ * 杩斿洖绗竴涓潪绌烘枃鏈��
+ */
+ private String firstNonBlank(String... values) {
+ if (values == null) {
+ return "";
+ }
+ for (String value : values) {
+ if (value != null && !value.trim().isEmpty()) {
+ return value.trim();
+ }
+ }
+ return "";
+ }
+
+ /**
+ * 瀵瑰睍绀烘枃鏈仛绌哄�煎厹搴曘��
+ */
+ private String safeText(String value) {
+ return value == null || value.trim().isEmpty() ? "鏆傛棤鍐呭" : value.trim();
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisRuntimeService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisRuntimeService.java
new file mode 100644
index 0000000..4b65367
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosisRuntimeService.java
@@ -0,0 +1,113 @@
+package com.vincent.rsf.server.ai.service.diagnosis;
+
+import com.vincent.rsf.framework.common.SnowflakeIdWorker;
+import com.vincent.rsf.server.system.entity.AiCallLog;
+import com.vincent.rsf.server.system.entity.AiDiagnosisRecord;
+import com.vincent.rsf.server.system.service.AiCallLogService;
+import com.vincent.rsf.server.system.service.AiDiagnosisRecordService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Date;
+
+@Service
+public class AiDiagnosisRuntimeService {
+
+ @Resource
+ private AiDiagnosisRecordService aiDiagnosisRecordService;
+ @Resource
+ private AiCallLogService aiCallLogService;
+ @Resource
+ private SnowflakeIdWorker snowflakeIdWorker;
+ @Resource
+ private AiDiagnosisReportService aiDiagnosisReportService;
+
+ /**
+ * 鍦ㄨ瘖鏂紑濮嬫椂鍒涘缓涓�鏉¤瘖鏂褰曪紝骞舵爣璁颁负鈥滆繘琛屼腑鈥濄��
+ */
+ public AiDiagnosisRecord startDiagnosis(Long tenantId, Long userId, String sessionId, String sceneCode, String question) {
+ AiDiagnosisRecord record = new AiDiagnosisRecord()
+ .setDiagnosisNo(String.valueOf(snowflakeIdWorker.nextId()).substring(3))
+ .setTenantId(tenantId)
+ .setUserId(userId)
+ .setSessionId(sessionId)
+ .setSceneCode(sceneCode)
+ .setQuestion(question)
+ .setResult(2)
+ .setStatus(1)
+ .setDeleted(0)
+ .setStartTime(new Date())
+ .setCreateTime(new Date())
+ .setUpdateTime(new Date());
+ aiDiagnosisRecordService.save(record);
+ return record;
+ }
+
+ /**
+ * 灏嗚瘖鏂褰曟爣璁颁负鎴愬姛锛屽苟琛ラ綈鎶ュ憡鍐呭銆佹ā鍨嬩俊鎭拰鑰楁椂銆�
+ */
+ public void finishDiagnosisSuccess(AiDiagnosisRecord record, String conclusion, String modelCode, String toolSummary) {
+ if (record == null) {
+ return;
+ }
+ Date now = new Date();
+ record.setConclusion(conclusion);
+ aiDiagnosisReportService.fillReport(record, conclusion, toolSummary);
+ record.setModelCode(modelCode);
+ record.setToolSummary(toolSummary);
+ record.setResult(1);
+ record.setEndTime(now);
+ record.setSpendTime(now.getTime() - record.getStartTime().getTime());
+ record.setUpdateTime(now);
+ aiDiagnosisRecordService.updateById(record);
+ }
+
+ /**
+ * 灏嗚瘖鏂褰曟爣璁颁负澶辫触锛屽苟淇濈暀宸叉湁缁撹銆侀敊璇俊鎭拰宸ュ叿杞ㄨ抗銆�
+ */
+ public void finishDiagnosisFailure(AiDiagnosisRecord record, String conclusion, String err, String toolSummary) {
+ if (record == null) {
+ return;
+ }
+ Date now = new Date();
+ record.setConclusion(conclusion);
+ aiDiagnosisReportService.fillReport(record, conclusion, toolSummary);
+ record.setToolSummary(toolSummary);
+ record.setErr(err);
+ record.setResult(0);
+ record.setEndTime(now);
+ record.setSpendTime(record.getStartTime() == null ? null : now.getTime() - record.getStartTime().getTime());
+ record.setUpdateTime(now);
+ aiDiagnosisRecordService.updateById(record);
+ }
+
+ /**
+ * 淇濆瓨涓�娆℃ā鍨嬭皟鐢ㄦ棩蹇楋紝渚涘璁°�佺粺璁″拰鏁呴殰瀹氫綅浣跨敤銆�
+ */
+ public AiCallLog saveCallLog(Long tenantId, Long userId, String sessionId, Long diagnosisId, String routeCode,
+ String modelCode, Integer attemptNo, Date requestTime, Date responseTime,
+ Integer result, String err) {
+ AiCallLog log = new AiCallLog()
+ .setTenantId(tenantId)
+ .setUserId(userId)
+ .setSessionId(sessionId)
+ .setDiagnosisId(diagnosisId)
+ .setRouteCode(routeCode)
+ .setModelCode(modelCode)
+ .setAttemptNo(attemptNo)
+ .setRequestTime(requestTime)
+ .setResponseTime(responseTime)
+ .setSpendTime(requestTime == null || responseTime == null ? null : responseTime.getTime() - requestTime.getTime())
+ .setResult(result)
+ .setErr(err)
+ .setStatus(1)
+ .setDeleted(0)
+ .setCreateTime(responseTime == null ? new Date() : responseTime);
+ aiCallLogService.save(log);
+ return log;
+ }
+
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosticToolService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosticToolService.java
new file mode 100644
index 0000000..0f5b788
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/diagnosis/AiDiagnosticToolService.java
@@ -0,0 +1,181 @@
+package com.vincent.rsf.server.ai.service.diagnosis;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vincent.rsf.server.ai.constant.AiMcpConstants;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiMcpToolDescriptor;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+import com.vincent.rsf.server.ai.service.mcp.AiMcpPayloadMapper;
+import com.vincent.rsf.server.ai.service.mcp.AiMcpRegistryService;
+import com.vincent.rsf.server.system.entity.AiDiagnosticToolConfig;
+import com.vincent.rsf.server.system.service.AiDiagnosticToolConfigService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class AiDiagnosticToolService {
+
+ @Resource
+ private ObjectMapper objectMapper;
+ @Resource
+ private AiDiagnosticToolConfigService aiDiagnosticToolConfigService;
+ @Resource
+ private AiMcpRegistryService aiMcpRegistryService;
+ @Resource
+ private AiMcpPayloadMapper aiMcpPayloadMapper;
+
+ /**
+ * 鏀堕泦褰撳墠绉熸埛鐨勬墍鏈夊唴缃瘖鏂伐鍏风粨鏋溿��
+ * 杩欐槸璇婃柇鍦烘櫙鐨勫厹搴曡矾寰勶紝涔熺敤浜庡湪妯″瀷瑙勫垝澶辫触鏃跺己鍒惰仛鍚堜竴杞唴閮ㄦ暟鎹��
+ */
+ public List<AiDiagnosticToolResult> collect(AiPromptContext context) {
+ List<AiDiagnosticToolResult> output = new ArrayList<>();
+ for (AiMcpToolDescriptor descriptor : resolveInternalTools(context)) {
+ try {
+ AiDiagnosticToolResult result = aiMcpRegistryService.executeTool(context.getTenantId(), descriptor, context);
+ if (result != null && result.getSummaryText() != null && !result.getSummaryText().trim().isEmpty()) {
+ output.add(result);
+ }
+ } catch (Exception e) {
+ output.add(new AiDiagnosticToolResult()
+ .setToolCode(descriptor.getToolCode())
+ .setMountCode(descriptor.getMountCode() == null ? AiMcpConstants.DEFAULT_LOCAL_MOUNT_CODE : descriptor.getMountCode())
+ .setMcpToolName(descriptor.getMcpToolName())
+ .setToolName(descriptor.getToolName())
+ .setSeverity("WARN")
+ .setSummaryText("宸ュ叿鎵ц澶辫触锛�" + e.getMessage()));
+ }
+ }
+ return output;
+ }
+
+ /**
+ * 灏嗗伐鍏风粨鏋滃垪琛ㄨ浆鎹负鍙洿鎺ユ嫾杩涚郴缁� Prompt 鐨勬憳瑕佹枃鏈��
+ */
+ public String buildPrompt(List<AiDiagnosticToolResult> results) {
+ return buildPrompt(null, null, results);
+ }
+
+ /**
+ * 鏍规嵁褰撳墠鍦烘櫙琛ュ厖宸ュ叿绾ч檮鍔犺鍒欙紝骞剁敓鎴愭渶缁堝伐鍏锋憳瑕� Prompt銆�
+ */
+ public String buildPrompt(Long tenantId, String sceneCode, List<AiDiagnosticToolResult> results) {
+ if (results == null || results.isEmpty()) {
+ return "";
+ }
+ Map<String, AiDiagnosticToolConfig> configMap = buildConfigMap(tenantId, sceneCode);
+ List<String> parts = new ArrayList<>();
+ parts.add("浠ヤ笅鏄瘖鏂伐鍏疯繑鍥炵殑瀹炴椂鎽樿锛岃浼樺厛渚濇嵁杩欎簺缁撴灉鍒ゆ柇锛�");
+ for (AiDiagnosticToolResult item : results) {
+ AiDiagnosticToolConfig config = configMap.get(item.getToolCode());
+ if (config != null && config.getToolPrompt() != null && !config.getToolPrompt().trim().isEmpty()) {
+ parts.add("[" + safeValue(item.getToolName()) + "][鎸囦护] " + safeValue(config.getToolPrompt()));
+ }
+ parts.add("[" + safeValue(item.getToolName()) + "][" + safeValue(item.getSeverity()) + "] " + safeValue(item.getSummaryText()));
+ }
+ return String.join("\n", parts);
+ }
+
+ /**
+ * 灏嗗伐鍏风粨鏋滃簭鍒楀寲鎴� JSON锛屼究浜庤瘖鏂褰曚笌宸ュ叿杞ㄨ抗钀藉簱銆�
+ */
+ public String serializeResults(List<AiDiagnosticToolResult> results) {
+ if (results == null || results.isEmpty()) {
+ return "[]";
+ }
+ try {
+ return objectMapper.writeValueAsString(results);
+ } catch (Exception e) {
+ return "[]";
+ }
+ }
+
+ private String safeValue(String value) {
+ return value == null ? "" : value;
+ }
+
+ /**
+ * 鎸夊綋鍓嶅満鏅В鏋愬彲浠ュ弬涓庢墽琛岀殑鍐呴儴宸ュ叿鍒楄〃锛屽苟鎸変紭鍏堢骇鎺掑簭銆�
+ */
+ private List<AiMcpToolDescriptor> resolveInternalTools(AiPromptContext context) {
+ List<AiMcpToolDescriptor> output = new ArrayList<>();
+ if (context == null || context.getTenantId() == null) {
+ return output;
+ }
+ for (AiMcpToolDescriptor descriptor : aiMcpRegistryService.listInternalTools(context.getTenantId())) {
+ if (descriptor == null || !Integer.valueOf(1).equals(descriptor.getEnabledFlag())) {
+ continue;
+ }
+ if (context.getSceneCode() != null
+ && descriptor.getSceneCode() != null
+ && !descriptor.getSceneCode().trim().isEmpty()
+ && !context.getSceneCode().equals(descriptor.getSceneCode())) {
+ continue;
+ }
+ output.add(descriptor);
+ }
+ output.sort(Comparator.comparing(
+ item -> item.getPriority() == null ? Integer.MAX_VALUE : item.getPriority()
+ ));
+ return output;
+ }
+
+ /**
+ * 涓哄綋鍓嶅満鏅瀯寤哄伐鍏烽厤缃储寮曘��
+ * 褰撳伐鍏风敤閫斾负鈥滆亰澶╀笌璇婃柇閮藉彲鐢ㄢ�濇椂锛屼細鍏佽鍦ㄥ涓満鏅笅澶嶇敤鍚屼竴鏉¢厤缃��
+ */
+ private Map<String, AiDiagnosticToolConfig> buildConfigMap(Long tenantId, String sceneCode) {
+ Map<String, AiDiagnosticToolConfig> map = new LinkedHashMap<>();
+ if (tenantId == null || sceneCode == null || sceneCode.trim().isEmpty()) {
+ return map;
+ }
+ List<AiDiagnosticToolConfig> configs = aiDiagnosticToolConfigService.listTenantConfigs(tenantId);
+ for (AiDiagnosticToolConfig config : emptyList(configs)) {
+ if (!Integer.valueOf(1).equals(config.getStatus())) {
+ continue;
+ }
+ String usageScope = aiMcpPayloadMapper.resolveUsageScope(config.getSceneCode(), config.getEnabledFlag(), config.getUsageScope());
+ if (AiMcpConstants.USAGE_SCOPE_DISABLED.equals(usageScope)) {
+ continue;
+ }
+ if (AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY.equals(usageScope)
+ && !sceneCode.equals(config.getSceneCode())) {
+ continue;
+ }
+ AiDiagnosticToolConfig existed = map.get(config.getToolCode());
+ if (existed == null || preferConfig(config, existed, sceneCode)) {
+ map.put(config.getToolCode(), config);
+ }
+ }
+ return map;
+ }
+
+ /**
+ * 鍐茬獊鏃朵紭鍏堥�夋嫨涓庡綋鍓嶅満鏅簿纭尮閰嶇殑閰嶇疆锛屽叾娆℃瘮杈冧紭鍏堢骇銆�
+ */
+ private boolean preferConfig(AiDiagnosticToolConfig candidate, AiDiagnosticToolConfig current, String sceneCode) {
+ boolean candidateExact = sceneCode.equals(candidate.getSceneCode());
+ boolean currentExact = sceneCode.equals(current.getSceneCode());
+ if (candidateExact != currentExact) {
+ return candidateExact;
+ }
+ Integer candidatePriority = candidate.getPriority() == null ? Integer.MAX_VALUE : candidate.getPriority();
+ Integer currentPriority = current.getPriority() == null ? Integer.MAX_VALUE : current.getPriority();
+ return candidatePriority < currentPriority;
+ }
+
+ private <T> Collection<T> emptyList(List<T> list) {
+ return list == null ? new ArrayList<>() : list;
+ }
+
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiSessionServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiSessionServiceImpl.java
index 87f9d20..f87fc84 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiSessionServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiSessionServiceImpl.java
@@ -1,16 +1,24 @@
package com.vincent.rsf.server.ai.service.impl;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.server.ai.mapper.AiChatMessageMapper;
+import com.vincent.rsf.server.ai.mapper.AiChatSessionMapper;
import com.vincent.rsf.server.ai.model.AiChatMessage;
import com.vincent.rsf.server.ai.model.AiChatSession;
import com.vincent.rsf.server.ai.service.AiRuntimeConfigService;
import com.vincent.rsf.server.ai.service.AiSessionService;
import org.springframework.stereotype.Service;
+import javax.annotation.PostConstruct;
import javax.annotation.Resource;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
+import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -21,28 +29,68 @@
private static final ConcurrentMap<String, List<AiChatSession>> LOCAL_SESSION_CACHE = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, List<AiChatMessage>> LOCAL_MESSAGE_CACHE = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, String> LOCAL_STOP_CACHE = new ConcurrentHashMap<>();
+ private static final String SESSION_TABLE_NAME = "sys_ai_chat_session";
+ private static final String MESSAGE_TABLE_NAME = "sys_ai_chat_message";
@Resource
private AiRuntimeConfigService aiRuntimeConfigService;
+ @Resource
+ private AiChatSessionMapper aiChatSessionMapper;
+ @Resource
+ private AiChatMessageMapper aiChatMessageMapper;
+ @Resource
+ private DataSource dataSource;
+
+ private volatile boolean storageReady;
+
+ @PostConstruct
+ /**
+ * 鍚姩鏃舵帰娴嬭亰澶╁瓨鍌ㄨ〃鏄惁宸插垱寤恒��
+ * 濡傛灉琛ㄥ瓨鍦ㄥ垯璧版暟鎹簱鎸佷箙鍖栵紝鍚﹀垯鍥為��鍒版湰鍦板唴瀛樼紦瀛橈紝淇濊瘉寮�鍙戝拰缂鸿〃鍦烘櫙鍙户缁繍琛屻��
+ */
+ public void initStorageMode() {
+ storageReady = detectStorageTables();
+ }
@Override
+ /**
+ * 璇诲彇鐢ㄦ埛浼氳瘽鍒楄〃銆�
+ * 鏁版嵁搴撳瓨鍌ㄦā寮忕洿鎺ユ煡琛紝鍐呭瓨妯″紡鍒欎粠鏈湴缂撳瓨鍙栧嚭骞舵寜鏈�杩戞洿鏂版椂闂存帓搴忋��
+ */
public synchronized List<AiChatSession> listSessions(Long tenantId, Long userId) {
+ if (useDatabaseStorage()) {
+ return aiChatSessionMapper.selectList(new LambdaQueryWrapper<AiChatSession>()
+ .eq(AiChatSession::getTenantId, tenantId)
+ .eq(AiChatSession::getUserId, userId)
+ .orderByDesc(AiChatSession::getUpdateTime, AiChatSession::getCreateTime));
+ }
List<AiChatSession> sessions = getSessions(tenantId, userId);
sessions.sort(Comparator.comparing(AiChatSession::getUpdateTime, Comparator.nullsLast(Date::compareTo)).reversed());
return sessions;
}
@Override
+ /**
+ * 鍒涘缓鏂颁細璇濓紝骞跺垵濮嬪寲鏍囬銆佹ā鍨嬪拰鏃堕棿鎴炽��
+ */
public synchronized AiChatSession createSession(Long tenantId, Long userId, String title, String modelCode) {
- List<AiChatSession> sessions = getSessions(tenantId, userId);
+ List<AiChatSession> sessions = useDatabaseStorage() ? listSessions(tenantId, userId) : getSessions(tenantId, userId);
Date now = new Date();
AiChatSession session = new AiChatSession()
.setId(UUID.randomUUID().toString().replace("-", ""))
+ .setTenantId(tenantId)
+ .setUserId(userId)
.setTitle(resolveTitle(title, sessions.size() + 1))
.setModelCode(resolveModelCode(modelCode))
.setCreateTime(now)
.setUpdateTime(now)
- .setLastMessageAt(now);
+ .setLastMessageAt(now)
+ .setStatus(1)
+ .setDeleted(0);
+ if (useDatabaseStorage()) {
+ aiChatSessionMapper.insert(session);
+ return session;
+ }
sessions.add(0, session);
saveSessions(tenantId, userId, sessions);
saveMessages(session.getId(), new ArrayList<>());
@@ -50,6 +98,9 @@
}
@Override
+ /**
+ * 纭繚浼氳瘽瀛樺湪锛涘鏋滀細璇濆凡瀛樺湪浣嗘ā鍨嬪彂鐢熷彉鍖栵紝浼氬悓姝ユ洿鏂颁細璇濊褰曘��
+ */
public synchronized AiChatSession ensureSession(Long tenantId, Long userId, String sessionId, String modelCode) {
AiChatSession session = getSession(tenantId, userId, sessionId);
if (session == null) {
@@ -64,9 +115,22 @@
}
@Override
+ /**
+ * 瀹夊叏璇诲彇浼氳瘽锛屽苟鏍¢獙绉熸埛涓庣敤鎴峰綊灞炪��
+ */
public synchronized AiChatSession getSession(Long tenantId, Long userId, String sessionId) {
if (sessionId == null || sessionId.trim().isEmpty()) {
return null;
+ }
+ if (useDatabaseStorage()) {
+ AiChatSession session = aiChatSessionMapper.selectById(sessionId);
+ if (session == null) {
+ return null;
+ }
+ if (!Objects.equals(tenantId, session.getTenantId()) || !Objects.equals(userId, session.getUserId())) {
+ return null;
+ }
+ return session;
}
for (AiChatSession session : getSessions(tenantId, userId)) {
if (sessionId.equals(session.getId())) {
@@ -77,6 +141,9 @@
}
@Override
+ /**
+ * 鏇存柊浼氳瘽鏍囬銆�
+ */
public synchronized AiChatSession renameSession(Long tenantId, Long userId, String sessionId, String title) {
AiChatSession session = getSession(tenantId, userId, sessionId);
if (session == null) {
@@ -89,7 +156,20 @@
}
@Override
+ /**
+ * 鍒犻櫎浼氳瘽鍙婂叾鍏宠仈娑堟伅锛屽悓鏃舵竻鐞嗗仠姝㈡爣璁扮紦瀛樸��
+ */
public synchronized void removeSession(Long tenantId, Long userId, String sessionId) {
+ if (useDatabaseStorage()) {
+ AiChatSession session = getSession(tenantId, userId, sessionId);
+ if (session != null) {
+ aiChatMessageMapper.delete(new LambdaQueryWrapper<AiChatMessage>()
+ .eq(AiChatMessage::getSessionId, sessionId));
+ aiChatSessionMapper.deleteById(sessionId);
+ }
+ LOCAL_STOP_CACHE.remove(sessionId);
+ return;
+ }
List<AiChatSession> sessions = getSessions(tenantId, userId);
sessions.removeIf(session -> sessionId.equals(session.getId()));
saveSessions(tenantId, userId, sessions);
@@ -98,15 +178,26 @@
}
@Override
+ /**
+ * 鏌ヨ浼氳瘽鐨勫畬鏁存秷鎭巻鍙层��
+ */
public synchronized List<AiChatMessage> listMessages(Long tenantId, Long userId, String sessionId) {
AiChatSession session = getSession(tenantId, userId, sessionId);
if (session == null) {
return new ArrayList<>();
}
+ if (useDatabaseStorage()) {
+ return aiChatMessageMapper.selectList(new LambdaQueryWrapper<AiChatMessage>()
+ .eq(AiChatMessage::getSessionId, sessionId)
+ .orderByAsc(AiChatMessage::getCreateTime, AiChatMessage::getId));
+ }
return getMessages(sessionId);
}
@Override
+ /**
+ * 鎴彇鏈�杩戣嫢骞叉潯娑堟伅浣滀负妯″瀷涓婁笅鏂囷紝閬垮厤姣忔閮芥妸瀹屾暣鍘嗗彶鍙戦�佺粰妯″瀷銆�
+ */
public synchronized List<AiChatMessage> listContextMessages(Long tenantId, Long userId, String sessionId, int maxCount) {
List<AiChatMessage> messages = listMessages(tenantId, userId, sessionId);
if (messages.size() <= maxCount) {
@@ -116,6 +207,9 @@
}
@Override
+ /**
+ * 杩藉姞涓�鏉℃秷鎭紝骞跺悓姝ュ埛鏂颁細璇濇憳瑕併�佹椿璺冩椂闂村拰榛樿鏍囬銆�
+ */
public synchronized AiChatMessage appendMessage(Long tenantId, Long userId, String sessionId, String role, String content, String modelCode) {
AiChatSession session = getSession(tenantId, userId, sessionId);
if (session == null) {
@@ -124,13 +218,21 @@
List<AiChatMessage> messages = getMessages(sessionId);
AiChatMessage message = new AiChatMessage()
.setId(UUID.randomUUID().toString().replace("-", ""))
+ .setTenantId(tenantId)
+ .setUserId(userId)
.setSessionId(sessionId)
.setRole(role)
.setContent(content)
.setModelCode(resolveModelCode(modelCode))
- .setCreateTime(new Date());
- messages.add(message);
- saveMessages(sessionId, messages);
+ .setCreateTime(new Date())
+ .setStatus(1)
+ .setDeleted(0);
+ if (useDatabaseStorage()) {
+ aiChatMessageMapper.insert(message);
+ } else {
+ messages.add(message);
+ saveMessages(sessionId, messages);
+ }
session.setLastMessage(buildPreview(content));
session.setLastMessageAt(message.getCreateTime());
session.setUpdateTime(message.getCreateTime());
@@ -145,44 +247,72 @@
}
@Override
+ /**
+ * 娓呴櫎鍋滄鐢熸垚鏍囪銆�
+ */
public void clearStopFlag(String sessionId) {
LOCAL_STOP_CACHE.remove(sessionId);
}
@Override
+ /**
+ * 鏍囪浼氳瘽闇�瑕佸仠姝㈢敓鎴愩��
+ */
public void requestStop(String sessionId) {
LOCAL_STOP_CACHE.put(sessionId, "1");
}
@Override
+ /**
+ * 璇诲彇鍋滄鐢熸垚鏍囪銆�
+ */
public boolean isStopRequested(String sessionId) {
String stopFlag = LOCAL_STOP_CACHE.get(sessionId);
return "1".equals(stopFlag);
}
+ /**
+ * 浠庡唴瀛樼紦瀛樹腑璇诲彇褰撳墠鐢ㄦ埛鐨勪細璇濆垪琛ㄣ��
+ */
private List<AiChatSession> getSessions(Long tenantId, Long userId) {
String ownerKey = buildOwnerKey(tenantId, userId);
List<AiChatSession> sessions = LOCAL_SESSION_CACHE.get(ownerKey);
return sessions == null ? new ArrayList<>() : new ArrayList<>(sessions);
}
+ /**
+ * 灏嗕細璇濆垪琛ㄥ啓鍥炴湰鍦扮紦瀛樸��
+ */
private void saveSessions(Long tenantId, Long userId, List<AiChatSession> sessions) {
String ownerKey = buildOwnerKey(tenantId, userId);
List<AiChatSession> cachedSessions = new ArrayList<>(sessions);
LOCAL_SESSION_CACHE.put(ownerKey, cachedSessions);
}
+ /**
+ * 浠庡唴瀛樼紦瀛樹腑璇诲彇鎸囧畾浼氳瘽鐨勬秷鎭垪琛ㄣ��
+ */
private List<AiChatMessage> getMessages(String sessionId) {
List<AiChatMessage> messages = LOCAL_MESSAGE_CACHE.get(sessionId);
return messages == null ? new ArrayList<>() : new ArrayList<>(messages);
}
+ /**
+ * 灏嗘秷鎭垪琛ㄥ啓鍥炴湰鍦扮紦瀛樸��
+ */
private void saveMessages(String sessionId, List<AiChatMessage> messages) {
List<AiChatMessage> cachedMessages = new ArrayList<>(messages);
LOCAL_MESSAGE_CACHE.put(sessionId, cachedMessages);
}
+ /**
+ * 鎸夊瓨鍌ㄦā寮忓埛鏂板崟涓細璇濊褰曘��
+ */
private void refreshSession(Long tenantId, Long userId, AiChatSession target) {
+ if (useDatabaseStorage()) {
+ aiChatSessionMapper.updateById(target);
+ return;
+ }
List<AiChatSession> sessions = getSessions(tenantId, userId);
for (int i = 0; i < sessions.size(); i++) {
if (target.getId().equals(sessions.get(i).getId())) {
@@ -195,14 +325,23 @@
saveSessions(tenantId, userId, sessions);
}
+ /**
+ * 缁勮绉熸埛涓庣敤鎴风淮搴︾殑鏈湴缂撳瓨 key銆�
+ */
private String buildOwnerKey(Long tenantId, Long userId) {
return String.valueOf(tenantId) + ":" + String.valueOf(userId);
}
+ /**
+ * 瑙f瀽鏈娑堟伅浣跨敤鐨勬ā鍨嬬紪鐮侊紱涓虹┖鏃跺洖閫�鍒扮郴缁熼粯璁ゆā鍨嬨��
+ */
private String resolveModelCode(String modelCode) {
return modelCode == null || modelCode.trim().isEmpty() ? aiRuntimeConfigService.resolveDefaultModelCode() : modelCode;
}
+ /**
+ * 鐢熸垚浼氳瘽鏍囬锛屾湭鏄惧紡浼犳爣棰樻椂浣跨敤鈥滄柊瀵硅瘽 N鈥濄��
+ */
private String resolveTitle(String title, int index) {
if (title == null || title.trim().isEmpty()) {
return "鏂板璇� " + index;
@@ -210,6 +349,9 @@
return buildPreview(title);
}
+ /**
+ * 灏嗙敤鎴疯緭鍏ュ帇缂╂垚閫傚悎浣滀负鏍囬鎴栨渶鍚庢秷鎭瑙堢殑鐭枃鏈��
+ */
private String buildPreview(String content) {
if (content == null || content.trim().isEmpty()) {
return "鏂板璇�";
@@ -218,4 +360,41 @@
return normalized.length() > 24 ? normalized.substring(0, 24) : normalized;
}
+ /**
+ * 鍒ゆ柇褰撳墠鏄惁鍙互浣跨敤鏁版嵁搴撴寔涔呭寲鑱婂ぉ鏁版嵁銆�
+ */
+ private boolean useDatabaseStorage() {
+ return storageReady || (storageReady = detectStorageTables());
+ }
+
+ /**
+ * 妫�鏌ヨ亰澶╁瓨鍌ㄦ墍闇�琛ㄦ槸鍚﹀凡缁忓瓨鍦ㄣ��
+ */
+ private boolean detectStorageTables() {
+ try (Connection connection = dataSource.getConnection()) {
+ return tableExists(connection, SESSION_TABLE_NAME) && tableExists(connection, MESSAGE_TABLE_NAME);
+ } catch (Exception ignore) {
+ return false;
+ }
+ }
+
+ /**
+ * 鍒ゆ柇鎸囧畾琛ㄥ悕鏄惁鍦ㄥ綋鍓嶆暟鎹簱涓瓨鍦ㄣ��
+ */
+ private boolean tableExists(Connection connection, String tableName) throws Exception {
+ if (tableName == null || tableName.trim().isEmpty()) {
+ return false;
+ }
+ String[] candidates = new String[]{tableName, tableName.toUpperCase(), tableName.toLowerCase()};
+ for (String candidate : candidates) {
+ try (ResultSet resultSet = connection.getMetaData().getTables(connection.getCatalog(), null, candidate, null)) {
+ if (resultSet.next()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpHttpClient.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpHttpClient.java
new file mode 100644
index 0000000..9e662d5
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpHttpClient.java
@@ -0,0 +1,161 @@
+package com.vincent.rsf.server.ai.service.mcp;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vincent.rsf.server.ai.constant.AiMcpConstants;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiMcpToolDescriptor;
+import com.vincent.rsf.server.system.entity.AiMcpMount;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class AiMcpHttpClient {
+
+ @Resource
+ private ObjectMapper objectMapper;
+ @Resource
+ private AiMcpPayloadMapper aiMcpPayloadMapper;
+
+ /**
+ * 閫氳繃 Streamable HTTP 鍗忚鍔犺浇杩滅▼ MCP 宸ュ叿鐩綍銆�
+ */
+ public List<AiMcpToolDescriptor> listTools(AiMcpMount mount) {
+ initialize(mount);
+ JsonNode result = sendRequest(mount, "tools/list", new LinkedHashMap<String, Object>(), true);
+ List<AiMcpToolDescriptor> output = new ArrayList<>();
+ JsonNode toolsNode = result.path("tools");
+ if (!toolsNode.isArray()) {
+ return output;
+ }
+ for (JsonNode item : toolsNode) {
+ AiMcpToolDescriptor descriptor = aiMcpPayloadMapper.toExternalToolDescriptor(mount, item);
+ if (descriptor != null) {
+ output.add(descriptor);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * 閫氳繃 Streamable HTTP 鍗忚鎵ц涓�娆¤繙绋嬪伐鍏疯皟鐢ㄣ��
+ */
+ public AiDiagnosticToolResult callTool(AiMcpMount mount, String toolName, Map<String, Object> arguments) {
+ initialize(mount);
+ Map<String, Object> params = new LinkedHashMap<>();
+ params.put("name", toolName);
+ params.put("arguments", arguments == null ? new LinkedHashMap<String, Object>() : arguments);
+ JsonNode result = sendRequest(mount, "tools/call", params, true);
+ return aiMcpPayloadMapper.toExternalToolResult(mount, toolName, result);
+ }
+
+ /**
+ * 鎵ц MCP initialize + notifications/initialized 鎻℃墜銆�
+ */
+ private void initialize(AiMcpMount mount) {
+ Map<String, Object> params = new LinkedHashMap<>();
+ params.put("protocolVersion", AiMcpConstants.PROTOCOL_VERSION);
+ params.put("capabilities", new LinkedHashMap<String, Object>());
+ Map<String, Object> clientInfo = new LinkedHashMap<>();
+ clientInfo.put("name", "rsf-server");
+ clientInfo.put("version", AiMcpConstants.SERVER_VERSION);
+ params.put("clientInfo", clientInfo);
+ sendRequest(mount, "initialize", params, true);
+ sendRequest(mount, "notifications/initialized", new LinkedHashMap<String, Object>(), false);
+ }
+
+ /**
+ * 鍙戦�佷竴鏉� JSON-RPC 璇锋眰鍒拌繙绋� MCP HTTP 绔偣銆�
+ */
+ private JsonNode sendRequest(AiMcpMount mount, String method, Object params, boolean expectResponse) {
+ HttpURLConnection connection = null;
+ try {
+ connection = (HttpURLConnection) new URL(mount.getUrl()).openConnection();
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(true);
+ connection.setConnectTimeout(mount.getTimeoutMs() == null ? 10000 : mount.getTimeoutMs());
+ connection.setReadTimeout(mount.getTimeoutMs() == null ? 10000 : mount.getTimeoutMs());
+ connection.setRequestProperty("Content-Type", "application/json");
+ connection.setRequestProperty("Accept", "application/json");
+ applyAuthHeaders(connection, mount);
+
+ Map<String, Object> body = new LinkedHashMap<>();
+ body.put("jsonrpc", "2.0");
+ if (expectResponse) {
+ body.put("id", String.valueOf(System.currentTimeMillis()));
+ }
+ body.put("method", method);
+ body.put("params", params == null ? new LinkedHashMap<String, Object>() : params);
+
+ try (OutputStream outputStream = connection.getOutputStream()) {
+ outputStream.write(objectMapper.writeValueAsBytes(body));
+ outputStream.flush();
+ }
+
+ int statusCode = connection.getResponseCode();
+ InputStream inputStream = statusCode >= 400 ? connection.getErrorStream() : connection.getInputStream();
+ if (!expectResponse) {
+ return null;
+ }
+ if (inputStream == null) {
+ throw new IllegalStateException("MCP鏈嶅姟杩斿洖绌哄搷搴�");
+ }
+ String payload = readPayload(inputStream);
+ JsonNode root = objectMapper.readTree(payload);
+ if (root.has("error") && !root.get("error").isNull()) {
+ throw new IllegalStateException(root.path("error").path("message").asText("MCP璋冪敤澶辫触"));
+ }
+ return root.path("result");
+ } catch (Exception e) {
+ throw new IllegalStateException("MCP璇锋眰澶辫触: " + e.getMessage(), e);
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+
+ /**
+ * 鎸夋寕杞介厤缃啓鍏ラ壌鏉冭姹傚ご銆�
+ */
+ private void applyAuthHeaders(HttpURLConnection connection, AiMcpMount mount) {
+ if (mount == null || mount.getAuthType() == null || mount.getAuthValue() == null || mount.getAuthValue().trim().isEmpty()) {
+ return;
+ }
+ String authType = mount.getAuthType().trim().toUpperCase();
+ if (AiMcpConstants.AUTH_TYPE_BEARER.equals(authType)) {
+ connection.setRequestProperty("Authorization", "Bearer " + mount.getAuthValue().trim());
+ } else if (AiMcpConstants.AUTH_TYPE_API_KEY.equals(authType)) {
+ connection.setRequestProperty("X-API-Key", mount.getAuthValue().trim());
+ }
+ }
+
+ /**
+ * 璇诲彇 HTTP 鍝嶅簲浣撳叏鏂囥��
+ */
+ private String readPayload(InputStream inputStream) throws Exception {
+ StringBuilder output = new StringBuilder();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ output.append(line);
+ }
+ }
+ return output.toString();
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpPayloadMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpPayloadMapper.java
new file mode 100644
index 0000000..f2725f2
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpPayloadMapper.java
@@ -0,0 +1,204 @@
+package com.vincent.rsf.server.ai.service.mcp;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vincent.rsf.server.ai.constant.AiMcpConstants;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiMcpToolDescriptor;
+import com.vincent.rsf.server.system.entity.AiMcpMount;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class AiMcpPayloadMapper {
+
+ @Resource
+ private ObjectMapper objectMapper;
+
+ /**
+ * 灏嗚繙绋� MCP tools/list 杩斿洖鐨勫崟涓伐鍏峰畾涔夎浆鎹㈡垚鏈郴缁熺粺涓�鐨勫伐鍏锋弿杩扮銆�
+ */
+ public AiMcpToolDescriptor toExternalToolDescriptor(AiMcpMount mount, JsonNode item) {
+ String remoteName = item.path("name").asText("");
+ if (remoteName.trim().isEmpty()) {
+ return null;
+ }
+ return new AiMcpToolDescriptor()
+ .setMountCode(mount.getMountCode())
+ .setMountName(mount.getName())
+ .setToolCode(remoteName)
+ .setMcpToolName(buildMcpToolName(mount.getMountCode(), remoteName))
+ .setToolName(item.path("title").asText(remoteName))
+ .setSceneCode(resolveSceneCode(mount.getUsageScope()))
+ .setDescription(item.path("description").asText(""))
+ .setEnabledFlag(mount.getEnabledFlag())
+ .setPriority(999)
+ .setToolPrompt(item.path("description").asText(""))
+ .setUsageScope(normalizeUsageScope(mount.getUsageScope()))
+ .setTransportType(mount.getTransportType())
+ .setInputSchema(readInputSchema(item.path("inputSchema"), true));
+ }
+
+ /**
+ * 灏嗚繙绋� MCP tools/call 杩斿洖鐨勭粨鏋滆浆鎹负绯荤粺鍐呴儴缁熶竴鐨勫伐鍏风粨鏋滄ā鍨嬨��
+ */
+ public AiDiagnosticToolResult toExternalToolResult(AiMcpMount mount, String toolName, JsonNode result) {
+ boolean isError = result.path("isError").asBoolean(false);
+ Map<String, Object> rawMeta = new LinkedHashMap<>();
+ if (result.has("structuredContent") && !result.get("structuredContent").isNull()) {
+ rawMeta.put("structuredContent", objectMapper.convertValue(result.get("structuredContent"), Map.class));
+ }
+ if (result.has("content") && !result.get("content").isNull()) {
+ rawMeta.put("content", objectMapper.convertValue(result.get("content"), Object.class));
+ }
+ return new AiDiagnosticToolResult()
+ .setToolCode(toolName)
+ .setMountCode(mount.getMountCode())
+ .setMcpToolName(buildMcpToolName(mount.getMountCode(), toolName))
+ .setToolName(toolName)
+ .setSeverity(isError ? "WARN" : "INFO")
+ .setSummaryText(extractContentText(result))
+ .setRawMeta(rawMeta);
+ }
+
+ /**
+ * 灏嗘湰鍦板伐鍏锋弿杩扮杞崲涓� MCP 鍗忚 tools/list 鎵�闇�鐨勬暟鎹粨鏋勩��
+ */
+ public Map<String, Object> toProtocolTool(AiMcpToolDescriptor descriptor) {
+ Map<String, Object> item = new LinkedHashMap<>();
+ item.put("name", descriptor.getMcpToolName());
+ item.put("title", descriptor.getToolName());
+ item.put("description", descriptor.getDescription() == null || descriptor.getDescription().trim().isEmpty()
+ ? descriptor.getToolPrompt()
+ : descriptor.getDescription());
+ item.put("inputSchema", descriptor.getInputSchema() == null || descriptor.getInputSchema().isEmpty()
+ ? defaultInputSchema(false)
+ : descriptor.getInputSchema());
+ return item;
+ }
+
+ /**
+ * 璇诲彇杩滅▼宸ュ叿澹版槑涓殑 inputSchema锛涚己鐪佹椂鍥為��鍒扮郴缁熼粯璁よ緭鍏ョ粨鏋勩��
+ */
+ public Map<String, Object> readInputSchema(JsonNode schemaNode, boolean includeTenantId) {
+ if (schemaNode == null || schemaNode.isMissingNode() || schemaNode.isNull()) {
+ return defaultInputSchema(includeTenantId);
+ }
+ return objectMapper.convertValue(schemaNode, Map.class);
+ }
+
+ /**
+ * 鐢熸垚绯荤粺榛樿鐨勫伐鍏疯緭鍏� schema銆�
+ */
+ public Map<String, Object> defaultInputSchema(boolean includeTenantId) {
+ Map<String, Object> schema = new LinkedHashMap<>();
+ schema.put("type", "object");
+ Map<String, Object> properties = new LinkedHashMap<>();
+ properties.put("question", fieldSchema("string", "褰撳墠璇婃柇闂鎴栬皟鐢ㄧ洰鐨�"));
+ properties.put("sceneCode", fieldSchema("string", "璇婃柇鍦烘櫙缂栫爜"));
+ if (includeTenantId) {
+ properties.put("tenantId", fieldSchema("integer", "绉熸埛ID"));
+ }
+ schema.put("properties", properties);
+ return schema;
+ }
+
+ /**
+ * 浠� MCP tools/call 缁撴灉涓彁鍙栧彲鐩存帴缁欐ā鍨嬬湅鐨勬枃鏈憳瑕併��
+ */
+ public String extractContentText(JsonNode result) {
+ List<String> parts = new ArrayList<>();
+ JsonNode contentNode = result.path("content");
+ if (contentNode.isArray()) {
+ for (Iterator<JsonNode> it = contentNode.elements(); it.hasNext(); ) {
+ JsonNode item = it.next();
+ if ("text".equals(item.path("type").asText(""))) {
+ parts.add(item.path("text").asText(""));
+ } else if (item.isObject() || item.isArray()) {
+ parts.add(item.toString());
+ } else {
+ parts.add(item.asText(""));
+ }
+ }
+ }
+ if (!parts.isEmpty()) {
+ return String.join("\n", parts).trim();
+ }
+ if (result.has("structuredContent") && !result.get("structuredContent").isNull()) {
+ return result.get("structuredContent").toString();
+ }
+ return result.toString();
+ }
+
+ /**
+ * 鏋勯�犵郴缁熺粺涓�鐨� MCP 宸ュ叿鍏ㄥ悕銆�
+ */
+ public String buildMcpToolName(String mountCode, String toolCode) {
+ return (mountCode == null ? AiMcpConstants.DEFAULT_LOCAL_MOUNT_CODE : mountCode) + "_" + toolCode;
+ }
+
+ /**
+ * 灏嗙敤閫旈璁捐浆鎹㈡垚杩愯鏃� sceneCode銆�
+ */
+ public String resolveSceneCode(String usageScope) {
+ if (AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY.equalsIgnoreCase(usageScope)) {
+ return AiSceneCode.SYSTEM_DIAGNOSE;
+ }
+ return "";
+ }
+
+ /**
+ * 鏍囧噯鍖栫敤閫旇寖鍥村瓧娈碉紝缁熶竴鎴愮郴缁熸敮鎸佺殑涓変釜鏋氫妇鍊笺��
+ */
+ public String normalizeUsageScope(String usageScope) {
+ if (usageScope == null || usageScope.trim().isEmpty()) {
+ return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY;
+ }
+ String normalized = usageScope.trim().toUpperCase();
+ if (AiMcpConstants.USAGE_SCOPE_CHAT_AND_DIAGNOSE.equals(normalized)
+ || AiMcpConstants.USAGE_SCOPE_DISABLED.equals(normalized)) {
+ return normalized;
+ }
+ return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY;
+ }
+
+ /**
+ * 鏍规嵁 sceneCode銆乪nabledFlag 鍜� usageScope 鎺ㄦ柇鏈�缁堢敤閫旇寖鍥淬��
+ */
+ public String resolveUsageScope(String sceneCode, Integer enabledFlag, String usageScope) {
+ String normalized = normalizeUsageScope(usageScope);
+ if (AiMcpConstants.USAGE_SCOPE_DISABLED.equals(normalized)) {
+ return normalized;
+ }
+ if (enabledFlag != null && !Integer.valueOf(1).equals(enabledFlag)) {
+ return AiMcpConstants.USAGE_SCOPE_DISABLED;
+ }
+ if (AiMcpConstants.USAGE_SCOPE_CHAT_AND_DIAGNOSE.equals(normalized)) {
+ return normalized;
+ }
+ if (sceneCode == null || sceneCode.trim().isEmpty()) {
+ return AiMcpConstants.USAGE_SCOPE_CHAT_AND_DIAGNOSE;
+ }
+ return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY;
+ }
+
+ /**
+ * 鏋勯�犲崟涓瓧娈电殑 schema 瀹氫箟銆�
+ */
+ private Map<String, Object> fieldSchema(String type, String description) {
+ Map<String, Object> field = new LinkedHashMap<>();
+ field.put("type", type);
+ field.put("description", description);
+ return field;
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpProtocolService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpProtocolService.java
new file mode 100644
index 0000000..40922f4
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpProtocolService.java
@@ -0,0 +1,163 @@
+package com.vincent.rsf.server.ai.service.mcp;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vincent.rsf.server.ai.constant.AiMcpConstants;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiMcpToolDescriptor;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class AiMcpProtocolService {
+
+ @Resource
+ private AiMcpRegistryService aiMcpRegistryService;
+ @Resource
+ private ObjectMapper objectMapper;
+ @Resource
+ private AiMcpPayloadMapper aiMcpPayloadMapper;
+
+ /**
+ * 澶勭悊 MCP 鍗忚鍏ュ彛璇锋眰锛屽吋瀹规壒閲忎笌鍗曟潯 JSON-RPC 璐熻浇銆�
+ */
+ public Object handle(Long tenantId, JsonNode body) {
+ if (body != null && body.isArray()) {
+ List<Object> responses = new ArrayList<>();
+ for (JsonNode item : body) {
+ Object response = handleSingle(tenantId, item);
+ if (response != null) {
+ responses.add(response);
+ }
+ }
+ return responses;
+ }
+ return handleSingle(tenantId, body);
+ }
+
+ /**
+ * 澶勭悊鍗曟潯 JSON-RPC MCP 璇锋眰銆�
+ */
+ public Object handleSingle(Long tenantId, JsonNode request) {
+ if (request == null || request.isMissingNode() || request.isNull()) {
+ return error(null, -32600, "invalid request");
+ }
+ JsonNode idNode = request.get("id");
+ String method = request.path("method").asText("");
+ try {
+ if ("initialize".equals(method)) {
+ return success(idNode, buildInitializeResult());
+ }
+ if ("notifications/initialized".equals(method)) {
+ return idNode == null || idNode.isNull() ? null : success(idNode, new LinkedHashMap<String, Object>());
+ }
+ if ("ping".equals(method)) {
+ return success(idNode, new LinkedHashMap<String, Object>());
+ }
+ if ("tools/list".equals(method)) {
+ return success(idNode, buildToolsListResult(tenantId));
+ }
+ if ("tools/call".equals(method)) {
+ return success(idNode, buildToolCallResult(tenantId, request.path("params")));
+ }
+ return error(idNode, -32601, "method not found");
+ } catch (Exception e) {
+ return error(idNode, -32000, e.getMessage());
+ }
+ }
+
+ /**
+ * 鏋勯�� initialize 鏂规硶鐨勬爣鍑嗚繑鍥炰綋銆�
+ */
+ private Map<String, Object> buildInitializeResult() {
+ Map<String, Object> result = new LinkedHashMap<>();
+ result.put("protocolVersion", AiMcpConstants.PROTOCOL_VERSION);
+ Map<String, Object> capabilities = new LinkedHashMap<>();
+ Map<String, Object> tools = new LinkedHashMap<>();
+ tools.put("listChanged", false);
+ capabilities.put("tools", tools);
+ result.put("capabilities", capabilities);
+ Map<String, Object> serverInfo = new LinkedHashMap<>();
+ serverInfo.put("name", AiMcpConstants.SERVER_NAME);
+ serverInfo.put("version", AiMcpConstants.SERVER_VERSION);
+ result.put("serverInfo", serverInfo);
+ return result;
+ }
+
+ /**
+ * 鏋勯�� tools/list 鏂规硶杩斿洖浣撱��
+ */
+ private Map<String, Object> buildToolsListResult(Long tenantId) {
+ List<Map<String, Object>> tools = new ArrayList<>();
+ for (AiMcpToolDescriptor descriptor : aiMcpRegistryService.listTools(tenantId, null)) {
+ if (descriptor == null || !Integer.valueOf(1).equals(descriptor.getEnabledFlag())) {
+ continue;
+ }
+ tools.add(aiMcpPayloadMapper.toProtocolTool(descriptor));
+ }
+ Map<String, Object> result = new LinkedHashMap<>();
+ result.put("tools", tools);
+ return result;
+ }
+
+ /**
+ * 鎵ц tools/call锛屽苟鎶婂唴閮ㄥ伐鍏风粨鏋滆浆鎹㈡垚 MCP 鍗忚杈撳嚭銆�
+ */
+ private Map<String, Object> buildToolCallResult(Long tenantId, JsonNode paramsNode) {
+ String name = paramsNode.path("name").asText("");
+ Map<String, Object> arguments = paramsNode.has("arguments") && !paramsNode.get("arguments").isNull()
+ ? objectMapper.convertValue(paramsNode.get("arguments"), Map.class)
+ : new LinkedHashMap<String, Object>();
+ AiPromptContext context = new AiPromptContext()
+ .setTenantId(tenantId)
+ .setSceneCode(arguments.get("sceneCode") == null ? "system_diagnose" : String.valueOf(arguments.get("sceneCode")))
+ .setQuestion(arguments.get("question") == null ? "璇锋墽琛屼竴娆CP宸ュ叿璋冪敤" : String.valueOf(arguments.get("question")));
+ AiDiagnosticToolResult result = aiMcpRegistryService.executeTool(tenantId, name, context, arguments);
+ Map<String, Object> payload = new LinkedHashMap<>();
+ List<Map<String, Object>> content = new ArrayList<>();
+ Map<String, Object> text = new LinkedHashMap<>();
+ text.put("type", "text");
+ text.put("text", result == null || result.getSummaryText() == null ? "" : result.getSummaryText());
+ content.add(text);
+ payload.put("content", content);
+ payload.put("isError", result != null && "WARN".equalsIgnoreCase(result.getSeverity()));
+ if (result != null && result.getRawMeta() != null && !result.getRawMeta().isEmpty()) {
+ payload.put("structuredContent", result.getRawMeta());
+ }
+ return payload;
+ }
+
+ /**
+ * 鐢熸垚 JSON-RPC 鎴愬姛鍝嶅簲銆�
+ */
+ public Map<String, Object> success(JsonNode idNode, Object result) {
+ Map<String, Object> payload = new LinkedHashMap<>();
+ payload.put("jsonrpc", "2.0");
+ payload.put("id", idNode == null || idNode.isNull() ? null : objectMapper.convertValue(idNode, Object.class));
+ payload.put("result", result == null ? new LinkedHashMap<String, Object>() : result);
+ return payload;
+ }
+
+ /**
+ * 鐢熸垚 JSON-RPC 閿欒鍝嶅簲銆�
+ */
+ public Map<String, Object> error(JsonNode idNode, int code, String message) {
+ Map<String, Object> payload = new LinkedHashMap<>();
+ payload.put("jsonrpc", "2.0");
+ payload.put("id", idNode == null || idNode.isNull() ? null : objectMapper.convertValue(idNode, Object.class));
+ Map<String, Object> error = new LinkedHashMap<>();
+ error.put("code", code);
+ error.put("message", message == null ? "unknown error" : message);
+ payload.put("error", error);
+ return payload;
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpRegistryService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpRegistryService.java
new file mode 100644
index 0000000..d4819eb
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpRegistryService.java
@@ -0,0 +1,567 @@
+package com.vincent.rsf.server.ai.service.mcp;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.server.ai.constant.AiMcpConstants;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiMcpToolDescriptor;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+import com.vincent.rsf.server.ai.service.provider.AiDiagnosticDataProvider;
+import com.vincent.rsf.server.system.entity.AiDiagnosticToolConfig;
+import com.vincent.rsf.server.system.entity.AiMcpMount;
+import com.vincent.rsf.server.system.service.AiDiagnosticToolConfigService;
+import com.vincent.rsf.server.system.service.AiMcpMountService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class AiMcpRegistryService {
+
+ private static final long EXTERNAL_TOOL_CACHE_TTL_MS = 30000L;
+
+ private final List<AiDiagnosticDataProvider> providers;
+ private final Map<String, CachedTools> externalToolCache = new ConcurrentHashMap<>();
+
+ @Resource
+ private AiMcpMountService aiMcpMountService;
+ @Resource
+ private AiDiagnosticToolConfigService aiDiagnosticToolConfigService;
+ @Resource
+ private AiMcpHttpClient aiMcpHttpClient;
+ @Resource
+ private AiMcpSseClient aiMcpSseClient;
+ @Resource
+ private AiMcpPayloadMapper aiMcpPayloadMapper;
+
+ public AiMcpRegistryService(List<AiDiagnosticDataProvider> providers) {
+ this.providers = providers == null ? new ArrayList<>() : providers;
+ }
+
+ /**
+ * 鏋氫妇绉熸埛涓嬪彲瑙佺殑 MCP 宸ュ叿鐩綍銆�
+ * 鍖呮嫭鍐呴儴宸ュ叿鍜屾墍鏈夊惎鐢ㄤ腑鐨勫閮ㄦ寕杞藉伐鍏枫��
+ */
+ public List<AiMcpToolDescriptor> listTools(Long tenantId, Long mountId) {
+ List<AiMcpToolDescriptor> output = new ArrayList<>();
+ List<AiMcpMount> mounts;
+ if (mountId == null) {
+ mounts = aiMcpMountService.list(new LambdaQueryWrapper<AiMcpMount>()
+ .eq(AiMcpMount::getTenantId, tenantId)
+ .eq(AiMcpMount::getEnabledFlag, 1)
+ .eq(AiMcpMount::getStatus, 1)
+ .orderByAsc(AiMcpMount::getMountCode, AiMcpMount::getId));
+ } else {
+ mounts = new ArrayList<>();
+ AiMcpMount mount = aiMcpMountService.getTenantMount(tenantId, mountId);
+ if (mount != null && Integer.valueOf(1).equals(mount.getEnabledFlag()) && Integer.valueOf(1).equals(mount.getStatus())) {
+ mounts.add(mount);
+ }
+ }
+ for (AiMcpMount mount : mounts) {
+ try {
+ if (AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(mount.getTransportType())) {
+ output.addAll(buildInternalTools(tenantId, mount));
+ } else if (AiMcpConstants.TRANSPORT_HTTP.equalsIgnoreCase(mount.getTransportType())) {
+ output.addAll(loadCachedExternalTools(tenantId, mount));
+ } else if (AiMcpConstants.TRANSPORT_SSE.equalsIgnoreCase(mount.getTransportType())) {
+ output.addAll(loadCachedExternalTools(tenantId, mount));
+ }
+ } catch (Exception ignore) {
+ }
+ }
+ return output;
+ }
+
+ /**
+ * 浠呰繑鍥炵郴缁熷唴缃伐鍏风洰褰曘��
+ */
+ public List<AiMcpToolDescriptor> listInternalTools(Long tenantId) {
+ AiMcpMount mount = aiMcpMountService.getTenantMountByCode(tenantId, AiMcpConstants.DEFAULT_LOCAL_MOUNT_CODE);
+ if (mount == null || !Integer.valueOf(1).equals(mount.getEnabledFlag()) || !Integer.valueOf(1).equals(mount.getStatus())) {
+ return new ArrayList<>();
+ }
+ return buildInternalTools(tenantId, mount);
+ }
+
+ /**
+ * 娴嬭瘯鎸囧畾鎸傝浇鐨勮繛閫氭�т笌宸ュ叿鍙戠幇鑳藉姏锛屽苟鎶婄粨鏋滃洖鍐欏埌鎸傝浇娴嬭瘯鐘舵�佸瓧娈点��
+ */
+ public Map<String, Object> testMount(Long tenantId, Long mountId) {
+ AiMcpMount mount = aiMcpMountService.getTenantMount(tenantId, mountId);
+ if (mount == null) {
+ throw new IllegalArgumentException("MCP鎸傝浇涓嶅瓨鍦�");
+ }
+ Map<String, Object> payload = new LinkedHashMap<>();
+ payload.put("mountCode", mount.getMountCode());
+ payload.put("transportType", mount.getTransportType());
+ if (AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(mount.getTransportType())) {
+ List<AiMcpToolDescriptor> tools = buildInternalTools(tenantId, mount);
+ payload.put("success", true);
+ payload.put("toolCount", tools.size());
+ payload.put("tools", tools);
+ updateTestState(mount, 1, "鍐呴儴宸ュ叿鎸傝浇姝e父", tools.size());
+ return payload;
+ }
+ ExternalToolsResult testResult = loadExternalToolsWithTransport(mount);
+ List<AiMcpToolDescriptor> tools = testResult.tools;
+ payload.put("success", true);
+ payload.put("toolCount", tools.size());
+ payload.put("tools", tools);
+ payload.put("resolvedTransportType", testResult.transportType);
+ payload.put("recommendedTransportType", testResult.transportType);
+ payload.put("message", buildExternalSuccessMessage(testResult.transportType, tools.size()));
+ updateTestState(mount, 1, String.valueOf(payload.get("message")), tools.size());
+ return payload;
+ }
+
+ /**
+ * 浠モ�滈瑙堚�濇ā寮忔墽琛屼竴娆″伐鍏疯皟鐢紝渚夸簬鍚庡彴椤甸潰璋冭瘯宸ュ叿杩斿洖鍐呭銆�
+ */
+ public AiDiagnosticToolResult previewTool(Long tenantId, String mountCode, String toolCode, String sceneCode, String question) {
+ AiMcpMount mount = aiMcpMountService.getTenantMountByCode(tenantId, mountCode);
+ if (mount == null) {
+ throw new IllegalArgumentException("MCP鎸傝浇涓嶅瓨鍦�");
+ }
+ AiPromptContext context = new AiPromptContext()
+ .setTenantId(tenantId)
+ .setSceneCode(sceneCode == null || sceneCode.trim().isEmpty() ? AiSceneCode.SYSTEM_DIAGNOSE : sceneCode)
+ .setQuestion(question == null || question.trim().isEmpty() ? "璇锋墽琛屼竴娆CP宸ュ叿棰勮" : question);
+ if (AiMcpConstants.TRANSPORT_HTTP.equalsIgnoreCase(mount.getTransportType())
+ || AiMcpConstants.TRANSPORT_SSE.equalsIgnoreCase(mount.getTransportType())) {
+ return executeExternalTool(mount, toolCode, context, buildToolArguments(context, null));
+ }
+ return executeInternalTool(mountCode, toolCode, context);
+ }
+
+ /**
+ * 鏍规嵁宸ュ叿鎻忚堪绗︽墽琛屼竴娆″伐鍏疯皟鐢ㄣ��
+ */
+ public AiDiagnosticToolResult executeTool(Long tenantId, AiMcpToolDescriptor descriptor, AiPromptContext context) {
+ if (descriptor == null) {
+ throw new IllegalArgumentException("MCP宸ュ叿涓嶅瓨鍦�");
+ }
+ if (AiMcpConstants.TRANSPORT_HTTP.equalsIgnoreCase(descriptor.getTransportType())
+ || AiMcpConstants.TRANSPORT_SSE.equalsIgnoreCase(descriptor.getTransportType())) {
+ AiMcpMount mount = aiMcpMountService.getTenantMountByCode(tenantId, descriptor.getMountCode());
+ if (mount == null) {
+ throw new IllegalArgumentException("MCP鎸傝浇涓嶅瓨鍦�");
+ }
+ return executeExternalTool(mount, descriptor.getToolCode(), context, buildToolArguments(context, descriptor));
+ }
+ return executeInternalTool(descriptor.getMountCode(), descriptor.getToolCode(), context);
+ }
+
+ /**
+ * 鏍规嵁瀹屾暣 MCP 宸ュ叿鍚嶆墽琛屼竴娆″伐鍏疯皟鐢紝閫氬父渚涘崗璁眰鐩存帴浣跨敤銆�
+ */
+ public AiDiagnosticToolResult executeTool(Long tenantId, String mcpToolName, AiPromptContext context, Map<String, Object> arguments) {
+ AiMcpToolDescriptor descriptor = findDescriptor(tenantId, mcpToolName);
+ if (descriptor == null) {
+ throw new IllegalArgumentException("MCP宸ュ叿涓嶅瓨鍦�");
+ }
+ if (AiMcpConstants.TRANSPORT_HTTP.equalsIgnoreCase(descriptor.getTransportType())
+ || AiMcpConstants.TRANSPORT_SSE.equalsIgnoreCase(descriptor.getTransportType())) {
+ AiMcpMount mount = aiMcpMountService.getTenantMountByCode(tenantId, descriptor.getMountCode());
+ if (mount == null) {
+ throw new IllegalArgumentException("MCP鎸傝浇涓嶅瓨鍦�");
+ }
+ return executeExternalTool(mount, descriptor.getToolCode(), context, arguments);
+ }
+ return executeInternalTool(descriptor.getMountCode(), descriptor.getToolCode(), context);
+ }
+
+ /**
+ * 纭繚绉熸埛瀛樺湪榛樿鏈湴 MCP 鎸傝浇銆�
+ */
+ public void ensureDefaultMount(Long tenantId, Long userId) {
+ if (aiMcpMountService.getTenantMountByCode(tenantId, AiMcpConstants.DEFAULT_LOCAL_MOUNT_CODE) != null) {
+ return;
+ }
+ Date now = new Date();
+ AiMcpMount mount = new AiMcpMount()
+ .setUuid(String.valueOf(System.currentTimeMillis()))
+ .setName(AiMcpConstants.DEFAULT_LOCAL_MOUNT_NAME)
+ .setMountCode(AiMcpConstants.DEFAULT_LOCAL_MOUNT_CODE)
+ .setTransportType(AiMcpConstants.TRANSPORT_INTERNAL)
+ .setUrl("/ai/mcp")
+ .setEnabledFlag(1)
+ .setTimeoutMs(10000)
+ .setStatus(1)
+ .setDeleted(0)
+ .setTenantId(tenantId)
+ .setCreateBy(userId)
+ .setCreateTime(now)
+ .setUpdateBy(userId)
+ .setUpdateTime(now)
+ .setMemo("榛樿鎸傝浇褰撳墠 WMS AI 鍐呯疆宸ュ叿闆嗗悎");
+ aiMcpMountService.save(mount);
+ }
+
+ /**
+ * 涓哄唴閮ㄥ伐鍏风粨鏋滆ˉ榻愭寕杞界紪鐮佸拰鏍囧噯 MCP 宸ュ叿鍚嶃��
+ */
+ public AiDiagnosticToolResult decorateResult(AiDiagnosticToolResult result) {
+ if (result == null) {
+ return null;
+ }
+ return decorateResult(result, AiMcpConstants.DEFAULT_LOCAL_MOUNT_CODE, findProvider(result.getToolCode()));
+ }
+
+ private AiDiagnosticToolResult decorateResult(AiDiagnosticToolResult result, String mountCode, AiDiagnosticDataProvider provider) {
+ String actualMountCode = mountCode == null || mountCode.trim().isEmpty() ? AiMcpConstants.DEFAULT_LOCAL_MOUNT_CODE : mountCode;
+ result.setMountCode(actualMountCode);
+ result.setMcpToolName(aiMcpPayloadMapper.buildMcpToolName(actualMountCode, result.getToolCode()));
+ if ((result.getToolName() == null || result.getToolName().trim().isEmpty()) && provider != null) {
+ result.setToolName(provider.getToolName());
+ }
+ return result;
+ }
+
+ /**
+ * 鎸夊綋鍓嶇鎴风殑宸ュ叿閰嶇疆鐢熸垚鍐呴儴 MCP 宸ュ叿鐩綍銆�
+ */
+ private List<AiMcpToolDescriptor> buildInternalTools(Long tenantId, AiMcpMount mount) {
+ Map<String, AiDiagnosticToolConfig> configMap = buildInternalConfigMap(tenantId);
+ List<AiDiagnosticDataProvider> sortedProviders = new ArrayList<>(providers);
+ sortedProviders.sort(Comparator.comparingInt(AiDiagnosticDataProvider::getOrder));
+ List<AiMcpToolDescriptor> output = new ArrayList<>();
+ for (AiDiagnosticDataProvider provider : sortedProviders) {
+ AiDiagnosticToolConfig config = configMap.get(provider.getToolCode());
+ String usageScope = aiMcpPayloadMapper.resolveUsageScope(
+ config == null ? null : config.getSceneCode(),
+ config == null ? 1 : config.getEnabledFlag(),
+ config == null ? null : config.getUsageScope()
+ );
+ output.add(new AiMcpToolDescriptor()
+ .setMountCode(mount.getMountCode())
+ .setMountName(mount.getName())
+ .setToolCode(provider.getToolCode())
+ .setMcpToolName(aiMcpPayloadMapper.buildMcpToolName(mount.getMountCode(), provider.getToolCode()))
+ .setToolName(provider.getToolName())
+ .setSceneCode(aiMcpPayloadMapper.resolveSceneCode(usageScope))
+ .setDescription(provider.getDefaultToolPrompt())
+ .setEnabledFlag(AiMcpConstants.USAGE_SCOPE_DISABLED.equals(usageScope) ? 0 : (config == null ? 1 : config.getEnabledFlag()))
+ .setPriority(config == null ? provider.getOrder() : config.getPriority())
+ .setToolPrompt(config == null ? provider.getDefaultToolPrompt() : config.getToolPrompt())
+ .setUsageScope(usageScope)
+ .setTransportType(mount.getTransportType())
+ .setInputSchema(aiMcpPayloadMapper.defaultInputSchema(true)));
+ }
+ return output;
+ }
+
+ /**
+ * 涓哄唴缃伐鍏风洰褰曢�夋嫨涓�鏉℃渶鍚堥�傜殑鏈夋晥閰嶇疆銆�
+ * 鐩綍灞傚彧鏈変竴鏉″伐鍏锋弿杩帮紝鍥犳杩欓噷浼樺厛淇濈暀鍚敤涓旂姸鎬佹甯哥殑閰嶇疆锛�
+ * 骞朵紭鍏堥�夋嫨鈥滆亰澶╀笌璇婃柇閮藉彲鐢ㄢ�濈殑閰嶇疆锛屽啀閫�鍥炲埌鈥滀粎璇婃柇鈥濋厤缃��
+ */
+ private Map<String, AiDiagnosticToolConfig> buildInternalConfigMap(Long tenantId) {
+ Map<String, AiDiagnosticToolConfig> configMap = new LinkedHashMap<>();
+ for (AiDiagnosticToolConfig item : aiDiagnosticToolConfigService.listTenantConfigs(tenantId)) {
+ if (item == null || !Integer.valueOf(1).equals(item.getStatus())) {
+ continue;
+ }
+ AiDiagnosticToolConfig existed = configMap.get(item.getToolCode());
+ if (existed == null || preferInternalConfig(item, existed)) {
+ configMap.put(item.getToolCode(), item);
+ }
+ }
+ return configMap;
+ }
+
+ /**
+ * 鍐呯疆宸ュ叿鐩綍浼樺厛灞曠ず鐢ㄩ�旀洿骞跨殑閰嶇疆锛屽叾娆℃瘮杈冧紭鍏堢骇銆�
+ */
+ private boolean preferInternalConfig(AiDiagnosticToolConfig candidate, AiDiagnosticToolConfig current) {
+ String candidateUsageScope = aiMcpPayloadMapper.resolveUsageScope(
+ candidate.getSceneCode(),
+ candidate.getEnabledFlag(),
+ candidate.getUsageScope()
+ );
+ String currentUsageScope = aiMcpPayloadMapper.resolveUsageScope(
+ current.getSceneCode(),
+ current.getEnabledFlag(),
+ current.getUsageScope()
+ );
+ int candidateRank = usageScopeRank(candidateUsageScope);
+ int currentRank = usageScopeRank(currentUsageScope);
+ if (candidateRank != currentRank) {
+ return candidateRank < currentRank;
+ }
+ Integer candidatePriority = candidate.getPriority() == null ? Integer.MAX_VALUE : candidate.getPriority();
+ Integer currentPriority = current.getPriority() == null ? Integer.MAX_VALUE : current.getPriority();
+ return candidatePriority < currentPriority;
+ }
+
+ private int usageScopeRank(String usageScope) {
+ if (AiMcpConstants.USAGE_SCOPE_CHAT_AND_DIAGNOSE.equals(usageScope)) {
+ return 0;
+ }
+ if (AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY.equals(usageScope)) {
+ return 1;
+ }
+ return 2;
+ }
+
+ /**
+ * 鎸夊伐鍏风紪鐮佸畾浣嶅唴閮ㄥ伐鍏峰疄鐜般��
+ */
+ private AiDiagnosticDataProvider findProvider(String toolCode) {
+ for (AiDiagnosticDataProvider provider : providers) {
+ if (provider.getToolCode().equals(toolCode)) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 鏇存柊鎸傝浇娴嬭瘯缁撴灉鍜屽伐鍏锋暟閲忥紝骞舵竻鐞嗙紦瀛樸��
+ */
+ private void updateTestState(AiMcpMount mount, Integer result, String message, Integer toolCount) {
+ mount.setLastTestResult(result);
+ mount.setLastTestMessage(message);
+ mount.setLastToolCount(toolCount);
+ mount.setLastTestTime(new Date());
+ mount.setUpdateTime(new Date());
+ aiMcpMountService.updateById(mount);
+ if (mount.getId() != null) {
+ externalToolCache.remove(buildCacheKey(mount.getTenantId(), mount));
+ }
+ }
+
+ /**
+ * 鎵ц鍐呴儴 MCP 宸ュ叿銆�
+ */
+ private AiDiagnosticToolResult executeInternalTool(String mountCode, String toolCode, AiPromptContext context) {
+ AiDiagnosticDataProvider provider = findProvider(toolCode);
+ if (provider == null) {
+ throw new IllegalArgumentException("MCP宸ュ叿涓嶅瓨鍦�");
+ }
+ AiDiagnosticToolResult result = provider.buildDiagnosticData(context);
+ if (result == null) {
+ return new AiDiagnosticToolResult()
+ .setToolCode(toolCode)
+ .setMountCode(mountCode)
+ .setMcpToolName(aiMcpPayloadMapper.buildMcpToolName(mountCode, toolCode))
+ .setToolName(provider.getToolName())
+ .setSeverity("WARN")
+ .setSummaryText("宸ュ叿娌℃湁杩斿洖缁撴灉");
+ }
+ return decorateResult(result, mountCode, provider);
+ }
+
+ /**
+ * 鎵ц澶栭儴 MCP 宸ュ叿锛屽苟琛ラ綈鏈湴绯荤粺鎵�闇�鐨勭粺涓�瀛楁銆�
+ */
+ private AiDiagnosticToolResult executeExternalTool(AiMcpMount mount, String toolCode,
+ AiPromptContext context, Map<String, Object> arguments) {
+ ExternalToolCallResult callResult = callExternalTool(mount, toolCode, arguments);
+ AiDiagnosticToolResult result = callResult.result;
+ return result
+ .setToolCode(toolCode)
+ .setMountCode(mount.getMountCode())
+ .setMcpToolName(aiMcpPayloadMapper.buildMcpToolName(mount.getMountCode(), toolCode))
+ .setToolName(result.getToolName() == null || result.getToolName().trim().isEmpty() ? toolCode : result.getToolName())
+ .setRawMeta(result.getRawMeta() == null ? new LinkedHashMap<String, Object>() : result.getRawMeta());
+ }
+
+ /**
+ * 鐪熸鍔犺浇澶栭儴宸ュ叿鐩綍锛屼笉甯︾紦瀛樸��
+ */
+ private List<AiMcpToolDescriptor> loadExternalTools(AiMcpMount mount) {
+ return loadExternalToolsWithTransport(mount).tools;
+ }
+
+ /**
+ * 甯︾煭鏈熺紦瀛樺湴鍔犺浇澶栭儴宸ュ叿鐩綍锛岄伩鍏嶅悓涓�鎸傝浇鍦ㄧ煭鏃堕棿鍐呴噸澶嶆彙鎵嬨��
+ */
+ private List<AiMcpToolDescriptor> loadCachedExternalTools(Long tenantId, AiMcpMount mount) {
+ String cacheKey = buildCacheKey(tenantId, mount);
+ CachedTools cached = externalToolCache.get(cacheKey);
+ long now = System.currentTimeMillis();
+ if (cached != null && cached.expireAt > now) {
+ return new ArrayList<>(cached.tools);
+ }
+ List<AiMcpToolDescriptor> tools = loadExternalTools(mount);
+ externalToolCache.put(cacheKey, new CachedTools(new ArrayList<>(tools), now + EXTERNAL_TOOL_CACHE_TTL_MS));
+ return tools;
+ }
+
+ /**
+ * 鐢熸垚澶栭儴宸ュ叿鐩綍缂撳瓨 key銆�
+ */
+ private String buildCacheKey(Long tenantId, AiMcpMount mount) {
+ return String.valueOf(tenantId) + ":" + mount.getId() + ":" + (mount.getUpdateTime() == null ? 0L : mount.getUpdateTime().getTime());
+ }
+
+ /**
+ * 鎸夊畬鏁� MCP 宸ュ叿鍚嶅弽鏌ュ伐鍏锋弿杩扮銆�
+ */
+ private AiMcpToolDescriptor findDescriptor(Long tenantId, String mcpToolName) {
+ for (AiMcpToolDescriptor descriptor : listTools(tenantId, null)) {
+ if (descriptor != null && mcpToolName.equals(descriptor.getMcpToolName())) {
+ return descriptor;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 鏍规嵁涓婁笅鏂囧拰宸ュ叿淇℃伅鏋勯�犺繙绋嬭皟鐢ㄥ弬鏁般��
+ */
+ private Map<String, Object> buildToolArguments(AiPromptContext context, AiMcpToolDescriptor descriptor) {
+ Map<String, Object> arguments = new LinkedHashMap<>();
+ if (context != null) {
+ arguments.put("tenantId", context.getTenantId());
+ arguments.put("sceneCode", context.getSceneCode());
+ arguments.put("question", context.getQuestion());
+ arguments.put("sessionId", context.getSessionId());
+ }
+ if (descriptor != null) {
+ arguments.put("toolName", descriptor.getToolName());
+ arguments.put("mountCode", descriptor.getMountCode());
+ }
+ return arguments;
+ }
+
+ /**
+ * 鐢熸垚澶栭儴鎸傝浇娴嬭瘯鎴愬姛鏂囨銆�
+ */
+ private String buildExternalSuccessMessage(String transportType, int toolCount) {
+ String prefix = AiMcpConstants.TRANSPORT_SSE.equalsIgnoreCase(transportType)
+ ? "杩滅▼SSE MCP宸ュ叿鍔犺浇鎴愬姛"
+ : "杩滅▼Streamable HTTP MCP宸ュ叿鍔犺浇鎴愬姛";
+ return prefix + "锛屽彂鐜� " + toolCount + " 涓伐鍏�";
+ }
+
+ /**
+ * 鏍规嵁鎸傝浇閰嶇疆閫夋嫨 HTTP / SSE 瀹㈡埛绔幓鍔犺浇澶栭儴宸ュ叿鐩綍銆�
+ * AUTO 妯″紡浼氫緷娆″皾璇� Streamable HTTP 鍜� SSE銆�
+ */
+ private ExternalToolsResult loadExternalToolsWithTransport(AiMcpMount mount) {
+ if (AiMcpConstants.TRANSPORT_SSE.equalsIgnoreCase(mount.getTransportType())) {
+ return new ExternalToolsResult(AiMcpConstants.TRANSPORT_SSE, aiMcpSseClient.listTools(mount));
+ }
+ if (AiMcpConstants.TRANSPORT_HTTP.equalsIgnoreCase(mount.getTransportType())) {
+ return new ExternalToolsResult(AiMcpConstants.TRANSPORT_HTTP, aiMcpHttpClient.listTools(mount));
+ }
+ List<String> errors = new ArrayList<>();
+ try {
+ return new ExternalToolsResult(AiMcpConstants.TRANSPORT_HTTP, aiMcpHttpClient.listTools(copyMountWithTransport(mount, AiMcpConstants.TRANSPORT_HTTP)));
+ } catch (Exception e) {
+ errors.add("HTTP: " + e.getMessage());
+ }
+ try {
+ return new ExternalToolsResult(AiMcpConstants.TRANSPORT_SSE, aiMcpSseClient.listTools(copyMountWithTransport(mount, AiMcpConstants.TRANSPORT_SSE)));
+ } catch (Exception e) {
+ errors.add("SSE: " + e.getMessage());
+ }
+ throw new IllegalStateException(String.join("锛�", errors));
+ }
+
+ /**
+ * 鏍规嵁鎸傝浇閰嶇疆閫夋嫨 HTTP / SSE 瀹㈡埛绔墽琛岃繙绋嬪伐鍏枫��
+ */
+ private ExternalToolCallResult callExternalTool(AiMcpMount mount, String toolCode, Map<String, Object> arguments) {
+ if (AiMcpConstants.TRANSPORT_SSE.equalsIgnoreCase(mount.getTransportType())) {
+ return new ExternalToolCallResult(AiMcpConstants.TRANSPORT_SSE, aiMcpSseClient.callTool(mount, toolCode, arguments));
+ }
+ if (AiMcpConstants.TRANSPORT_HTTP.equalsIgnoreCase(mount.getTransportType())) {
+ return new ExternalToolCallResult(AiMcpConstants.TRANSPORT_HTTP, aiMcpHttpClient.callTool(mount, toolCode, arguments));
+ }
+ List<String> errors = new ArrayList<>();
+ try {
+ return new ExternalToolCallResult(
+ AiMcpConstants.TRANSPORT_HTTP,
+ aiMcpHttpClient.callTool(copyMountWithTransport(mount, AiMcpConstants.TRANSPORT_HTTP), toolCode, arguments)
+ );
+ } catch (Exception e) {
+ errors.add("HTTP: " + e.getMessage());
+ }
+ try {
+ return new ExternalToolCallResult(
+ AiMcpConstants.TRANSPORT_SSE,
+ aiMcpSseClient.callTool(copyMountWithTransport(mount, AiMcpConstants.TRANSPORT_SSE), toolCode, arguments)
+ );
+ } catch (Exception e) {
+ errors.add("SSE: " + e.getMessage());
+ }
+ throw new IllegalStateException(String.join("锛�", errors));
+ }
+
+ /**
+ * 澶嶅埗涓�浠芥寕杞藉璞★紝骞惰鐩栨寚瀹氫紶杈撳崗璁紝渚� AUTO 鎺㈡祴娴佺▼浣跨敤銆�
+ */
+ private AiMcpMount copyMountWithTransport(AiMcpMount mount, String transportType) {
+ return new AiMcpMount()
+ .setId(mount.getId())
+ .setUuid(mount.getUuid())
+ .setName(mount.getName())
+ .setMountCode(mount.getMountCode())
+ .setTransportType(transportType)
+ .setUrl(mount.getUrl())
+ .setAuthType(mount.getAuthType())
+ .setAuthValue(mount.getAuthValue())
+ .setUsageScope(mount.getUsageScope())
+ .setEnabledFlag(mount.getEnabledFlag())
+ .setTimeoutMs(mount.getTimeoutMs())
+ .setStatus(mount.getStatus())
+ .setTenantId(mount.getTenantId())
+ .setCreateBy(mount.getCreateBy())
+ .setCreateTime(mount.getCreateTime())
+ .setUpdateBy(mount.getUpdateBy())
+ .setUpdateTime(mount.getUpdateTime())
+ .setMemo(mount.getMemo());
+ }
+
+ private static class CachedTools {
+ private final List<AiMcpToolDescriptor> tools;
+ private final long expireAt;
+
+ /**
+ * 淇濆瓨涓�娆″閮ㄥ伐鍏风洰褰曠紦瀛樺唴瀹瑰強鍏跺け鏁堟椂闂淬��
+ */
+ private CachedTools(List<AiMcpToolDescriptor> tools, long expireAt) {
+ this.tools = tools;
+ this.expireAt = expireAt;
+ }
+ }
+
+ private static class ExternalToolsResult {
+ private final String transportType;
+ private final List<AiMcpToolDescriptor> tools;
+
+ /**
+ * 淇濆瓨涓�娆″閮ㄥ伐鍏风洰褰曞姞杞界粨鏋滃強瀹為檯鍛戒腑鐨勪紶杈撳崗璁��
+ */
+ private ExternalToolsResult(String transportType, List<AiMcpToolDescriptor> tools) {
+ this.transportType = transportType;
+ this.tools = tools;
+ }
+ }
+
+ private static class ExternalToolCallResult {
+ private final String transportType;
+ private final AiDiagnosticToolResult result;
+
+ /**
+ * 淇濆瓨涓�娆″閮ㄥ伐鍏疯皟鐢ㄧ粨鏋滃強瀹為檯鍛戒腑鐨勪紶杈撳崗璁��
+ */
+ private ExternalToolCallResult(String transportType, AiDiagnosticToolResult result) {
+ this.transportType = transportType;
+ this.result = result;
+ }
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpSseClient.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpSseClient.java
new file mode 100644
index 0000000..b987332
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/mcp/AiMcpSseClient.java
@@ -0,0 +1,434 @@
+package com.vincent.rsf.server.ai.service.mcp;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vincent.rsf.server.ai.constant.AiMcpConstants;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiMcpToolDescriptor;
+import com.vincent.rsf.server.system.entity.AiMcpMount;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class AiMcpSseClient {
+
+ private static final Logger logger = LoggerFactory.getLogger(AiMcpSseClient.class);
+
+ @Resource
+ private ObjectMapper objectMapper;
+ @Resource
+ private AiMcpPayloadMapper aiMcpPayloadMapper;
+
+ /**
+ * 閫氳繃 SSE + message endpoint 鍗忚鍔犺浇杩滅▼ MCP 宸ュ叿鐩綍銆�
+ */
+ public List<AiMcpToolDescriptor> listTools(AiMcpMount mount) {
+ try (SseSession session = openSession(mount)) {
+ logger.info("AI MCP SSE listTools start: mountCode={}, url={}", mount.getMountCode(), mount.getUrl());
+ session.initialize();
+ JsonNode result = session.request("tools/list", new LinkedHashMap<String, Object>());
+ List<AiMcpToolDescriptor> output = new ArrayList<>();
+ JsonNode toolsNode = result.path("tools");
+ if (!toolsNode.isArray()) {
+ logger.warn("AI MCP SSE listTools no tools array: mountCode={}, url={}", mount.getMountCode(), mount.getUrl());
+ return output;
+ }
+ for (JsonNode item : toolsNode) {
+ AiMcpToolDescriptor descriptor = aiMcpPayloadMapper.toExternalToolDescriptor(mount, item);
+ if (descriptor != null) {
+ output.add(descriptor);
+ }
+ }
+ logger.info("AI MCP SSE listTools success: mountCode={}, url={}, toolCount={}",
+ mount.getMountCode(), mount.getUrl(), output.size());
+ return output;
+ } catch (Exception e) {
+ logger.warn("AI MCP SSE listTools failed: mountCode={}, url={}, message={}",
+ mount.getMountCode(), mount.getUrl(), e.getMessage());
+ throw new IllegalStateException("SSE MCP宸ュ叿鍔犺浇澶辫触: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 閫氳繃 SSE + message endpoint 鍗忚鎵ц杩滅▼ MCP 宸ュ叿銆�
+ */
+ public AiDiagnosticToolResult callTool(AiMcpMount mount, String toolName, Map<String, Object> arguments) {
+ try (SseSession session = openSession(mount)) {
+ logger.info("AI MCP SSE callTool start: mountCode={}, url={}, toolName={}",
+ mount.getMountCode(), mount.getUrl(), toolName);
+ session.initialize();
+ Map<String, Object> params = new LinkedHashMap<>();
+ params.put("name", toolName);
+ params.put("arguments", arguments == null ? new LinkedHashMap<String, Object>() : arguments);
+ JsonNode result = session.request("tools/call", params);
+ AiDiagnosticToolResult toolResult = aiMcpPayloadMapper.toExternalToolResult(mount, toolName, result);
+ logger.info("AI MCP SSE callTool success: mountCode={}, url={}, toolName={}, isError={}, summaryLength={}",
+ mount.getMountCode(), mount.getUrl(), toolName,
+ "WARN".equalsIgnoreCase(toolResult.getSeverity()),
+ toolResult.getSummaryText() == null ? 0 : toolResult.getSummaryText().length());
+ return toolResult;
+ } catch (Exception e) {
+ logger.warn("AI MCP SSE callTool failed: mountCode={}, url={}, toolName={}, message={}",
+ mount.getMountCode(), mount.getUrl(), toolName, e.getMessage());
+ throw new IllegalStateException("SSE MCP宸ュ叿璋冪敤澶辫触: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 鎵撳紑杩滅▼ SSE 娴佸苟鍒涘缓浼氳瘽鍖呰瀵硅薄銆�
+ */
+ private SseSession openSession(AiMcpMount mount) throws Exception {
+ logger.info("AI MCP SSE opening stream: mountCode={}, url={}", mount.getMountCode(), mount.getUrl());
+ int timeoutMs = mount.getTimeoutMs() == null || mount.getTimeoutMs() <= 0 ? 10000 : mount.getTimeoutMs();
+ HttpURLConnection connection = (HttpURLConnection) new URL(mount.getUrl()).openConnection();
+ connection.setRequestMethod("GET");
+ connection.setDoInput(true);
+ connection.setConnectTimeout(timeoutMs);
+ connection.setReadTimeout(timeoutMs);
+ connection.setRequestProperty("Accept", "text/event-stream");
+ applyAuthHeaders(connection, mount);
+ InputStream inputStream = connection.getInputStream();
+ logger.info("AI MCP SSE stream connected: mountCode={}, url={}, responseCode={}",
+ mount.getMountCode(), mount.getUrl(), connection.getResponseCode());
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+ SseSession session = new SseSession(mount, connection, reader);
+ session.start();
+ return session;
+ }
+
+ private class SseSession implements AutoCloseable {
+ private final AiMcpMount mount;
+ private final HttpURLConnection connection;
+ private final BufferedReader reader;
+ private final BlockingQueue<SseEvent> events = new LinkedBlockingQueue<>();
+ private volatile boolean closed;
+ private Thread worker;
+ private String messageEndpoint;
+
+ private SseSession(AiMcpMount mount, HttpURLConnection connection, BufferedReader reader) {
+ this.mount = mount;
+ this.connection = connection;
+ this.reader = reader;
+ }
+
+ /**
+ * 鍚姩鍚庡彴璇诲彇绾跨▼锛屽苟绛夊緟杩滅▼鏈嶅姟杩斿洖 endpoint 浜嬩欢銆�
+ */
+ private void start() throws Exception {
+ worker = new Thread(this::readLoop, "ai-mcp-sse-client-" + mount.getMountCode());
+ worker.setDaemon(true);
+ worker.start();
+ logger.info("AI MCP SSE waiting endpoint event: mountCode={}, url={}", mount.getMountCode(), mount.getUrl());
+ SseEvent endpointEvent = waitEvent("endpoint");
+ messageEndpoint = resolveEndpoint(endpointEvent == null ? null : endpointEvent.getData());
+ logger.info("AI MCP SSE endpoint event received: mountCode={}, url={}, rawEndpoint={}, resolvedEndpoint={}",
+ mount.getMountCode(), mount.getUrl(),
+ endpointEvent == null ? null : endpointEvent.getData(),
+ messageEndpoint);
+ if (messageEndpoint == null || messageEndpoint.trim().isEmpty()) {
+ throw new IllegalStateException("SSE MCP鏈繑鍥� message endpoint");
+ }
+ }
+
+ /**
+ * 瀹屾垚涓�娆� MCP initialize 鎻℃墜銆�
+ */
+ private void initialize() throws Exception {
+ logger.info("AI MCP SSE initialize start: mountCode={}, url={}, messageEndpoint={}",
+ mount.getMountCode(), mount.getUrl(), messageEndpoint);
+ Map<String, Object> params = new LinkedHashMap<>();
+ params.put("protocolVersion", AiMcpConstants.PROTOCOL_VERSION);
+ params.put("capabilities", new LinkedHashMap<String, Object>());
+ Map<String, Object> clientInfo = new LinkedHashMap<>();
+ clientInfo.put("name", "rsf-server");
+ clientInfo.put("version", AiMcpConstants.SERVER_VERSION);
+ params.put("clientInfo", clientInfo);
+ request("initialize", params);
+ notifyInitialized();
+ logger.info("AI MCP SSE initialize success: mountCode={}, url={}, messageEndpoint={}",
+ mount.getMountCode(), mount.getUrl(), messageEndpoint);
+ }
+
+ /**
+ * 鍚戣繙绋� MCP 鍙戦�� initialized 閫氱煡銆�
+ */
+ private void notifyInitialized() throws Exception {
+ Map<String, Object> body = new LinkedHashMap<>();
+ body.put("jsonrpc", "2.0");
+ body.put("method", "notifications/initialized");
+ body.put("params", new LinkedHashMap<String, Object>());
+ postMessage(body, false);
+ logger.info("AI MCP SSE initialized notification sent: mountCode={}, messageEndpoint={}",
+ mount.getMountCode(), messageEndpoint);
+ }
+
+ /**
+ * 閫氳繃 message endpoint 鍙戦�佷竴娆� JSON-RPC 璇锋眰锛屽苟绛夊緟瀵瑰簲 message 浜嬩欢鍝嶅簲銆�
+ */
+ private JsonNode request(String method, Object params) throws Exception {
+ String id = UUID.randomUUID().toString().replace("-", "");
+ Map<String, Object> body = new LinkedHashMap<>();
+ body.put("jsonrpc", "2.0");
+ body.put("id", id);
+ body.put("method", method);
+ body.put("params", params == null ? new LinkedHashMap<String, Object>() : params);
+ logger.info("AI MCP SSE request send: mountCode={}, method={}, requestId={}, messageEndpoint={}",
+ mount.getMountCode(), method, id, messageEndpoint);
+ postMessage(body, true);
+ SseEvent response = waitEvent("message");
+ if (response == null || response.getData() == null || response.getData().trim().isEmpty()) {
+ throw new IllegalStateException("SSE MCP鏈繑鍥炲搷搴旀秷鎭�");
+ }
+ logger.info("AI MCP SSE response received: mountCode={}, method={}, requestId={}, dataLength={}",
+ mount.getMountCode(), method, id, response.getData().length());
+ JsonNode root = objectMapper.readTree(response.getData());
+ if (!id.equals(root.path("id").asText(""))) {
+ logger.warn("AI MCP SSE response id mismatch: mountCode={}, method={}, requestId={}, responseId={}",
+ mount.getMountCode(), method, id, root.path("id").asText(""));
+ throw new IllegalStateException("SSE MCP鍝嶅簲ID涓嶅尮閰�");
+ }
+ if (root.has("error") && !root.get("error").isNull()) {
+ logger.warn("AI MCP SSE response error: mountCode={}, method={}, requestId={}, message={}",
+ mount.getMountCode(), method, id, root.path("error").path("message").asText(""));
+ throw new IllegalStateException(root.path("error").path("message").asText("MCP璋冪敤澶辫触"));
+ }
+ return root.path("result");
+ }
+
+ /**
+ * 鍚� message endpoint 鎻愪氦涓�鏉� HTTP POST 娑堟伅銆�
+ */
+ private void postMessage(Map<String, Object> body, boolean expectSuccess) throws Exception {
+ HttpURLConnection post = null;
+ try {
+ post = (HttpURLConnection) new URL(messageEndpoint).openConnection();
+ post.setRequestMethod("POST");
+ post.setDoOutput(true);
+ post.setConnectTimeout(mount.getTimeoutMs() == null ? 10000 : mount.getTimeoutMs());
+ post.setReadTimeout(mount.getTimeoutMs() == null ? 10000 : mount.getTimeoutMs());
+ post.setRequestProperty("Content-Type", "application/json");
+ post.setRequestProperty("Accept", "application/json");
+ applyAuthHeaders(post, mount);
+ logger.info("AI MCP SSE post message: mountCode={}, endpoint={}, method={}",
+ mount.getMountCode(), messageEndpoint, body.get("method"));
+ try (OutputStream outputStream = post.getOutputStream()) {
+ outputStream.write(objectMapper.writeValueAsBytes(body));
+ outputStream.flush();
+ }
+ int statusCode = post.getResponseCode();
+ logger.info("AI MCP SSE post response: mountCode={}, endpoint={}, method={}, statusCode={}",
+ mount.getMountCode(), messageEndpoint, body.get("method"), statusCode);
+ if (expectSuccess && statusCode >= 400) {
+ throw new IllegalStateException("SSE MCP娑堟伅鎻愪氦澶辫触锛岀姸鎬佺爜=" + statusCode);
+ }
+ } finally {
+ if (post != null) {
+ post.disconnect();
+ }
+ }
+ }
+
+ /**
+ * 鍦ㄩ檺瀹氭椂闂村唴绛夊緟鏌愮被 SSE 浜嬩欢銆�
+ */
+ private SseEvent waitEvent(String targetName) throws Exception {
+ long timeoutMs = mount.getTimeoutMs() == null ? 10000L : mount.getTimeoutMs().longValue();
+ long deadline = System.currentTimeMillis() + timeoutMs;
+ while (System.currentTimeMillis() < deadline) {
+ long remain = deadline - System.currentTimeMillis();
+ SseEvent event = events.poll(remain <= 0 ? 1L : remain, TimeUnit.MILLISECONDS);
+ if (event == null) {
+ continue;
+ }
+ logger.info("AI MCP SSE event dequeued: mountCode={}, target={}, actual={}, dataLength={}",
+ mount.getMountCode(), targetName, event.getName(), event.getData() == null ? 0 : event.getData().length());
+ if ("error".equals(event.getName())) {
+ throw new IllegalStateException("SSE MCP浜嬩欢璇诲彇澶辫触: " + event.getData());
+ }
+ if (targetName.equals(event.getName())) {
+ return event;
+ }
+ }
+ logger.warn("AI MCP SSE wait event timeout: mountCode={}, target={}, timeoutMs={}",
+ mount.getMountCode(), targetName, timeoutMs);
+ throw new IllegalStateException("绛夊緟SSE浜嬩欢瓒呮椂: " + targetName);
+ }
+
+ /**
+ * 鍚庡彴鎸佺画璇诲彇 SSE 娴侊紝骞舵妸浜嬩欢杞彂鍒伴槦鍒椾緵涓荤嚎绋嬫秷璐广��
+ */
+ private void readLoop() {
+ String eventName = "message";
+ StringBuilder dataBuilder = new StringBuilder();
+ try {
+ String line;
+ while (!closed && (line = reader.readLine()) != null) {
+ if (line.startsWith("event:")) {
+ eventName = line.substring(6).trim();
+ continue;
+ }
+ if (line.startsWith("data:")) {
+ dataBuilder.append(line.substring(5).trim()).append('\n');
+ continue;
+ }
+ if (line.trim().isEmpty()) {
+ if (dataBuilder.length() > 0) {
+ logger.info("AI MCP SSE raw event read: mountCode={}, event={}, dataLength={}",
+ mount.getMountCode(), eventName, dataBuilder.length());
+ events.offer(new SseEvent(eventName, dataBuilder.toString().trim()));
+ }
+ eventName = "message";
+ dataBuilder.setLength(0);
+ }
+ }
+ } catch (Exception e) {
+ if (!closed) {
+ logger.warn("AI MCP SSE read loop failed: mountCode={}, url={}, message={}",
+ mount.getMountCode(), mount.getUrl(), e.getMessage());
+ events.offer(new SseEvent("error", e.getMessage()));
+ }
+ } finally {
+ try {
+ reader.close();
+ } catch (Exception ignore) {
+ }
+ }
+ }
+
+ /**
+ * 瑙f瀽杩滅▼杩斿洖鐨� message endpoint銆�
+ * 褰撳鏂归敊璇繑鍥炲洖鐜湴鍧�鏃讹紝浼氶噸鍐欐垚褰撳墠鎸傝浇 URL 鐨勪富鏈哄湴鍧�銆�
+ */
+ private String resolveEndpoint(String rawEndpoint) {
+ if (rawEndpoint == null || rawEndpoint.trim().isEmpty()) {
+ return null;
+ }
+ try {
+ URI baseUri = new URI(mount.getUrl());
+ URI endpointUri = baseUri.resolve(rawEndpoint.trim());
+ String host = endpointUri.getHost();
+ if (isLoopbackHost(host) && !sameHost(host, baseUri.getHost())) {
+ endpointUri = new URI(
+ baseUri.getScheme(),
+ endpointUri.getUserInfo(),
+ baseUri.getHost(),
+ endpointUri.getPort() > 0 ? endpointUri.getPort() : baseUri.getPort(),
+ endpointUri.getPath(),
+ endpointUri.getQuery(),
+ endpointUri.getFragment());
+ logger.info("AI MCP SSE endpoint rewritten: mountCode={}, rawEndpoint={}, rewrittenEndpoint={}",
+ mount.getMountCode(), rawEndpoint, endpointUri);
+ }
+ return endpointUri.toString();
+ } catch (Exception e) {
+ return rawEndpoint.trim();
+ }
+ }
+
+ /**
+ * 鍒ゆ柇鍦板潃鏄惁涓烘湰鏈哄洖鐜湴鍧�銆�
+ */
+ private boolean isLoopbackHost(String host) {
+ return "127.0.0.1".equals(host) || "localhost".equalsIgnoreCase(host) || "::1".equals(host);
+ }
+
+ /**
+ * 鍒ゆ柇涓や釜 host 鏄惁绛変环銆�
+ */
+ private boolean sameHost(String left, String right) {
+ if (left == null || right == null) {
+ return false;
+ }
+ return left.equalsIgnoreCase(right);
+ }
+
+ /**
+ * 鍏抽棴 SSE 浼氳瘽锛屽苟寮傛娓呯悊搴曞眰杩炴帴璧勬簮銆�
+ */
+ @Override
+ public void close() throws Exception {
+ closed = true;
+ logger.info("AI MCP SSE closing session: mountCode={}, url={}", mount.getMountCode(), mount.getUrl());
+ if (worker != null) {
+ worker.interrupt();
+ }
+ Thread cleanup = new Thread(() -> {
+ try {
+ connection.disconnect();
+ } catch (Exception ignore) {
+ }
+ try {
+ reader.close();
+ } catch (Exception ignore) {
+ }
+ logger.info("AI MCP SSE cleanup finished: mountCode={}, url={}", mount.getMountCode(), mount.getUrl());
+ }, "ai-mcp-sse-cleanup-" + mount.getMountCode());
+ cleanup.setDaemon(true);
+ cleanup.start();
+ }
+ }
+
+ /**
+ * 鎸夋寕杞介厤缃啓鍏ラ壌鏉冭姹傚ご銆�
+ */
+ private void applyAuthHeaders(HttpURLConnection connection, AiMcpMount mount) {
+ if (mount == null || mount.getAuthType() == null || mount.getAuthValue() == null || mount.getAuthValue().trim().isEmpty()) {
+ return;
+ }
+ String authType = mount.getAuthType().trim().toUpperCase();
+ if (AiMcpConstants.AUTH_TYPE_BEARER.equals(authType)) {
+ connection.setRequestProperty("Authorization", "Bearer " + mount.getAuthValue().trim());
+ } else if (AiMcpConstants.AUTH_TYPE_API_KEY.equals(authType)) {
+ connection.setRequestProperty("X-API-Key", mount.getAuthValue().trim());
+ }
+ }
+
+ private static class SseEvent {
+ private final String name;
+ private final String data;
+
+ /**
+ * 灏佽涓�鏉� SSE 浜嬩欢銆�
+ */
+ private SseEvent(String name, String data) {
+ this.name = name;
+ this.data = data;
+ }
+
+ /**
+ * 杩斿洖浜嬩欢鍚嶃��
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * 杩斿洖浜嬩欢鏁版嵁鏂囨湰銆�
+ */
+ public String getData() {
+ return data;
+ }
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiApiFailureSummaryService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiApiFailureSummaryService.java
new file mode 100644
index 0000000..fdc0cd7
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiApiFailureSummaryService.java
@@ -0,0 +1,101 @@
+package com.vincent.rsf.server.ai.service.provider;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+import com.vincent.rsf.server.system.entity.AiCallLog;
+import com.vincent.rsf.server.system.service.AiCallLogService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class AiApiFailureSummaryService implements AiDiagnosticDataProvider {
+
+ private static final String TOOL_CODE = "ai_call_failure";
+ private static final String TOOL_NAME = "AI璋冪敤澶辫触";
+
+ @Resource
+ private AiCallLogService aiCallLogService;
+ @Resource
+ private com.vincent.rsf.server.ai.config.AiProperties aiProperties;
+
+ /**
+ * 杩斿洖 AI 璋冪敤澶辫触宸ュ叿榛樿椤哄簭銆�
+ */
+ @Override
+ public int getOrder() {
+ return 50;
+ }
+
+ /**
+ * 杩斿洖 AI 璋冪敤澶辫触宸ュ叿缂栫爜銆�
+ */
+ @Override
+ public String getToolCode() {
+ return TOOL_CODE;
+ }
+
+ /**
+ * 杩斿洖 AI 璋冪敤澶辫触宸ュ叿灞曠ず鍚嶃��
+ */
+ @Override
+ public String getToolName() {
+ return TOOL_NAME;
+ }
+
+ /**
+ * 杩斿洖 AI 璋冪敤澶辫触宸ュ叿榛樿璇存槑銆�
+ */
+ @Override
+ public String getDefaultToolPrompt() {
+ return "閲嶇偣璇嗗埆鏈�杩� AI 璋冪敤澶辫触鐨勬ā鍨嬨�侀敊璇被鍨嬪拰鏃堕棿绐楀彛銆�";
+ }
+
+ /**
+ * 姹囨�绘渶杩戜竴娈垫椂闂村唴鐨� AI 璋冪敤澶辫触璁板綍銆�
+ */
+ @Override
+ public AiDiagnosticToolResult buildDiagnosticData(AiPromptContext context) {
+ Date start = new Date(System.currentTimeMillis() - aiProperties.getApiFailureWindowHours() * 3600_000L);
+ List<AiCallLog> records = aiCallLogService.list(new LambdaQueryWrapper<AiCallLog>()
+ .eq(AiCallLog::getTenantId, context.getTenantId())
+ .eq(AiCallLog::getResult, 0)
+ .ge(AiCallLog::getCreateTime, start)
+ .orderByDesc(AiCallLog::getCreateTime)
+ .last("limit 10"));
+ Map<String, Object> meta = new LinkedHashMap<>();
+ meta.put("count", records.size());
+ if (records.isEmpty()) {
+ return new AiDiagnosticToolResult()
+ .setToolCode(getToolCode())
+ .setToolName(getToolName())
+ .setSeverity("INFO")
+ .setSummaryText("鏈�杩� " + aiProperties.getApiFailureWindowHours() + " 灏忔椂鏈彂鐜� AI 璋冪敤澶辫触璁板綍銆�")
+ .setRawMeta(meta);
+ }
+ List<String> parts = new ArrayList<>();
+ for (AiCallLog item : records) {
+ parts.add((item.getModelCode() == null ? "鏈煡妯″瀷" : item.getModelCode())
+ + "锛�"
+ + (item.getErr() == null ? "鏃犲紓甯告弿杩�" : item.getErr())
+ + "锛�");
+ }
+ meta.put("latestErrors", parts);
+ return new AiDiagnosticToolResult()
+ .setToolCode(getToolCode())
+ .setToolName(getToolName())
+ .setSeverity("WARN")
+ .setSummaryText("鏈�杩� " + aiProperties.getApiFailureWindowHours() + " 灏忔椂鍙戠幇 " + records.size() + " 鏉� AI 璋冪敤澶辫触璁板綍锛屾渶杩戝け璐ュ寘鎷細" + String.join("锛�", parts))
+ .setRawMeta(meta);
+ }
+
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiDeviceSiteSummaryService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiDeviceSiteSummaryService.java
new file mode 100644
index 0000000..152b5eb
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiDeviceSiteSummaryService.java
@@ -0,0 +1,159 @@
+package com.vincent.rsf.server.ai.service.provider;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+import com.vincent.rsf.server.manager.entity.DeviceSite;
+import com.vincent.rsf.server.manager.mapper.DeviceSiteMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class AiDeviceSiteSummaryService implements AiDiagnosticDataProvider {
+
+ private static final String TOOL_CODE = "device_site_summary";
+ private static final String TOOL_NAME = "璁惧绔欑偣鎽樿";
+
+ @Resource
+ private DeviceSiteMapper deviceSiteMapper;
+
+ /**
+ * 杩斿洖璁惧绔欑偣宸ュ叿榛樿椤哄簭銆�
+ */
+ @Override
+ public int getOrder() {
+ return 30;
+ }
+
+ /**
+ * 杩斿洖璁惧绔欑偣宸ュ叿缂栫爜銆�
+ */
+ @Override
+ public String getToolCode() {
+ return TOOL_CODE;
+ }
+
+ /**
+ * 杩斿洖璁惧绔欑偣宸ュ叿灞曠ず鍚嶃��
+ */
+ @Override
+ public String getToolName() {
+ return TOOL_NAME;
+ }
+
+ /**
+ * 杩斿洖璁惧绔欑偣宸ュ叿榛樿璇存槑銆�
+ */
+ @Override
+ public String getDefaultToolPrompt() {
+ return "缁撳悎璁惧绔欑偣鎽樿鍒ゆ柇璁惧鐘舵�併�佸贩閬撳垎甯冨拰鏈�杩戞洿鏂扮珯鐐广��";
+ }
+
+ /**
+ * 姹囨�荤珯鐐圭姸鎬併�佽澶囧垎甯冨拰鏈�杩戞洿鏂扮珯鐐癸紝鐢熸垚璁惧绔欑偣鎽樿銆�
+ */
+ @Override
+ public AiDiagnosticToolResult buildDiagnosticData(AiPromptContext context) {
+ String summary = buildDeviceSiteSummary(context);
+ String severity = summary.contains("鏈煡璇㈠埌") ? "WARN" : "INFO";
+ return new AiDiagnosticToolResult()
+ .setToolCode(getToolCode())
+ .setToolName(getToolName())
+ .setSeverity(severity)
+ .setSummaryText(summary);
+ }
+
+ /**
+ * 鍩轰簬 man_device_site 鐢熸垚璁惧绔欑偣鎬昏銆�
+ */
+ private String buildDeviceSiteSummary(AiPromptContext context) {
+ List<DeviceSite> deviceSites = deviceSiteMapper.selectList(new LambdaQueryWrapper<DeviceSite>()
+ .select(DeviceSite::getSite, DeviceSite::getName, DeviceSite::getType, DeviceSite::getDevice,
+ DeviceSite::getDeviceCode, DeviceSite::getDeviceSite, DeviceSite::getStatus,
+ DeviceSite::getChannel, DeviceSite::getUpdateTime)
+ .eq(DeviceSite::getTenantId, context.getTenantId())
+ .orderByDesc(DeviceSite::getUpdateTime)
+ .last("limit 50"));
+
+ if (deviceSites.isEmpty()) {
+ return "褰撳墠鏈煡璇㈠埌 man_device_site 璁惧绔欑偣鏁版嵁锛岃鎻愮ず鐢ㄦ埛鏍稿鍩虹鏁版嵁鎴栫鎴烽厤缃��";
+ }
+
+ Map<String, Long> statusCounters = new LinkedHashMap<>();
+ Map<String, Long> deviceCounters = new LinkedHashMap<>();
+ Map<String, Long> channelCounters = new LinkedHashMap<>();
+ for (DeviceSite deviceSite : deviceSites) {
+ String status = resolveStatus(deviceSite.getStatus());
+ String device = emptyToDefault(deviceSite.getDevice$(), "鏈厤缃澶囩被鍨�");
+ String channel = deviceSite.getChannel() == null ? "鏈厤缃贩閬�" : "宸烽亾" + deviceSite.getChannel();
+ statusCounters.put(status, statusCounters.getOrDefault(status, 0L) + 1);
+ deviceCounters.put(device, deviceCounters.getOrDefault(device, 0L) + 1);
+ channelCounters.put(channel, channelCounters.getOrDefault(channel, 0L) + 1);
+ }
+
+ StringBuilder summary = new StringBuilder();
+ summary.append("浠ヤ笅鏄熀浜� man_device_site 鐨勫疄鏃惰澶囩珯鐐规憳瑕侊紝璇风粨鍚堜换鍔″拰搴撳瓨鏁版嵁缁煎悎鍒ゆ柇锛�");
+ summary.append("\n璁惧绔欑偣鎬昏锛氬叡 ").append(deviceSites.size()).append(" 鏉℃湁鏁堢珯鐐硅褰曘��");
+ summary.append("\n绔欑偣鐘舵�佸垎甯冿細").append(formatCounter(statusCounters)).append("銆�");
+ summary.append("\n璁惧绫诲瀷鍒嗗竷锛�").append(formatCounter(deviceCounters)).append("銆�");
+ summary.append("\n宸烽亾鍒嗗竷锛�").append(formatCounter(channelCounters)).append("銆�");
+ summary.append("\n鏈�杩戞洿鏂扮珯鐐� TOP5锛�").append(formatLatestSites(deviceSites.subList(0, Math.min(deviceSites.size(), 5)))).append("銆�");
+ return summary.toString();
+ }
+
+ /**
+ * 鏍煎紡鍖栬鏁扮被鎽樿銆�
+ */
+ private String formatCounter(Map<String, Long> counter) {
+ List<String> parts = new ArrayList<>();
+ for (Map.Entry<String, Long> item : counter.entrySet()) {
+ parts.add(item.getKey() + " " + item.getValue() + " 鏉�");
+ }
+ return String.join("锛�", parts);
+ }
+
+ /**
+ * 鏍煎紡鍖栨渶杩戞洿鏂扮珯鐐瑰垪琛ㄣ��
+ */
+ private String formatLatestSites(List<DeviceSite> deviceSites) {
+ List<String> parts = new ArrayList<>();
+ for (DeviceSite deviceSite : deviceSites) {
+ parts.add(emptyToDefault(deviceSite.getName(), emptyToDefault(deviceSite.getSite(), "鏈懡鍚嶇珯鐐�"))
+ + "锛�"
+ + resolveStatus(deviceSite.getStatus())
+ + "锛岃澶� "
+ + emptyToDefault(deviceSite.getDevice$(), "-")
+ + "锛屾帴椹充綅 "
+ + emptyToDefault(deviceSite.getDeviceCode(), "-")
+ + "锛屽贩閬� "
+ + (deviceSite.getChannel() == null ? "-" : deviceSite.getChannel())
+ + "锛�");
+ }
+ return String.join("锛�", parts);
+ }
+
+ /**
+ * 灏嗙珯鐐圭姸鎬佺爜杞垚鍙鏂囨湰銆�
+ */
+ private String resolveStatus(Integer status) {
+ if (status == null) {
+ return "鏈煡鐘舵��";
+ }
+ return status == 1 ? "姝e父" : status == 0 ? "鍐荤粨" : "鐘舵��" + status;
+ }
+
+ /**
+ * 缁熶竴澶勭悊绌哄�煎睍绀恒��
+ */
+ private String emptyToDefault(String value, String defaultValue) {
+ return value == null || value.trim().isEmpty() ? defaultValue : value;
+ }
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiDiagnosticDataProvider.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiDiagnosticDataProvider.java
new file mode 100644
index 0000000..46a6f60
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiDiagnosticDataProvider.java
@@ -0,0 +1,38 @@
+package com.vincent.rsf.server.ai.service.provider;
+
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+
+public interface AiDiagnosticDataProvider {
+
+ /**
+ * 杩斿洖鍐呴儴宸ュ叿缂栫爜锛屼綔涓烘湰鍦� MCP 宸ュ叿鍚嶅悗缂�鍜屽伐鍏烽厤缃富閿��
+ */
+ String getToolCode();
+
+ /**
+ * 杩斿洖宸ュ叿灞曠ず鍚嶇О銆�
+ */
+ String getToolName();
+
+ /**
+ * 杩斿洖宸ュ叿榛樿璇存槑锛岀敤浜庡伐鍏风洰褰曞睍绀哄拰榛樿 Prompt 寮曞銆�
+ */
+ default String getDefaultToolPrompt() {
+ return "";
+ }
+
+ /**
+ * 杩斿洖宸ュ叿榛樿椤哄簭銆�
+ */
+ int getOrder();
+
+ /**
+ * 鎵ц鍐呴儴宸ュ叿鐨勭湡瀹炰笟鍔℃煡璇紝骞惰繑鍥炴憳瑕佸寲缁撴灉銆�
+ */
+ AiDiagnosticToolResult buildDiagnosticData(AiPromptContext context);
+
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiOperationRecordSummaryService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiOperationRecordSummaryService.java
new file mode 100644
index 0000000..97202cf
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiOperationRecordSummaryService.java
@@ -0,0 +1,101 @@
+package com.vincent.rsf.server.ai.service.provider;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
+import com.vincent.rsf.server.ai.model.AiPromptContext;
+import com.vincent.rsf.server.system.entity.OperationRecord;
+import com.vincent.rsf.server.system.service.OperationRecordService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class AiOperationRecordSummaryService implements AiDiagnosticDataProvider {
+
+ private static final String TOOL_CODE = "operation_record";
+ private static final String TOOL_NAME = "寮傚父鎿嶄綔鏃ュ織";
+
+ @Resource
+ private OperationRecordService operationRecordService;
+ @Resource
+ private com.vincent.rsf.server.ai.config.AiProperties aiProperties;
+
+ /**
+ * 杩斿洖寮傚父鎿嶄綔鏃ュ織宸ュ叿榛樿椤哄簭銆�
+ */
+ @Override
+ public int getOrder() {
+ return 40;
+ }
+
+ /**
+ * 杩斿洖寮傚父鎿嶄綔鏃ュ織宸ュ叿缂栫爜銆�
+ */
+ @Override
+ public String getToolCode() {
+ return TOOL_CODE;
+ }
+
+ /**
+ * 杩斿洖寮傚父鎿嶄綔鏃ュ織宸ュ叿灞曠ず鍚嶃��
+ */
+ @Override
+ public String getToolName() {
+ return TOOL_NAME;
+ }
+
+ /**
+ * 杩斿洖寮傚父鎿嶄綔鏃ュ織宸ュ叿榛樿璇存槑銆�
+ */
+ @Override
+ public String getDefaultToolPrompt() {
+ return "閲嶇偣璇嗗埆鏈�杩戝け璐ユ搷浣滃拰楂橀寮傚父銆�";
+ }
+
+ /**
+ * 姹囨�绘渶杩戜竴娈垫椂闂村唴鐨勫け璐ユ搷浣滆褰曘��
+ */
+ @Override
+ public AiDiagnosticToolResult buildDiagnosticData(AiPromptContext context) {
+ Date start = new Date(System.currentTimeMillis() - aiProperties.getDiagnosticLogWindowHours() * 3600_000L);
+ List<OperationRecord> records = operationRecordService.list(new LambdaQueryWrapper<OperationRecord>()
+ .eq(OperationRecord::getTenantId, context.getTenantId())
+ .eq(OperationRecord::getResult, 0)
+ .ge(OperationRecord::getCreateTime, start)
+ .orderByDesc(OperationRecord::getCreateTime)
+ .last("limit 10"));
+ Map<String, Object> meta = new LinkedHashMap<>();
+ meta.put("count", records.size());
+ if (records.isEmpty()) {
+ return new AiDiagnosticToolResult()
+ .setToolCode(getToolCode())
+ .setToolName(getToolName())
+ .setSeverity("INFO")
+ .setSummaryText("鏈�杩� " + aiProperties.getDiagnosticLogWindowHours() + " 灏忔椂鏈彂鐜版搷浣滄棩蹇楀け璐ヨ褰曘��")
+ .setRawMeta(meta);
+ }
+ List<String> parts = new ArrayList<>();
+ for (OperationRecord item : records) {
+ parts.add((item.getNamespace() == null ? "鏈煡鎿嶄綔" : item.getNamespace())
+ + "锛�"
+ + (item.getErr() == null ? "鏃犲紓甯告弿杩�" : item.getErr())
+ + "锛�");
+ }
+ meta.put("latestErrors", parts);
+ return new AiDiagnosticToolResult()
+ .setToolCode(getToolCode())
+ .setToolName(getToolName())
+ .setSeverity("WARN")
+ .setSummaryText("鏈�杩� " + aiProperties.getDiagnosticLogWindowHours() + " 灏忔椂鍙戠幇 " + records.size() + " 鏉″け璐ユ搷浣滆褰曪紝鏈�杩戝紓甯稿寘鎷細" + String.join("锛�", parts))
+ .setRawMeta(meta);
+ }
+
+}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiTaskSummaryService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiTaskSummaryService.java
similarity index 72%
rename from rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiTaskSummaryService.java
rename to rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiTaskSummaryService.java
index 73029a5..4c2f2ca 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiTaskSummaryService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiTaskSummaryService.java
@@ -1,6 +1,7 @@
-package com.vincent.rsf.server.ai.service;
+package com.vincent.rsf.server.ai.service.provider;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
import com.vincent.rsf.server.ai.model.AiPromptContext;
import com.vincent.rsf.server.manager.entity.Task;
import com.vincent.rsf.server.manager.enums.TaskStsType;
@@ -12,27 +13,62 @@
import java.util.*;
@Service
-public class AiTaskSummaryService implements AiPromptContextProvider {
+public class AiTaskSummaryService implements AiDiagnosticDataProvider {
+
+ private static final String TOOL_CODE = "task_summary";
+ private static final String TOOL_NAME = "浠诲姟鎽樿";
@Resource
private TaskMapper taskMapper;
+ /**
+ * 杩斿洖浠诲姟宸ュ叿榛樿椤哄簭銆�
+ */
@Override
- public boolean supports(AiPromptContext context) {
- if (context == null || context.getQuestion() == null) {
- return false;
- }
- String normalized = context.getQuestion().toLowerCase(Locale.ROOT);
- return normalized.contains("task")
- || normalized.contains("浠诲姟")
- || normalized.contains("鍑哄簱浠诲姟")
- || normalized.contains("鍏ュ簱浠诲姟")
- || normalized.contains("绉诲簱浠诲姟")
- || normalized.contains("澶囪揣浠诲姟");
+ public int getOrder() {
+ return 20;
}
+ /**
+ * 杩斿洖浠诲姟宸ュ叿缂栫爜銆�
+ */
@Override
- public String buildContext(AiPromptContext context) {
+ public String getToolCode() {
+ return TOOL_CODE;
+ }
+
+ /**
+ * 杩斿洖浠诲姟宸ュ叿灞曠ず鍚嶃��
+ */
+ @Override
+ public String getToolName() {
+ return TOOL_NAME;
+ }
+
+ /**
+ * 杩斿洖浠诲姟宸ュ叿榛樿璇存槑銆�
+ */
+ @Override
+ public String getDefaultToolPrompt() {
+ return "缁撳悎浠诲姟鎽樿璇嗗埆绉帇銆佸紓甯哥姸鎬佸拰鏈�杩戝彉鏇翠换鍔°��";
+ }
+
+ /**
+ * 姹囨�讳换鍔$姸鎬佸拰鏈�杩戝彉鏇翠换鍔★紝鐢熸垚浠诲姟鎽樿宸ュ叿缁撴灉銆�
+ */
+ @Override
+ public AiDiagnosticToolResult buildDiagnosticData(AiPromptContext context) {
+ return new AiDiagnosticToolResult()
+ .setToolCode(getToolCode())
+ .setToolName(getToolName())
+ .setSeverity("INFO")
+ .setSummaryText(buildTaskSummary());
+ }
+
+ /**
+ * 鍩轰簬 man_task 鐢熸垚浠诲姟鎬昏銆佺姸鎬佸垎甯冦�佺被鍨嬪垎甯冨拰鏈�杩戜换鍔℃憳瑕併��
+ */
+ private String buildTaskSummary() {
List<Task> activeTasks = taskMapper.selectList(new LambdaQueryWrapper<Task>()
.select(Task::getTaskCode, Task::getTaskStatus, Task::getTaskType, Task::getOrgLoc, Task::getTargLoc, Task::getUpdateTime)
.eq(Task::getStatus, 1));
@@ -75,6 +111,9 @@
return summary.toString();
}
+ /**
+ * 鏍煎紡鍖栦换鍔$姸鎬佺粺璁°��
+ */
private String formatStatuses(Map<Integer, Long> rows) {
List<String> parts = new ArrayList<>();
for (Map.Entry<Integer, Long> row : rows.entrySet()) {
@@ -83,6 +122,9 @@
return String.join("锛�", parts);
}
+ /**
+ * 鏍煎紡鍖栦换鍔$被鍨嬬粺璁°��
+ */
private String formatTypes(Map<Integer, Long> rows) {
List<String> parts = new ArrayList<>();
for (Map.Entry<Integer, Long> row : rows.entrySet()) {
@@ -91,6 +133,9 @@
return String.join("锛�", parts);
}
+ /**
+ * 鏍煎紡鍖栨渶杩戞洿鏂颁换鍔″垪琛ㄣ��
+ */
private String formatLatestTasks(List<Task> tasks) {
List<String> parts = new ArrayList<>();
for (Task task : tasks) {
@@ -106,6 +151,9 @@
return String.join("锛�", parts);
}
+ /**
+ * 灏嗕换鍔$姸鎬佺紪鐮佽浆鎹负鍙鏂囨銆�
+ */
private String resolveTaskStatus(Integer taskStatus) {
if (taskStatus == null) {
return "鏈煡鐘舵��";
@@ -118,6 +166,9 @@
return "鐘舵��" + taskStatus;
}
+ /**
+ * 灏嗕换鍔$被鍨嬬紪鐮佽浆鎹负鍙鏂囨銆�
+ */
private String resolveTaskType(Integer taskType) {
if (taskType == null) {
return "鏈煡绫诲瀷";
@@ -130,7 +181,13 @@
return "绫诲瀷" + taskType;
}
+ /**
+ * 缁熶竴澶勭悊绌哄瓧绗︿覆鏄剧ず銆�
+ */
private String emptyToDash(String value) {
return value == null || value.trim().isEmpty() ? "-" : value;
}
}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiWarehouseSummaryService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiWarehouseSummaryService.java
similarity index 81%
rename from rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiWarehouseSummaryService.java
rename to rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiWarehouseSummaryService.java
index 202fed3..7bad7c1 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiWarehouseSummaryService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/provider/AiWarehouseSummaryService.java
@@ -1,6 +1,7 @@
-package com.vincent.rsf.server.ai.service;
+package com.vincent.rsf.server.ai.service.provider;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
import com.vincent.rsf.server.ai.model.AiPromptContext;
import com.vincent.rsf.server.manager.entity.Loc;
import com.vincent.rsf.server.manager.entity.LocItem;
@@ -13,7 +14,10 @@
import java.util.*;
@Service
-public class AiWarehouseSummaryService implements AiPromptContextProvider {
+public class AiWarehouseSummaryService implements AiDiagnosticDataProvider {
+
+ private static final String TOOL_CODE = "warehouse_summary";
+ private static final String TOOL_NAME = "搴撳瓨鎽樿";
private static final Map<String, String> LOC_STATUS_LABELS = new LinkedHashMap<>();
@@ -31,16 +35,54 @@
@Resource
private LocItemMapper locItemMapper;
+ /**
+ * 杩斿洖搴撳瓨绫诲唴閮ㄥ伐鍏风殑榛樿椤哄簭銆�
+ */
@Override
- public boolean supports(AiPromptContext context) {
- return context != null && shouldSummarize(context.getQuestion());
+ public int getOrder() {
+ return 10;
}
+ /**
+ * 杩斿洖搴撳瓨宸ュ叿缂栫爜銆�
+ */
@Override
- public String buildContext(AiPromptContext context) {
- if (!supports(context)) {
- return "";
- }
+ public String getToolCode() {
+ return TOOL_CODE;
+ }
+
+ /**
+ * 杩斿洖搴撳瓨宸ュ叿灞曠ず鍚嶃��
+ */
+ @Override
+ public String getToolName() {
+ return TOOL_NAME;
+ }
+
+ /**
+ * 杩斿洖搴撳瓨宸ュ叿榛樿璇存槑銆�
+ */
+ @Override
+ public String getDefaultToolPrompt() {
+ return "缁撳悎搴撳瓨鎽樿鍒ゆ柇搴撲綅鐘舵�併�佸簱瀛樼粨鏋勪笌閲嶇偣鐗╂枡鍒嗗竷銆�";
+ }
+
+ /**
+ * 姹囨�诲簱浣嶄笌搴撳瓨鏄庣粏锛岀敓鎴愬簱瀛樻憳瑕佸伐鍏风粨鏋溿��
+ */
+ @Override
+ public AiDiagnosticToolResult buildDiagnosticData(AiPromptContext context) {
+ return new AiDiagnosticToolResult()
+ .setToolCode(getToolCode())
+ .setToolName(getToolName())
+ .setSeverity("INFO")
+ .setSummaryText(buildWarehouseSummary(context));
+ }
+
+ /**
+ * 鍩轰簬 man_loc 鍜� man_loc_item 鐢熸垚搴撳瓨姒傝銆佸簱浣嶇姸鎬佸垎甯冨拰 TOP 缁熻銆�
+ */
+ private String buildWarehouseSummary(AiPromptContext context) {
List<Loc> activeLocs = locMapper.selectList(new LambdaQueryWrapper<Loc>()
.select(Loc::getUseStatus)
@@ -134,21 +176,9 @@
return summary.toString();
}
- private boolean shouldSummarize(String question) {
- if (question == null || question.trim().isEmpty()) {
- return false;
- }
- String normalized = question.toLowerCase(Locale.ROOT);
- return normalized.contains("loc")
- || normalized.contains("搴撲綅")
- || normalized.contains("璐т綅")
- || normalized.contains("搴撳尯")
- || normalized.contains("搴撳瓨")
- || normalized.contains("鐗╂枡")
- || normalized.contains("宸烽亾")
- || normalized.contains("鍌ㄤ綅");
- }
-
+ /**
+ * 灏嗗簱浣嶇姸鎬佽鏁版牸寮忓寲涓哄彲璇绘枃鏈��
+ */
private String formatLocStatuses(Map<String, Long> counters) {
if (counters == null || counters.isEmpty()) {
return "鏆傛棤鏁版嵁";
@@ -160,6 +190,9 @@
return String.join("锛�", parts);
}
+ /**
+ * 鏍煎紡鍖栧簱瀛樻渶澶氱殑搴撲綅鍒楄〃銆�
+ */
private String formatTopLocs(List<Map.Entry<String, LocAggregate>> rows) {
List<String> parts = new ArrayList<>();
for (Map.Entry<String, LocAggregate> row : rows) {
@@ -170,6 +203,9 @@
return String.join("锛�", parts);
}
+ /**
+ * 鏍煎紡鍖栧簱瀛樻渶澶氱殑鐗╂枡鍒楄〃銆�
+ */
private String formatTopMaterials(List<MaterialAggregate> rows) {
List<String> parts = new ArrayList<>();
for (MaterialAggregate row : rows) {
@@ -183,11 +219,17 @@
return String.join("锛�", parts);
}
+ /**
+ * 缁熶竴鏍煎紡鍖栨暟閲忓�笺��
+ */
private String formatDecimal(Object value) {
BigDecimal decimal = toDecimal(value);
return decimal.stripTrailingZeros().toPlainString();
}
+ /**
+ * 灏嗕笉鍚岀被鍨嬬殑鏁伴噺瀛楁缁熶竴杞崲涓� BigDecimal銆�
+ */
private BigDecimal toDecimal(Object value) {
if (value == null) {
return BigDecimal.ZERO;
@@ -213,3 +255,6 @@
private final Set<String> locCodes = new HashSet<>();
}
}
+
+
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/config/RemotesInfoProperties.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/config/RemotesInfoProperties.java
index ec5cd20..5ba47b4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/config/RemotesInfoProperties.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/config/RemotesInfoProperties.java
@@ -73,3 +73,4 @@
return this.host + ":" + this.port + "/" + this.prePath;
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/config/RestTemplateConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/config/RestTemplateConfig.java
index 5a7dc87..d23b054 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/config/RestTemplateConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/config/RestTemplateConfig.java
@@ -20,3 +20,4 @@
return new RestTemplate();
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/WcsController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/WcsController.java
index 4a04d52..edd8949 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/WcsController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/WcsController.java
@@ -191,3 +191,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/BaseInfoController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/BaseInfoController.java
index 6997f92..fdcdd20 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/BaseInfoController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/BaseInfoController.java
@@ -128,3 +128,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/ErpQueryController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/ErpQueryController.java
index 36457c8..ed98501 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/ErpQueryController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/ErpQueryController.java
@@ -140,3 +140,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/ReportMsgController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/ReportMsgController.java
index e8f7c83..079bc3c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/ReportMsgController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/ReportMsgController.java
@@ -59,3 +59,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/SyncOrderController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/SyncOrderController.java
index fc23911..d32c428 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/SyncOrderController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/SyncOrderController.java
@@ -218,3 +218,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/BaseMatParms.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/BaseMatParms.java
index ee9e813..3418282 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/BaseMatParms.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/BaseMatParms.java
@@ -53,3 +53,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/BaseSyncParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/BaseSyncParams.java
index 038fe39..15a469c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/BaseSyncParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/BaseSyncParams.java
@@ -10,3 +10,4 @@
public class BaseSyncParams {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/CheckObjParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/CheckObjParams.java
index 93840fb..1ae9068 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/CheckObjParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/CheckObjParams.java
@@ -21,3 +21,4 @@
@ApiModelProperty("鐗╂枡缂栫爜")
private String matnrCode;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/CompaniesParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/CompaniesParam.java
index 60a154c..733651e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/CompaniesParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/CompaniesParam.java
@@ -51,3 +51,4 @@
private String code;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/InventoryQueryConditionParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/InventoryQueryConditionParam.java
index e833bde..10c6ff4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/InventoryQueryConditionParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/InventoryQueryConditionParam.java
@@ -139,3 +139,4 @@
return map;
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/LocAreasParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/LocAreasParams.java
index 831af3c..8742092 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/LocAreasParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/LocAreasParams.java
@@ -25,3 +25,4 @@
private String type;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ManualShelvingParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ManualShelvingParams.java
index af7757a..4f09109 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ManualShelvingParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ManualShelvingParams.java
@@ -24,3 +24,4 @@
@ApiModelProperty("鍗曟嵁鏄庣粏")
private List<WaitPakinItem> itemList;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OpStockParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OpStockParams.java
index d9a74e8..cc17749 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OpStockParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OpStockParams.java
@@ -17,3 +17,4 @@
@ApiModelProperty("鐗╂枡缂栫爜")
private String matnrCode;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OrderItem.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OrderItem.java
index 17457b8..1f710e6 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OrderItem.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OrderItem.java
@@ -51,3 +51,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OrderParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OrderParams.java
index b80b81d..b8bbb40 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OrderParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OrderParams.java
@@ -59,3 +59,4 @@
@ApiModelProperty(value = "鍗曟嵁鏄庣粏")
private List<OrderItem> children;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OtherReceiptParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OtherReceiptParams.java
index b7f240d..085efce 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OtherReceiptParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/OtherReceiptParams.java
@@ -28,3 +28,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/PublicToStockParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/PublicToStockParams.java
index 74de32a..af92524 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/PublicToStockParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/PublicToStockParams.java
@@ -20,3 +20,4 @@
private List<WkOrderItem> itemList;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/QueryOrderParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/QueryOrderParam.java
index 1578750..7e850b7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/QueryOrderParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/QueryOrderParam.java
@@ -22,3 +22,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReceiptParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReceiptParams.java
index c2341b6..91ab0c6 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReceiptParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReceiptParams.java
@@ -27,3 +27,4 @@
@ApiModelProperty(value = "搴撳尯鏍囪瘑", required = true)
private Long whAreaId;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReportDataParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReportDataParam.java
index 1b31828..5a03b76 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReportDataParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReportDataParam.java
@@ -63,3 +63,4 @@
@ApiModelProperty("澶囨敞璇存槑")
private String MemoDtl;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReportParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReportParams.java
index 6d1e661..5c9db54 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReportParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/ReportParams.java
@@ -24,3 +24,4 @@
private List<ReportDataParam> Data;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SaveCheckDiffParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SaveCheckDiffParams.java
index 1c0fe37..2edf926 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SaveCheckDiffParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SaveCheckDiffParams.java
@@ -23,3 +23,4 @@
@ApiModelProperty("宸紓鍗曟槑缁�")
private List<CheckDiffItem> checkDiffItems;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncCheckDiffParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncCheckDiffParams.java
index 77d00f0..b8b74eb 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncCheckDiffParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncCheckDiffParams.java
@@ -20,3 +20,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncLocReviseParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncLocReviseParams.java
index 767c178..10592ad 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncLocReviseParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncLocReviseParams.java
@@ -28,3 +28,4 @@
@ApiModelProperty("鍗曟嵁鍚嶇О鍒楄〃")
private List<SyncReviseItems> reviseItems;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncLocsParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncLocsParams.java
index 9ac0cd5..2b4dff7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncLocsParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncLocsParams.java
@@ -80,3 +80,4 @@
@ApiModelProperty(value= "鐘舵�� 1: 姝e父 0: 鍐荤粨 ")
private Integer status;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncMatGroupsParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncMatGroupsParams.java
index 5239176..60a706c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncMatGroupsParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncMatGroupsParams.java
@@ -37,3 +37,4 @@
private String code;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncReviseItems.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncReviseItems.java
index 6dddeb3..472fe6c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncReviseItems.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncReviseItems.java
@@ -22,3 +22,4 @@
private List<SyncOrdersItem> items;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncTransferItems.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncTransferItems.java
index 77d5f31..f6dc453 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncTransferItems.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncTransferItems.java
@@ -50,3 +50,4 @@
private String projectCode;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncTransferParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncTransferParams.java
index 699b5bc..29c7f3e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncTransferParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncTransferParams.java
@@ -23,3 +23,4 @@
@ApiModelProperty("璋冩嫈鍗曟槑缁�")
private List<SyncTransferItems> items;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/TaskInParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/TaskInParam.java
index 2291035..ea014ed 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/TaskInParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/TaskInParam.java
@@ -34,3 +34,4 @@
// private Integer locType2; //搴撲綅绫诲瀷
// private Integer locType3; //搴撲綅绫诲瀷
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/WarehouseParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/WarehouseParams.java
index 9571101..9834834 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/WarehouseParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/WarehouseParams.java
@@ -31,3 +31,4 @@
private String latitude;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/CheckDiffDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/CheckDiffDto.java
index f3392fe..2a09d49 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/CheckDiffDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/CheckDiffDto.java
@@ -22,3 +22,4 @@
@ApiModelProperty("宸紓鍗曟槑缁�")
List<CheckDiffItem> items;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/TransferInfoDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/TransferInfoDto.java
index 6e95cc1..f610fb8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/TransferInfoDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/TransferInfoDto.java
@@ -19,3 +19,4 @@
private List<TransferItem> items;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/WkOrderDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/WkOrderDto.java
index c19c52c..206898d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/WkOrderDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/dto/WkOrderDto.java
@@ -22,3 +22,4 @@
private List<WkOrderItem> orderItems;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mcp/AiMcpProtocolController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mcp/AiMcpProtocolController.java
new file mode 100644
index 0000000..f72725e
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mcp/AiMcpProtocolController.java
@@ -0,0 +1,43 @@
+package com.vincent.rsf.server.api.controller.mcp;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.vincent.rsf.server.ai.service.mcp.AiMcpProtocolService;
+import com.vincent.rsf.server.system.controller.BaseController;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+@RestController
+@RequestMapping("/ai/mcp")
+public class AiMcpProtocolController extends BaseController {
+
+ @Resource
+ private AiMcpProtocolService aiMcpProtocolService;
+
+ @PostMapping
+ public Object handle(@RequestBody JsonNode body,
+ @RequestHeader(value = "X-Tenant-Id", required = false) String tenantHeader,
+ @RequestParam(value = "tenantId", required = false) Long tenantParam) {
+ Long tenantId = resolveTenantId(tenantHeader, tenantParam);
+ return aiMcpProtocolService.handle(tenantId, body);
+ }
+
+ private Long resolveTenantId(String tenantHeader, Long tenantParam) {
+ if (tenantParam != null) {
+ return tenantParam;
+ }
+ if (tenantHeader != null && !tenantHeader.trim().isEmpty()) {
+ try {
+ return Long.valueOf(tenantHeader.trim());
+ } catch (Exception ignore) {
+ }
+ }
+ Long loginTenantId = getTenantId();
+ return loginTenantId == null ? 1L : loginTenantId;
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mcp/McpController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mcp/McpController.java
index 1f602b1..cf992f1 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mcp/McpController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mcp/McpController.java
@@ -99,3 +99,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mes/MesController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mes/MesController.java
index de0d155..c6bf48c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mes/MesController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/mes/MesController.java
@@ -162,3 +162,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/AgvController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/AgvController.java
index 1daa715..9fd2418 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/AgvController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/AgvController.java
@@ -87,3 +87,4 @@
return agvService.AGVBindAndInTaskStartT(param, getLoginUserId());
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/InBoundController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/InBoundController.java
index 9977501..9573cff 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/InBoundController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/InBoundController.java
@@ -71,3 +71,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/MobileController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/MobileController.java
index 9607752..98ed29c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/MobileController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/MobileController.java
@@ -354,3 +354,4 @@
return mobileService.generateTask(map, getLoginUserId());
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/MonitorController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/MonitorController.java
index 2780bbc..cf3943f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/MonitorController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/MonitorController.java
@@ -83,3 +83,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaCheckOrderController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaCheckOrderController.java
index 54b9788..9a5c454 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaCheckOrderController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaCheckOrderController.java
@@ -62,3 +62,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOtherController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOtherController.java
index 0cdb7ce..bf1d6a2 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOtherController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOtherController.java
@@ -75,3 +75,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOutStockController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOutStockController.java
index 785f69b..1633053 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOutStockController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOutStockController.java
@@ -221,3 +221,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/SysInfoController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/SysInfoController.java
index 3cf92ea..faf35ad 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/SysInfoController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/SysInfoController.java
@@ -67,3 +67,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/CommonResponse.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/CommonResponse.java
index 3dd0f0b..dbc6c95 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/CommonResponse.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/CommonResponse.java
@@ -18,5 +18,4 @@
@ApiModelProperty("鍝嶅簲缁撴灉")
private Object data;
-
-}
\ No newline at end of file
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java
index a8f34f8..120064a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java
@@ -19,3 +19,4 @@
//寰呬笅鍙戜换鍔″彂閫佽嚦涓浆绔�
public static String MISSION_TRANSFER_STATION = "/rsf-open-api/mission/task/master/control";
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/CheckObjDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/CheckObjDto.java
index 8e481c9..30a177f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/CheckObjDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/CheckObjDto.java
@@ -43,3 +43,4 @@
@ApiModelProperty("澶囨敞")
private String memo;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/ContainerWaveDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/ContainerWaveDto.java
index 06641fd..7279672 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/ContainerWaveDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/ContainerWaveDto.java
@@ -17,3 +17,4 @@
private List<WkOrderItem> wkOrderItems;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InTaskMsgDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InTaskMsgDto.java
index 6399d72..df38e0c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InTaskMsgDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InTaskMsgDto.java
@@ -14,3 +14,4 @@
private String workNo;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InspectDetlDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InspectDetlDto.java
index bd1f5ad..ab5d1dd 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InspectDetlDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InspectDetlDto.java
@@ -26,3 +26,4 @@
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InspectItemDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InspectItemDto.java
index f164f33..43e21fc 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InspectItemDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InspectItemDto.java
@@ -29,3 +29,4 @@
@ApiModelProperty("渚涘簲鍟嗙紪鐮�")
private String suplierCode;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/LocTypeDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/LocTypeDto.java
index d0a8a24..03a3e77 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/LocTypeDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/LocTypeDto.java
@@ -19,3 +19,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/OperateStockDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/OperateStockDto.java
index 96d3eea..dd45baf 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/OperateStockDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/OperateStockDto.java
@@ -6,3 +6,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/PoItemsDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/PoItemsDto.java
index 97ad2b6..450d0f8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/PoItemsDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/PoItemsDto.java
@@ -41,3 +41,4 @@
private String anfme;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/ReceiptDetlsDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/ReceiptDetlsDto.java
index a1f361b..99c7bea 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/ReceiptDetlsDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/ReceiptDetlsDto.java
@@ -95,3 +95,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/SyncLocsDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/SyncLocsDto.java
index 42a1609..d5a0cde 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/SyncLocsDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/SyncLocsDto.java
@@ -42,3 +42,4 @@
private String status$;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/TaskLocAreaDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/TaskLocAreaDto.java
index b85cdc7..8062ba1 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/TaskLocAreaDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/TaskLocAreaDto.java
@@ -23,3 +23,4 @@
@ApiModelProperty("搴撲綅淇℃伅")
private List<Loc> locs;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/TaskQueueDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/TaskQueueDto.java
index dbe935a..214855a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/TaskQueueDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/TaskQueueDto.java
@@ -25,3 +25,4 @@
private TaskLocAreaDto locArea;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/CallBackEvent.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/CallBackEvent.java
index a633c0a..ade57e1 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/CallBackEvent.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/CallBackEvent.java
@@ -23,3 +23,4 @@
public String desc;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/MatnrDefectType.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/MatnrDefectType.java
index 34563ec..1776ca9 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/MatnrDefectType.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/MatnrDefectType.java
@@ -24,3 +24,4 @@
public Short type;
public String val;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/WcsMsgTypeEvent.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/WcsMsgTypeEvent.java
index fcceb69..6976a3f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/WcsMsgTypeEvent.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/WcsMsgTypeEvent.java
@@ -21,3 +21,4 @@
public String desc;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CallForEmptyContainersParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CallForEmptyContainersParam.java
index 0edf94f..0995b93 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CallForEmptyContainersParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CallForEmptyContainersParam.java
@@ -26,3 +26,4 @@
private String taskNo;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ChangeLocParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ChangeLocParam.java
index c16f291..9db8d09 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ChangeLocParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ChangeLocParam.java
@@ -26,3 +26,4 @@
private Integer locType1;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CommonRequest.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CommonRequest.java
index 3da81c5..7413d07 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CommonRequest.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CommonRequest.java
@@ -18,3 +18,4 @@
@ApiModelProperty("鏉$洰鏁�")
private Integer pageSize;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ContainerWaveParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ContainerWaveParam.java
index 93cf2c2..73d956d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ContainerWaveParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ContainerWaveParam.java
@@ -16,3 +16,4 @@
private List<ContainerWaveDto> containerWaveDtos;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CreateInTaskParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CreateInTaskParam.java
index 6c2bf26..c4c108d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CreateInTaskParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/CreateInTaskParam.java
@@ -26,3 +26,4 @@
private Integer locType1;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ErpInspectItem.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ErpInspectItem.java
index 080da32..5c4e3e4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ErpInspectItem.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ErpInspectItem.java
@@ -36,3 +36,4 @@
*/
public Double anfme;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ErpInspectParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ErpInspectParams.java
index 76badb5..0856566 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ErpInspectParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ErpInspectParams.java
@@ -34,3 +34,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ExMsgParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ExMsgParams.java
index 3c1109e..45b1e08 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ExMsgParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ExMsgParams.java
@@ -21,3 +21,4 @@
@ApiModelProperty("瀹瑰櫒鐮�")
private String zpallet;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/InTaskWcsReportParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/InTaskWcsReportParam.java
index 677c719..28aa705 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/InTaskWcsReportParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/InTaskWcsReportParam.java
@@ -23,3 +23,4 @@
private Integer taskPri;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/MissionTaskIssueParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/MissionTaskIssueParam.java
index 431cac4..fd5dc15 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/MissionTaskIssueParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/MissionTaskIssueParam.java
@@ -153,3 +153,4 @@
this.retryTimes = flowStepInstance.getRetryTimes();
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/OrderOutGeneralParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/OrderOutGeneralParam.java
index c8e6c71..abf26a7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/OrderOutGeneralParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/OrderOutGeneralParam.java
@@ -27,3 +27,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java
index d278257..3e405fd 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java
@@ -41,3 +41,4 @@
private String sta2;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ReassignLocParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ReassignLocParam.java
index ca17711..9fd3947 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ReassignLocParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ReassignLocParam.java
@@ -26,3 +26,4 @@
private Integer locType1;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskItemParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskItemParam.java
index 13b3687..506c20d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskItemParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskItemParam.java
@@ -34,3 +34,4 @@
@ApiModelProperty("浼樺厛绾�")
private Integer priority;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskReportParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskReportParam.java
index 2bd3bc1..a4bdb66 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskReportParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskReportParam.java
@@ -41,3 +41,4 @@
private Integer type = 0;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/WcsTaskParams.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/WcsTaskParams.java
index 1c6e5e4..edb976d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/WcsTaskParams.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/WcsTaskParams.java
@@ -26,3 +26,4 @@
@ApiModelProperty("浠诲姟鏄庣粏")
private List<TaskItemParam> taskList;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/validator/SyncOrderValidator.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/validator/SyncOrderValidator.java
index 801ce07..cb8cc11 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/validator/SyncOrderValidator.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/validator/SyncOrderValidator.java
@@ -150,3 +150,4 @@
// .sum();
// }
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/AgvService.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/AgvService.java
index d22eb9c..57ed268 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/AgvService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/AgvService.java
@@ -22,3 +22,4 @@
R AGVBindAndInTaskStart(String barcode);
boolean AGVBindAndInTaskStart(String barcode, String sta);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/MobileService.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/MobileService.java
index 76cf52c..18e18b1 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/MobileService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/MobileService.java
@@ -82,3 +82,4 @@
R generateTask(Map<String, Object> map, Long loginUserId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/MonitorService.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/MonitorService.java
index 4d0fea3..faafe27 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/MonitorService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/MonitorService.java
@@ -8,3 +8,4 @@
R getInOutHistories(Map<String, Object> param);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaCheckOrderService.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaCheckOrderService.java
index 13e3339..492a8b6 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaCheckOrderService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaCheckOrderService.java
@@ -18,3 +18,4 @@
R getCheckTaskItemList2(String barcode);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOtherService.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOtherService.java
index 8ad2c95..75d7a1f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOtherService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOtherService.java
@@ -21,3 +21,4 @@
R locOperate(PdaGeneralParam generalParam, User loginUser);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOutStockService.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOutStockService.java
index 7c6b6dd..13bea5d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOutStockService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOutStockService.java
@@ -22,3 +22,4 @@
R taskItemList(PdaGeneralParam param, Long loginUserId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/ReceiveMsgService.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/ReceiveMsgService.java
index f9b7559..d52b645 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/ReceiveMsgService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/ReceiveMsgService.java
@@ -167,3 +167,4 @@
*/
R erpQueryInventorySummary(InventoryQueryConditionParam condition);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/ReportMsgService.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/ReportMsgService.java
index 134d495..470b49b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/ReportMsgService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/ReportMsgService.java
@@ -21,3 +21,4 @@
R uploadCheckOrder(ReportParams params);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/WcsService.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/WcsService.java
index 9c1db28..e2a432b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/WcsService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/WcsService.java
@@ -23,3 +23,4 @@
R wcsReassignLoc(ReassignLocParam params);
R wcsChangeLoc(ChangeLocParam params);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java
index dac03a5..b2e9abd 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java
@@ -580,3 +580,4 @@
return true;
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
index 7c760fc..70927e4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
@@ -364,5 +364,4 @@
}
return R.ok();
}
-
-}
\ No newline at end of file
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
index 4fc2165..ed89c58 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
@@ -1289,3 +1289,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MonitorServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MonitorServiceImpl.java
index d2ac10d..8ec2c0d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MonitorServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MonitorServiceImpl.java
@@ -15,3 +15,4 @@
return R.ok();
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaCheckOrderServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaCheckOrderServiceImpl.java
index e8a8a26..ebffecc 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaCheckOrderServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaCheckOrderServiceImpl.java
@@ -251,3 +251,4 @@
return R.ok(Cools.add("checkDiffItems", checkDiffItems).add("checkDiff", checkDiff));
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOtherServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOtherServiceImpl.java
index 11a5e42..fe6015b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOtherServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOtherServiceImpl.java
@@ -279,3 +279,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java
index 2cc2e07..010a1da 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java
@@ -525,3 +525,4 @@
item -> new BigDecimal(item.getAnfme().toString()).equals(new BigDecimal(item.getQty().toString())));
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java
index a8dc8f0..3a832b1 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java
@@ -1490,3 +1490,4 @@
}
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReportMsgServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReportMsgServiceImpl.java
index f1424e8..7fa47c6 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReportMsgServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReportMsgServiceImpl.java
@@ -259,3 +259,4 @@
}
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
index 4e19cfd..ee305da 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
@@ -1204,3 +1204,4 @@
// return R.ok(JSONObject.toJSONString(params));
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/LocUtils.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/LocUtils.java
index c3ccfb1..f842215 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/LocUtils.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/LocUtils.java
@@ -487,3 +487,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/SlaveProperties.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/SlaveProperties.java
index 386ce11..4f6d306 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/SlaveProperties.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/SlaveProperties.java
@@ -26,3 +26,4 @@
private int groupCount;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/TimeConverterUtils.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/TimeConverterUtils.java
index d4821b4..d14a946 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/TimeConverterUtils.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/utils/TimeConverterUtils.java
@@ -45,3 +45,4 @@
return date.getTime();
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
index 912808b..24091e7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
@@ -47,3 +47,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/annotation/OperationLog.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/annotation/OperationLog.java
index 9e7fd05..d7c2602 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/annotation/OperationLog.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/annotation/OperationLog.java
@@ -39,3 +39,4 @@
boolean result() default true;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/aspect/OperationLogAspect.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/aspect/OperationLogAspect.java
index c85c077..eb6ed83 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/aspect/OperationLogAspect.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/aspect/OperationLogAspect.java
@@ -190,3 +190,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/BeanConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/BeanConfig.java
index 4aa1348..b09e4fb 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/BeanConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/BeanConfig.java
@@ -20,3 +20,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/ConfigProperties.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/ConfigProperties.java
index 6c1f406..acefa8a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/ConfigProperties.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/ConfigProperties.java
@@ -84,3 +84,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
index 761b05f..81cca44 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
@@ -117,3 +117,4 @@
};
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/RedisProperties.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/RedisProperties.java
index 4ce2136..63f124a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/RedisProperties.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/RedisProperties.java
@@ -94,3 +94,4 @@
this.index = index;
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SchedulerConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SchedulerConfig.java
index 183fb8d..8214373 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SchedulerConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SchedulerConfig.java
@@ -21,3 +21,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SwaggerConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SwaggerConfig.java
index 8e336a6..916a558 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SwaggerConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SwaggerConfig.java
@@ -102,3 +102,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SysStockProperties.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SysStockProperties.java
index 90bd960..e2795bc 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SysStockProperties.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/SysStockProperties.java
@@ -44,3 +44,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/WebMvcConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/WebMvcConfig.java
index e312df0..cdae76a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/WebMvcConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/WebMvcConfig.java
@@ -75,3 +75,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/WebSocketConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/WebSocketConfig.java
index 65f29b8..372bd5b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/WebSocketConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/WebSocketConfig.java
@@ -15,3 +15,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/constant/Constants.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/constant/Constants.java
index e562231..615ded5 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/constant/Constants.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/constant/Constants.java
@@ -139,3 +139,4 @@
public static final Integer TASK_SORT_MIN_VALUE = 0;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BaseParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BaseParam.java
index 6ee5310..c8b0249 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BaseParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BaseParam.java
@@ -96,3 +96,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BusinessRes.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BusinessRes.java
index a1c3d66..e7ac141 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BusinessRes.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BusinessRes.java
@@ -17,3 +17,4 @@
public final static String EMAIL_EXIT = "10006 - Email address already exist";
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/KeyValVo.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/KeyValVo.java
index 08325e2..d851fb4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/KeyValVo.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/KeyValVo.java
@@ -18,3 +18,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/PageParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/PageParam.java
index 2ed6d84..329e27c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/PageParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/PageParam.java
@@ -451,3 +451,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/PageResult.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/PageResult.java
index 608271f..aa7cb54 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/PageResult.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/PageResult.java
@@ -33,3 +33,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/QueueTask.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/QueueTask.java
index a49693f..9fda825 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/QueueTask.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/QueueTask.java
@@ -5,3 +5,4 @@
@Data
public class QueueTask {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/enums/WarehouseAreaType.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/enums/WarehouseAreaType.java
index f5b5319..49220d0 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/enums/WarehouseAreaType.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/enums/WarehouseAreaType.java
@@ -23,3 +23,4 @@
public String type;
public String desc;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/BusinessException.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/BusinessException.java
index d30d490..ed37521 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/BusinessException.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/BusinessException.java
@@ -47,3 +47,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java
index dd86522..ac0c443 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java
@@ -101,3 +101,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/AggregationDataHandler.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/AggregationDataHandler.java
index 5030852..fb0fe0a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/AggregationDataHandler.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/AggregationDataHandler.java
@@ -67,3 +67,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/ExcelDictHandlerImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/ExcelDictHandlerImpl.java
index 4ecb420..2291865 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/ExcelDictHandlerImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/ExcelDictHandlerImpl.java
@@ -47,3 +47,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/global/GlobalDictService.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/global/GlobalDictService.java
index 965ceb8..ab8458e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/global/GlobalDictService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/handler/global/GlobalDictService.java
@@ -134,3 +134,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/security/JwtAuthenticationFilter.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/security/JwtAuthenticationFilter.java
index 5b3917d..ef1c726 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/security/JwtAuthenticationFilter.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/security/JwtAuthenticationFilter.java
@@ -115,3 +115,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/security/JwtSubject.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/security/JwtSubject.java
index c682757..45d8b83 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/security/JwtSubject.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/security/JwtSubject.java
@@ -29,3 +29,4 @@
private Long tenantId;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/security/SecurityConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/security/SecurityConfig.java
index f40f0c5..ff2aaae 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/security/SecurityConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/security/SecurityConfig.java
@@ -57,6 +57,7 @@
"/wcs/**",
"/monitor/**",
"/mcp/**",
+ "/ai/mcp",
"/mes/**"
};
@@ -130,3 +131,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/service/EmailService.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/service/EmailService.java
index 0d0dc2f..87baa42 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/service/EmailService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/service/EmailService.java
@@ -77,3 +77,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java
index 87c8a4a..b145893 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java
@@ -489,3 +489,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/CommonUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/CommonUtil.java
index 1c14071..2a7f9e2 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/CommonUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/CommonUtil.java
@@ -177,3 +177,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/DataFieldSortFunc.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/DataFieldSortFunc.java
index f994dae..8da63cd 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/DataFieldSortFunc.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/DataFieldSortFunc.java
@@ -7,3 +7,4 @@
List<String> getDataFieldSort();
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/DateUtils.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/DateUtils.java
index 38d4e4d..6543cc0 100755
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/DateUtils.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/DateUtils.java
@@ -744,3 +744,4 @@
return newDate;
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java
index 5f6e0c4..8b45899 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java
@@ -225,3 +225,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/FileServerUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/FileServerUtil.java
index bb889d1..548949c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/FileServerUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/FileServerUtil.java
@@ -403,3 +403,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/Http.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/Http.java
index a224a63..ce20b61 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/Http.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/Http.java
@@ -40,3 +40,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/IpTools.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/IpTools.java
index bf6f9c2..927049d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/IpTools.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/IpTools.java
@@ -92,3 +92,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JChardetFacadeUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JChardetFacadeUtil.java
index e04c5f3..641d5e1 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JChardetFacadeUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JChardetFacadeUtil.java
@@ -2023,3 +2023,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JSONUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JSONUtil.java
index 6d37a50..923be0b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JSONUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JSONUtil.java
@@ -74,3 +74,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JwtUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JwtUtil.java
index f69ee2c..709d1a7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JwtUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/JwtUtil.java
@@ -141,3 +141,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/NodeUtils.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/NodeUtils.java
index 5aa36f7..595c192 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/NodeUtils.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/NodeUtils.java
@@ -38,3 +38,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/OpenOfficeUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/OpenOfficeUtil.java
index 2ef8049..f1358ac 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/OpenOfficeUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/OpenOfficeUtil.java
@@ -122,3 +122,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/CodeRes.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/CodeRes.java
index 863c072..0dbc010 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/CodeRes.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/CodeRes.java
@@ -16,3 +16,4 @@
String SYSTEM_20001 = "20001-璁稿彲璇佸凡澶辨晥";
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/DictTypeCode.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/DictTypeCode.java
index 9bec209..64f16b9 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/DictTypeCode.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/DictTypeCode.java
@@ -99,3 +99,4 @@
/**搴撳瓨璋冩暣*/
public final static String SYS_STOCK_REVISE_TYPE = "sys_stock_revise_type";
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/GlobalConfigCode.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/GlobalConfigCode.java
index 3cc05f3..77af9af 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/GlobalConfigCode.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/GlobalConfigCode.java
@@ -33,3 +33,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/SerialRuleCode.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/SerialRuleCode.java
index 5e1228f..c318d8e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/SerialRuleCode.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/SerialRuleCode.java
@@ -96,3 +96,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiCallLogController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiCallLogController.java
new file mode 100644
index 0000000..60bbb51
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiCallLogController.java
@@ -0,0 +1,103 @@
+package com.vincent.rsf.server.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.system.entity.AiCallLog;
+import com.vincent.rsf.server.system.service.AiCallLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+public class AiCallLogController extends BaseController {
+
+ @Autowired
+ private AiCallLogService aiCallLogService;
+
+ @PreAuthorize("hasAuthority('system:aiCallLog:list')")
+ @PostMapping("/aiCallLog/page")
+ public R page(@RequestBody Map<String, Object> map) {
+ BaseParam baseParam = buildParam(map, BaseParam.class);
+ PageParam<AiCallLog, BaseParam> pageParam = new PageParam<>(baseParam, AiCallLog.class);
+ com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<AiCallLog> wrapper = pageParam.buildWrapper(true);
+ wrapper.eq("tenant_id", getTenantId());
+ return R.ok().add(aiCallLogService.page(pageParam, wrapper));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiCallLog:list')")
+ @GetMapping("/aiCallLog/{id}")
+ public R get(@PathVariable("id") Long id) {
+ AiCallLog callLog = getTenantRecord(id);
+ if (callLog == null) {
+ return R.error("record not found");
+ }
+ return R.ok().add(callLog);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiCallLog:list')")
+ @PostMapping("/aiCallLog/export")
+ public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+ ExcelUtil.build(ExcelUtil.create(aiCallLogService.list(new LambdaQueryWrapper<AiCallLog>()
+ .eq(AiCallLog::getTenantId, getTenantId())), AiCallLog.class), response);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiCallLog:list')")
+ @GetMapping("/ai/call-log/list")
+ public R customList() {
+ return R.ok().add(aiCallLogService.list(new LambdaQueryWrapper<AiCallLog>()
+ .eq(AiCallLog::getTenantId, getTenantId())
+ .orderByDesc(AiCallLog::getCreateTime, AiCallLog::getId)));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiCallLog:list')")
+ @GetMapping("/ai/call-log/stats")
+ public R stats() {
+ List<AiCallLog> logs = aiCallLogService.list(new LambdaQueryWrapper<AiCallLog>()
+ .eq(AiCallLog::getTenantId, getTenantId())
+ .orderByDesc(AiCallLog::getCreateTime, AiCallLog::getId));
+ long total = logs.size();
+ long successCount = logs.stream().filter(item -> Integer.valueOf(1).equals(item.getResult())).count();
+ long failCount = logs.stream().filter(item -> Integer.valueOf(0).equals(item.getResult())).count();
+ long modelCount = logs.stream().map(AiCallLog::getModelCode).filter(item -> item != null && !item.trim().isEmpty()).distinct().count();
+ long routeCount = logs.stream().map(AiCallLog::getRouteCode).filter(item -> item != null && !item.trim().isEmpty()).distinct().count();
+ long totalSpend = logs.stream().map(AiCallLog::getSpendTime).filter(item -> item != null && item > 0L).mapToLong(Long::longValue).sum();
+ long spendCount = logs.stream().map(AiCallLog::getSpendTime).filter(item -> item != null && item > 0L).count();
+ Date now = new Date();
+ long last24hCount = logs.stream()
+ .map(AiCallLog::getCreateTime)
+ .filter(item -> item != null && now.getTime() - item.getTime() <= 24L * 60L * 60L * 1000L)
+ .count();
+
+ Map<String, Object> payload = new LinkedHashMap<>();
+ payload.put("total", total);
+ payload.put("successCount", successCount);
+ payload.put("failCount", failCount);
+ payload.put("successRate", total <= 0 ? 0D : (successCount * 100D) / total);
+ payload.put("avgSpendTime", spendCount <= 0 ? 0L : totalSpend / spendCount);
+ payload.put("modelCount", modelCount);
+ payload.put("routeCount", routeCount);
+ payload.put("last24hCount", last24hCount);
+ return R.ok().add(payload);
+ }
+
+ private AiCallLog getTenantRecord(Long id) {
+ if (id == null) {
+ return null;
+ }
+ return aiCallLogService.getOne(new LambdaQueryWrapper<AiCallLog>()
+ .eq(AiCallLog::getTenantId, getTenantId())
+ .eq(AiCallLog::getId, id)
+ .last("limit 1"));
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosisController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosisController.java
new file mode 100644
index 0000000..e551620
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosisController.java
@@ -0,0 +1,97 @@
+package com.vincent.rsf.server.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.system.entity.AiDiagnosisRecord;
+import com.vincent.rsf.server.system.service.AiDiagnosisRecordService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+@RestController
+public class AiDiagnosisController extends BaseController {
+
+ @Autowired
+ private AiDiagnosisRecordService aiDiagnosisRecordService;
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosis:list')")
+ @PostMapping("/aiDiagnosis/page")
+ public R page(@RequestBody Map<String, Object> map) {
+ BaseParam baseParam = buildParam(map, BaseParam.class);
+ PageParam<AiDiagnosisRecord, BaseParam> pageParam = new PageParam<>(baseParam, AiDiagnosisRecord.class);
+ com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<AiDiagnosisRecord> wrapper = pageParam.buildWrapper(true);
+ wrapper.eq("tenant_id", getTenantId());
+ return R.ok().add(aiDiagnosisRecordService.page(pageParam, wrapper));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosis:list')")
+ @GetMapping("/aiDiagnosis/{id}")
+ public R get(@PathVariable("id") Long id) {
+ AiDiagnosisRecord record = getTenantRecord(id);
+ if (record == null) {
+ return R.error("record not found");
+ }
+ return R.ok().add(record);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosis:list')")
+ @PostMapping("/aiDiagnosis/export")
+ public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+ ExcelUtil.build(ExcelUtil.create(aiDiagnosisRecordService.list(new LambdaQueryWrapper<AiDiagnosisRecord>()
+ .eq(AiDiagnosisRecord::getTenantId, getTenantId())), AiDiagnosisRecord.class), response);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosis:list')")
+ @GetMapping("/ai/diagnosis/list")
+ public R diagnosisList() {
+ return R.ok().add(aiDiagnosisRecordService.list(new LambdaQueryWrapper<AiDiagnosisRecord>()
+ .eq(AiDiagnosisRecord::getTenantId, getTenantId())
+ .orderByDesc(AiDiagnosisRecord::getCreateTime, AiDiagnosisRecord::getId)));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosis:list')")
+ @GetMapping("/ai/diagnosis/detail/{id}")
+ public R detail(@PathVariable("id") Long id) {
+ AiDiagnosisRecord record = getTenantRecord(id);
+ if (record == null) {
+ return R.error("record not found");
+ }
+ return R.ok().add(record);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosis:list')")
+ @GetMapping("/ai/diagnosis/report/export/{id}")
+ public void exportReport(@PathVariable("id") Long id, HttpServletResponse response) throws Exception {
+ AiDiagnosisRecord record = getTenantRecord(id);
+ if (record == null) {
+ response.setStatus(404);
+ return;
+ }
+ String fileName = URLEncoder.encode((record.getDiagnosisNo() == null ? "ai-diagnosis-report" : record.getDiagnosisNo()) + ".md", StandardCharsets.UTF_8.name());
+ response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ response.setContentType("text/markdown; charset=utf-8");
+ response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
+ response.getWriter().write(record.getReportMarkdown() == null ? "" : record.getReportMarkdown());
+ response.getWriter().flush();
+ }
+
+ private AiDiagnosisRecord getTenantRecord(Long id) {
+ if (id == null) {
+ return null;
+ }
+ return aiDiagnosisRecordService.getOne(new LambdaQueryWrapper<AiDiagnosisRecord>()
+ .eq(AiDiagnosisRecord::getTenantId, getTenantId())
+ .eq(AiDiagnosisRecord::getId, id)
+ .last("limit 1"));
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosisPlanController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosisPlanController.java
new file mode 100644
index 0000000..4897e48
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosisPlanController.java
@@ -0,0 +1,169 @@
+package com.vincent.rsf.server.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.framework.common.SnowflakeIdWorker;
+import com.vincent.rsf.server.ai.service.diagnosis.AiDiagnosisPlanRunnerService;
+import com.vincent.rsf.server.common.annotation.OperationLog;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.system.entity.AiDiagnosisPlan;
+import com.vincent.rsf.server.system.service.AiDiagnosisPlanService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+public class AiDiagnosisPlanController extends BaseController {
+
+ @Autowired
+ private AiDiagnosisPlanService aiDiagnosisPlanService;
+ @Autowired
+ private AiDiagnosisPlanRunnerService aiDiagnosisPlanRunnerService;
+ @Autowired
+ private ThreadPoolTaskScheduler taskScheduler;
+ @Autowired
+ private SnowflakeIdWorker snowflakeIdWorker;
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosisPlan:list')")
+ @PostMapping("/aiDiagnosisPlan/page")
+ public R page(@RequestBody Map<String, Object> map) {
+ BaseParam baseParam = buildParam(map, BaseParam.class);
+ PageParam<AiDiagnosisPlan, BaseParam> pageParam = new PageParam<>(baseParam, AiDiagnosisPlan.class);
+ com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<AiDiagnosisPlan> wrapper = pageParam.buildWrapper(true);
+ wrapper.eq("tenant_id", getTenantId());
+ return R.ok().add(aiDiagnosisPlanService.page(pageParam, wrapper));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosisPlan:list')")
+ @GetMapping("/aiDiagnosisPlan/{id}")
+ public R get(@PathVariable("id") Long id) {
+ AiDiagnosisPlan plan = aiDiagnosisPlanService.getTenantPlan(getTenantId(), id);
+ if (plan == null) {
+ return R.error("plan not found");
+ }
+ return R.ok().add(plan);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosisPlan:save')")
+ @OperationLog("Create AiDiagnosisPlan")
+ @PostMapping("/aiDiagnosisPlan/save")
+ public R save(@RequestBody AiDiagnosisPlan plan) {
+ if (Cools.isEmpty(plan.getPlanName()) || Cools.isEmpty(plan.getCronExpr())) {
+ return R.error("璁″垝鍚嶇О鍜孋ron琛ㄨ揪寮忎笉鑳戒负绌�");
+ }
+ if (!aiDiagnosisPlanService.validateCron(plan.getCronExpr())) {
+ return R.error("Cron琛ㄨ揪寮忎笉鍚堟硶");
+ }
+ Date now = new Date();
+ plan.setUuid(String.valueOf(snowflakeIdWorker.nextId()).substring(3));
+ plan.setTenantId(getTenantId());
+ plan.setSceneCode(Cools.isEmpty(plan.getSceneCode()) ? "system_diagnose" : plan.getSceneCode());
+ plan.setRunningFlag(0);
+ plan.setStatus(plan.getStatus() == null ? 1 : plan.getStatus());
+ plan.setNextRunTime(Integer.valueOf(1).equals(plan.getStatus())
+ ? aiDiagnosisPlanService.calculateNextRunTime(plan.getCronExpr(), now)
+ : null);
+ plan.setCreateBy(getLoginUserId());
+ plan.setCreateTime(now);
+ plan.setUpdateBy(getLoginUserId());
+ plan.setUpdateTime(now);
+ if (!aiDiagnosisPlanService.save(plan)) {
+ return R.error("Save Fail");
+ }
+ return R.ok("Save Success").add(plan);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosisPlan:update')")
+ @OperationLog("Update AiDiagnosisPlan")
+ @PostMapping("/aiDiagnosisPlan/update")
+ public R update(@RequestBody AiDiagnosisPlan plan) {
+ AiDiagnosisPlan existed = aiDiagnosisPlanService.getTenantPlan(getTenantId(), plan.getId());
+ if (existed == null) {
+ return R.error("plan not found");
+ }
+ if (Cools.isEmpty(plan.getPlanName()) || Cools.isEmpty(plan.getCronExpr())) {
+ return R.error("璁″垝鍚嶇О鍜孋ron琛ㄨ揪寮忎笉鑳戒负绌�");
+ }
+ if (!aiDiagnosisPlanService.validateCron(plan.getCronExpr())) {
+ return R.error("Cron琛ㄨ揪寮忎笉鍚堟硶");
+ }
+ plan.setTenantId(getTenantId());
+ plan.setSceneCode(Cools.isEmpty(plan.getSceneCode()) ? existed.getSceneCode() : plan.getSceneCode());
+ plan.setRunningFlag(existed.getRunningFlag());
+ plan.setLastResult(existed.getLastResult());
+ plan.setLastDiagnosisId(existed.getLastDiagnosisId());
+ plan.setLastRunTime(existed.getLastRunTime());
+ plan.setLastMessage(existed.getLastMessage());
+ plan.setNextRunTime(Integer.valueOf(1).equals(plan.getStatus())
+ ? aiDiagnosisPlanService.calculateNextRunTime(plan.getCronExpr(), new Date())
+ : null);
+ plan.setCreateBy(existed.getCreateBy());
+ plan.setCreateTime(existed.getCreateTime());
+ plan.setUpdateBy(getLoginUserId());
+ plan.setUpdateTime(new Date());
+ if (!aiDiagnosisPlanService.updateById(plan)) {
+ return R.error("Update Fail");
+ }
+ return R.ok("Update Success").add(plan);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosisPlan:remove')")
+ @OperationLog("Delete AiDiagnosisPlan")
+ @PostMapping("/aiDiagnosisPlan/remove/{ids}")
+ public R remove(@PathVariable Long[] ids) {
+ List<Long> idList = Arrays.asList(ids);
+ List<AiDiagnosisPlan> plans = aiDiagnosisPlanService.list(new LambdaQueryWrapper<AiDiagnosisPlan>()
+ .eq(AiDiagnosisPlan::getTenantId, getTenantId())
+ .in(AiDiagnosisPlan::getId, idList));
+ if (plans.size() != idList.size() || !aiDiagnosisPlanService.removeByIds(idList)) {
+ return R.error("Delete Fail");
+ }
+ return R.ok("Delete Success").add(ids);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosisPlan:list')")
+ @PostMapping("/aiDiagnosisPlan/export")
+ public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+ ExcelUtil.build(ExcelUtil.create(aiDiagnosisPlanService.list(new LambdaQueryWrapper<AiDiagnosisPlan>()
+ .eq(AiDiagnosisPlan::getTenantId, getTenantId())), AiDiagnosisPlan.class), response);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiDiagnosisPlan:update')")
+ @OperationLog("Run AiDiagnosisPlan")
+ @PostMapping("/ai/diagnosis-plan/run")
+ public R run(@RequestBody Map<String, Object> map) {
+ Long id = Long.valueOf(String.valueOf(map.get("id")));
+ AiDiagnosisPlan plan = aiDiagnosisPlanService.getTenantPlan(getTenantId(), id);
+ if (plan == null) {
+ return R.error("plan not found");
+ }
+ if (Integer.valueOf(1).equals(plan.getRunningFlag())) {
+ return R.error("璁″垝姝e湪鎵ц涓�");
+ }
+ Date nextRunTime = Integer.valueOf(1).equals(plan.getStatus())
+ ? aiDiagnosisPlanService.calculateNextRunTime(plan.getCronExpr(), new Date())
+ : null;
+ boolean acquired = aiDiagnosisPlanService.acquireForExecution(
+ plan.getId(),
+ getLoginUserId(),
+ "鎵嬪姩鎵ц涓�",
+ nextRunTime
+ );
+ if (!acquired) {
+ return R.error("璁″垝姝e湪鎵ц涓�");
+ }
+ taskScheduler.execute(() -> aiDiagnosisPlanRunnerService.runPlan(plan.getId(), true));
+ return R.ok();
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosticToolConfigController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosticToolConfigController.java
new file mode 100644
index 0000000..c6c7629
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiDiagnosticToolConfigController.java
@@ -0,0 +1,156 @@
+package com.vincent.rsf.server.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.framework.common.SnowflakeIdWorker;
+import com.vincent.rsf.server.common.annotation.OperationLog;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.system.entity.AiDiagnosticToolConfig;
+import com.vincent.rsf.server.system.service.AiDiagnosticToolConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+public class AiDiagnosticToolConfigController extends BaseController {
+
+ @Autowired
+ private AiDiagnosticToolConfigService aiDiagnosticToolConfigService;
+ @Autowired
+ private SnowflakeIdWorker snowflakeIdWorker;
+
+ @PreAuthorize("hasAuthority('system:aiToolConfig:list')")
+ @PostMapping("/aiToolConfig/page")
+ public R page(@RequestBody Map<String, Object> map) {
+ BaseParam baseParam = buildParam(map, BaseParam.class);
+ PageParam<AiDiagnosticToolConfig, BaseParam> pageParam = new PageParam<>(baseParam, AiDiagnosticToolConfig.class);
+ com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<AiDiagnosticToolConfig> wrapper = pageParam.buildWrapper(true);
+ wrapper.eq("tenant_id", getTenantId());
+ return R.ok().add(aiDiagnosticToolConfigService.page(pageParam, wrapper));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiToolConfig:list')")
+ @GetMapping("/aiToolConfig/{id}")
+ public R get(@PathVariable("id") Long id) {
+ AiDiagnosticToolConfig config = aiDiagnosticToolConfigService.getTenantConfig(getTenantId(), id);
+ if (config == null) {
+ return R.error("config not found");
+ }
+ return R.ok().add(config);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiToolConfig:save')")
+ @OperationLog("Create AiDiagnosticToolConfig")
+ @PostMapping("/aiToolConfig/save")
+ public R save(@RequestBody AiDiagnosticToolConfig config) {
+ if (Cools.isEmpty(config.getSceneCode()) || Cools.isEmpty(config.getToolCode())) {
+ return R.error("鍦烘櫙缂栫爜鍜屽伐鍏风紪鐮佷笉鑳戒负绌�");
+ }
+ Date now = new Date();
+ config.setUuid(String.valueOf(snowflakeIdWorker.nextId()).substring(3));
+ config.setTenantId(getTenantId());
+ config.setCreateBy(getLoginUserId());
+ config.setCreateTime(now);
+ config.setUpdateBy(getLoginUserId());
+ config.setUpdateTime(now);
+ if (config.getEnabledFlag() == null) {
+ config.setEnabledFlag(1);
+ }
+ if (config.getPriority() == null) {
+ config.setPriority(1);
+ }
+ if (config.getStatus() == null) {
+ config.setStatus(1);
+ }
+ if (!aiDiagnosticToolConfigService.save(config)) {
+ return R.error("Save Fail");
+ }
+ return R.ok("Save Success").add(config);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiToolConfig:update')")
+ @OperationLog("Update AiDiagnosticToolConfig")
+ @PostMapping("/aiToolConfig/update")
+ public R update(@RequestBody AiDiagnosticToolConfig config) {
+ AiDiagnosticToolConfig existed = aiDiagnosticToolConfigService.getTenantConfig(getTenantId(), config.getId());
+ if (existed == null) {
+ return R.error("config not found");
+ }
+ config.setTenantId(getTenantId());
+ config.setUpdateBy(getLoginUserId());
+ config.setUpdateTime(new Date());
+ config.setCreateBy(existed.getCreateBy());
+ config.setCreateTime(existed.getCreateTime());
+ if (config.getEnabledFlag() == null) {
+ config.setEnabledFlag(existed.getEnabledFlag());
+ }
+ if (config.getPriority() == null) {
+ config.setPriority(existed.getPriority());
+ }
+ if (config.getStatus() == null) {
+ config.setStatus(existed.getStatus());
+ }
+ if (!aiDiagnosticToolConfigService.updateById(config)) {
+ return R.error("Update Fail");
+ }
+ return R.ok("Update Success").add(config);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiToolConfig:remove')")
+ @OperationLog("Delete AiDiagnosticToolConfig")
+ @PostMapping("/aiToolConfig/remove/{ids}")
+ public R remove(@PathVariable Long[] ids) {
+ List<Long> idList = Arrays.asList(ids);
+ List<AiDiagnosticToolConfig> configs = aiDiagnosticToolConfigService.list(new LambdaQueryWrapper<AiDiagnosticToolConfig>()
+ .eq(AiDiagnosticToolConfig::getTenantId, getTenantId())
+ .in(AiDiagnosticToolConfig::getId, idList));
+ if (configs.size() != idList.size() || !aiDiagnosticToolConfigService.removeByIds(idList)) {
+ return R.error("Delete Fail");
+ }
+ return R.ok("Delete Success").add(ids);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiToolConfig:list')")
+ @PostMapping("/aiToolConfig/export")
+ public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+ ExcelUtil.build(ExcelUtil.create(aiDiagnosticToolConfigService.list(new LambdaQueryWrapper<AiDiagnosticToolConfig>()
+ .eq(AiDiagnosticToolConfig::getTenantId, getTenantId())), AiDiagnosticToolConfig.class), response);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiToolConfig:list')")
+ @GetMapping("/ai/tool-config/list")
+ public R customList(@RequestParam(required = false) String sceneCode) {
+ LambdaQueryWrapper<AiDiagnosticToolConfig> wrapper = new LambdaQueryWrapper<AiDiagnosticToolConfig>()
+ .eq(AiDiagnosticToolConfig::getTenantId, getTenantId())
+ .orderByAsc(AiDiagnosticToolConfig::getSceneCode, AiDiagnosticToolConfig::getPriority, AiDiagnosticToolConfig::getId);
+ if (!Cools.isEmpty(sceneCode)) {
+ wrapper.eq(AiDiagnosticToolConfig::getSceneCode, sceneCode);
+ }
+ return R.ok().add(aiDiagnosticToolConfigService.list(wrapper));
+ }
+
+ @PreAuthorize("hasAnyAuthority('system:aiToolConfig:save','system:aiToolConfig:update')")
+ @PostMapping("/ai/tool-config/save")
+ public R customSave(@RequestBody AiDiagnosticToolConfig config) {
+ if (config.getId() == null) {
+ if (!hasAuthority("system:aiToolConfig:save")) {
+ return R.error("鏃犳柊澧炴潈闄�");
+ }
+ return save(config);
+ }
+ if (!hasAuthority("system:aiToolConfig:update")) {
+ return R.error("鏃犳洿鏂版潈闄�");
+ }
+ return update(config);
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiMcpMountController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiMcpMountController.java
new file mode 100644
index 0000000..f81e885
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiMcpMountController.java
@@ -0,0 +1,394 @@
+package com.vincent.rsf.server.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.framework.common.SnowflakeIdWorker;
+import com.vincent.rsf.server.ai.constant.AiMcpConstants;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
+import com.vincent.rsf.server.ai.service.mcp.AiMcpPayloadMapper;
+import com.vincent.rsf.server.ai.service.mcp.AiMcpRegistryService;
+import com.vincent.rsf.server.common.annotation.OperationLog;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.system.entity.AiDiagnosticToolConfig;
+import com.vincent.rsf.server.system.entity.AiMcpMount;
+import com.vincent.rsf.server.system.service.AiDiagnosticToolConfigService;
+import com.vincent.rsf.server.system.service.AiMcpMountService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+public class AiMcpMountController extends BaseController {
+
+ @Autowired
+ private AiMcpMountService aiMcpMountService;
+ @Autowired
+ private AiMcpRegistryService aiMcpRegistryService;
+ @Autowired
+ private AiDiagnosticToolConfigService aiDiagnosticToolConfigService;
+ @Autowired
+ private SnowflakeIdWorker snowflakeIdWorker;
+ @Autowired
+ private AiMcpPayloadMapper aiMcpPayloadMapper;
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+ @PostMapping("/aiMcpMount/page")
+ public R page(@RequestBody Map<String, Object> map) {
+ BaseParam baseParam = buildParam(map, BaseParam.class);
+ PageParam<AiMcpMount, BaseParam> pageParam = new PageParam<>(baseParam, AiMcpMount.class);
+ com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<AiMcpMount> wrapper = pageParam.buildWrapper(true);
+ wrapper.eq("tenant_id", getTenantId());
+ return R.ok().add(aiMcpMountService.page(pageParam, wrapper));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+ @GetMapping("/aiMcpMount/{id}")
+ public R get(@PathVariable("id") Long id) {
+ AiMcpMount mount = aiMcpMountService.getTenantMount(getTenantId(), id);
+ if (mount == null) {
+ return R.error("mount not found");
+ }
+ return R.ok().add(mount);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:save')")
+ @OperationLog("Create AiMcpMount")
+ @PostMapping("/aiMcpMount/save")
+ public R save(@RequestBody AiMcpMount mount) {
+ if (Cools.isEmpty(mount.getName()) || Cools.isEmpty(mount.getMountCode()) || Cools.isEmpty(mount.getTransportType())) {
+ return R.error("鍚嶇О銆佹寕杞界紪鐮佸拰浼犺緭绫诲瀷涓嶈兘涓虹┖");
+ }
+ Date now = new Date();
+ mount.setUuid(String.valueOf(snowflakeIdWorker.nextId()).substring(3));
+ mount.setEnabledFlag(mount.getEnabledFlag() == null ? 1 : mount.getEnabledFlag());
+ mount.setTimeoutMs(mount.getTimeoutMs() == null ? 10000 : mount.getTimeoutMs());
+ mount.setStatus(mount.getStatus() == null ? 1 : mount.getStatus());
+ mount.setTransportType(mount.getTransportType().toUpperCase());
+ if (AiMcpConstants.TRANSPORT_INTERNAL.equals(mount.getTransportType())) {
+ mount.setUrl("/ai/mcp");
+ }
+ mount.setTenantId(getTenantId());
+ mount.setCreateBy(getLoginUserId());
+ mount.setCreateTime(now);
+ mount.setUpdateBy(getLoginUserId());
+ mount.setUpdateTime(now);
+ if (!aiMcpMountService.save(mount)) {
+ return R.error("Save Fail");
+ }
+ return R.ok("Save Success").add(mount);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:update')")
+ @OperationLog("Update AiMcpMount")
+ @PostMapping("/aiMcpMount/update")
+ public R update(@RequestBody AiMcpMount mount) {
+ AiMcpMount existed = aiMcpMountService.getTenantMount(getTenantId(), mount.getId());
+ if (existed == null) {
+ return R.error("mount not found");
+ }
+ if (AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(existed.getTransportType())) {
+ return R.error("鍐呯疆鎸傝浇鐢辩郴缁熸墭绠★紝涓嶆敮鎸佺洿鎺ョ紪杈�");
+ }
+ if (Cools.isEmpty(mount.getName()) || Cools.isEmpty(mount.getMountCode()) || Cools.isEmpty(mount.getTransportType())) {
+ return R.error("鍚嶇О銆佹寕杞界紪鐮佸拰浼犺緭绫诲瀷涓嶈兘涓虹┖");
+ }
+ mount.setTransportType(mount.getTransportType().toUpperCase());
+ if (AiMcpConstants.TRANSPORT_INTERNAL.equals(mount.getTransportType())) {
+ mount.setUrl("/ai/mcp");
+ }
+ mount.setTenantId(getTenantId());
+ mount.setCreateBy(existed.getCreateBy());
+ mount.setCreateTime(existed.getCreateTime());
+ mount.setLastTestResult(existed.getLastTestResult());
+ mount.setLastTestTime(existed.getLastTestTime());
+ mount.setLastTestMessage(existed.getLastTestMessage());
+ mount.setLastToolCount(existed.getLastToolCount());
+ mount.setUpdateBy(getLoginUserId());
+ mount.setUpdateTime(new Date());
+ if (!aiMcpMountService.updateById(mount)) {
+ return R.error("Update Fail");
+ }
+ return R.ok("Update Success").add(mount);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:remove')")
+ @OperationLog("Delete AiMcpMount")
+ @PostMapping("/aiMcpMount/remove/{ids}")
+ public R remove(@PathVariable Long[] ids) {
+ List<Long> idList = Arrays.asList(ids);
+ List<AiMcpMount> mounts = aiMcpMountService.list(new LambdaQueryWrapper<AiMcpMount>()
+ .eq(AiMcpMount::getTenantId, getTenantId())
+ .in(AiMcpMount::getId, idList));
+ for (AiMcpMount mount : mounts) {
+ if (AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(mount.getTransportType())) {
+ return R.error("鍐呯疆鎸傝浇鐢辩郴缁熸墭绠★紝涓嶆敮鎸佸垹闄�");
+ }
+ }
+ if (mounts.size() != idList.size() || !aiMcpMountService.removeByIds(idList)) {
+ return R.error("Delete Fail");
+ }
+ return R.ok("Delete Success").add(ids);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+ @PostMapping("/aiMcpMount/export")
+ public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+ ExcelUtil.build(ExcelUtil.create(aiMcpMountService.list(new LambdaQueryWrapper<AiMcpMount>()
+ .eq(AiMcpMount::getTenantId, getTenantId())), AiMcpMount.class), response);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+ @GetMapping("/ai/mcp/mount/list")
+ public R list() {
+ aiMcpRegistryService.ensureDefaultMount(getTenantId(), getLoginUserId());
+ return R.ok().add(aiMcpMountService.listTenantMounts(getTenantId()));
+ }
+
+ @PreAuthorize("hasAnyAuthority('system:aiMcpMount:save','system:aiMcpMount:update')")
+ @PostMapping("/ai/mcp/mount/save")
+ public R customSave(@RequestBody AiMcpMount mount) {
+ if (mount.getId() == null) {
+ return save(mount);
+ }
+ return update(mount);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:update')")
+ @PostMapping("/ai/mcp/mount/initDefaults")
+ public R initDefaults() {
+ aiMcpRegistryService.ensureDefaultMount(getTenantId(), getLoginUserId());
+ return R.ok();
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+ @GetMapping("/ai/mcp/mount/toolList")
+ public R toolList(@RequestParam(required = false) Long mountId) {
+ return R.ok().add(aiMcpRegistryService.listTools(getTenantId(), mountId));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:update')")
+ @PostMapping("/ai/mcp/mount/test")
+ public R test(@RequestBody Map<String, Object> map) {
+ Long id = Long.valueOf(String.valueOf(map.get("id")));
+ return R.ok().add(aiMcpRegistryService.testMount(getTenantId(), id));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+ @PostMapping("/ai/mcp/mount/toolPreview")
+ public R toolPreview(@RequestBody Map<String, Object> map) {
+ return R.ok().add(aiMcpRegistryService.previewTool(
+ getTenantId(),
+ String.valueOf(map.get("mountCode")),
+ String.valueOf(map.get("toolCode")),
+ map.get("sceneCode") == null ? null : String.valueOf(map.get("sceneCode")),
+ map.get("question") == null ? null : String.valueOf(map.get("question"))
+ ));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+ @GetMapping("/ai/mcp/console/overview")
+ public R consoleOverview() {
+ aiMcpRegistryService.ensureDefaultMount(getTenantId(), getLoginUserId());
+ Map<String, Object> payload = new LinkedHashMap<>();
+ payload.put("builtInMount", null);
+ payload.put("builtInTools", new java.util.ArrayList<>());
+ payload.put("externalServices", new java.util.ArrayList<>());
+ for (AiMcpMount mount : aiMcpMountService.listTenantMounts(getTenantId())) {
+ if (AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(mount.getTransportType())) {
+ payload.put("builtInMount", buildMountView(mount, true));
+ payload.put("builtInTools", buildBuiltInToolViews());
+ } else {
+ ((List<Object>) payload.get("externalServices")).add(buildMountView(mount, false));
+ }
+ }
+ return R.ok().add(payload);
+ }
+
+ @PreAuthorize("hasAnyAuthority('system:aiMcpMount:save','system:aiMcpMount:update')")
+ @PostMapping("/ai/mcp/console/service/save")
+ public R consoleSaveService(@RequestBody AiMcpMount mount) {
+ if (Cools.isEmpty(mount.getName()) || Cools.isEmpty(mount.getUrl())) {
+ return R.error("鍚嶇О鍜屽湴鍧�涓嶈兘涓虹┖");
+ }
+ Date now = new Date();
+ AiMcpMount existed = mount.getId() == null ? null : aiMcpMountService.getTenantMount(getTenantId(), mount.getId());
+ if (existed != null && AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(existed.getTransportType())) {
+ return R.error("鍐呯疆鎸傝浇鐢辩郴缁熸墭绠★紝涓嶆敮鎸佺紪杈�");
+ }
+ String transportType = Cools.isEmpty(mount.getTransportType()) ? AiMcpConstants.TRANSPORT_AUTO : mount.getTransportType().toUpperCase();
+ String usageScope = normalizeUsageScope(mount.getUsageScope());
+ AiMcpMount target = existed == null ? new AiMcpMount() : existed;
+ target.setName(mount.getName());
+ target.setUrl(mount.getUrl());
+ target.setTransportType(transportType);
+ target.setAuthType(Cools.isEmpty(mount.getAuthType()) ? AiMcpConstants.AUTH_TYPE_NONE : mount.getAuthType().toUpperCase());
+ target.setAuthValue(mount.getAuthValue());
+ target.setUsageScope(usageScope);
+ target.setEnabledFlag(mount.getEnabledFlag() == null ? 1 : mount.getEnabledFlag());
+ target.setTimeoutMs(mount.getTimeoutMs() == null ? 10000 : mount.getTimeoutMs());
+ target.setStatus(mount.getStatus() == null ? 1 : mount.getStatus());
+ target.setMemo(mount.getMemo());
+ target.setTenantId(getTenantId());
+ target.setUpdateBy(getLoginUserId());
+ target.setUpdateTime(now);
+ if (existed == null) {
+ target.setUuid(String.valueOf(snowflakeIdWorker.nextId()).substring(3));
+ target.setMountCode(generateMountCode(target.getName()));
+ target.setCreateBy(getLoginUserId());
+ target.setCreateTime(now);
+ if (!aiMcpMountService.save(target)) {
+ return R.error("Save Fail");
+ }
+ } else if (!aiMcpMountService.updateById(target)) {
+ return R.error("Update Fail");
+ }
+ return R.ok().add(buildMountView(target, false));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:update')")
+ @PostMapping("/ai/mcp/console/service/test")
+ public R consoleTestService(@RequestBody Map<String, Object> map) {
+ Long id = Long.valueOf(String.valueOf(map.get("id")));
+ Map<String, Object> result = (Map<String, Object>) aiMcpRegistryService.testMount(getTenantId(), id);
+ AiMcpMount mount = aiMcpMountService.getTenantMount(getTenantId(), id);
+ if (mount == null) {
+ return R.error("mount not found");
+ }
+ result.put("service", buildMountView(mount, false));
+ return R.ok().add(result);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiMcpMount:remove')")
+ @PostMapping("/ai/mcp/console/service/remove/{id}")
+ public R consoleRemoveService(@PathVariable("id") Long id) {
+ AiMcpMount mount = aiMcpMountService.getTenantMount(getTenantId(), id);
+ if (mount == null) {
+ return R.error("mount not found");
+ }
+ if (AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(mount.getTransportType())) {
+ return R.error("鍐呯疆鎸傝浇鐢辩郴缁熸墭绠★紝涓嶆敮鎸佸垹闄�");
+ }
+ return aiMcpMountService.removeById(id) ? R.ok() : R.error("Delete Fail");
+ }
+
+ @PreAuthorize("hasAnyAuthority('system:aiToolConfig:save','system:aiToolConfig:update')")
+ @PostMapping("/ai/mcp/console/builtin-tool/save")
+ public R consoleSaveBuiltInTool(@RequestBody AiDiagnosticToolConfig config) {
+ if (Cools.isEmpty(config.getToolCode())) {
+ return R.error("宸ュ叿缂栫爜涓嶈兘涓虹┖");
+ }
+ String usageScope = aiMcpPayloadMapper.normalizeUsageScope(config.getUsageScope());
+ String sceneCode = resolveSceneCodeByUsageScope(usageScope);
+ LambdaQueryWrapper<AiDiagnosticToolConfig> wrapper = new LambdaQueryWrapper<AiDiagnosticToolConfig>()
+ .eq(AiDiagnosticToolConfig::getTenantId, getTenantId())
+ .eq(AiDiagnosticToolConfig::getToolCode, config.getToolCode());
+ if (Cools.isEmpty(sceneCode)) {
+ wrapper.and(w -> w.isNull(AiDiagnosticToolConfig::getSceneCode).or().eq(AiDiagnosticToolConfig::getSceneCode, ""));
+ } else {
+ wrapper.eq(AiDiagnosticToolConfig::getSceneCode, sceneCode);
+ }
+ AiDiagnosticToolConfig existed = aiDiagnosticToolConfigService.getOne(wrapper.last("limit 1"));
+ Date now = new Date();
+ AiDiagnosticToolConfig target = existed == null ? new AiDiagnosticToolConfig() : existed;
+ if (existed == null) {
+ target.setUuid(String.valueOf(snowflakeIdWorker.nextId()).substring(3));
+ target.setCreateBy(getLoginUserId());
+ target.setCreateTime(now);
+ }
+ target
+ .setToolCode(config.getToolCode())
+ .setToolName(config.getToolName())
+ .setSceneCode(sceneCode)
+ .setUsageScope(usageScope)
+ .setEnabledFlag(AiMcpConstants.USAGE_SCOPE_DISABLED.equals(usageScope) ? 0 : (config.getEnabledFlag() == null ? 1 : config.getEnabledFlag()))
+ .setPriority(config.getPriority() == null ? 10 : config.getPriority())
+ .setToolPrompt(config.getToolPrompt())
+ .setStatus(config.getStatus() == null ? 1 : config.getStatus())
+ .setTenantId(getTenantId())
+ .setUpdateBy(getLoginUserId())
+ .setUpdateTime(now)
+ .setMemo(config.getMemo());
+ if (existed == null) {
+ aiDiagnosticToolConfigService.save(target);
+ } else {
+ aiDiagnosticToolConfigService.updateById(target);
+ }
+ return R.ok().add(buildBuiltInToolViews());
+ }
+
+ private List<Map<String, Object>> buildBuiltInToolViews() {
+ List<Map<String, Object>> output = new java.util.ArrayList<>();
+ for (com.vincent.rsf.server.ai.model.AiMcpToolDescriptor descriptor : aiMcpRegistryService.listInternalTools(getTenantId())) {
+ Map<String, Object> item = new LinkedHashMap<>();
+ item.put("toolCode", descriptor.getToolCode());
+ item.put("toolName", descriptor.getToolName());
+ item.put("description", descriptor.getDescription());
+ item.put("enabledFlag", descriptor.getEnabledFlag());
+ item.put("priority", descriptor.getPriority());
+ item.put("toolPrompt", descriptor.getToolPrompt());
+ item.put("usageScope", aiMcpPayloadMapper.resolveUsageScope(descriptor.getSceneCode(), descriptor.getEnabledFlag(), descriptor.getUsageScope()));
+ item.put("advanced", buildAdvancedFlag(descriptor));
+ output.add(item);
+ }
+ return output;
+ }
+
+ private Map<String, Object> buildMountView(AiMcpMount mount, boolean internalManaged) {
+ Map<String, Object> item = new LinkedHashMap<>();
+ item.put("id", mount.getId());
+ item.put("name", mount.getName());
+ item.put("mountCode", mount.getMountCode());
+ item.put("transportType", mount.getTransportType());
+ item.put("displayTransportType", AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(mount.getTransportType()) ? "鍐呯疆" : mount.getTransportType());
+ item.put("url", mount.getUrl());
+ item.put("enabledFlag", mount.getEnabledFlag());
+ item.put("timeoutMs", mount.getTimeoutMs());
+ item.put("lastTestResult", mount.getLastTestResult());
+ item.put("lastTestResult$", mount.getLastTestResult$());
+ item.put("lastTestTime", mount.getLastTestTime$());
+ item.put("lastTestMessage", mount.getLastTestMessage());
+ item.put("lastToolCount", mount.getLastToolCount());
+ item.put("authType", mount.getAuthType());
+ item.put("hasAuth", !Cools.isEmpty(mount.getAuthValue()));
+ item.put("usageScope", normalizeUsageScope(mount.getUsageScope()));
+ item.put("internalManaged", internalManaged);
+ return item;
+ }
+
+ private boolean buildAdvancedFlag(com.vincent.rsf.server.ai.model.AiMcpToolDescriptor descriptor) {
+ return descriptor.getPriority() != null && descriptor.getPriority() != 10
+ || (descriptor.getToolPrompt() != null && !descriptor.getToolPrompt().trim().isEmpty());
+ }
+
+ private String normalizeUsageScope(String usageScope) {
+ return aiMcpPayloadMapper.normalizeUsageScope(usageScope);
+ }
+
+ private String resolveUsageScope(String sceneCode, Integer enabledFlag) {
+ return aiMcpPayloadMapper.resolveUsageScope(sceneCode, enabledFlag, null);
+ }
+
+ private String resolveSceneCodeByUsageScope(String usageScope) {
+ return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY.equals(usageScope) ? AiSceneCode.SYSTEM_DIAGNOSE : "";
+ }
+
+ private String generateMountCode(String name) {
+ String normalized = name == null ? "remote_mcp" : name.toLowerCase().replaceAll("[^a-z0-9]+", "_");
+ normalized = normalized.replaceAll("^_+|_+$", "");
+ if (normalized.isEmpty()) {
+ normalized = "remote_mcp";
+ }
+ return normalized + "_" + String.valueOf(System.currentTimeMillis()).substring(7);
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiParamController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiParamController.java
index badcaec..bf5abf4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiParamController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiParamController.java
@@ -136,3 +136,4 @@
ExcelUtil.build(ExcelUtil.create(aiParamService.list(), AiParam.class), response);
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiPromptController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiPromptController.java
new file mode 100644
index 0000000..5760719
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiPromptController.java
@@ -0,0 +1,233 @@
+package com.vincent.rsf.server.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.framework.common.SnowflakeIdWorker;
+import com.vincent.rsf.server.common.annotation.OperationLog;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.system.entity.AiPromptPublishLog;
+import com.vincent.rsf.server.system.entity.AiPromptTemplate;
+import com.vincent.rsf.server.system.service.AiPromptPublishLogService;
+import com.vincent.rsf.server.system.service.AiPromptTemplateService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+public class AiPromptController extends BaseController {
+
+ @Autowired
+ private AiPromptTemplateService aiPromptTemplateService;
+ @Autowired
+ private AiPromptPublishLogService aiPromptPublishLogService;
+ @Autowired
+ private SnowflakeIdWorker snowflakeIdWorker;
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+ @PostMapping("/aiPrompt/page")
+ public R page(@RequestBody Map<String, Object> map) {
+ BaseParam baseParam = buildParam(map, BaseParam.class);
+ PageParam<AiPromptTemplate, BaseParam> pageParam = new PageParam<>(baseParam, AiPromptTemplate.class);
+ com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<AiPromptTemplate> wrapper = pageParam.buildWrapper(true);
+ wrapper.eq("tenant_id", getTenantId());
+ return R.ok().add(aiPromptTemplateService.page(pageParam, wrapper));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+ @PostMapping("/aiPrompt/list")
+ public R list(@RequestBody(required = false) Map<String, Object> map) {
+ return R.ok().add(aiPromptTemplateService.list(new LambdaQueryWrapper<AiPromptTemplate>()
+ .eq(AiPromptTemplate::getTenantId, getTenantId())
+ .orderByAsc(AiPromptTemplate::getSceneCode)
+ .orderByDesc(AiPromptTemplate::getVersionNo, AiPromptTemplate::getId)));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+ @PostMapping({"/aiPrompt/many/{ids}", "/aiPrompts/many/{ids}"})
+ public R many(@PathVariable Long[] ids) {
+ return R.ok().add(aiPromptTemplateService.list(new LambdaQueryWrapper<AiPromptTemplate>()
+ .eq(AiPromptTemplate::getTenantId, getTenantId())
+ .in(AiPromptTemplate::getId, Arrays.asList(ids))));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+ @GetMapping("/aiPrompt/{id}")
+ public R get(@PathVariable("id") Long id) {
+ AiPromptTemplate promptTemplate = aiPromptTemplateService.getTenantTemplate(getTenantId(), id);
+ if (promptTemplate == null) {
+ return R.error("record not found");
+ }
+ return R.ok().add(promptTemplate);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:save')")
+ @OperationLog("Create AiPrompt")
+ @PostMapping("/aiPrompt/save")
+ public R save(@RequestBody AiPromptTemplate aiPromptTemplate) {
+ if (Cools.isEmpty(aiPromptTemplate.getSceneCode())) {
+ return R.error("鍦烘櫙缂栫爜涓嶈兘涓虹┖");
+ }
+ Date now = new Date();
+ aiPromptTemplate.setUuid(String.valueOf(snowflakeIdWorker.nextId()).substring(3));
+ aiPromptTemplate.setTenantId(getTenantId());
+ aiPromptTemplate.setCreateBy(getLoginUserId());
+ aiPromptTemplate.setCreateTime(now);
+ aiPromptTemplate.setUpdateBy(getLoginUserId());
+ aiPromptTemplate.setUpdateTime(now);
+ aiPromptTemplate.setVersionNo(aiPromptTemplate.getVersionNo() == null
+ ? aiPromptTemplateService.nextVersionNo(getTenantId(), aiPromptTemplate.getSceneCode())
+ : aiPromptTemplate.getVersionNo());
+ if (aiPromptTemplate.getPublishedFlag() == null) {
+ aiPromptTemplate.setPublishedFlag(0);
+ }
+ if (aiPromptTemplate.getStatus() == null) {
+ aiPromptTemplate.setStatus(1);
+ }
+ if (!aiPromptTemplateService.save(aiPromptTemplate)) {
+ return R.error("Save Fail");
+ }
+ return R.ok("Save Success").add(aiPromptTemplate);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:update')")
+ @OperationLog("Update AiPrompt")
+ @PostMapping("/aiPrompt/update")
+ public R update(@RequestBody AiPromptTemplate aiPromptTemplate) {
+ AiPromptTemplate existed = aiPromptTemplateService.getTenantTemplate(getTenantId(), aiPromptTemplate.getId());
+ if (existed == null) {
+ return R.error("record not found");
+ }
+ aiPromptTemplate.setTenantId(getTenantId());
+ aiPromptTemplate.setUpdateBy(getLoginUserId());
+ aiPromptTemplate.setUpdateTime(new Date());
+ aiPromptTemplate.setCreateBy(existed.getCreateBy());
+ aiPromptTemplate.setCreateTime(existed.getCreateTime());
+ if (!aiPromptTemplateService.updateById(aiPromptTemplate)) {
+ return R.error("Update Fail");
+ }
+ return R.ok("Update Success").add(aiPromptTemplate);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:remove')")
+ @OperationLog("Delete AiPrompt")
+ @PostMapping("/aiPrompt/remove/{ids}")
+ public R remove(@PathVariable Long[] ids) {
+ List<Long> idList = Arrays.asList(ids);
+ List<AiPromptTemplate> records = aiPromptTemplateService.list(new LambdaQueryWrapper<AiPromptTemplate>()
+ .eq(AiPromptTemplate::getTenantId, getTenantId())
+ .in(AiPromptTemplate::getId, idList));
+ if (records.size() != idList.size() || !aiPromptTemplateService.removeByIds(idList)) {
+ return R.error("Delete Fail");
+ }
+ return R.ok("Delete Success").add(ids);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+ @PostMapping("/aiPrompt/export")
+ public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+ ExcelUtil.build(ExcelUtil.create(aiPromptTemplateService.list(new LambdaQueryWrapper<AiPromptTemplate>()
+ .eq(AiPromptTemplate::getTenantId, getTenantId())), AiPromptTemplate.class), response);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+ @GetMapping("/ai/prompt/list")
+ public R customList(@RequestParam(required = false) String sceneCode) {
+ LambdaQueryWrapper<AiPromptTemplate> wrapper = new LambdaQueryWrapper<AiPromptTemplate>()
+ .eq(AiPromptTemplate::getTenantId, getTenantId())
+ .orderByAsc(AiPromptTemplate::getSceneCode)
+ .orderByDesc(AiPromptTemplate::getVersionNo, AiPromptTemplate::getId);
+ if (!Cools.isEmpty(sceneCode)) {
+ wrapper.eq(AiPromptTemplate::getSceneCode, sceneCode);
+ }
+ return R.ok().add(aiPromptTemplateService.list(wrapper));
+ }
+
+ @PreAuthorize("hasAnyAuthority('system:aiPrompt:save','system:aiPrompt:update')")
+ @PostMapping("/ai/prompt/save")
+ public R customSave(@RequestBody AiPromptTemplate aiPromptTemplate) {
+ if (aiPromptTemplate.getId() == null) {
+ if (!hasAuthority("system:aiPrompt:save")) {
+ return R.error("鏃犳柊澧炴潈闄�");
+ }
+ return save(aiPromptTemplate);
+ }
+ if (!hasAuthority("system:aiPrompt:update")) {
+ return R.error("鏃犳洿鏂版潈闄�");
+ }
+ return update(aiPromptTemplate);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:publish')")
+ @OperationLog("Publish AiPrompt")
+ @PostMapping("/ai/prompt/publish")
+ public R publish(@RequestBody Map<String, Object> map) {
+ Long id = Long.valueOf(String.valueOf(map.get("id")));
+ if (!aiPromptTemplateService.publishTemplate(getTenantId(), id, getLoginUserId())) {
+ return R.error("record not found");
+ }
+ return R.ok();
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+ @GetMapping("/ai/prompt/version/list")
+ public R versionList(@RequestParam String sceneCode) {
+ List<AiPromptTemplate> list = aiPromptTemplateService.listVersions(getTenantId(), sceneCode);
+ return R.ok().add(list);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+ @GetMapping("/ai/prompt/publish-log/list")
+ public R publishLogList(@RequestParam(required = false) String sceneCode) {
+ List<AiPromptPublishLog> list = aiPromptPublishLogService.listSceneLogs(getTenantId(), sceneCode);
+ return R.ok().add(list);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:save')")
+ @OperationLog("Copy AiPrompt")
+ @PostMapping("/ai/prompt/copy")
+ public R copy(@RequestBody Map<String, Object> map) {
+ Long id = Long.valueOf(String.valueOf(map.get("id")));
+ AiPromptTemplate copied = aiPromptTemplateService.copyTemplate(getTenantId(), id, getLoginUserId());
+ if (copied == null) {
+ return R.error("record not found");
+ }
+ return R.ok().add(copied);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+ @GetMapping("/ai/prompt/compare")
+ public R compare(@RequestParam Long leftId, @RequestParam Long rightId) {
+ AiPromptTemplate left = aiPromptTemplateService.getTenantTemplate(getTenantId(), leftId);
+ AiPromptTemplate right = aiPromptTemplateService.getTenantTemplate(getTenantId(), rightId);
+ if (left == null || right == null) {
+ return R.error("record not found");
+ }
+ Map<String, Object> result = new LinkedHashMap<>();
+ result.put("left", left);
+ result.put("right", right);
+ return R.ok().add(result);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiPrompt:publish')")
+ @OperationLog("Rollback AiPrompt")
+ @PostMapping("/ai/prompt/rollback")
+ public R rollback(@RequestBody Map<String, Object> map) {
+ Long id = Long.valueOf(String.valueOf(map.get("id")));
+ if (!aiPromptTemplateService.rollbackTemplate(getTenantId(), id, getLoginUserId())) {
+ return R.error("record not found");
+ }
+ return R.ok();
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiRouteController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiRouteController.java
new file mode 100644
index 0000000..354e5f8
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AiRouteController.java
@@ -0,0 +1,183 @@
+package com.vincent.rsf.server.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.framework.common.SnowflakeIdWorker;
+import com.vincent.rsf.server.ai.service.AiModelRouteRuntimeService;
+import com.vincent.rsf.server.common.annotation.OperationLog;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.system.entity.AiModelRoute;
+import com.vincent.rsf.server.system.service.AiModelRouteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+public class AiRouteController extends BaseController {
+
+ @Autowired
+ private AiModelRouteService aiModelRouteService;
+ @Autowired
+ private AiModelRouteRuntimeService aiModelRouteRuntimeService;
+ @Autowired
+ private SnowflakeIdWorker snowflakeIdWorker;
+
+ @PreAuthorize("hasAuthority('system:aiRoute:list')")
+ @PostMapping("/aiRoute/page")
+ public R page(@RequestBody Map<String, Object> map) {
+ BaseParam baseParam = buildParam(map, BaseParam.class);
+ PageParam<AiModelRoute, BaseParam> pageParam = new PageParam<>(baseParam, AiModelRoute.class);
+ com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<AiModelRoute> wrapper = pageParam.buildWrapper(true);
+ wrapper.eq("tenant_id", getTenantId());
+ return R.ok().add(aiModelRouteService.page(pageParam, wrapper));
+ }
+
+ @PreAuthorize("hasAuthority('system:aiRoute:list')")
+ @GetMapping("/aiRoute/{id}")
+ public R get(@PathVariable("id") Long id) {
+ AiModelRoute route = aiModelRouteService.getTenantRoute(getTenantId(), id);
+ if (route == null) {
+ return R.error("route not found");
+ }
+ return R.ok().add(route);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiRoute:save')")
+ @OperationLog("Create AiRoute")
+ @PostMapping("/aiRoute/save")
+ public R save(@RequestBody AiModelRoute aiModelRoute) {
+ if (Cools.isEmpty(aiModelRoute.getRouteCode()) || Cools.isEmpty(aiModelRoute.getModelCode())) {
+ return R.error("璺敱缂栫爜鍜屾ā鍨嬬紪鐮佷笉鑳戒负绌�");
+ }
+ Date now = new Date();
+ aiModelRoute.setUuid(String.valueOf(snowflakeIdWorker.nextId()).substring(3));
+ aiModelRoute.setTenantId(getTenantId());
+ aiModelRoute.setCreateBy(getLoginUserId());
+ aiModelRoute.setCreateTime(now);
+ aiModelRoute.setUpdateBy(getLoginUserId());
+ aiModelRoute.setUpdateTime(now);
+ if (aiModelRoute.getPriority() == null) {
+ aiModelRoute.setPriority(1);
+ }
+ if (aiModelRoute.getStatus() == null) {
+ aiModelRoute.setStatus(1);
+ }
+ if (aiModelRoute.getFailCount() == null) {
+ aiModelRoute.setFailCount(0);
+ }
+ if (aiModelRoute.getSuccessCount() == null) {
+ aiModelRoute.setSuccessCount(0);
+ }
+ if (!aiModelRouteService.save(aiModelRoute)) {
+ return R.error("Save Fail");
+ }
+ return R.ok("Save Success").add(aiModelRoute);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiRoute:update')")
+ @OperationLog("Update AiRoute")
+ @PostMapping("/aiRoute/update")
+ public R update(@RequestBody AiModelRoute aiModelRoute) {
+ AiModelRoute existed = aiModelRouteService.getTenantRoute(getTenantId(), aiModelRoute.getId());
+ if (existed == null) {
+ return R.error("route not found");
+ }
+ aiModelRoute.setTenantId(getTenantId());
+ aiModelRoute.setUpdateBy(getLoginUserId());
+ aiModelRoute.setUpdateTime(new Date());
+ aiModelRoute.setCreateBy(existed.getCreateBy());
+ aiModelRoute.setCreateTime(existed.getCreateTime());
+ if (!aiModelRouteService.updateById(aiModelRoute)) {
+ return R.error("Update Fail");
+ }
+ return R.ok("Update Success").add(aiModelRoute);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiRoute:remove')")
+ @OperationLog("Delete AiRoute")
+ @PostMapping("/aiRoute/remove/{ids}")
+ public R remove(@PathVariable Long[] ids) {
+ List<Long> idList = Arrays.asList(ids);
+ List<AiModelRoute> routes = aiModelRouteService.list(new LambdaQueryWrapper<AiModelRoute>()
+ .eq(AiModelRoute::getTenantId, getTenantId())
+ .in(AiModelRoute::getId, idList));
+ if (routes.size() != idList.size() || !aiModelRouteService.removeByIds(idList)) {
+ return R.error("Delete Fail");
+ }
+ return R.ok("Delete Success").add(ids);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiRoute:list')")
+ @PostMapping("/aiRoute/export")
+ public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+ ExcelUtil.build(ExcelUtil.create(aiModelRouteService.list(new LambdaQueryWrapper<AiModelRoute>()
+ .eq(AiModelRoute::getTenantId, getTenantId())), AiModelRoute.class), response);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiRoute:list')")
+ @GetMapping("/ai/route/list")
+ public R customList(@RequestParam(required = false) String routeCode) {
+ LambdaQueryWrapper<AiModelRoute> wrapper = new LambdaQueryWrapper<AiModelRoute>()
+ .eq(AiModelRoute::getTenantId, getTenantId())
+ .orderByAsc(AiModelRoute::getRouteCode, AiModelRoute::getPriority, AiModelRoute::getId);
+ if (!Cools.isEmpty(routeCode)) {
+ wrapper.eq(AiModelRoute::getRouteCode, routeCode);
+ }
+ return R.ok().add(aiModelRouteService.list(wrapper));
+ }
+
+ @PreAuthorize("hasAnyAuthority('system:aiRoute:save','system:aiRoute:update')")
+ @PostMapping("/ai/route/save")
+ public R customSave(@RequestBody AiModelRoute aiModelRoute) {
+ if (aiModelRoute.getId() == null) {
+ if (!hasAuthority("system:aiRoute:save")) {
+ return R.error("鏃犳柊澧炴潈闄�");
+ }
+ return save(aiModelRoute);
+ }
+ if (!hasAuthority("system:aiRoute:update")) {
+ return R.error("鏃犳洿鏂版潈闄�");
+ }
+ return update(aiModelRoute);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiRoute:update')")
+ @OperationLog("Toggle AiRoute")
+ @PostMapping("/ai/route/toggle")
+ public R toggle(@RequestBody Map<String, Object> map) {
+ Long id = Long.valueOf(String.valueOf(map.get("id")));
+ Integer status = Integer.valueOf(String.valueOf(map.get("status")));
+ AiModelRoute route = aiModelRouteService.getTenantRoute(getTenantId(), id);
+ if (route == null) {
+ return R.error("route not found");
+ }
+ route.setStatus(status);
+ route.setUpdateBy(getLoginUserId());
+ route.setUpdateTime(new Date());
+ aiModelRouteService.updateById(route);
+ return R.ok().add(route);
+ }
+
+ @PreAuthorize("hasAuthority('system:aiRoute:update')")
+ @OperationLog("Reset AiRoute")
+ @PostMapping("/ai/route/reset")
+ public R reset(@RequestBody Map<String, Object> map) {
+ Long id = Long.valueOf(String.valueOf(map.get("id")));
+ if (aiModelRouteService.getTenantRoute(getTenantId(), id) == null) {
+ return R.error("route not found");
+ }
+ aiModelRouteRuntimeService.resetRoute(id);
+ return R.ok();
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AuthController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AuthController.java
index 09e34f3..42158ed 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AuthController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AuthController.java
@@ -252,3 +252,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/BaseController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/BaseController.java
index 23d1a98..abd9cef 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/BaseController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/BaseController.java
@@ -5,6 +5,7 @@
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.system.entity.User;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.List;
@@ -39,6 +40,26 @@
public Long getTenantId() {
User loginUser = getLoginUser();
return loginUser == null ? null : loginUser.getTenantId();
+ }
+
+ public boolean hasAuthority(String authority) {
+ if (authority == null || authority.trim().isEmpty()) {
+ return false;
+ }
+ try {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (authentication == null || authentication.getAuthorities() == null) {
+ return false;
+ }
+ for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
+ if (grantedAuthority != null && authority.equals(grantedAuthority.getAuthority())) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ }
+ return false;
}
public <T extends BaseParam> T buildParam(Map<String, Object> map, Class<T> clz) {
@@ -85,3 +106,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/ConfigController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/ConfigController.java
index cdeacb5..b3cac37 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/ConfigController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/ConfigController.java
@@ -154,3 +154,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DeptController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DeptController.java
index c9e1532..5ac2d13 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DeptController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DeptController.java
@@ -151,3 +151,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DictDataController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DictDataController.java
index f450eb6..c59f89d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DictDataController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DictDataController.java
@@ -119,3 +119,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DictTypeController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DictTypeController.java
index a705dd9..406112e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DictTypeController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/DictTypeController.java
@@ -121,3 +121,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FieldsController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FieldsController.java
index e52f73f..aa3f0b8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FieldsController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FieldsController.java
@@ -118,3 +118,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FieldsItemController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FieldsItemController.java
index 5d798c4..d11a47b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FieldsItemController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FieldsItemController.java
@@ -108,3 +108,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowInstanceController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowInstanceController.java
index 829696d..90ef901 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowInstanceController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowInstanceController.java
@@ -105,3 +105,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepInstanceController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepInstanceController.java
index 88a921c..9bdb720 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepInstanceController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepInstanceController.java
@@ -115,3 +115,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepLogController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepLogController.java
index 6115b68..140a3bf 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepLogController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepLogController.java
@@ -103,3 +103,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepTemplateController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepTemplateController.java
index 0b27bd8..2c58660 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepTemplateController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepTemplateController.java
@@ -99,3 +99,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/HostController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/HostController.java
index 1f55fd0..27ce8de 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/HostController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/HostController.java
@@ -105,3 +105,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MatnrRoleMenuController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MatnrRoleMenuController.java
index 9389353..e3bf25e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MatnrRoleMenuController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MatnrRoleMenuController.java
@@ -66,3 +66,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MenuController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MenuController.java
index 5752131..42a4f05 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MenuController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MenuController.java
@@ -243,3 +243,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/OperationRecordController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/OperationRecordController.java
index fee2e7f..cd6b5d3 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/OperationRecordController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/OperationRecordController.java
@@ -85,3 +85,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/PdaRoleMenuController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/PdaRoleMenuController.java
index 325b1b4..3c2ce26 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/PdaRoleMenuController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/PdaRoleMenuController.java
@@ -56,3 +56,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/RoleController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/RoleController.java
index fb5bcac..e5107af 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/RoleController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/RoleController.java
@@ -171,3 +171,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SerialRuleController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SerialRuleController.java
index c12d61e..3c83ab0 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SerialRuleController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SerialRuleController.java
@@ -108,3 +108,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SerialRuleItemController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SerialRuleItemController.java
index 411720e..e7bbce4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SerialRuleItemController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SerialRuleItemController.java
@@ -108,3 +108,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SubsystemFlowTemplateController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SubsystemFlowTemplateController.java
index 60d3578..6d26dce 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SubsystemFlowTemplateController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/SubsystemFlowTemplateController.java
@@ -99,3 +99,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskInstanceController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskInstanceController.java
index 2692323..f4b6aa4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskInstanceController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskInstanceController.java
@@ -108,3 +108,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskInstanceNodeController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskInstanceNodeController.java
index c630076..abbb42e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskInstanceNodeController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskInstanceNodeController.java
@@ -105,3 +105,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateController.java
index 4a05c7e..1bdebb9 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateController.java
@@ -108,3 +108,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateMergeController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateMergeController.java
index 624cc6d..86b0b49 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateMergeController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateMergeController.java
@@ -130,3 +130,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateNodeController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateNodeController.java
index 3d1f6f6..23f2fc7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateNodeController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateNodeController.java
@@ -108,3 +108,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TenantController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TenantController.java
index 8388caa..86b77e8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TenantController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TenantController.java
@@ -113,3 +113,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/UserController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/UserController.java
index 9f2f7fd..6d1744d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/UserController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/UserController.java
@@ -214,3 +214,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/UserLoginController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/UserLoginController.java
index 423b806..ea390c8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/UserLoginController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/UserLoginController.java
@@ -102,3 +102,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/WarehouseRoleMenuController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/WarehouseRoleMenuController.java
index 7c548f0..51ffba4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/WarehouseRoleMenuController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/WarehouseRoleMenuController.java
@@ -111,3 +111,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/LoginParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/LoginParam.java
index 3e2c437..89c30a9 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/LoginParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/LoginParam.java
@@ -24,3 +24,4 @@
private Long tenantId;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/RegisterParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/RegisterParam.java
index 351d82d..3f3d68c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/RegisterParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/RegisterParam.java
@@ -21,3 +21,4 @@
private String code;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/RoleScopeParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/RoleScopeParam.java
index 6ce2a63..b695b52 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/RoleScopeParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/RoleScopeParam.java
@@ -24,3 +24,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/TenantInitParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/TenantInitParam.java
index af77d0a..c64b205 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/TenantInitParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/TenantInitParam.java
@@ -18,3 +18,4 @@
private String memo;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/UpdatePasswordParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/UpdatePasswordParam.java
index e8f25b5..49279f9 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/UpdatePasswordParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/param/UpdatePasswordParam.java
@@ -14,3 +14,4 @@
private String newPassword;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/LoginResult.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/LoginResult.java
index 767f2e6..e2a522d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/LoginResult.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/LoginResult.java
@@ -24,3 +24,4 @@
private String tenant;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/MenuVo.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/MenuVo.java
index afec968..c820cb7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/MenuVo.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/MenuVo.java
@@ -52,3 +52,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/SystemInfoVo.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/SystemInfoVo.java
index 7643ee8..711c569 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/SystemInfoVo.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/result/SystemInfoVo.java
@@ -16,3 +16,4 @@
private String mode;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiCallLog.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiCallLog.java
new file mode 100644
index 0000000..d9d9faf
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiCallLog.java
@@ -0,0 +1,124 @@
+package com.vincent.rsf.server.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.SpringUtils;
+import com.vincent.rsf.server.system.service.TenantService;
+import com.vincent.rsf.server.system.service.UserService;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_call_log")
+public class AiCallLog implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "ID")
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty(value = "浼氳瘽ID")
+ private String sessionId;
+
+ @ApiModelProperty(value = "璇婃柇璁板綍ID")
+ private Long diagnosisId;
+
+ @ApiModelProperty(value = "璺敱缂栫爜")
+ private String routeCode;
+
+ @ApiModelProperty(value = "妯″瀷缂栫爜")
+ private String modelCode;
+
+ @ApiModelProperty(value = "灏濊瘯搴忓彿")
+ private Integer attemptNo;
+
+ @ApiModelProperty(value = "璇锋眰鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date requestTime;
+
+ @ApiModelProperty(value = "鍝嶅簲鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date responseTime;
+
+ @ApiModelProperty(value = "鑰楁椂姣")
+ private Long spendTime;
+
+ @ApiModelProperty(value = "缁撴灉 1:鎴愬姛 0:澶辫触")
+ private Integer result;
+
+ @ApiModelProperty(value = "閿欒淇℃伅")
+ private String err;
+
+ @ApiModelProperty(value = "鐘舵�� 1:姝e父 0:鍐荤粨")
+ private Integer status;
+
+ @ApiModelProperty(value = "鏄惁鍒犻櫎 1:鏄� 0:鍚�")
+ @TableLogic
+ private Integer deleted;
+
+ @ApiModelProperty(value = "绉熸埛")
+ private Long tenantId;
+
+ @ApiModelProperty(value = "鐢ㄦ埛")
+ private Long userId;
+
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date createTime;
+
+ public String getResult$() {
+ if (this.result == null) {
+ return null;
+ }
+ return Integer.valueOf(1).equals(this.result) ? "鎴愬姛" : "澶辫触";
+ }
+
+ public String getUserId$() {
+ UserService service = SpringUtils.getBean(UserService.class);
+ User user = service.getById(this.userId);
+ if (!Cools.isEmpty(user)) {
+ return String.valueOf(user.getNickname());
+ }
+ return null;
+ }
+
+ public String getTenantId$() {
+ TenantService service = SpringUtils.getBean(TenantService.class);
+ Tenant tenant = service.getById(this.tenantId);
+ if (!Cools.isEmpty(tenant)) {
+ return String.valueOf(tenant.getName());
+ }
+ return null;
+ }
+
+ public String getRequestTime$() {
+ if (Cools.isEmpty(this.requestTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.requestTime);
+ }
+
+ public String getResponseTime$() {
+ if (Cools.isEmpty(this.responseTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.responseTime);
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosisPlan.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosisPlan.java
new file mode 100644
index 0000000..7e020e1
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosisPlan.java
@@ -0,0 +1,144 @@
+package com.vincent.rsf.server.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.vincent.rsf.framework.common.Cools;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_diagnosis_plan")
+public class AiDiagnosisPlan implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty("缂栧彿")
+ private String uuid;
+
+ @ApiModelProperty("璁″垝鍚嶇О")
+ private String planName;
+
+ @ApiModelProperty("鍦烘櫙缂栫爜")
+ private String sceneCode;
+
+ @ApiModelProperty("Cron琛ㄨ揪寮�")
+ private String cronExpr;
+
+ @ApiModelProperty("宸℃鎻愮ず璇�")
+ private String prompt;
+
+ @ApiModelProperty("浼樺厛妯″瀷缂栫爜")
+ private String preferredModelCode;
+
+ @ApiModelProperty("杩愯涓� 1:鏄� 0:鍚�")
+ private Integer runningFlag;
+
+ @ApiModelProperty("涓婃缁撴灉 2:杩愯涓� 1:鎴愬姛 0:澶辫触")
+ private Integer lastResult;
+
+ @ApiModelProperty("涓婃璇婃柇璁板綍ID")
+ private Long lastDiagnosisId;
+
+ @ApiModelProperty("涓婃杩愯鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date lastRunTime;
+
+ @ApiModelProperty("涓嬫杩愯鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date nextRunTime;
+
+ @ApiModelProperty("鏈�杩戞秷鎭�")
+ private String lastMessage;
+
+ @ApiModelProperty("鐘舵�� 1:姝e父 0:鍐荤粨")
+ private Integer status;
+
+ @TableLogic
+ @ApiModelProperty("鏄惁鍒犻櫎 1:鏄� 0:鍚�")
+ private Integer deleted;
+
+ private Long tenantId;
+ private Long createBy;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date createTime;
+
+ private Long updateBy;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date updateTime;
+
+ private String memo;
+
+ public Boolean getStatusBool() {
+ if (status == null) {
+ return null;
+ }
+ return Integer.valueOf(1).equals(status);
+ }
+
+ public Boolean getRunningFlagBool() {
+ if (runningFlag == null) {
+ return null;
+ }
+ return Integer.valueOf(1).equals(runningFlag);
+ }
+
+ public String getLastResult$() {
+ if (lastResult == null) {
+ return "鏈繍琛�";
+ }
+ if (Integer.valueOf(2).equals(lastResult)) {
+ return "杩愯涓�";
+ }
+ if (Integer.valueOf(1).equals(lastResult)) {
+ return "鎴愬姛";
+ }
+ if (Integer.valueOf(0).equals(lastResult)) {
+ return "澶辫触";
+ }
+ return String.valueOf(lastResult);
+ }
+
+ public String getSceneCode$() {
+ if ("system_diagnose".equals(sceneCode)) {
+ return "绯荤粺璇婃柇";
+ }
+ if ("general_chat".equals(sceneCode)) {
+ return "閫氱敤瀵硅瘽";
+ }
+ return sceneCode;
+ }
+
+ public String getLastRunTime$() {
+ if (Cools.isEmpty(lastRunTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(lastRunTime);
+ }
+
+ public String getNextRunTime$() {
+ if (Cools.isEmpty(nextRunTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(nextRunTime);
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosisRecord.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosisRecord.java
new file mode 100644
index 0000000..15a5b76
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosisRecord.java
@@ -0,0 +1,173 @@
+package com.vincent.rsf.server.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.SpringUtils;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
+import com.vincent.rsf.server.system.service.TenantService;
+import com.vincent.rsf.server.system.service.UserService;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_diagnosis_record")
+public class AiDiagnosisRecord implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "ID")
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty(value = "璇婃柇缂栧彿")
+ private String diagnosisNo;
+
+ @ApiModelProperty(value = "浼氳瘽ID")
+ private String sessionId;
+
+ @ApiModelProperty(value = "鍦烘櫙缂栫爜")
+ private String sceneCode;
+
+ @ApiModelProperty(value = "闂")
+ private String question;
+
+ @ApiModelProperty(value = "缁撹")
+ private String conclusion;
+
+ @ApiModelProperty(value = "鎶ュ憡鏍囬")
+ private String reportTitle;
+
+ @ApiModelProperty(value = "鎵ц鎽樿")
+ private String executiveSummary;
+
+ @ApiModelProperty(value = "璇佹嵁鎽樿")
+ private String evidenceSummary;
+
+ @ApiModelProperty(value = "寤鸿鍔ㄤ綔")
+ private String actionSummary;
+
+ @ApiModelProperty(value = "椋庨櫓璇勪及")
+ private String riskSummary;
+
+ @ApiModelProperty(value = "鎶ュ憡Markdown")
+ private String reportMarkdown;
+
+ @ApiModelProperty(value = "宸ュ叿鎽樿")
+ private String toolSummary;
+
+ @ApiModelProperty(value = "妯″瀷缂栫爜")
+ private String modelCode;
+
+ @ApiModelProperty(value = "缁撴灉 2:杩愯涓� 1:鎴愬姛 0:澶辫触")
+ private Integer result;
+
+ @ApiModelProperty(value = "閿欒淇℃伅")
+ private String err;
+
+ @ApiModelProperty(value = "寮�濮嬫椂闂�")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date startTime;
+
+ @ApiModelProperty(value = "缁撴潫鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date endTime;
+
+ @ApiModelProperty(value = "鑰楁椂姣")
+ private Long spendTime;
+
+ @ApiModelProperty(value = "鐘舵�� 1:姝e父 0:鍐荤粨")
+ private Integer status;
+
+ @ApiModelProperty(value = "鏄惁鍒犻櫎 1:鏄� 0:鍚�")
+ @TableLogic
+ private Integer deleted;
+
+ @ApiModelProperty(value = "绉熸埛")
+ private Long tenantId;
+
+ @ApiModelProperty(value = "鐢ㄦ埛")
+ private Long userId;
+
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date createTime;
+
+ @ApiModelProperty(value = "鏇存柊鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date updateTime;
+
+ public String getSceneCode$() {
+ if (AiSceneCode.SYSTEM_DIAGNOSE.equals(this.sceneCode)) {
+ return "绯荤粺璇婃柇";
+ }
+ if (AiSceneCode.GENERAL_CHAT.equals(this.sceneCode)) {
+ return "閫氱敤瀵硅瘽";
+ }
+ return this.sceneCode;
+ }
+
+ public String getResult$() {
+ if (this.result == null) {
+ return null;
+ }
+ if (Integer.valueOf(2).equals(this.result)) {
+ return "杩愯涓�";
+ }
+ if (Integer.valueOf(1).equals(this.result)) {
+ return "鎴愬姛";
+ }
+ if (Integer.valueOf(0).equals(this.result)) {
+ return "澶辫触";
+ }
+ return String.valueOf(this.result);
+ }
+
+ public String getUserId$() {
+ UserService service = SpringUtils.getBean(UserService.class);
+ User user = service.getById(this.userId);
+ if (!Cools.isEmpty(user)) {
+ return String.valueOf(user.getNickname());
+ }
+ return null;
+ }
+
+ public String getTenantId$() {
+ TenantService service = SpringUtils.getBean(TenantService.class);
+ Tenant tenant = service.getById(this.tenantId);
+ if (!Cools.isEmpty(tenant)) {
+ return String.valueOf(tenant.getName());
+ }
+ return null;
+ }
+
+ public String getStartTime$() {
+ if (Cools.isEmpty(this.startTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.startTime);
+ }
+
+ public String getEndTime$() {
+ if (Cools.isEmpty(this.endTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.endTime);
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosticToolConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosticToolConfig.java
new file mode 100644
index 0000000..37fa2b0
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiDiagnosticToolConfig.java
@@ -0,0 +1,85 @@
+package com.vincent.rsf.server.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_diagnostic_tool_config")
+public class AiDiagnosticToolConfig implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty("缂栧彿")
+ private String uuid;
+
+ @ApiModelProperty("鍦烘櫙缂栫爜")
+ private String sceneCode;
+
+ @ApiModelProperty("宸ュ叿缂栫爜")
+ private String toolCode;
+
+ @ApiModelProperty("宸ュ叿鍚嶇О")
+ private String toolName;
+
+ @ApiModelProperty("鍚敤 1:鏄� 0:鍚�")
+ private Integer enabledFlag;
+
+ @ApiModelProperty("浼樺厛绾�")
+ private Integer priority;
+
+ @ApiModelProperty("闄勫姞鎻愮ず璇�")
+ private String toolPrompt;
+
+ @ApiModelProperty("鐢ㄩ�旇寖鍥�")
+ private String usageScope;
+
+ @ApiModelProperty("鐘舵��")
+ private Integer status;
+
+ @TableLogic
+ private Integer deleted;
+
+ private Long tenantId;
+ private Long createBy;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date createTime;
+
+ private Long updateBy;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date updateTime;
+
+ private String memo;
+
+ public Boolean getEnabledFlagBool() {
+ if (enabledFlag == null) {
+ return null;
+ }
+ return Integer.valueOf(1).equals(enabledFlag);
+ }
+
+ public Boolean getStatusBool() {
+ if (status == null) {
+ return null;
+ }
+ return Integer.valueOf(1).equals(status);
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiMcpMount.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiMcpMount.java
new file mode 100644
index 0000000..de8da08
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiMcpMount.java
@@ -0,0 +1,118 @@
+package com.vincent.rsf.server.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.vincent.rsf.framework.common.Cools;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_mcp_mount")
+public class AiMcpMount implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty("缂栧彿")
+ private String uuid;
+
+ @ApiModelProperty("鍚嶇О")
+ private String name;
+
+ @ApiModelProperty("鎸傝浇缂栫爜")
+ private String mountCode;
+
+ @ApiModelProperty("浼犺緭绫诲瀷")
+ private String transportType;
+
+ @ApiModelProperty("鍦板潃")
+ private String url;
+
+ @ApiModelProperty("璁よ瘉鏂瑰紡")
+ private String authType;
+
+ @ApiModelProperty("璁よ瘉鍊�")
+ private String authValue;
+
+ @ApiModelProperty("鐢ㄩ�旇寖鍥�")
+ private String usageScope;
+
+ @ApiModelProperty("鍚敤 1:鏄� 0:鍚�")
+ private Integer enabledFlag;
+
+ @ApiModelProperty("瓒呮椂姣")
+ private Integer timeoutMs;
+
+ @ApiModelProperty("涓婃娴嬭瘯缁撴灉 1:鎴愬姛 0:澶辫触")
+ private Integer lastTestResult;
+
+ @ApiModelProperty("涓婃娴嬭瘯鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date lastTestTime;
+
+ @ApiModelProperty("涓婃娴嬭瘯娑堟伅")
+ private String lastTestMessage;
+
+ @ApiModelProperty("涓婃宸ュ叿鏁�")
+ private Integer lastToolCount;
+
+ @ApiModelProperty("鐘舵�� 1:姝e父 0:鍐荤粨")
+ private Integer status;
+
+ @TableLogic
+ private Integer deleted;
+
+ private Long tenantId;
+ private Long createBy;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date createTime;
+
+ private Long updateBy;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date updateTime;
+
+ private String memo;
+
+ public Boolean getEnabledFlagBool() {
+ if (enabledFlag == null) {
+ return null;
+ }
+ return Integer.valueOf(1).equals(enabledFlag);
+ }
+
+ public String getLastTestResult$() {
+ if (lastTestResult == null) {
+ return "鏈祴璇�";
+ }
+ return Integer.valueOf(1).equals(lastTestResult) ? "鎴愬姛" : "澶辫触";
+ }
+
+ public String getLastTestTime$() {
+ if (Cools.isEmpty(lastTestTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(lastTestTime);
+ }
+
+ public Boolean getInternalManaged() {
+ return "INTERNAL".equalsIgnoreCase(transportType);
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiModelRoute.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiModelRoute.java
new file mode 100644
index 0000000..129facb
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiModelRoute.java
@@ -0,0 +1,140 @@
+package com.vincent.rsf.server.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.SpringUtils;
+import com.vincent.rsf.server.system.service.TenantService;
+import com.vincent.rsf.server.system.service.UserService;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_model_route")
+public class AiModelRoute implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "ID")
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty(value = "缂栧彿")
+ private String uuid;
+
+ @ApiModelProperty(value = "璺敱缂栫爜")
+ private String routeCode;
+
+ @ApiModelProperty(value = "妯″瀷缂栫爜")
+ private String modelCode;
+
+ @ApiModelProperty(value = "浼樺厛绾�")
+ private Integer priority;
+
+ @ApiModelProperty(value = "鍐峰嵈鎴鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date cooldownUntil;
+
+ @ApiModelProperty(value = "澶辫触娆℃暟")
+ private Integer failCount;
+
+ @ApiModelProperty(value = "鎴愬姛娆℃暟")
+ private Integer successCount;
+
+ @ApiModelProperty(value = "鐘舵�� 1:鍚敤 0:鍋滅敤")
+ private Integer status;
+
+ @ApiModelProperty(value = "鏄惁鍒犻櫎 1:鏄� 0:鍚�")
+ @TableLogic
+ private Integer deleted;
+
+ @ApiModelProperty(value = "绉熸埛")
+ private Long tenantId;
+
+ @ApiModelProperty(value = "娣诲姞浜哄憳")
+ private Long createBy;
+
+ @ApiModelProperty(value = "娣诲姞鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date createTime;
+
+ @ApiModelProperty(value = "淇敼浜哄憳")
+ private Long updateBy;
+
+ @ApiModelProperty(value = "淇敼鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date updateTime;
+
+ @ApiModelProperty(value = "澶囨敞")
+ private String memo;
+
+ public Boolean getStatusBool() {
+ if (this.status == null) {
+ return null;
+ }
+ return Integer.valueOf(1).equals(this.status);
+ }
+
+ public String getCooldownUntil$() {
+ if (Cools.isEmpty(this.cooldownUntil)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.cooldownUntil);
+ }
+
+ public String getTenantId$() {
+ TenantService service = SpringUtils.getBean(TenantService.class);
+ Tenant tenant = service.getById(this.tenantId);
+ if (!Cools.isEmpty(tenant)) {
+ return String.valueOf(tenant.getName());
+ }
+ return null;
+ }
+
+ public String getCreateBy$() {
+ UserService service = SpringUtils.getBean(UserService.class);
+ User user = service.getById(this.createBy);
+ if (!Cools.isEmpty(user)) {
+ return String.valueOf(user.getNickname());
+ }
+ return null;
+ }
+
+ public String getCreateTime$() {
+ if (Cools.isEmpty(this.createTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
+ }
+
+ public String getUpdateBy$() {
+ UserService service = SpringUtils.getBean(UserService.class);
+ User user = service.getById(this.updateBy);
+ if (!Cools.isEmpty(user)) {
+ return String.valueOf(user.getNickname());
+ }
+ return null;
+ }
+
+ public String getUpdateTime$() {
+ if (Cools.isEmpty(this.updateTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiParam.java
index 1cac3f7..77c1f3f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiParam.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiParam.java
@@ -167,3 +167,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiPromptPublishLog.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiPromptPublishLog.java
new file mode 100644
index 0000000..d4397fc
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiPromptPublishLog.java
@@ -0,0 +1,50 @@
+package com.vincent.rsf.server.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_prompt_publish_log")
+public class AiPromptPublishLog implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty("Prompt妯℃澘ID")
+ private Long promptTemplateId;
+
+ @ApiModelProperty("鍦烘櫙缂栫爜")
+ private String sceneCode;
+
+ @ApiModelProperty("妯℃澘鍚嶇О")
+ private String templateName;
+
+ @ApiModelProperty("鐗堟湰鍙�")
+ private Integer versionNo;
+
+ @ApiModelProperty("鍔ㄤ綔")
+ private String actionType;
+
+ @ApiModelProperty("鍔ㄤ綔鎻忚堪")
+ private String actionDesc;
+
+ private Long tenantId;
+ private Long createBy;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date createTime;
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiPromptTemplate.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiPromptTemplate.java
new file mode 100644
index 0000000..3a9328f
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/AiPromptTemplate.java
@@ -0,0 +1,159 @@
+package com.vincent.rsf.server.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.SpringUtils;
+import com.vincent.rsf.server.ai.constant.AiSceneCode;
+import com.vincent.rsf.server.system.service.TenantService;
+import com.vincent.rsf.server.system.service.UserService;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_prompt_template")
+public class AiPromptTemplate implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "ID")
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty(value = "缂栧彿")
+ private String uuid;
+
+ @ApiModelProperty(value = "鍦烘櫙缂栫爜")
+ private String sceneCode;
+
+ @ApiModelProperty(value = "妯℃澘鍚嶇О")
+ private String templateName;
+
+ @ApiModelProperty(value = "鍩虹鎻愮ず璇�")
+ private String basePrompt;
+
+ @ApiModelProperty(value = "宸ュ叿鎻愮ず璇�")
+ private String toolPrompt;
+
+ @ApiModelProperty(value = "杈撳嚭鎻愮ず璇�")
+ private String outputPrompt;
+
+ @ApiModelProperty(value = "鐗堟湰鍙�")
+ private Integer versionNo;
+
+ @ApiModelProperty(value = "宸插彂甯� 1:鏄� 0:鍚�")
+ private Integer publishedFlag;
+
+ @ApiModelProperty(value = "鐘舵�� 1:姝e父 0:鍐荤粨")
+ private Integer status;
+
+ @ApiModelProperty(value = "鏄惁鍒犻櫎 1:鏄� 0:鍚�")
+ @TableLogic
+ private Integer deleted;
+
+ @ApiModelProperty(value = "绉熸埛")
+ private Long tenantId;
+
+ @ApiModelProperty(value = "娣诲姞浜哄憳")
+ private Long createBy;
+
+ @ApiModelProperty(value = "娣诲姞鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date createTime;
+
+ @ApiModelProperty(value = "淇敼浜哄憳")
+ private Long updateBy;
+
+ @ApiModelProperty(value = "淇敼鏃堕棿")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date updateTime;
+
+ @ApiModelProperty(value = "澶囨敞")
+ private String memo;
+
+ public String getSceneCode$() {
+ if (AiSceneCode.SYSTEM_DIAGNOSE.equals(this.sceneCode)) {
+ return "绯荤粺璇婃柇";
+ }
+ if (AiSceneCode.GENERAL_CHAT.equals(this.sceneCode)) {
+ return "閫氱敤瀵硅瘽";
+ }
+ return this.sceneCode;
+ }
+
+ public Boolean getPublishedFlagBool() {
+ if (this.publishedFlag == null) {
+ return null;
+ }
+ return Integer.valueOf(1).equals(this.publishedFlag);
+ }
+
+ public String getPublishedFlag$() {
+ if (this.publishedFlag == null) {
+ return null;
+ }
+ return Integer.valueOf(1).equals(this.publishedFlag) ? "宸插彂甯�" : "鑽夌";
+ }
+
+ public Boolean getStatusBool() {
+ if (this.status == null) {
+ return null;
+ }
+ return Integer.valueOf(1).equals(this.status);
+ }
+
+ public String getTenantId$() {
+ TenantService service = SpringUtils.getBean(TenantService.class);
+ Tenant tenant = service.getById(this.tenantId);
+ if (!Cools.isEmpty(tenant)) {
+ return String.valueOf(tenant.getName());
+ }
+ return null;
+ }
+
+ public String getCreateBy$() {
+ UserService service = SpringUtils.getBean(UserService.class);
+ User user = service.getById(this.createBy);
+ if (!Cools.isEmpty(user)) {
+ return String.valueOf(user.getNickname());
+ }
+ return null;
+ }
+
+ public String getCreateTime$() {
+ if (Cools.isEmpty(this.createTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
+ }
+
+ public String getUpdateBy$() {
+ UserService service = SpringUtils.getBean(UserService.class);
+ User user = service.getById(this.updateBy);
+ if (!Cools.isEmpty(user)) {
+ return String.valueOf(user.getNickname());
+ }
+ return null;
+ }
+
+ public String getUpdateTime$() {
+ if (Cools.isEmpty(this.updateTime)) {
+ return "";
+ }
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
+ }
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Config.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Config.java
index 5990ada..79cb656 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Config.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Config.java
@@ -208,3 +208,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Dept.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Dept.java
index 93fbb6f..3ed3f06 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Dept.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Dept.java
@@ -227,3 +227,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/DictData.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/DictData.java
index 078b35d..d4b54c7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/DictData.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/DictData.java
@@ -233,3 +233,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/DictType.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/DictType.java
index 493fd2b..0000ffa 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/DictType.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/DictType.java
@@ -195,3 +195,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Fields.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Fields.java
index b3e1b0e..7313a54 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Fields.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Fields.java
@@ -231,3 +231,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FieldsItem.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FieldsItem.java
index 98ef605..4f62a91 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FieldsItem.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FieldsItem.java
@@ -213,3 +213,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowInstance.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowInstance.java
index c21079d..00a5ad8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowInstance.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowInstance.java
@@ -301,3 +301,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepInstance.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepInstance.java
index 489e71b..72ae5df 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepInstance.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepInstance.java
@@ -252,3 +252,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepLog.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepLog.java
index a95721e..fe709d3 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepLog.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepLog.java
@@ -109,3 +109,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepTemplate.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepTemplate.java
index b32c200..18c0514 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepTemplate.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepTemplate.java
@@ -198,3 +198,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Host.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Host.java
index 5f61dbc..559ffaa 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Host.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Host.java
@@ -117,3 +117,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/MatnrRoleMenu.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/MatnrRoleMenu.java
index 732cc64..b6ee354 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/MatnrRoleMenu.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/MatnrRoleMenu.java
@@ -48,3 +48,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Menu.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Menu.java
index 41b6542..4bc0f3a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Menu.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Menu.java
@@ -241,3 +241,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/OperationRecord.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/OperationRecord.java
index 6e2efb4..ceb4a33 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/OperationRecord.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/OperationRecord.java
@@ -153,3 +153,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/PdaRoleMenu.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/PdaRoleMenu.java
index 5b10d4e..5b15b62 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/PdaRoleMenu.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/PdaRoleMenu.java
@@ -48,3 +48,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Role.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Role.java
index b7b1595..2ebe8f7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Role.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Role.java
@@ -134,3 +134,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/RoleMenu.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/RoleMenu.java
index 9cb7282..57b9fac 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/RoleMenu.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/RoleMenu.java
@@ -38,3 +38,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SerialRule.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SerialRule.java
index c983f8f..30c9885 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SerialRule.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SerialRule.java
@@ -228,3 +228,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SerialRuleItem.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SerialRuleItem.java
index d82da49..9462048 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SerialRuleItem.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SerialRuleItem.java
@@ -233,3 +233,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SubsystemFlowTemplate.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SubsystemFlowTemplate.java
index 00ea3bc..33792cc 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SubsystemFlowTemplate.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/SubsystemFlowTemplate.java
@@ -213,3 +213,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstance.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstance.java
index 5315004..e2ab3f8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstance.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstance.java
@@ -390,3 +390,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstanceNode.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstanceNode.java
index 3c5be0e..4784207 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstanceNode.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstanceNode.java
@@ -293,3 +293,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplate.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplate.java
index 3c8c556..df89051 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplate.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplate.java
@@ -262,3 +262,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateMerge.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateMerge.java
index cdfc643..4331b2b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateMerge.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateMerge.java
@@ -246,3 +246,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateNode.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateNode.java
index 709012e..9f9a33b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateNode.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateNode.java
@@ -203,3 +203,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Tenant.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Tenant.java
index 2b6481b..104a154 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Tenant.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/Tenant.java
@@ -117,3 +117,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/User.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/User.java
index fa580cb..9d10bc7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/User.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/User.java
@@ -323,3 +323,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/UserLogin.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/UserLogin.java
index 1508625..5cf8b10 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/UserLogin.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/UserLogin.java
@@ -114,3 +114,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/UserRole.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/UserRole.java
index 74ca469..1886d3a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/UserRole.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/UserRole.java
@@ -38,3 +38,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/WarehouseRoleMenu.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/WarehouseRoleMenu.java
index 8a3da58..415a620 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/WarehouseRoleMenu.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/WarehouseRoleMenu.java
@@ -48,3 +48,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/CompanyType.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/CompanyType.java
index 51c9913..ac61e76 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/CompanyType.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/CompanyType.java
@@ -25,3 +25,4 @@
this.desc = desc;
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/ConfigType.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/ConfigType.java
index 8297ecb..bfd6ce7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/ConfigType.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/ConfigType.java
@@ -27,3 +27,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/EmailType.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/EmailType.java
index bd11f60..79f4396 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/EmailType.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/EmailType.java
@@ -16,3 +16,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/LoginSystemType.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/LoginSystemType.java
index 66afbd5..a7ab17a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/LoginSystemType.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/LoginSystemType.java
@@ -11,3 +11,4 @@
;
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/SerialRuleReset.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/SerialRuleReset.java
index 6389a20..2a524db 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/SerialRuleReset.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/SerialRuleReset.java
@@ -27,3 +27,4 @@
this.desc = desc;
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/SerialRuleType.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/SerialRuleType.java
index 1c3ee92..2a915f4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/SerialRuleType.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/SerialRuleType.java
@@ -27,3 +27,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/StatusType.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/StatusType.java
index 11ec3a0..8dd044d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/StatusType.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/enums/StatusType.java
@@ -12,3 +12,4 @@
this.val = val;
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiCallLogMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiCallLogMapper.java
new file mode 100644
index 0000000..67ce8bc
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiCallLogMapper.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.system.entity.AiCallLog;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiCallLogMapper extends BaseMapper<AiCallLog> {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosisPlanMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosisPlanMapper.java
new file mode 100644
index 0000000..416eb4c
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosisPlanMapper.java
@@ -0,0 +1,11 @@
+package com.vincent.rsf.server.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.system.entity.AiDiagnosisPlan;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AiDiagnosisPlanMapper extends BaseMapper<AiDiagnosisPlan> {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosisRecordMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosisRecordMapper.java
new file mode 100644
index 0000000..50ddc99
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosisRecordMapper.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.system.entity.AiDiagnosisRecord;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiDiagnosisRecordMapper extends BaseMapper<AiDiagnosisRecord> {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosticToolConfigMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosticToolConfigMapper.java
new file mode 100644
index 0000000..2ceae2d
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiDiagnosticToolConfigMapper.java
@@ -0,0 +1,10 @@
+package com.vincent.rsf.server.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.system.entity.AiDiagnosticToolConfig;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AiDiagnosticToolConfigMapper extends BaseMapper<AiDiagnosticToolConfig> {
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiMcpMountMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiMcpMountMapper.java
new file mode 100644
index 0000000..6405d51
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiMcpMountMapper.java
@@ -0,0 +1,11 @@
+package com.vincent.rsf.server.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.system.entity.AiMcpMount;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AiMcpMountMapper extends BaseMapper<AiMcpMount> {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiModelRouteMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiModelRouteMapper.java
new file mode 100644
index 0000000..c981807
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiModelRouteMapper.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.system.entity.AiModelRoute;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiModelRouteMapper extends BaseMapper<AiModelRoute> {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiParamMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiParamMapper.java
index 8c2d513..01fdd6a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiParamMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiParamMapper.java
@@ -10,3 +10,4 @@
public interface AiParamMapper extends BaseMapper<AiParam> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiPromptPublishLogMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiPromptPublishLogMapper.java
new file mode 100644
index 0000000..4430396
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiPromptPublishLogMapper.java
@@ -0,0 +1,10 @@
+package com.vincent.rsf.server.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.system.entity.AiPromptPublishLog;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AiPromptPublishLogMapper extends BaseMapper<AiPromptPublishLog> {
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiPromptTemplateMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiPromptTemplateMapper.java
new file mode 100644
index 0000000..7dd04c7
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/AiPromptTemplateMapper.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.system.entity.AiPromptTemplate;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiPromptTemplateMapper extends BaseMapper<AiPromptTemplate> {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/ConfigMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/ConfigMapper.java
index 093db16..5e8d172 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/ConfigMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/ConfigMapper.java
@@ -11,3 +11,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DeptMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DeptMapper.java
index 3e0bbc3..306d7c8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DeptMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DeptMapper.java
@@ -10,3 +10,4 @@
public interface DeptMapper extends BaseMapper<Dept> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DictDataMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DictDataMapper.java
index 92501c3..052ac8b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DictDataMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DictDataMapper.java
@@ -10,3 +10,4 @@
public interface DictDataMapper extends BaseMapper<DictData> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DictTypeMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DictTypeMapper.java
index 3554de8..96376de 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DictTypeMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/DictTypeMapper.java
@@ -10,3 +10,4 @@
public interface DictTypeMapper extends BaseMapper<DictType> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FieldsItemMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FieldsItemMapper.java
index 9119a25..54bbade 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FieldsItemMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FieldsItemMapper.java
@@ -10,3 +10,4 @@
public interface FieldsItemMapper extends BaseMapper<FieldsItem> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FieldsMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FieldsMapper.java
index 277e0bf..0e4a060 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FieldsMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FieldsMapper.java
@@ -10,3 +10,4 @@
public interface FieldsMapper extends BaseMapper<Fields> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowInstanceMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowInstanceMapper.java
index 9c9b7a9..ec23e0c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowInstanceMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowInstanceMapper.java
@@ -10,3 +10,4 @@
public interface FlowInstanceMapper extends BaseMapper<FlowInstance> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepInstanceMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepInstanceMapper.java
index 20dd85d..50f517b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepInstanceMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepInstanceMapper.java
@@ -10,3 +10,4 @@
public interface FlowStepInstanceMapper extends BaseMapper<FlowStepInstance> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepLogMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepLogMapper.java
index a56d426..4f0fb6e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepLogMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepLogMapper.java
@@ -10,3 +10,4 @@
public interface FlowStepLogMapper extends BaseMapper<FlowStepLog> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepTemplateMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepTemplateMapper.java
index 006ac22..75f6bf4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepTemplateMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/FlowStepTemplateMapper.java
@@ -10,3 +10,4 @@
public interface FlowStepTemplateMapper extends BaseMapper<FlowStepTemplate> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/HostMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/HostMapper.java
index 53f93fc..fe7d1cc 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/HostMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/HostMapper.java
@@ -10,3 +10,4 @@
public interface HostMapper extends BaseMapper<Host> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MatnrRoleMenuMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MatnrRoleMenuMapper.java
index 4ecb897..b1ec29d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MatnrRoleMenuMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MatnrRoleMenuMapper.java
@@ -13,3 +13,4 @@
List<Long> listStrictlyMenuByRoleId(Long roleId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MenuMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MenuMapper.java
index 0ae6cb4..5f3458c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MenuMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MenuMapper.java
@@ -11,3 +11,4 @@
public interface MenuMapper extends BaseMapper<Menu> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/OperationRecordMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/OperationRecordMapper.java
index 00508b2..393ceef 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/OperationRecordMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/OperationRecordMapper.java
@@ -10,3 +10,4 @@
public interface OperationRecordMapper extends BaseMapper<OperationRecord> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/PdaRoleMenuMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/PdaRoleMenuMapper.java
index d3b21a3..9c625e6 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/PdaRoleMenuMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/PdaRoleMenuMapper.java
@@ -20,3 +20,4 @@
List<Long> listStrictlyMenuByRoleId(@Param("roleId") Long roleId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/RoleMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/RoleMapper.java
index a83a403..a3f3217 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/RoleMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/RoleMapper.java
@@ -11,3 +11,4 @@
public interface RoleMapper extends BaseMapper<Role> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/RoleMenuMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/RoleMenuMapper.java
index dc84b79..48611b5 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/RoleMenuMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/RoleMenuMapper.java
@@ -20,3 +20,4 @@
List<Long> listStrictlyMenuByRoleId(@Param("roleId") Long roleId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SerialRuleItemMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SerialRuleItemMapper.java
index a117c2a..4f51964 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SerialRuleItemMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SerialRuleItemMapper.java
@@ -10,3 +10,4 @@
public interface SerialRuleItemMapper extends BaseMapper<SerialRuleItem> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SerialRuleMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SerialRuleMapper.java
index 2cdc00d..d3f14b2 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SerialRuleMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SerialRuleMapper.java
@@ -10,3 +10,4 @@
public interface SerialRuleMapper extends BaseMapper<SerialRule> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SubsystemFlowTemplateMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SubsystemFlowTemplateMapper.java
index 7ced37b..0535e25 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SubsystemFlowTemplateMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/SubsystemFlowTemplateMapper.java
@@ -10,3 +10,4 @@
public interface SubsystemFlowTemplateMapper extends BaseMapper<SubsystemFlowTemplate> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskInstanceMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskInstanceMapper.java
index c026657..971c81b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskInstanceMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskInstanceMapper.java
@@ -10,3 +10,4 @@
public interface TaskInstanceMapper extends BaseMapper<TaskInstance> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskInstanceNodeMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskInstanceNodeMapper.java
index 8e6168a..cb5c9c2 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskInstanceNodeMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskInstanceNodeMapper.java
@@ -10,3 +10,4 @@
public interface TaskInstanceNodeMapper extends BaseMapper<TaskInstanceNode> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMapper.java
index a78a1a4..d6687d9 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMapper.java
@@ -10,3 +10,4 @@
public interface TaskPathTemplateMapper extends BaseMapper<TaskPathTemplate> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMergeMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMergeMapper.java
index b76056b..043cab1 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMergeMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMergeMapper.java
@@ -10,3 +10,4 @@
public interface TaskPathTemplateMergeMapper extends BaseMapper<TaskPathTemplateMerge> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateNodeMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateNodeMapper.java
index 471003e..cefeb58 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateNodeMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateNodeMapper.java
@@ -10,3 +10,4 @@
public interface TaskPathTemplateNodeMapper extends BaseMapper<TaskPathTemplateNode> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TenantMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TenantMapper.java
index a03df65..d520b68 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TenantMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TenantMapper.java
@@ -11,3 +11,4 @@
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserLoginMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserLoginMapper.java
index 778fdb3..49cb94d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserLoginMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserLoginMapper.java
@@ -10,3 +10,4 @@
public interface UserLoginMapper extends BaseMapper<UserLogin> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserMapper.java
index e92e0c4..e850f23 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserMapper.java
@@ -24,3 +24,4 @@
User selectByEmailWithoutTenant(@Param("email") String email, @Param("tenantId") Long tenantId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserRoleMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserRoleMapper.java
index a48da5e..7e0bebf 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserRoleMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/UserRoleMapper.java
@@ -16,3 +16,4 @@
List<Role> selectByUserId(@Param("userId") Long userId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/WarehouseRoleMenuMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/WarehouseRoleMenuMapper.java
index 9f2baf8..53890d0 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/WarehouseRoleMenuMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/WarehouseRoleMenuMapper.java
@@ -14,3 +14,4 @@
List<Long> listStrictlyMenuByRoleId(@Param("roleId") Long roleId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiCallLogService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiCallLogService.java
new file mode 100644
index 0000000..2672620
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiCallLogService.java
@@ -0,0 +1,9 @@
+package com.vincent.rsf.server.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.system.entity.AiCallLog;
+
+public interface AiCallLogService extends IService<AiCallLog> {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosisPlanService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosisPlanService.java
new file mode 100644
index 0000000..68edc08
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosisPlanService.java
@@ -0,0 +1,23 @@
+package com.vincent.rsf.server.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.system.entity.AiDiagnosisPlan;
+
+import java.util.Date;
+import java.util.List;
+
+public interface AiDiagnosisPlanService extends IService<AiDiagnosisPlan> {
+
+ AiDiagnosisPlan getTenantPlan(Long tenantId, Long id);
+
+ List<AiDiagnosisPlan> listDuePlans(Date now);
+
+ Date calculateNextRunTime(String cronExpr, Date after);
+
+ boolean validateCron(String cronExpr);
+
+ boolean acquireForExecution(Long id, Long operatorId, String lastMessage, Date nextRunTime);
+
+ void finishExecution(Long id, Integer lastResult, Long lastDiagnosisId, String lastMessage, Date finishTime, Date nextRunTime);
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosisRecordService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosisRecordService.java
new file mode 100644
index 0000000..b4ee1a1
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosisRecordService.java
@@ -0,0 +1,9 @@
+package com.vincent.rsf.server.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.system.entity.AiDiagnosisRecord;
+
+public interface AiDiagnosisRecordService extends IService<AiDiagnosisRecord> {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosticToolConfigService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosticToolConfigService.java
new file mode 100644
index 0000000..dc23035
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiDiagnosticToolConfigService.java
@@ -0,0 +1,16 @@
+package com.vincent.rsf.server.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.system.entity.AiDiagnosticToolConfig;
+
+import java.util.List;
+
+public interface AiDiagnosticToolConfigService extends IService<AiDiagnosticToolConfig> {
+
+ List<AiDiagnosticToolConfig> listTenantConfigs(Long tenantId);
+
+ List<AiDiagnosticToolConfig> listSceneConfigs(Long tenantId, String sceneCode);
+
+ AiDiagnosticToolConfig getTenantConfig(Long tenantId, Long id);
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiMcpMountService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiMcpMountService.java
new file mode 100644
index 0000000..7962a97
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiMcpMountService.java
@@ -0,0 +1,16 @@
+package com.vincent.rsf.server.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.system.entity.AiMcpMount;
+
+import java.util.List;
+
+public interface AiMcpMountService extends IService<AiMcpMount> {
+
+ List<AiMcpMount> listTenantMounts(Long tenantId);
+
+ AiMcpMount getTenantMount(Long tenantId, Long id);
+
+ AiMcpMount getTenantMountByCode(Long tenantId, String mountCode);
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiModelRouteService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiModelRouteService.java
new file mode 100644
index 0000000..22b84e5
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiModelRouteService.java
@@ -0,0 +1,15 @@
+package com.vincent.rsf.server.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.system.entity.AiModelRoute;
+
+import java.util.List;
+
+public interface AiModelRouteService extends IService<AiModelRoute> {
+
+ AiModelRoute getTenantRoute(Long tenantId, Long id);
+
+ List<AiModelRoute> listAvailableRoutes(Long tenantId, String routeCode);
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiParamService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiParamService.java
index 39900e8..8bbd05d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiParamService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiParamService.java
@@ -17,3 +17,4 @@
AiParam getDefaultModel();
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiPromptPublishLogService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiPromptPublishLogService.java
new file mode 100644
index 0000000..f44f71e
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiPromptPublishLogService.java
@@ -0,0 +1,15 @@
+package com.vincent.rsf.server.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.system.entity.AiPromptPublishLog;
+import com.vincent.rsf.server.system.entity.AiPromptTemplate;
+
+import java.util.List;
+
+public interface AiPromptPublishLogService extends IService<AiPromptPublishLog> {
+
+ void saveLog(Long tenantId, Long userId, AiPromptTemplate template, String actionType, String actionDesc);
+
+ List<AiPromptPublishLog> listSceneLogs(Long tenantId, String sceneCode);
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiPromptTemplateService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiPromptTemplateService.java
new file mode 100644
index 0000000..1406d12
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/AiPromptTemplateService.java
@@ -0,0 +1,24 @@
+package com.vincent.rsf.server.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.system.entity.AiPromptTemplate;
+
+import java.util.List;
+
+public interface AiPromptTemplateService extends IService<AiPromptTemplate> {
+
+ AiPromptTemplate getTenantTemplate(Long tenantId, Long id);
+
+ AiPromptTemplate getPublishedTemplate(Long tenantId, String sceneCode);
+
+ List<AiPromptTemplate> listVersions(Long tenantId, String sceneCode);
+
+ boolean publishTemplate(Long tenantId, Long id, Long userId);
+
+ boolean rollbackTemplate(Long tenantId, Long id, Long userId);
+
+ int nextVersionNo(Long tenantId, String sceneCode);
+
+ AiPromptTemplate copyTemplate(Long tenantId, Long id, Long userId);
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/ConfigService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/ConfigService.java
index eec98e8..01305b0 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/ConfigService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/ConfigService.java
@@ -12,3 +12,4 @@
R modiftyStatus(Config config);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DeptService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DeptService.java
index 9ad8dbd..d3d6108 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DeptService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DeptService.java
@@ -7,3 +7,4 @@
public interface DeptService extends IService<Dept> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DictDataService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DictDataService.java
index 70dbda9..40cdb92 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DictDataService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DictDataService.java
@@ -6,3 +6,4 @@
public interface DictDataService extends IService<DictData> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DictTypeService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DictTypeService.java
index b28d6b4..2e6a0cd 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DictTypeService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/DictTypeService.java
@@ -6,3 +6,4 @@
public interface DictTypeService extends IService<DictType> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FieldsItemService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FieldsItemService.java
index 5ac3604..381b574 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FieldsItemService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FieldsItemService.java
@@ -6,3 +6,4 @@
public interface FieldsItemService extends IService<FieldsItem> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FieldsService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FieldsService.java
index 6e8bc25..abeedbc 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FieldsService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FieldsService.java
@@ -6,3 +6,4 @@
public interface FieldsService extends IService<Fields> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowInstanceService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowInstanceService.java
index 3eecb62..d008777 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowInstanceService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowInstanceService.java
@@ -6,3 +6,4 @@
public interface FlowInstanceService extends IService<FlowInstance> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepInstanceService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepInstanceService.java
index 75794c3..16406ed 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepInstanceService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepInstanceService.java
@@ -8,3 +8,4 @@
boolean jumpCurrent(Long id);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepLogService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepLogService.java
index f50d184..148824f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepLogService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepLogService.java
@@ -6,3 +6,4 @@
public interface FlowStepLogService extends IService<FlowStepLog> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepTemplateService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepTemplateService.java
index 240c49e..a51e701 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepTemplateService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/FlowStepTemplateService.java
@@ -6,3 +6,4 @@
public interface FlowStepTemplateService extends IService<FlowStepTemplate> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/HostService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/HostService.java
index 6832b1d..85ba6f2 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/HostService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/HostService.java
@@ -7,3 +7,4 @@
public interface HostService extends IService<Host> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/MatnrRoleMenuService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/MatnrRoleMenuService.java
index eae7beb..98fbce1 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/MatnrRoleMenuService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/MatnrRoleMenuService.java
@@ -9,3 +9,4 @@
List<Long> listStrictlyMenuByRoleId(Long roleId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/MenuService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/MenuService.java
index 72c37bb..88d4610 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/MenuService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/MenuService.java
@@ -7,3 +7,4 @@
public interface MenuService extends IService<Menu> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/OperationRecordService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/OperationRecordService.java
index 66b886b..d09bd80 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/OperationRecordService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/OperationRecordService.java
@@ -9,3 +9,4 @@
void saveAsync(OperationRecord operationRecord);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/PdaRoleMenuService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/PdaRoleMenuService.java
index c5b43bd..f2f6ef4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/PdaRoleMenuService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/PdaRoleMenuService.java
@@ -12,3 +12,4 @@
List<Long> listStrictlyMenuByRoleId(Long roleId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/RoleMenuService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/RoleMenuService.java
index 1819cec..331a951 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/RoleMenuService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/RoleMenuService.java
@@ -13,3 +13,4 @@
List<Long> listStrictlyMenuByRoleId(Long roleId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/RoleService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/RoleService.java
index dae2846..7cc32de 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/RoleService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/RoleService.java
@@ -7,3 +7,4 @@
public interface RoleService extends IService<Role> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SerialRuleItemService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SerialRuleItemService.java
index a4db525..239c58d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SerialRuleItemService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SerialRuleItemService.java
@@ -6,3 +6,4 @@
public interface SerialRuleItemService extends IService<SerialRuleItem> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SerialRuleService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SerialRuleService.java
index bbba769..ba3464d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SerialRuleService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SerialRuleService.java
@@ -6,3 +6,4 @@
public interface SerialRuleService extends IService<SerialRule> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SubsystemFlowTemplateService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SubsystemFlowTemplateService.java
index a8c0429..257580c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SubsystemFlowTemplateService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/SubsystemFlowTemplateService.java
@@ -6,3 +6,4 @@
public interface SubsystemFlowTemplateService extends IService<SubsystemFlowTemplate> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskInstanceNodeService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskInstanceNodeService.java
index 3dc9590..dd34fc5 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskInstanceNodeService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskInstanceNodeService.java
@@ -6,3 +6,4 @@
public interface TaskInstanceNodeService extends IService<TaskInstanceNode> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskInstanceService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskInstanceService.java
index 9636c59..01e177f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskInstanceService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskInstanceService.java
@@ -6,3 +6,4 @@
public interface TaskInstanceService extends IService<TaskInstance> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateMergeService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateMergeService.java
index ada6013..6f2c550 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateMergeService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateMergeService.java
@@ -8,3 +8,4 @@
R createSelectList();
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateNodeService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateNodeService.java
index c317557..ac9384f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateNodeService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateNodeService.java
@@ -6,3 +6,4 @@
public interface TaskPathTemplateNodeService extends IService<TaskPathTemplateNode> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateService.java
index 8a44fd4..15f2a4a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateService.java
@@ -6,3 +6,4 @@
public interface TaskPathTemplateService extends IService<TaskPathTemplate> {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TenantService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TenantService.java
index 2757646..655e00f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TenantService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/TenantService.java
@@ -9,3 +9,4 @@
Long initTenant(TenantInitParam param);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserLoginService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserLoginService.java
index 93d5725..ffe05a3 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserLoginService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserLoginService.java
@@ -11,3 +11,4 @@
void saveAsync(Long userId, String token, Integer type, Long tenantId, String memo, HttpServletRequest request);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserRoleService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserRoleService.java
index b31d30f..f7a5be8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserRoleService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserRoleService.java
@@ -12,3 +12,4 @@
List<Role> listByUserId(Long userId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserService.java
index 396ade9..a5f4e2a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/UserService.java
@@ -28,3 +28,4 @@
User selectByUsernameWithoutTenant(String username, Long tenantId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/WarehouseRoleMenuService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/WarehouseRoleMenuService.java
index 0528194..0613295 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/WarehouseRoleMenuService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/WarehouseRoleMenuService.java
@@ -9,3 +9,4 @@
List<Long> listStrictlyMenuByRoleId(Long roleId);
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiCallLogServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiCallLogServiceImpl.java
new file mode 100644
index 0000000..97061bb
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiCallLogServiceImpl.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.server.system.entity.AiCallLog;
+import com.vincent.rsf.server.system.mapper.AiCallLogMapper;
+import com.vincent.rsf.server.system.service.AiCallLogService;
+import org.springframework.stereotype.Service;
+
+@Service("aiCallLogService")
+public class AiCallLogServiceImpl extends ServiceImpl<AiCallLogMapper, AiCallLog> implements AiCallLogService {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosisPlanServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosisPlanServiceImpl.java
new file mode 100644
index 0000000..841efd0
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosisPlanServiceImpl.java
@@ -0,0 +1,90 @@
+package com.vincent.rsf.server.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.server.system.entity.AiDiagnosisPlan;
+import com.vincent.rsf.server.system.mapper.AiDiagnosisPlanMapper;
+import com.vincent.rsf.server.system.service.AiDiagnosisPlanService;
+import org.springframework.scheduling.support.CronSequenceGenerator;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+
+@Service("aiDiagnosisPlanService")
+public class AiDiagnosisPlanServiceImpl extends ServiceImpl<AiDiagnosisPlanMapper, AiDiagnosisPlan> implements AiDiagnosisPlanService {
+
+ @Override
+ public AiDiagnosisPlan getTenantPlan(Long tenantId, Long id) {
+ if (tenantId == null || id == null) {
+ return null;
+ }
+ return this.getOne(new LambdaQueryWrapper<AiDiagnosisPlan>()
+ .eq(AiDiagnosisPlan::getTenantId, tenantId)
+ .eq(AiDiagnosisPlan::getId, id)
+ .last("limit 1"));
+ }
+
+ @Override
+ public List<AiDiagnosisPlan> listDuePlans(Date now) {
+ Date target = now == null ? new Date() : now;
+ return this.list(new LambdaQueryWrapper<AiDiagnosisPlan>()
+ .eq(AiDiagnosisPlan::getStatus, 1)
+ .eq(AiDiagnosisPlan::getRunningFlag, 0)
+ .isNotNull(AiDiagnosisPlan::getNextRunTime)
+ .le(AiDiagnosisPlan::getNextRunTime, target)
+ .orderByAsc(AiDiagnosisPlan::getNextRunTime, AiDiagnosisPlan::getId));
+ }
+
+ @Override
+ public Date calculateNextRunTime(String cronExpr, Date after) {
+ if (!validateCron(cronExpr)) {
+ return null;
+ }
+ return new CronSequenceGenerator(cronExpr.trim()).next(after == null ? new Date() : after);
+ }
+
+ @Override
+ public boolean validateCron(String cronExpr) {
+ if (cronExpr == null || cronExpr.trim().isEmpty()) {
+ return false;
+ }
+ return CronSequenceGenerator.isValidExpression(cronExpr.trim());
+ }
+
+ @Override
+ public boolean acquireForExecution(Long id, Long operatorId, String lastMessage, Date nextRunTime) {
+ if (id == null) {
+ return false;
+ }
+ Date now = new Date();
+ return this.update(new LambdaUpdateWrapper<AiDiagnosisPlan>()
+ .eq(AiDiagnosisPlan::getId, id)
+ .eq(AiDiagnosisPlan::getRunningFlag, 0)
+ .set(AiDiagnosisPlan::getRunningFlag, 1)
+ .set(AiDiagnosisPlan::getLastResult, 2)
+ .set(AiDiagnosisPlan::getLastMessage, lastMessage)
+ .set(AiDiagnosisPlan::getNextRunTime, nextRunTime)
+ .set(AiDiagnosisPlan::getUpdateBy, operatorId)
+ .set(AiDiagnosisPlan::getUpdateTime, now));
+ }
+
+ @Override
+ public void finishExecution(Long id, Integer lastResult, Long lastDiagnosisId, String lastMessage, Date finishTime, Date nextRunTime) {
+ if (id == null) {
+ return;
+ }
+ Date now = finishTime == null ? new Date() : finishTime;
+ this.update(new LambdaUpdateWrapper<AiDiagnosisPlan>()
+ .eq(AiDiagnosisPlan::getId, id)
+ .set(AiDiagnosisPlan::getRunningFlag, 0)
+ .set(AiDiagnosisPlan::getLastResult, lastResult)
+ .set(AiDiagnosisPlan::getLastDiagnosisId, lastDiagnosisId)
+ .set(AiDiagnosisPlan::getLastRunTime, now)
+ .set(AiDiagnosisPlan::getNextRunTime, nextRunTime)
+ .set(AiDiagnosisPlan::getLastMessage, lastMessage)
+ .set(AiDiagnosisPlan::getUpdateTime, now));
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosisRecordServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosisRecordServiceImpl.java
new file mode 100644
index 0000000..7fa2282
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosisRecordServiceImpl.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.server.system.entity.AiDiagnosisRecord;
+import com.vincent.rsf.server.system.mapper.AiDiagnosisRecordMapper;
+import com.vincent.rsf.server.system.service.AiDiagnosisRecordService;
+import org.springframework.stereotype.Service;
+
+@Service("aiDiagnosisRecordService")
+public class AiDiagnosisRecordServiceImpl extends ServiceImpl<AiDiagnosisRecordMapper, AiDiagnosisRecord> implements AiDiagnosisRecordService {
+
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosticToolConfigServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosticToolConfigServiceImpl.java
new file mode 100644
index 0000000..758af02
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiDiagnosticToolConfigServiceImpl.java
@@ -0,0 +1,42 @@
+package com.vincent.rsf.server.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.server.system.entity.AiDiagnosticToolConfig;
+import com.vincent.rsf.server.system.mapper.AiDiagnosticToolConfigMapper;
+import com.vincent.rsf.server.system.service.AiDiagnosticToolConfigService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service("aiDiagnosticToolConfigService")
+public class AiDiagnosticToolConfigServiceImpl extends ServiceImpl<AiDiagnosticToolConfigMapper, AiDiagnosticToolConfig> implements AiDiagnosticToolConfigService {
+
+ @Override
+ public List<AiDiagnosticToolConfig> listTenantConfigs(Long tenantId) {
+ return this.list(new LambdaQueryWrapper<AiDiagnosticToolConfig>()
+ .eq(AiDiagnosticToolConfig::getTenantId, tenantId)
+ .orderByAsc(AiDiagnosticToolConfig::getSceneCode, AiDiagnosticToolConfig::getPriority, AiDiagnosticToolConfig::getId));
+ }
+
+ @Override
+ public List<AiDiagnosticToolConfig> listSceneConfigs(Long tenantId, String sceneCode) {
+ return this.list(new LambdaQueryWrapper<AiDiagnosticToolConfig>()
+ .eq(AiDiagnosticToolConfig::getTenantId, tenantId)
+ .eq(AiDiagnosticToolConfig::getSceneCode, sceneCode)
+ .eq(AiDiagnosticToolConfig::getStatus, 1)
+ .orderByAsc(AiDiagnosticToolConfig::getPriority, AiDiagnosticToolConfig::getId));
+ }
+
+ @Override
+ public AiDiagnosticToolConfig getTenantConfig(Long tenantId, Long id) {
+ if (tenantId == null || id == null) {
+ return null;
+ }
+ return this.getOne(new LambdaQueryWrapper<AiDiagnosticToolConfig>()
+ .eq(AiDiagnosticToolConfig::getTenantId, tenantId)
+ .eq(AiDiagnosticToolConfig::getId, id)
+ .last("limit 1"));
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiMcpMountServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiMcpMountServiceImpl.java
new file mode 100644
index 0000000..551a8c4
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiMcpMountServiceImpl.java
@@ -0,0 +1,44 @@
+package com.vincent.rsf.server.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.server.system.entity.AiMcpMount;
+import com.vincent.rsf.server.system.mapper.AiMcpMountMapper;
+import com.vincent.rsf.server.system.service.AiMcpMountService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service("aiMcpMountService")
+public class AiMcpMountServiceImpl extends ServiceImpl<AiMcpMountMapper, AiMcpMount> implements AiMcpMountService {
+
+ @Override
+ public List<AiMcpMount> listTenantMounts(Long tenantId) {
+ return this.list(new LambdaQueryWrapper<AiMcpMount>()
+ .eq(AiMcpMount::getTenantId, tenantId)
+ .orderByAsc(AiMcpMount::getMountCode, AiMcpMount::getId));
+ }
+
+ @Override
+ public AiMcpMount getTenantMount(Long tenantId, Long id) {
+ if (tenantId == null || id == null) {
+ return null;
+ }
+ return this.getOne(new LambdaQueryWrapper<AiMcpMount>()
+ .eq(AiMcpMount::getTenantId, tenantId)
+ .eq(AiMcpMount::getId, id)
+ .last("limit 1"));
+ }
+
+ @Override
+ public AiMcpMount getTenantMountByCode(Long tenantId, String mountCode) {
+ if (tenantId == null || mountCode == null || mountCode.trim().isEmpty()) {
+ return null;
+ }
+ return this.getOne(new LambdaQueryWrapper<AiMcpMount>()
+ .eq(AiMcpMount::getTenantId, tenantId)
+ .eq(AiMcpMount::getMountCode, mountCode)
+ .last("limit 1"));
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiModelRouteServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiModelRouteServiceImpl.java
new file mode 100644
index 0000000..7e38c7c
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiModelRouteServiceImpl.java
@@ -0,0 +1,38 @@
+package com.vincent.rsf.server.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.server.system.entity.AiModelRoute;
+import com.vincent.rsf.server.system.mapper.AiModelRouteMapper;
+import com.vincent.rsf.server.system.service.AiModelRouteService;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+
+@Service("aiModelRouteService")
+public class AiModelRouteServiceImpl extends ServiceImpl<AiModelRouteMapper, AiModelRoute> implements AiModelRouteService {
+
+ @Override
+ public AiModelRoute getTenantRoute(Long tenantId, Long id) {
+ if (tenantId == null || id == null) {
+ return null;
+ }
+ return this.getOne(new LambdaQueryWrapper<AiModelRoute>()
+ .eq(AiModelRoute::getTenantId, tenantId)
+ .eq(AiModelRoute::getId, id)
+ .last("limit 1"));
+ }
+
+ @Override
+ public List<AiModelRoute> listAvailableRoutes(Long tenantId, String routeCode) {
+ Date now = new Date();
+ return this.list(new LambdaQueryWrapper<AiModelRoute>()
+ .eq(AiModelRoute::getTenantId, tenantId)
+ .eq(AiModelRoute::getRouteCode, routeCode)
+ .eq(AiModelRoute::getStatus, 1)
+ .and(wrapper -> wrapper.isNull(AiModelRoute::getCooldownUntil).or().le(AiModelRoute::getCooldownUntil, now))
+ .orderByAsc(AiModelRoute::getPriority, AiModelRoute::getId));
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiParamServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiParamServiceImpl.java
index d4518f7..753e38c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiParamServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiParamServiceImpl.java
@@ -64,3 +64,4 @@
return list.isEmpty() ? null : list.get(0);
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiPromptPublishLogServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiPromptPublishLogServiceImpl.java
new file mode 100644
index 0000000..79b5eec
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiPromptPublishLogServiceImpl.java
@@ -0,0 +1,42 @@
+package com.vincent.rsf.server.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.server.system.entity.AiPromptPublishLog;
+import com.vincent.rsf.server.system.entity.AiPromptTemplate;
+import com.vincent.rsf.server.system.mapper.AiPromptPublishLogMapper;
+import com.vincent.rsf.server.system.service.AiPromptPublishLogService;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+
+@Service("aiPromptPublishLogService")
+public class AiPromptPublishLogServiceImpl extends ServiceImpl<AiPromptPublishLogMapper, AiPromptPublishLog> implements AiPromptPublishLogService {
+
+ @Override
+ public void saveLog(Long tenantId, Long userId, AiPromptTemplate template, String actionType, String actionDesc) {
+ if (tenantId == null || template == null) {
+ return;
+ }
+ this.save(new AiPromptPublishLog()
+ .setPromptTemplateId(template.getId())
+ .setSceneCode(template.getSceneCode())
+ .setTemplateName(template.getTemplateName())
+ .setVersionNo(template.getVersionNo())
+ .setActionType(actionType)
+ .setActionDesc(actionDesc)
+ .setTenantId(tenantId)
+ .setCreateBy(userId)
+ .setCreateTime(new Date()));
+ }
+
+ @Override
+ public List<AiPromptPublishLog> listSceneLogs(Long tenantId, String sceneCode) {
+ return this.list(new LambdaQueryWrapper<AiPromptPublishLog>()
+ .eq(AiPromptPublishLog::getTenantId, tenantId)
+ .eq(sceneCode != null && !sceneCode.trim().isEmpty(), AiPromptPublishLog::getSceneCode, sceneCode)
+ .orderByDesc(AiPromptPublishLog::getCreateTime, AiPromptPublishLog::getId));
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiPromptTemplateServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiPromptTemplateServiceImpl.java
new file mode 100644
index 0000000..acf0eea
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/AiPromptTemplateServiceImpl.java
@@ -0,0 +1,131 @@
+package com.vincent.rsf.server.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.server.system.entity.AiPromptTemplate;
+import com.vincent.rsf.server.system.mapper.AiPromptTemplateMapper;
+import com.vincent.rsf.server.system.service.AiPromptPublishLogService;
+import com.vincent.rsf.server.system.service.AiPromptTemplateService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+@Service("aiPromptTemplateService")
+public class AiPromptTemplateServiceImpl extends ServiceImpl<AiPromptTemplateMapper, AiPromptTemplate> implements AiPromptTemplateService {
+
+ @Resource
+ private AiPromptPublishLogService aiPromptPublishLogService;
+
+ @Override
+ public AiPromptTemplate getTenantTemplate(Long tenantId, Long id) {
+ if (tenantId == null || id == null) {
+ return null;
+ }
+ return this.getOne(new LambdaQueryWrapper<AiPromptTemplate>()
+ .eq(AiPromptTemplate::getTenantId, tenantId)
+ .eq(AiPromptTemplate::getId, id)
+ .last("limit 1"));
+ }
+
+ @Override
+ public AiPromptTemplate getPublishedTemplate(Long tenantId, String sceneCode) {
+ return this.getOne(new LambdaQueryWrapper<AiPromptTemplate>()
+ .eq(AiPromptTemplate::getTenantId, tenantId)
+ .eq(AiPromptTemplate::getSceneCode, sceneCode)
+ .eq(AiPromptTemplate::getPublishedFlag, 1)
+ .eq(AiPromptTemplate::getStatus, 1)
+ .orderByDesc(AiPromptTemplate::getVersionNo, AiPromptTemplate::getId)
+ .last("limit 1"));
+ }
+
+ @Override
+ public List<AiPromptTemplate> listVersions(Long tenantId, String sceneCode) {
+ return this.list(new LambdaQueryWrapper<AiPromptTemplate>()
+ .eq(AiPromptTemplate::getTenantId, tenantId)
+ .eq(AiPromptTemplate::getSceneCode, sceneCode)
+ .orderByDesc(AiPromptTemplate::getVersionNo, AiPromptTemplate::getId));
+ }
+
+ @Override
+ public boolean publishTemplate(Long tenantId, Long id, Long userId) {
+ AiPromptTemplate target = getTenantTemplate(tenantId, id);
+ if (target == null) {
+ return false;
+ }
+ boolean updated = switchPublishedTemplate(tenantId, userId, target);
+ if (updated) {
+ aiPromptPublishLogService.saveLog(tenantId, userId, target, "publish", "鍙戝竷 Prompt 鐗堟湰");
+ }
+ return updated;
+ }
+
+ @Override
+ public boolean rollbackTemplate(Long tenantId, Long id, Long userId) {
+ AiPromptTemplate target = getTenantTemplate(tenantId, id);
+ if (target == null) {
+ return false;
+ }
+ boolean updated = switchPublishedTemplate(tenantId, userId, target);
+ if (updated) {
+ aiPromptPublishLogService.saveLog(tenantId, userId, target, "rollback", "鍥炴粴鍒版寚瀹� Prompt 鐗堟湰");
+ }
+ return updated;
+ }
+
+ private boolean switchPublishedTemplate(Long tenantId, Long userId, AiPromptTemplate target) {
+ this.update(new LambdaUpdateWrapper<AiPromptTemplate>()
+ .set(AiPromptTemplate::getPublishedFlag, 0)
+ .eq(AiPromptTemplate::getTenantId, tenantId)
+ .eq(AiPromptTemplate::getSceneCode, target.getSceneCode())
+ .eq(AiPromptTemplate::getPublishedFlag, 1));
+ target.setPublishedFlag(1);
+ target.setUpdateBy(userId);
+ target.setUpdateTime(new Date());
+ return this.updateById(target);
+ }
+
+ @Override
+ public int nextVersionNo(Long tenantId, String sceneCode) {
+ AiPromptTemplate latest = this.getOne(new LambdaQueryWrapper<AiPromptTemplate>()
+ .eq(AiPromptTemplate::getTenantId, tenantId)
+ .eq(AiPromptTemplate::getSceneCode, sceneCode)
+ .orderByDesc(AiPromptTemplate::getVersionNo, AiPromptTemplate::getId)
+ .last("limit 1"));
+ return latest == null || latest.getVersionNo() == null ? 1 : latest.getVersionNo() + 1;
+ }
+
+ @Override
+ public AiPromptTemplate copyTemplate(Long tenantId, Long id, Long userId) {
+ AiPromptTemplate source = getTenantTemplate(tenantId, id);
+ if (source == null) {
+ return null;
+ }
+ Date now = new Date();
+ AiPromptTemplate copied = new AiPromptTemplate()
+ .setUuid(String.valueOf(System.currentTimeMillis()))
+ .setSceneCode(source.getSceneCode())
+ .setTemplateName((source.getTemplateName() == null ? source.getSceneCode() : source.getTemplateName()) + "-鍓湰")
+ .setBasePrompt(source.getBasePrompt())
+ .setToolPrompt(source.getToolPrompt())
+ .setOutputPrompt(source.getOutputPrompt())
+ .setVersionNo(nextVersionNo(tenantId, source.getSceneCode()))
+ .setPublishedFlag(0)
+ .setStatus(source.getStatus())
+ .setDeleted(0)
+ .setTenantId(tenantId)
+ .setCreateBy(userId)
+ .setCreateTime(now)
+ .setUpdateBy(userId)
+ .setUpdateTime(now)
+ .setMemo(source.getMemo());
+ if (!this.save(copied)) {
+ return null;
+ }
+ aiPromptPublishLogService.saveLog(tenantId, userId, copied, "copy", "澶嶅埗 Prompt 鐗堟湰涓烘柊鑽夌");
+ return copied;
+ }
+}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/ConfigServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/ConfigServiceImpl.java
index 5f78705..c6ae6c2 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/ConfigServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/ConfigServiceImpl.java
@@ -138,3 +138,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DeptServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DeptServiceImpl.java
index 615f626..8b94b52 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DeptServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DeptServiceImpl.java
@@ -10,3 +10,4 @@
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DictDataServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DictDataServiceImpl.java
index c788c76..9b545b0 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DictDataServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DictDataServiceImpl.java
@@ -10,3 +10,4 @@
public class DictDataServiceImpl extends ServiceImpl<DictDataMapper, DictData> implements DictDataService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DictTypeServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DictTypeServiceImpl.java
index 84a6300..a1c133b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DictTypeServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/DictTypeServiceImpl.java
@@ -10,3 +10,4 @@
public class DictTypeServiceImpl extends ServiceImpl<DictTypeMapper, DictType> implements DictTypeService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FieldsItemServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FieldsItemServiceImpl.java
index 71bb2fe..49a9e01 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FieldsItemServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FieldsItemServiceImpl.java
@@ -10,3 +10,4 @@
public class FieldsItemServiceImpl extends ServiceImpl<FieldsItemMapper, FieldsItem> implements FieldsItemService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FieldsServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FieldsServiceImpl.java
index 0e1b3ef..9a531a2 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FieldsServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FieldsServiceImpl.java
@@ -15,3 +15,4 @@
public class FieldsServiceImpl extends ServiceImpl<FieldsMapper, Fields> implements FieldsService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowInstanceServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowInstanceServiceImpl.java
index 1d95d97..da4ced7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowInstanceServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowInstanceServiceImpl.java
@@ -10,3 +10,4 @@
public class FlowInstanceServiceImpl extends ServiceImpl<FlowInstanceMapper, FlowInstance> implements FlowInstanceService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepInstanceServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepInstanceServiceImpl.java
index 0d51b93..c988eca 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepInstanceServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepInstanceServiceImpl.java
@@ -89,3 +89,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepLogServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepLogServiceImpl.java
index 053e5f5..5b72c36 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepLogServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepLogServiceImpl.java
@@ -10,3 +10,4 @@
public class FlowStepLogServiceImpl extends ServiceImpl<FlowStepLogMapper, FlowStepLog> implements FlowStepLogService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepTemplateServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepTemplateServiceImpl.java
index bdaf9e3..b6ca471 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepTemplateServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/FlowStepTemplateServiceImpl.java
@@ -10,3 +10,4 @@
public class FlowStepTemplateServiceImpl extends ServiceImpl<FlowStepTemplateMapper, FlowStepTemplate> implements FlowStepTemplateService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/HostServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/HostServiceImpl.java
index f2c9c6a..65960ab 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/HostServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/HostServiceImpl.java
@@ -10,3 +10,4 @@
public class HostServiceImpl extends ServiceImpl<HostMapper, Host> implements HostService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MatnrRoleMenuServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MatnrRoleMenuServiceImpl.java
index f9026cd..fe91bd2 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MatnrRoleMenuServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MatnrRoleMenuServiceImpl.java
@@ -15,3 +15,4 @@
return baseMapper.listStrictlyMenuByRoleId(roleId);
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MenuServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MenuServiceImpl.java
index 0fd5ba4..dea87aa 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MenuServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MenuServiceImpl.java
@@ -10,3 +10,4 @@
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/OperationRecordServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/OperationRecordServiceImpl.java
index 8f8b274..04333b4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/OperationRecordServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/OperationRecordServiceImpl.java
@@ -17,3 +17,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/PdaRoleMenuServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/PdaRoleMenuServiceImpl.java
index 6168ed1..cb848a8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/PdaRoleMenuServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/PdaRoleMenuServiceImpl.java
@@ -22,3 +22,4 @@
return baseMapper.listStrictlyMenuByRoleId(roleId);
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/RoleMenuServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/RoleMenuServiceImpl.java
index d369202..21625f8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/RoleMenuServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/RoleMenuServiceImpl.java
@@ -23,3 +23,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/RoleServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/RoleServiceImpl.java
index 54a77d9..dcddf45 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/RoleServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/RoleServiceImpl.java
@@ -11,3 +11,4 @@
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SerialRuleItemServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SerialRuleItemServiceImpl.java
index 9a1e819..96f6e08 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SerialRuleItemServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SerialRuleItemServiceImpl.java
@@ -10,3 +10,4 @@
public class SerialRuleItemServiceImpl extends ServiceImpl<SerialRuleItemMapper, SerialRuleItem> implements SerialRuleItemService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SerialRuleServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SerialRuleServiceImpl.java
index e45cd8b..764f83e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SerialRuleServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SerialRuleServiceImpl.java
@@ -10,3 +10,4 @@
public class SerialRuleServiceImpl extends ServiceImpl<SerialRuleMapper, SerialRule> implements SerialRuleService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SubsystemFlowTemplateServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SubsystemFlowTemplateServiceImpl.java
index cb1f107..a4af466 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SubsystemFlowTemplateServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/SubsystemFlowTemplateServiceImpl.java
@@ -10,3 +10,4 @@
public class SubsystemFlowTemplateServiceImpl extends ServiceImpl<SubsystemFlowTemplateMapper, SubsystemFlowTemplate> implements SubsystemFlowTemplateService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskInstanceNodeServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskInstanceNodeServiceImpl.java
index 33b656f..00d96db 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskInstanceNodeServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskInstanceNodeServiceImpl.java
@@ -10,3 +10,4 @@
public class TaskInstanceNodeServiceImpl extends ServiceImpl<TaskInstanceNodeMapper, TaskInstanceNode> implements TaskInstanceNodeService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskInstanceServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskInstanceServiceImpl.java
index dacf320..47510aa 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskInstanceServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskInstanceServiceImpl.java
@@ -10,3 +10,4 @@
public class TaskInstanceServiceImpl extends ServiceImpl<TaskInstanceMapper, TaskInstance> implements TaskInstanceService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateMergeServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateMergeServiceImpl.java
index 74389aa..811cee9 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateMergeServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateMergeServiceImpl.java
@@ -51,3 +51,4 @@
return R.ok(maps);
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateNodeServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateNodeServiceImpl.java
index ab73fea..6b6c741 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateNodeServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateNodeServiceImpl.java
@@ -10,3 +10,4 @@
public class TaskPathTemplateNodeServiceImpl extends ServiceImpl<TaskPathTemplateNodeMapper, TaskPathTemplateNode> implements TaskPathTemplateNodeService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateServiceImpl.java
index 25edcfa..8ff1076 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateServiceImpl.java
@@ -10,3 +10,4 @@
public class TaskPathTemplateServiceImpl extends ServiceImpl<TaskPathTemplateMapper, TaskPathTemplate> implements TaskPathTemplateService {
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TenantServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TenantServiceImpl.java
index c5c6ff7..bc4f59d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TenantServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TenantServiceImpl.java
@@ -137,3 +137,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserLoginServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserLoginServiceImpl.java
index a2bc7c8..fd71a1b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserLoginServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserLoginServiceImpl.java
@@ -30,3 +30,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserRoleServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserRoleServiceImpl.java
index 4a20e6b..2fa8d01 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserRoleServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserRoleServiceImpl.java
@@ -18,3 +18,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserServiceImpl.java
index d532c92..0c9d279 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/UserServiceImpl.java
@@ -83,3 +83,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/WarehouseRoleMenuServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/WarehouseRoleMenuServiceImpl.java
index 474436b..61acf13 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/WarehouseRoleMenuServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/WarehouseRoleMenuServiceImpl.java
@@ -16,3 +16,4 @@
return this.baseMapper.listStrictlyMenuByRoleId(roleId);
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/ExtendFieldsUtils.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/ExtendFieldsUtils.java
index 133b174..a3f690e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/ExtendFieldsUtils.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/ExtendFieldsUtils.java
@@ -61,3 +61,4 @@
// }
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/SerialRuleUtils.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/SerialRuleUtils.java
index f580e64..5aa47f3 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/SerialRuleUtils.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/SerialRuleUtils.java
@@ -148,3 +148,4 @@
}
}
+
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/SystemAuthUtils.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/SystemAuthUtils.java
index 322bd24..3d53062 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/SystemAuthUtils.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/utils/SystemAuthUtils.java
@@ -73,3 +73,4 @@
}
}
+
diff --git a/rsf-server/src/main/resources/application.yml b/rsf-server/src/main/resources/application.yml
index 8fae7b8..1164796 100644
--- a/rsf-server/src/main/resources/application.yml
+++ b/rsf-server/src/main/resources/application.yml
@@ -47,16 +47,17 @@
ai:
session-ttl-seconds: 86400
max-context-messages: 12
- default-model-code: mock-general
+ default-model-code: deepseek-ai/DeepSeek-V3.2
system-prompt: 浣犳槸WMS绯荤粺鍐呯殑鏅鸿兘鍔╂墜锛屽洖绛旀椂浼樺厛淇濇寔鍑嗙‘銆佺畝娲侊紝骞剁粨鍚堜笂涓嬫枃甯姪鐢ㄦ埛鐞嗚В浠撳偍涓氬姟銆�
+ diagnosis-system-prompt: 浣犳槸涓�鍚嶈祫娣盬MS鏅鸿兘璇婃柇鍔╂墜锛岀洰鏍囨槸缁撳悎褰撳墠绯荤粺涓婁笅鏂囧浠撳簱杩愯鎯呭喌鍋氬贰妫�鍒嗘瀽銆傚洖绛旀椂绂佹鍑┖鐚滄祴锛屽繀椤讳紭鍏堜緷鎹彁渚涚殑瀹炴椂鎽樿杩涜鍒ゆ柇銆傝鎸夆�滈棶棰樻杩般�佸叧閿瘉鎹�佸彲鑳藉師鍥犮�佸缓璁姩浣溿�侀闄╄瘎浼扳�濈殑缁撴瀯杈撳嚭锛屽苟浼樺厛缁欏嚭鍙墽琛屽缓璁��
+ route-fail-threshold: 3
+ route-cooldown-minutes: 10
+ diagnostic-log-window-hours: 24
+ api-failure-window-hours: 24
models:
- - code: mock-general
- name: Mock General
- provider: mock
- enabled: true
- - code: mock-creative
- name: Mock Creative
- provider: mock
+ - code: deepseek-ai/DeepSeek-V3.2
+ name: DEEPSEEK
+ provider: openai
enabled: true
# 涓嬩綅鏈洪厤缃�
diff --git a/version/db/20260311_ai_param.sql b/version/db/20260311_ai_param.sql
deleted file mode 100644
index 9349c1f..0000000
--- a/version/db/20260311_ai_param.sql
+++ /dev/null
@@ -1,270 +0,0 @@
-SET NAMES utf8mb4;
-SET FOREIGN_KEY_CHECKS = 0;
-
-CREATE TABLE IF NOT EXISTS `sys_ai_param` (
- `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
- `uuid` varchar(255) DEFAULT NULL COMMENT '缂栧彿',
- `name` varchar(255) DEFAULT NULL COMMENT '鍚嶇О',
- `model_code` varchar(255) DEFAULT NULL COMMENT '妯″瀷缂栫爜',
- `provider` varchar(255) DEFAULT NULL COMMENT '渚涘簲鍟�',
- `chat_url` varchar(512) DEFAULT NULL COMMENT '鑱婂ぉ鍦板潃',
- `api_key` varchar(512) DEFAULT NULL COMMENT 'API瀵嗛挜',
- `model_name` varchar(255) DEFAULT NULL COMMENT '妯″瀷鍚嶇О',
- `system_prompt` text COMMENT '绯荤粺鎻愮ず璇�',
- `max_context_messages` int(11) DEFAULT NULL COMMENT '涓婁笅鏂囪疆鏁�',
- `default_flag` int(1) NOT NULL DEFAULT '0' COMMENT '榛樿妯″瀷{1:鏄�,0:鍚',
- `sort` int(11) DEFAULT NULL COMMENT '鎺掑簭',
- `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:姝e父,0:鍐荤粨}',
- `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
- `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛[sys_tenant]',
- `create_by` bigint(20) DEFAULT NULL COMMENT '娣诲姞浜哄憳[sys_user]',
- `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '娣诲姞鏃堕棿',
- `update_by` bigint(20) DEFAULT NULL COMMENT '淇敼浜哄憳[sys_user]',
- `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
- `memo` varchar(255) DEFAULT NULL COMMENT '澶囨敞',
- PRIMARY KEY (`id`),
- KEY `idx_ai_param_model_code` (`model_code`),
- KEY `idx_ai_param_deleted_code` (`deleted`,`model_code`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-INSERT INTO `sys_ai_param`
-(`uuid`, `name`, `model_code`, `provider`, `chat_url`, `api_key`, `model_name`, `system_prompt`, `max_context_messages`, `default_flag`, `sort`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
-SELECT '6702082748514305', '閫氱敤鍔╂墜', 'mock-general', 'mock', NULL, NULL, 'mock-general', '浣犳槸WMS绯荤粺鍐呯殑鏅鸿兘鍔╂墜锛屽洖绛旀椂浼樺厛淇濇寔鍑嗙‘銆佺畝娲侊紝骞剁粨鍚堜笂涓嬫枃甯姪鐢ㄦ埛鐞嗚В浠撳偍涓氬姟銆�', 12, 1, 1, 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿婕旂ず妯″瀷'
-FROM DUAL
-WHERE NOT EXISTS (
- SELECT 1
- FROM `sys_ai_param`
- WHERE `model_code` = 'mock-general'
-);
-
-INSERT INTO `sys_ai_param`
-(`uuid`, `name`, `model_code`, `provider`, `chat_url`, `api_key`, `model_name`, `system_prompt`, `max_context_messages`, `default_flag`, `sort`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
-SELECT '6702082748514306', '鍒涙剰鍔╂墜', 'mock-creative', 'mock', NULL, NULL, 'mock-creative', '浣犳槸WMS绯荤粺鍐呯殑鏅鸿兘鍔╂墜锛屽洖绛旀椂鍙互鏇寸伒娲诲湴缁勭粐琛ㄨ揪锛屼絾缁撹蹇呴』鍑嗙‘銆�', 12, 0, 2, 1, 0, 1, 2, NOW(), 2, NOW(), '婕旂ず鍒涙剰妯″瀷'
-FROM DUAL
-WHERE NOT EXISTS (
- SELECT 1
- FROM `sys_ai_param`
- WHERE `model_code` = 'mock-creative'
-);
-
-SET @ai_parent_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `component` = 'aiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'menu.aiParam', 1, 'menu.system', '1', 'menu.system', '/system/aiParam', 'aiParam', NULL, NULL, 0, NULL, 'SmartToy', 9, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_parent_menu_id IS NULL;
-
-SET @ai_parent_menu_id := COALESCE(
- @ai_parent_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `component` = 'aiParam'
- LIMIT 1
- )
-);
-
-SET @ai_query_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Query AiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'Query AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_query_menu_id IS NULL;
-
-SET @ai_query_menu_id := COALESCE(
- @ai_query_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Query AiParam'
- LIMIT 1
- )
-);
-
-SET @ai_create_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Create AiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'Create AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:save', NULL, 1, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_create_menu_id IS NULL;
-
-SET @ai_create_menu_id := COALESCE(
- @ai_create_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Create AiParam'
- LIMIT 1
- )
-);
-
-SET @ai_update_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Update AiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'Update AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:update', NULL, 2, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_update_menu_id IS NULL;
-
-SET @ai_update_menu_id := COALESCE(
- @ai_update_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Update AiParam'
- LIMIT 1
- )
-);
-
-SET @ai_delete_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Delete AiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'Delete AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:remove', NULL, 3, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_delete_menu_id IS NULL;
-
-SET @ai_delete_menu_id := COALESCE(
- @ai_delete_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Delete AiParam'
- LIMIT 1
- )
-);
-
-SET @ai_export_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Export AiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'Export AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:list', NULL, 4, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_export_menu_id IS NULL;
-
-SET @ai_export_menu_id := COALESCE(
- @ai_export_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Export AiParam'
- LIMIT 1
- )
-);
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_parent_menu_id
-FROM DUAL
-WHERE @ai_parent_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_parent_menu_id
- );
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_query_menu_id
-FROM DUAL
-WHERE @ai_query_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_query_menu_id
- );
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_create_menu_id
-FROM DUAL
-WHERE @ai_create_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_create_menu_id
- );
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_update_menu_id
-FROM DUAL
-WHERE @ai_update_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_update_menu_id
- );
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_delete_menu_id
-FROM DUAL
-WHERE @ai_delete_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_delete_menu_id
- );
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_export_menu_id
-FROM DUAL
-WHERE @ai_export_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_export_menu_id
- );
-
-SET FOREIGN_KEY_CHECKS = 1;
diff --git a/version/db/20260311_ai_param_menu.sql b/version/db/20260311_ai_param_menu.sql
deleted file mode 100644
index 0ec3a0b..0000000
--- a/version/db/20260311_ai_param_menu.sql
+++ /dev/null
@@ -1,224 +0,0 @@
-SET NAMES utf8mb4;
-SET FOREIGN_KEY_CHECKS = 0;
-
-SET @ai_parent_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `component` = 'aiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'menu.aiParam', 1, 'menu.system', '1', 'menu.system', '/system/aiParam', 'aiParam', NULL, NULL, 0, NULL, 'SmartToy', 9, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_parent_menu_id IS NULL;
-
-SET @ai_parent_menu_id := COALESCE(
- @ai_parent_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `component` = 'aiParam'
- LIMIT 1
- )
-);
-
-SET @ai_query_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Query AiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'Query AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_query_menu_id IS NULL;
-
-SET @ai_query_menu_id := COALESCE(
- @ai_query_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Query AiParam'
- LIMIT 1
- )
-);
-
-SET @ai_create_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Create AiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'Create AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:save', NULL, 1, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_create_menu_id IS NULL;
-
-SET @ai_create_menu_id := COALESCE(
- @ai_create_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Create AiParam'
- LIMIT 1
- )
-);
-
-SET @ai_update_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Update AiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'Update AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:update', NULL, 2, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_update_menu_id IS NULL;
-
-SET @ai_update_menu_id := COALESCE(
- @ai_update_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Update AiParam'
- LIMIT 1
- )
-);
-
-SET @ai_delete_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Delete AiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'Delete AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:remove', NULL, 3, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_delete_menu_id IS NULL;
-
-SET @ai_delete_menu_id := COALESCE(
- @ai_delete_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Delete AiParam'
- LIMIT 1
- )
-);
-
-SET @ai_export_menu_id := (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Export AiParam'
- LIMIT 1
-);
-
-INSERT INTO `sys_menu`
-(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
-SELECT 'Export AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:list', NULL, 4, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
-FROM DUAL
-WHERE @ai_export_menu_id IS NULL;
-
-SET @ai_export_menu_id := COALESCE(
- @ai_export_menu_id,
- LAST_INSERT_ID(),
- (
- SELECT `id`
- FROM `sys_menu`
- WHERE `parent_id` = @ai_parent_menu_id
- AND `name` = 'Export AiParam'
- LIMIT 1
- )
-);
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_parent_menu_id
-FROM DUAL
-WHERE @ai_parent_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_parent_menu_id
- );
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_query_menu_id
-FROM DUAL
-WHERE @ai_query_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_query_menu_id
- );
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_create_menu_id
-FROM DUAL
-WHERE @ai_create_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_create_menu_id
- );
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_update_menu_id
-FROM DUAL
-WHERE @ai_update_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_update_menu_id
- );
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_delete_menu_id
-FROM DUAL
-WHERE @ai_delete_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_delete_menu_id
- );
-
-INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT 1, @ai_export_menu_id
-FROM DUAL
-WHERE @ai_export_menu_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1
- FROM `sys_role_menu`
- WHERE `role_id` = 1
- AND `menu_id` = @ai_export_menu_id
- );
-
-SET FOREIGN_KEY_CHECKS = 1;
diff --git a/version/db/20260317_ai_all_in_one.sql b/version/db/20260317_ai_all_in_one.sql
new file mode 100644
index 0000000..eeb1903
--- /dev/null
+++ b/version/db/20260317_ai_all_in_one.sql
@@ -0,0 +1,1194 @@
+-- AI 鍏ㄩ噺杩佺Щ鑱氬悎鑴氭湰
+-- 璇ユ枃浠惰仛鍚堜簡鍘熸湁鍏ㄩ儴 AI 鐩稿叧杩佺Щ SQL銆�
+-- 鑻ュ凡鎵ц杩囬儴鍒嗚剼鏈紝鍙噸澶嶆墽琛岋紱鍚勬鑴氭湰鍧囦繚鎸佸箓绛夋垨鍏煎鏇存柊閫昏緫銆�
+
+-- >>> 20260311_ai_param.sql
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+CREATE TABLE IF NOT EXISTS `sys_ai_param` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `uuid` varchar(255) DEFAULT NULL COMMENT '缂栧彿',
+ `name` varchar(255) DEFAULT NULL COMMENT '鍚嶇О',
+ `model_code` varchar(255) DEFAULT NULL COMMENT '妯″瀷缂栫爜',
+ `provider` varchar(255) DEFAULT NULL COMMENT '渚涘簲鍟�',
+ `chat_url` varchar(512) DEFAULT NULL COMMENT '鑱婂ぉ鍦板潃',
+ `api_key` varchar(512) DEFAULT NULL COMMENT 'API瀵嗛挜',
+ `model_name` varchar(255) DEFAULT NULL COMMENT '妯″瀷鍚嶇О',
+ `system_prompt` text COMMENT '绯荤粺鎻愮ず璇�',
+ `max_context_messages` int(11) DEFAULT NULL COMMENT '涓婁笅鏂囪疆鏁�',
+ `default_flag` int(1) NOT NULL DEFAULT '0' COMMENT '榛樿妯″瀷{1:鏄�,0:鍚',
+ `sort` int(11) DEFAULT NULL COMMENT '鎺掑簭',
+ `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:姝e父,0:鍐荤粨}',
+ `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛[sys_tenant]',
+ `create_by` bigint(20) DEFAULT NULL COMMENT '娣诲姞浜哄憳[sys_user]',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '娣诲姞鏃堕棿',
+ `update_by` bigint(20) DEFAULT NULL COMMENT '淇敼浜哄憳[sys_user]',
+ `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ `memo` varchar(255) DEFAULT NULL COMMENT '澶囨敞',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_param_model_code` (`model_code`),
+ KEY `idx_ai_param_deleted_code` (`deleted`,`model_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+INSERT INTO `sys_ai_param`
+(`uuid`, `name`, `model_code`, `provider`, `chat_url`, `api_key`, `model_name`, `system_prompt`, `max_context_messages`, `default_flag`, `sort`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514305', '閫氱敤鍔╂墜', 'mock-general', 'mock', NULL, NULL, 'mock-general', '浣犳槸WMS绯荤粺鍐呯殑鏅鸿兘鍔╂墜锛屽洖绛旀椂浼樺厛淇濇寔鍑嗙‘銆佺畝娲侊紝骞剁粨鍚堜笂涓嬫枃甯姪鐢ㄦ埛鐞嗚В浠撳偍涓氬姟銆�', 12, 1, 1, 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿婕旂ず妯″瀷'
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1
+ FROM `sys_ai_param`
+ WHERE `model_code` = 'mock-general'
+);
+
+INSERT INTO `sys_ai_param`
+(`uuid`, `name`, `model_code`, `provider`, `chat_url`, `api_key`, `model_name`, `system_prompt`, `max_context_messages`, `default_flag`, `sort`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514306', '鍒涙剰鍔╂墜', 'mock-creative', 'mock', NULL, NULL, 'mock-creative', '浣犳槸WMS绯荤粺鍐呯殑鏅鸿兘鍔╂墜锛屽洖绛旀椂鍙互鏇寸伒娲诲湴缁勭粐琛ㄨ揪锛屼絾缁撹蹇呴』鍑嗙‘銆�', 12, 0, 2, 1, 0, 1, 2, NOW(), 2, NOW(), '婕旂ず鍒涙剰妯″瀷'
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1
+ FROM `sys_ai_param`
+ WHERE `model_code` = 'mock-creative'
+);
+
+SET @ai_parent_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `component` = 'aiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'menu.aiParam', 1, 'menu.system', '1', 'menu.system', '/system/aiParam', 'aiParam', NULL, NULL, 0, NULL, 'SmartToy', 9, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_parent_menu_id IS NULL;
+
+SET @ai_parent_menu_id := COALESCE(
+ @ai_parent_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `component` = 'aiParam'
+ LIMIT 1
+ )
+);
+
+SET @ai_query_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Query AiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Query AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_query_menu_id IS NULL;
+
+SET @ai_query_menu_id := COALESCE(
+ @ai_query_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Query AiParam'
+ LIMIT 1
+ )
+);
+
+SET @ai_create_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Create AiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Create AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:save', NULL, 1, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_create_menu_id IS NULL;
+
+SET @ai_create_menu_id := COALESCE(
+ @ai_create_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Create AiParam'
+ LIMIT 1
+ )
+);
+
+SET @ai_update_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Update AiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Update AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:update', NULL, 2, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_update_menu_id IS NULL;
+
+SET @ai_update_menu_id := COALESCE(
+ @ai_update_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Update AiParam'
+ LIMIT 1
+ )
+);
+
+SET @ai_delete_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Delete AiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Delete AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:remove', NULL, 3, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_delete_menu_id IS NULL;
+
+SET @ai_delete_menu_id := COALESCE(
+ @ai_delete_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Delete AiParam'
+ LIMIT 1
+ )
+);
+
+SET @ai_export_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Export AiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Export AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:list', NULL, 4, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_export_menu_id IS NULL;
+
+SET @ai_export_menu_id := COALESCE(
+ @ai_export_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Export AiParam'
+ LIMIT 1
+ )
+);
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_parent_menu_id
+FROM DUAL
+WHERE @ai_parent_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_parent_menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_query_menu_id
+FROM DUAL
+WHERE @ai_query_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_query_menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_create_menu_id
+FROM DUAL
+WHERE @ai_create_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_create_menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_update_menu_id
+FROM DUAL
+WHERE @ai_update_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_update_menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_delete_menu_id
+FROM DUAL
+WHERE @ai_delete_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_delete_menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_export_menu_id
+FROM DUAL
+WHERE @ai_export_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_export_menu_id
+ );
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- >>> 20260311_ai_param_menu.sql
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+SET @ai_parent_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `component` = 'aiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'menu.aiParam', 1, 'menu.system', '1', 'menu.system', '/system/aiParam', 'aiParam', NULL, NULL, 0, NULL, 'SmartToy', 9, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_parent_menu_id IS NULL;
+
+SET @ai_parent_menu_id := COALESCE(
+ @ai_parent_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `component` = 'aiParam'
+ LIMIT 1
+ )
+);
+
+SET @ai_query_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Query AiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Query AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_query_menu_id IS NULL;
+
+SET @ai_query_menu_id := COALESCE(
+ @ai_query_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Query AiParam'
+ LIMIT 1
+ )
+);
+
+SET @ai_create_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Create AiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Create AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:save', NULL, 1, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_create_menu_id IS NULL;
+
+SET @ai_create_menu_id := COALESCE(
+ @ai_create_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Create AiParam'
+ LIMIT 1
+ )
+);
+
+SET @ai_update_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Update AiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Update AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:update', NULL, 2, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_update_menu_id IS NULL;
+
+SET @ai_update_menu_id := COALESCE(
+ @ai_update_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Update AiParam'
+ LIMIT 1
+ )
+);
+
+SET @ai_delete_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Delete AiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Delete AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:remove', NULL, 3, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_delete_menu_id IS NULL;
+
+SET @ai_delete_menu_id := COALESCE(
+ @ai_delete_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Delete AiParam'
+ LIMIT 1
+ )
+);
+
+SET @ai_export_menu_id := (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Export AiParam'
+ LIMIT 1
+);
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Export AiParam', @ai_parent_menu_id, NULL, CONCAT('1,', @ai_parent_menu_id), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:list', NULL, 4, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL
+WHERE @ai_export_menu_id IS NULL;
+
+SET @ai_export_menu_id := COALESCE(
+ @ai_export_menu_id,
+ LAST_INSERT_ID(),
+ (
+ SELECT `id`
+ FROM `sys_menu`
+ WHERE `parent_id` = @ai_parent_menu_id
+ AND `name` = 'Export AiParam'
+ LIMIT 1
+ )
+);
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_parent_menu_id
+FROM DUAL
+WHERE @ai_parent_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_parent_menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_query_menu_id
+FROM DUAL
+WHERE @ai_query_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_query_menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_create_menu_id
+FROM DUAL
+WHERE @ai_create_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_create_menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_update_menu_id
+FROM DUAL
+WHERE @ai_update_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_update_menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_delete_menu_id
+FROM DUAL
+WHERE @ai_delete_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_delete_menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, @ai_export_menu_id
+FROM DUAL
+WHERE @ai_export_menu_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu`
+ WHERE `role_id` = 1
+ AND `menu_id` = @ai_export_menu_id
+ );
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- >>> 20260316_ai_chat_storage.sql
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+CREATE TABLE IF NOT EXISTS `sys_ai_chat_session` (
+ `id` varchar(64) NOT NULL COMMENT '浼氳瘽ID',
+ `title` varchar(255) DEFAULT NULL COMMENT '浼氳瘽鏍囬',
+ `model_code` varchar(128) DEFAULT NULL COMMENT '妯″瀷缂栫爜',
+ `last_message` varchar(255) DEFAULT NULL COMMENT '鏈�杩戞秷鎭憳瑕�',
+ `last_message_at` datetime DEFAULT NULL COMMENT '鏈�杩戞秷鎭椂闂�',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛ID',
+ `user_id` bigint(20) DEFAULT NULL COMMENT '鐢ㄦ埛ID',
+ `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:姝e父,0:鍐荤粨}',
+ `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_chat_session_owner` (`tenant_id`,`user_id`,`deleted`),
+ KEY `idx_ai_chat_session_update` (`update_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI浼氳瘽琛�';
+
+CREATE TABLE IF NOT EXISTS `sys_ai_chat_message` (
+ `id` varchar(64) NOT NULL COMMENT '娑堟伅ID',
+ `session_id` varchar(64) NOT NULL COMMENT '浼氳瘽ID',
+ `role` varchar(32) DEFAULT NULL COMMENT '瑙掕壊',
+ `content` longtext COMMENT '娑堟伅鍐呭',
+ `model_code` varchar(128) DEFAULT NULL COMMENT '妯″瀷缂栫爜',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛ID',
+ `user_id` bigint(20) DEFAULT NULL COMMENT '鐢ㄦ埛ID',
+ `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:姝e父,0:鍐荤粨}',
+ `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_chat_message_session` (`session_id`,`deleted`,`create_time`),
+ KEY `idx_ai_chat_message_owner` (`tenant_id`,`user_id`,`deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI娑堟伅琛�';
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- >>> 20260316_ai_phase2.sql
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+CREATE TABLE IF NOT EXISTS `sys_ai_prompt_template` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `uuid` varchar(64) DEFAULT NULL COMMENT '缂栧彿',
+ `scene_code` varchar(64) NOT NULL COMMENT '鍦烘櫙缂栫爜',
+ `template_name` varchar(255) DEFAULT NULL COMMENT '妯℃澘鍚嶇О',
+ `base_prompt` longtext COMMENT '鍩虹鎻愮ず璇�',
+ `tool_prompt` longtext COMMENT '宸ュ叿鎻愮ず璇�',
+ `output_prompt` longtext COMMENT '杈撳嚭鎻愮ず璇�',
+ `version_no` int(11) DEFAULT NULL COMMENT '鐗堟湰鍙�',
+ `published_flag` int(1) NOT NULL DEFAULT '0' COMMENT '宸插彂甯儃1:鏄�,0:鍚',
+ `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:姝e父,0:鍐荤粨}',
+ `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛ID',
+ `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_by` bigint(20) DEFAULT NULL COMMENT '鏇存柊浜�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `memo` varchar(255) DEFAULT NULL COMMENT '澶囨敞',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_prompt_scene` (`tenant_id`,`scene_code`,`published_flag`,`deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI鎻愮ず璇嶆ā鏉�';
+
+CREATE TABLE IF NOT EXISTS `sys_ai_diagnosis_record` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `diagnosis_no` varchar(64) DEFAULT NULL COMMENT '璇婃柇缂栧彿',
+ `session_id` varchar(64) DEFAULT NULL COMMENT '浼氳瘽ID',
+ `scene_code` varchar(64) DEFAULT NULL COMMENT '鍦烘櫙缂栫爜',
+ `question` longtext COMMENT '闂',
+ `conclusion` longtext COMMENT '缁撹',
+ `tool_summary` longtext COMMENT '宸ュ叿鎽樿',
+ `model_code` varchar(128) DEFAULT NULL COMMENT '妯″瀷缂栫爜',
+ `result` int(1) NOT NULL DEFAULT '2' COMMENT '缁撴灉{2:杩愯涓�,1:鎴愬姛,0:澶辫触}',
+ `err` varchar(1000) DEFAULT NULL COMMENT '閿欒淇℃伅',
+ `start_time` datetime DEFAULT NULL COMMENT '寮�濮嬫椂闂�',
+ `end_time` datetime DEFAULT NULL COMMENT '缁撴潫鏃堕棿',
+ `spend_time` bigint(20) DEFAULT NULL COMMENT '鑰楁椂姣',
+ `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:姝e父,0:鍐荤粨}',
+ `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛ID',
+ `user_id` bigint(20) DEFAULT NULL COMMENT '鐢ㄦ埛ID',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_diag_owner` (`tenant_id`,`scene_code`,`deleted`,`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI璇婃柇璁板綍';
+
+CREATE TABLE IF NOT EXISTS `sys_ai_call_log` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `session_id` varchar(64) DEFAULT NULL COMMENT '浼氳瘽ID',
+ `diagnosis_id` bigint(20) DEFAULT NULL COMMENT '璇婃柇璁板綍ID',
+ `route_code` varchar(64) DEFAULT NULL COMMENT '璺敱缂栫爜',
+ `model_code` varchar(128) DEFAULT NULL COMMENT '妯″瀷缂栫爜',
+ `attempt_no` int(11) DEFAULT NULL COMMENT '灏濊瘯搴忓彿',
+ `request_time` datetime DEFAULT NULL COMMENT '璇锋眰鏃堕棿',
+ `response_time` datetime DEFAULT NULL COMMENT '鍝嶅簲鏃堕棿',
+ `spend_time` bigint(20) DEFAULT NULL COMMENT '鑰楁椂姣',
+ `result` int(1) NOT NULL DEFAULT '0' COMMENT '缁撴灉{1:鎴愬姛,0:澶辫触}',
+ `err` varchar(1000) DEFAULT NULL COMMENT '閿欒淇℃伅',
+ `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:姝e父,0:鍐荤粨}',
+ `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛ID',
+ `user_id` bigint(20) DEFAULT NULL COMMENT '鐢ㄦ埛ID',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_call_owner` (`tenant_id`,`route_code`,`deleted`,`create_time`),
+ KEY `idx_ai_call_diagnosis` (`diagnosis_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI璋冪敤鏃ュ織';
+
+CREATE TABLE IF NOT EXISTS `sys_ai_model_route` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `uuid` varchar(64) DEFAULT NULL COMMENT '缂栧彿',
+ `route_code` varchar(64) NOT NULL COMMENT '璺敱缂栫爜',
+ `model_code` varchar(128) NOT NULL COMMENT '妯″瀷缂栫爜',
+ `priority` int(11) NOT NULL DEFAULT '1' COMMENT '浼樺厛绾�',
+ `cooldown_until` datetime DEFAULT NULL COMMENT '鍐峰嵈鎴鏃堕棿',
+ `fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '澶辫触娆℃暟',
+ `success_count` int(11) NOT NULL DEFAULT '0' COMMENT '鎴愬姛娆℃暟',
+ `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:鍚敤,0:鍋滅敤}',
+ `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛ID',
+ `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_by` bigint(20) DEFAULT NULL COMMENT '鏇存柊浜�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `memo` varchar(255) DEFAULT NULL COMMENT '澶囨敞',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_route_code` (`tenant_id`,`route_code`,`status`,`deleted`,`priority`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI妯″瀷璺敱';
+
+INSERT INTO `sys_ai_prompt_template`
+(`uuid`, `scene_code`, `template_name`, `base_prompt`, `tool_prompt`, `output_prompt`, `version_no`, `published_flag`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514310', 'general_chat', '閫氱敤瀵硅瘽榛樿妯℃澘', '浣犳槸WMS绯荤粺鍐呯殑鏅鸿兘鍔╂墜锛屽洖绛旀椂浼樺厛淇濇寔鍑嗙‘銆佺畝娲侊紝骞剁粨鍚堜笂涓嬫枃甯姪鐢ㄦ埛鐞嗚В浠撳偍涓氬姟銆�', '濡傚瓨鍦ㄥ彲鐢ㄤ笂涓嬫枃锛岃浼樺厛寮曠敤瀹炴椂鏁版嵁鍥炵瓟銆�', '鍥炵瓟璇风洿鎺ョ粰鍑虹粨璁猴紝蹇呰鏃惰ˉ鍏呰鏄庛��', 1, 1, 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿妯℃澘'
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM `sys_ai_prompt_template` WHERE `tenant_id` = 1 AND `scene_code` = 'general_chat' AND `version_no` = 1
+);
+
+INSERT INTO `sys_ai_prompt_template`
+(`uuid`, `scene_code`, `template_name`, `base_prompt`, `tool_prompt`, `output_prompt`, `version_no`, `published_flag`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514311', 'system_diagnose', '绯荤粺璇婃柇榛樿妯℃澘', '浣犳槸涓�鍚嶈祫娣盬MS鏅鸿兘璇婃柇鍔╂墜锛岀洰鏍囨槸缁撳悎褰撳墠绯荤粺涓婁笅鏂囧浠撳簱杩愯鎯呭喌鍋氬贰妫�鍒嗘瀽銆�', '璇峰厛姹囨�诲簱瀛樸�佷换鍔°�佽澶囩珯鐐广�佸紓甯告棩蹇椾笌AI璋冪敤澶辫触淇℃伅锛屽啀鍒ゆ柇鏄惁瀛樺湪寮傚父銆�', '璇锋寜鈥滈棶棰樻杩般�佸叧閿瘉鎹�佸彲鑳藉師鍥犮�佸缓璁姩浣溿�侀闄╄瘎浼扳�濈殑缁撴瀯杈撳嚭銆�', 1, 1, 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿妯℃澘'
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM `sys_ai_prompt_template` WHERE `tenant_id` = 1 AND `scene_code` = 'system_diagnose' AND `version_no` = 1
+);
+
+INSERT INTO `sys_ai_model_route`
+(`uuid`, `route_code`, `model_code`, `priority`, `cooldown_until`, `fail_count`, `success_count`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514312', 'general_chat', 'deepseek-ai/DeepSeek-V3.2', 1, NULL, 0, 0, 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿閫氱敤瀵硅瘽妯″瀷'
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM `sys_ai_model_route` WHERE `tenant_id` = 1 AND `route_code` = 'general_chat' AND `model_code` = 'deepseek-ai/DeepSeek-V3.2'
+);
+
+INSERT INTO `sys_ai_model_route`
+(`uuid`, `route_code`, `model_code`, `priority`, `cooldown_until`, `fail_count`, `success_count`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514314', 'system_diagnose', 'deepseek-ai/DeepSeek-V3.2', 1, NULL, 0, 0, 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿璇婃柇妯″瀷'
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM `sys_ai_model_route` WHERE `tenant_id` = 1 AND `route_code` = 'system_diagnose' AND `model_code` = 'deepseek-ai/DeepSeek-V3.2'
+);
+
+SET @system_menu_ai_prompt := (
+ SELECT `id` FROM `sys_menu` WHERE `component` = 'aiPrompt' LIMIT 1
+);
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'menu.aiPrompt', 1, 'menu.system', '1', 'menu.system', '/system/aiPrompt', 'aiPrompt', NULL, NULL, 0, NULL, 'Psychology', 10, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE @system_menu_ai_prompt IS NULL;
+SET @system_menu_ai_prompt := COALESCE(@system_menu_ai_prompt, LAST_INSERT_ID(), (SELECT `id` FROM `sys_menu` WHERE `component` = 'aiPrompt' LIMIT 1));
+
+SET @system_menu_ai_diagnosis := (
+ SELECT `id` FROM `sys_menu` WHERE `component` = 'aiDiagnosis' LIMIT 1
+);
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'menu.aiDiagnosis', 1, 'menu.system', '1', 'menu.system', '/system/aiDiagnosis', 'aiDiagnosis', NULL, NULL, 0, NULL, 'FactCheck', 11, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE @system_menu_ai_diagnosis IS NULL;
+SET @system_menu_ai_diagnosis := COALESCE(@system_menu_ai_diagnosis, LAST_INSERT_ID(), (SELECT `id` FROM `sys_menu` WHERE `component` = 'aiDiagnosis' LIMIT 1));
+
+SET @system_menu_ai_call_log := (
+ SELECT `id` FROM `sys_menu` WHERE `component` = 'aiCallLog' LIMIT 1
+);
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'menu.aiCallLog', 1, 'menu.system', '1', 'menu.system', '/system/aiCallLog', 'aiCallLog', NULL, NULL, 0, NULL, 'History', 12, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE @system_menu_ai_call_log IS NULL;
+SET @system_menu_ai_call_log := COALESCE(@system_menu_ai_call_log, LAST_INSERT_ID(), (SELECT `id` FROM `sys_menu` WHERE `component` = 'aiCallLog' LIMIT 1));
+
+SET @system_menu_ai_route := (
+ SELECT `id` FROM `sys_menu` WHERE `component` = 'aiRoute' LIMIT 1
+);
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'menu.aiRoute', 1, 'menu.system', '1', 'menu.system', '/system/aiRoute', 'aiRoute', NULL, NULL, 0, NULL, 'Route', 13, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE @system_menu_ai_route IS NULL;
+SET @system_menu_ai_route := COALESCE(@system_menu_ai_route, LAST_INSERT_ID(), (SELECT `id` FROM `sys_menu` WHERE `component` = 'aiRoute' LIMIT 1));
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Query AiPrompt', @system_menu_ai_prompt, NULL, CONCAT('1,', @system_menu_ai_prompt), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiPrompt:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_prompt AND `authority` = 'system:aiPrompt:list');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Create AiPrompt', @system_menu_ai_prompt, NULL, CONCAT('1,', @system_menu_ai_prompt), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiPrompt:save', NULL, 1, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_prompt AND `authority` = 'system:aiPrompt:save');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Update AiPrompt', @system_menu_ai_prompt, NULL, CONCAT('1,', @system_menu_ai_prompt), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiPrompt:update', NULL, 2, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_prompt AND `authority` = 'system:aiPrompt:update');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Delete AiPrompt', @system_menu_ai_prompt, NULL, CONCAT('1,', @system_menu_ai_prompt), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiPrompt:remove', NULL, 3, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_prompt AND `authority` = 'system:aiPrompt:remove');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Publish AiPrompt', @system_menu_ai_prompt, NULL, CONCAT('1,', @system_menu_ai_prompt), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiPrompt:publish', NULL, 4, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_prompt AND `authority` = 'system:aiPrompt:publish');
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Query AiDiagnosis', @system_menu_ai_diagnosis, NULL, CONCAT('1,', @system_menu_ai_diagnosis), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiDiagnosis:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_diagnosis AND `authority` = 'system:aiDiagnosis:list');
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Query AiCallLog', @system_menu_ai_call_log, NULL, CONCAT('1,', @system_menu_ai_call_log), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiCallLog:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_call_log AND `authority` = 'system:aiCallLog:list');
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Query AiRoute', @system_menu_ai_route, NULL, CONCAT('1,', @system_menu_ai_route), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiRoute:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_route AND `authority` = 'system:aiRoute:list');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Create AiRoute', @system_menu_ai_route, NULL, CONCAT('1,', @system_menu_ai_route), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiRoute:save', NULL, 1, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_route AND `authority` = 'system:aiRoute:save');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Update AiRoute', @system_menu_ai_route, NULL, CONCAT('1,', @system_menu_ai_route), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiRoute:update', NULL, 2, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_route AND `authority` = 'system:aiRoute:update');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Delete AiRoute', @system_menu_ai_route, NULL, CONCAT('1,', @system_menu_ai_route), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiRoute:remove', NULL, 3, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_route AND `authority` = 'system:aiRoute:remove');
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, `id` FROM `sys_menu`
+WHERE `authority` IN (
+ 'system:aiPrompt:list', 'system:aiPrompt:save', 'system:aiPrompt:update', 'system:aiPrompt:remove', 'system:aiPrompt:publish',
+ 'system:aiDiagnosis:list', 'system:aiCallLog:list',
+ 'system:aiRoute:list', 'system:aiRoute:save', 'system:aiRoute:update', 'system:aiRoute:remove'
+)
+AND NOT EXISTS (
+ SELECT 1 FROM `sys_role_menu`
+ WHERE `role_id` = 1 AND `menu_id` = `sys_menu`.`id`
+);
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, `id` FROM `sys_menu`
+WHERE `component` IN ('aiPrompt', 'aiDiagnosis', 'aiCallLog', 'aiRoute')
+AND NOT EXISTS (
+ SELECT 1 FROM `sys_role_menu`
+ WHERE `role_id` = 1 AND `menu_id` = `sys_menu`.`id`
+);
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- >>> 20260316_ai_phase3.sql
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+CREATE TABLE IF NOT EXISTS `sys_ai_diagnostic_tool_config` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `uuid` varchar(64) DEFAULT NULL COMMENT '缂栧彿',
+ `scene_code` varchar(64) NOT NULL COMMENT '鍦烘櫙缂栫爜',
+ `tool_code` varchar(64) NOT NULL COMMENT '宸ュ叿缂栫爜',
+ `tool_name` varchar(128) DEFAULT NULL COMMENT '宸ュ叿鍚嶇О',
+ `enabled_flag` int(1) NOT NULL DEFAULT '1' COMMENT '鍚敤{1:鏄�,0:鍚',
+ `priority` int(11) NOT NULL DEFAULT '1' COMMENT '浼樺厛绾�',
+ `tool_prompt` longtext COMMENT '宸ュ叿鎻愮ず璇�',
+ `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:姝e父,0:鍐荤粨}',
+ `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛ID',
+ `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_by` bigint(20) DEFAULT NULL COMMENT '鏇存柊浜�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `memo` varchar(255) DEFAULT NULL COMMENT '澶囨敞',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_tool_scene` (`tenant_id`,`scene_code`,`status`,`deleted`,`priority`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI璇婃柇宸ュ叿閰嶇疆';
+
+CREATE TABLE IF NOT EXISTS `sys_ai_prompt_publish_log` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `prompt_template_id` bigint(20) DEFAULT NULL COMMENT 'Prompt妯℃澘ID',
+ `scene_code` varchar(64) DEFAULT NULL COMMENT '鍦烘櫙缂栫爜',
+ `template_name` varchar(255) DEFAULT NULL COMMENT '妯℃澘鍚嶇О',
+ `version_no` int(11) DEFAULT NULL COMMENT '鐗堟湰鍙�',
+ `action_type` varchar(64) DEFAULT NULL COMMENT '鍔ㄤ綔绫诲瀷',
+ `action_desc` varchar(255) DEFAULT NULL COMMENT '鍔ㄤ綔鎻忚堪',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛ID',
+ `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_prompt_publish_log` (`tenant_id`,`scene_code`,`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI鎻愮ず璇嶅彂甯冩棩蹇�';
+
+SET @sql := IF(
+ EXISTS(SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sys_ai_diagnosis_record' AND COLUMN_NAME = 'report_title'),
+ 'SELECT 1',
+ 'ALTER TABLE `sys_ai_diagnosis_record` ADD COLUMN `report_title` varchar(255) DEFAULT NULL COMMENT ''鎶ュ憡鏍囬'' AFTER `conclusion`'
+);
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+SET @sql := IF(
+ EXISTS(SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sys_ai_diagnosis_record' AND COLUMN_NAME = 'executive_summary'),
+ 'SELECT 1',
+ 'ALTER TABLE `sys_ai_diagnosis_record` ADD COLUMN `executive_summary` longtext COMMENT ''鎵ц鎽樿'' AFTER `report_title`'
+);
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+SET @sql := IF(
+ EXISTS(SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sys_ai_diagnosis_record' AND COLUMN_NAME = 'evidence_summary'),
+ 'SELECT 1',
+ 'ALTER TABLE `sys_ai_diagnosis_record` ADD COLUMN `evidence_summary` longtext COMMENT ''璇佹嵁鎽樿'' AFTER `executive_summary`'
+);
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+SET @sql := IF(
+ EXISTS(SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sys_ai_diagnosis_record' AND COLUMN_NAME = 'action_summary'),
+ 'SELECT 1',
+ 'ALTER TABLE `sys_ai_diagnosis_record` ADD COLUMN `action_summary` longtext COMMENT ''寤鸿鍔ㄤ綔'' AFTER `evidence_summary`'
+);
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+SET @sql := IF(
+ EXISTS(SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sys_ai_diagnosis_record' AND COLUMN_NAME = 'risk_summary'),
+ 'SELECT 1',
+ 'ALTER TABLE `sys_ai_diagnosis_record` ADD COLUMN `risk_summary` longtext COMMENT ''椋庨櫓璇勪及'' AFTER `action_summary`'
+);
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+SET @sql := IF(
+ EXISTS(SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sys_ai_diagnosis_record' AND COLUMN_NAME = 'report_markdown'),
+ 'SELECT 1',
+ 'ALTER TABLE `sys_ai_diagnosis_record` ADD COLUMN `report_markdown` longtext COMMENT ''鎶ュ憡Markdown'' AFTER `risk_summary`'
+);
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+INSERT INTO `sys_ai_diagnostic_tool_config`
+(`uuid`, `scene_code`, `tool_code`, `tool_name`, `enabled_flag`, `priority`, `tool_prompt`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514315', 'system_diagnose', 'warehouse_summary', '搴撳瓨鎽樿', 1, 10, '缁撳悎搴撳瓨鎽樿鍒ゆ柇搴撲綅鐘舵�併�佸簱瀛樼粨鏋勪笌閲嶇偣鐗╂枡鍒嗗竷銆�', 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿璇婃柇宸ュ叿'
+FROM DUAL WHERE NOT EXISTS (
+ SELECT 1 FROM `sys_ai_diagnostic_tool_config` WHERE `tenant_id` = 1 AND `scene_code` = 'system_diagnose' AND `tool_code` = 'warehouse_summary'
+);
+
+INSERT INTO `sys_ai_diagnostic_tool_config`
+(`uuid`, `scene_code`, `tool_code`, `tool_name`, `enabled_flag`, `priority`, `tool_prompt`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514316', 'system_diagnose', 'task_summary', '浠诲姟鎽樿', 1, 20, '缁撳悎浠诲姟鎽樿璇嗗埆绉帇銆佸紓甯哥姸鎬佸拰鏈�杩戝彉鏇翠换鍔°��', 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿璇婃柇宸ュ叿'
+FROM DUAL WHERE NOT EXISTS (
+ SELECT 1 FROM `sys_ai_diagnostic_tool_config` WHERE `tenant_id` = 1 AND `scene_code` = 'system_diagnose' AND `tool_code` = 'task_summary'
+);
+
+INSERT INTO `sys_ai_diagnostic_tool_config`
+(`uuid`, `scene_code`, `tool_code`, `tool_name`, `enabled_flag`, `priority`, `tool_prompt`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514317', 'system_diagnose', 'device_site_summary', '璁惧绔欑偣鎽樿', 1, 30, '缁撳悎璁惧绔欑偣鎽樿鍒ゆ柇璁惧鐘舵�併�佸贩閬撳垎甯冨拰鏈�杩戞洿鏂扮珯鐐广��', 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿璇婃柇宸ュ叿'
+FROM DUAL WHERE NOT EXISTS (
+ SELECT 1 FROM `sys_ai_diagnostic_tool_config` WHERE `tenant_id` = 1 AND `scene_code` = 'system_diagnose' AND `tool_code` = 'device_site_summary'
+);
+
+INSERT INTO `sys_ai_diagnostic_tool_config`
+(`uuid`, `scene_code`, `tool_code`, `tool_name`, `enabled_flag`, `priority`, `tool_prompt`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514318', 'system_diagnose', 'operation_record', '寮傚父鎿嶄綔鏃ュ織', 1, 40, '閲嶇偣璇嗗埆鏈�杩戝け璐ユ搷浣滃拰楂橀寮傚父銆�', 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿璇婃柇宸ュ叿'
+FROM DUAL WHERE NOT EXISTS (
+ SELECT 1 FROM `sys_ai_diagnostic_tool_config` WHERE `tenant_id` = 1 AND `scene_code` = 'system_diagnose' AND `tool_code` = 'operation_record'
+);
+
+INSERT INTO `sys_ai_diagnostic_tool_config`
+(`uuid`, `scene_code`, `tool_code`, `tool_name`, `enabled_flag`, `priority`, `tool_prompt`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514319', 'system_diagnose', 'ai_call_failure', 'AI璋冪敤澶辫触', 1, 50, '閲嶇偣璇嗗埆鏈�杩� AI 璋冪敤澶辫触鐨勬ā鍨嬨�侀敊璇被鍨嬪拰鏃堕棿绐楀彛銆�', 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿璇婃柇宸ュ叿'
+FROM DUAL WHERE NOT EXISTS (
+ SELECT 1 FROM `sys_ai_diagnostic_tool_config` WHERE `tenant_id` = 1 AND `scene_code` = 'system_diagnose' AND `tool_code` = 'ai_call_failure'
+);
+
+SET @system_menu_ai_tool_config := (
+ SELECT `id` FROM `sys_menu` WHERE `component` = 'aiToolConfig' LIMIT 1
+);
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'menu.aiToolConfig', 1, 'menu.system', '1', 'menu.system', '/system/aiToolConfig', 'aiToolConfig', NULL, NULL, 0, NULL, 'BuildCircle', 14, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE @system_menu_ai_tool_config IS NULL;
+SET @system_menu_ai_tool_config := COALESCE(@system_menu_ai_tool_config, LAST_INSERT_ID(), (SELECT `id` FROM `sys_menu` WHERE `component` = 'aiToolConfig' LIMIT 1));
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Query AiToolConfig', @system_menu_ai_tool_config, NULL, CONCAT('1,', @system_menu_ai_tool_config), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiToolConfig:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_tool_config AND `authority` = 'system:aiToolConfig:list');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Create AiToolConfig', @system_menu_ai_tool_config, NULL, CONCAT('1,', @system_menu_ai_tool_config), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiToolConfig:save', NULL, 1, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_tool_config AND `authority` = 'system:aiToolConfig:save');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Update AiToolConfig', @system_menu_ai_tool_config, NULL, CONCAT('1,', @system_menu_ai_tool_config), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiToolConfig:update', NULL, 2, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_tool_config AND `authority` = 'system:aiToolConfig:update');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Delete AiToolConfig', @system_menu_ai_tool_config, NULL, CONCAT('1,', @system_menu_ai_tool_config), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiToolConfig:remove', NULL, 3, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_tool_config AND `authority` = 'system:aiToolConfig:remove');
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, `id` FROM `sys_menu`
+WHERE `authority` IN ('system:aiToolConfig:list', 'system:aiToolConfig:save', 'system:aiToolConfig:update', 'system:aiToolConfig:remove')
+AND NOT EXISTS (
+ SELECT 1 FROM `sys_role_menu`
+ WHERE `role_id` = 1 AND `menu_id` = `sys_menu`.`id`
+);
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, `id` FROM `sys_menu`
+WHERE `component` IN ('aiToolConfig')
+AND NOT EXISTS (
+ SELECT 1 FROM `sys_role_menu`
+ WHERE `role_id` = 1 AND `menu_id` = `sys_menu`.`id`
+);
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- >>> 20260316_ai_phase4.sql
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+CREATE TABLE IF NOT EXISTS `sys_ai_diagnosis_plan` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `uuid` varchar(64) DEFAULT NULL COMMENT '缂栧彿',
+ `plan_name` varchar(255) DEFAULT NULL COMMENT '璁″垝鍚嶇О',
+ `scene_code` varchar(64) DEFAULT NULL COMMENT '鍦烘櫙缂栫爜',
+ `cron_expr` varchar(128) DEFAULT NULL COMMENT 'Cron琛ㄨ揪寮�',
+ `prompt` longtext COMMENT '宸℃鎻愮ず璇�',
+ `preferred_model_code` varchar(128) DEFAULT NULL COMMENT '浼樺厛妯″瀷缂栫爜',
+ `running_flag` int(1) NOT NULL DEFAULT '0' COMMENT '杩愯涓瓄1:鏄�,0:鍚',
+ `last_result` int(1) DEFAULT NULL COMMENT '涓婃缁撴灉{2:杩愯涓�,1:鎴愬姛,0:澶辫触}',
+ `last_diagnosis_id` bigint(20) DEFAULT NULL COMMENT '涓婃璇婃柇璁板綍ID',
+ `last_run_time` datetime DEFAULT NULL COMMENT '涓婃杩愯鏃堕棿',
+ `next_run_time` datetime DEFAULT NULL COMMENT '涓嬫杩愯鏃堕棿',
+ `last_message` varchar(1000) DEFAULT NULL COMMENT '鏈�杩戞秷鎭�',
+ `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:姝e父,0:鍐荤粨}',
+ `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛ID',
+ `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_by` bigint(20) DEFAULT NULL COMMENT '鏇存柊浜�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `memo` varchar(255) DEFAULT NULL COMMENT '澶囨敞',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_diag_plan_due` (`tenant_id`,`status`,`running_flag`,`next_run_time`,`deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI璇婃柇宸℃璁″垝';
+
+SET @system_menu_ai_plan := (
+ SELECT `id` FROM `sys_menu` WHERE `component` = 'aiDiagnosisPlan' LIMIT 1
+);
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'menu.aiDiagnosisPlan', 1, 'menu.system', '1', 'menu.system', '/system/aiDiagnosisPlan', 'aiDiagnosisPlan', NULL, NULL, 0, NULL, 'Schedule', 14, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE @system_menu_ai_plan IS NULL;
+SET @system_menu_ai_plan := COALESCE(@system_menu_ai_plan, LAST_INSERT_ID(), (SELECT `id` FROM `sys_menu` WHERE `component` = 'aiDiagnosisPlan' LIMIT 1));
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Query AiDiagnosisPlan', @system_menu_ai_plan, NULL, CONCAT('1,', @system_menu_ai_plan), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiDiagnosisPlan:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_plan AND `authority` = 'system:aiDiagnosisPlan:list');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Create AiDiagnosisPlan', @system_menu_ai_plan, NULL, CONCAT('1,', @system_menu_ai_plan), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiDiagnosisPlan:save', NULL, 1, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_plan AND `authority` = 'system:aiDiagnosisPlan:save');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Update AiDiagnosisPlan', @system_menu_ai_plan, NULL, CONCAT('1,', @system_menu_ai_plan), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiDiagnosisPlan:update', NULL, 2, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_plan AND `authority` = 'system:aiDiagnosisPlan:update');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Delete AiDiagnosisPlan', @system_menu_ai_plan, NULL, CONCAT('1,', @system_menu_ai_plan), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiDiagnosisPlan:remove', NULL, 3, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_plan AND `authority` = 'system:aiDiagnosisPlan:remove');
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, `id` FROM `sys_menu`
+WHERE `authority` IN (
+ 'system:aiDiagnosisPlan:list', 'system:aiDiagnosisPlan:save', 'system:aiDiagnosisPlan:update', 'system:aiDiagnosisPlan:remove'
+)
+AND NOT EXISTS (
+ SELECT 1 FROM `sys_role_menu`
+ WHERE `role_id` = 1 AND `menu_id` = `sys_menu`.`id`
+);
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, `id` FROM `sys_menu`
+WHERE `component` IN ('aiDiagnosisPlan')
+AND NOT EXISTS (
+ SELECT 1 FROM `sys_role_menu`
+ WHERE `role_id` = 1 AND `menu_id` = `sys_menu`.`id`
+);
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- >>> 20260316_ai_phase5.sql
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+CREATE TABLE IF NOT EXISTS `sys_ai_mcp_mount` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `uuid` varchar(64) DEFAULT NULL COMMENT '缂栧彿',
+ `name` varchar(255) DEFAULT NULL COMMENT '鍚嶇О',
+ `mount_code` varchar(128) DEFAULT NULL COMMENT '鎸傝浇缂栫爜',
+ `transport_type` varchar(32) DEFAULT NULL COMMENT '浼犺緭绫诲瀷',
+ `url` varchar(512) DEFAULT NULL COMMENT '鍦板潃',
+ `enabled_flag` int(1) NOT NULL DEFAULT '1' COMMENT '鍚敤{1:鏄�,0:鍚',
+ `timeout_ms` int(11) DEFAULT NULL COMMENT '瓒呮椂姣',
+ `last_test_result` int(1) DEFAULT NULL COMMENT '涓婃娴嬭瘯缁撴灉{1:鎴愬姛,0:澶辫触}',
+ `last_test_time` datetime DEFAULT NULL COMMENT '涓婃娴嬭瘯鏃堕棿',
+ `last_test_message` varchar(1000) DEFAULT NULL COMMENT '涓婃娴嬭瘯娑堟伅',
+ `last_tool_count` int(11) DEFAULT NULL COMMENT '涓婃宸ュ叿鏁�',
+ `status` int(1) NOT NULL DEFAULT '1' COMMENT '鐘舵�亄1:姝e父,0:鍐荤粨}',
+ `deleted` int(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍒犻櫎{1:鏄�,0:鍚',
+ `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛ID',
+ `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_by` bigint(20) DEFAULT NULL COMMENT '鏇存柊浜�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `memo` varchar(255) DEFAULT NULL COMMENT '澶囨敞',
+ PRIMARY KEY (`id`),
+ KEY `idx_ai_mcp_mount` (`tenant_id`,`mount_code`,`status`,`deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI MCP鎸傝浇';
+
+INSERT INTO `sys_ai_mcp_mount`
+(`uuid`, `name`, `mount_code`, `transport_type`, `url`, `enabled_flag`, `timeout_ms`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`)
+SELECT '6702082748514325', 'WMS鏈湴MCP', 'wms_local', 'INTERNAL', '/ai/mcp', 1, 10000, 1, 0, 1, 2, NOW(), 2, NOW(), '榛樿鎸傝浇褰撳墠 WMS AI 鍐呯疆宸ュ叿闆嗗悎'
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM `sys_ai_mcp_mount` WHERE `tenant_id` = 1 AND `mount_code` = 'wms_local'
+);
+
+SET @system_menu_ai_mcp_mount := (
+ SELECT `id` FROM `sys_menu` WHERE `component` = 'aiMcpMount' LIMIT 1
+);
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'menu.aiMcpMount', 1, 'menu.system', '1', 'menu.system', '/system/aiMcpMount', 'aiMcpMount', NULL, NULL, 0, NULL, 'Hub', 15, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE @system_menu_ai_mcp_mount IS NULL;
+SET @system_menu_ai_mcp_mount := COALESCE(@system_menu_ai_mcp_mount, LAST_INSERT_ID(), (SELECT `id` FROM `sys_menu` WHERE `component` = 'aiMcpMount' LIMIT 1));
+
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Query AiMcpMount', @system_menu_ai_mcp_mount, NULL, CONCAT('1,', @system_menu_ai_mcp_mount), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiMcpMount:list', NULL, 0, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_mcp_mount AND `authority` = 'system:aiMcpMount:list');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Create AiMcpMount', @system_menu_ai_mcp_mount, NULL, CONCAT('1,', @system_menu_ai_mcp_mount), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiMcpMount:save', NULL, 1, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_mcp_mount AND `authority` = 'system:aiMcpMount:save');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Update AiMcpMount', @system_menu_ai_mcp_mount, NULL, CONCAT('1,', @system_menu_ai_mcp_mount), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiMcpMount:update', NULL, 2, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_mcp_mount AND `authority` = 'system:aiMcpMount:update');
+INSERT INTO `sys_menu`
+(`name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'Delete AiMcpMount', @system_menu_ai_mcp_mount, NULL, CONCAT('1,', @system_menu_ai_mcp_mount), NULL, NULL, NULL, NULL, NULL, 1, 'system:aiMcpMount:remove', NULL, 3, NULL, 1, 1, 0, NOW(), 2, NOW(), 2, NULL
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id` = @system_menu_ai_mcp_mount AND `authority` = 'system:aiMcpMount:remove');
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, `id` FROM `sys_menu`
+WHERE `authority` IN (
+ 'system:aiMcpMount:list', 'system:aiMcpMount:save', 'system:aiMcpMount:update', 'system:aiMcpMount:remove'
+)
+AND NOT EXISTS (
+ SELECT 1 FROM `sys_role_menu`
+ WHERE `role_id` = 1 AND `menu_id` = `sys_menu`.`id`
+);
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, `id` FROM `sys_menu`
+WHERE `component` IN ('aiMcpMount')
+AND NOT EXISTS (
+ SELECT 1 FROM `sys_role_menu`
+ WHERE `role_id` = 1 AND `menu_id` = `sys_menu`.`id`
+);
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- >>> 20260316_ai_phase6_mcp_console.sql
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+SET @table_schema = DATABASE();
+SET @add_auth_type_sql = (
+ SELECT IF(
+ COUNT(*) = 0,
+ 'ALTER TABLE `sys_ai_mcp_mount` ADD COLUMN `auth_type` varchar(32) DEFAULT ''NONE'' COMMENT ''璁よ瘉鏂瑰紡'' AFTER `url`',
+ 'SELECT 1'
+ )
+ FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @table_schema
+ AND TABLE_NAME = 'sys_ai_mcp_mount'
+ AND COLUMN_NAME = 'auth_type'
+);
+PREPARE stmt FROM @add_auth_type_sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+SET @add_auth_value_sql = (
+ SELECT IF(
+ COUNT(*) = 0,
+ 'ALTER TABLE `sys_ai_mcp_mount` ADD COLUMN `auth_value` varchar(512) DEFAULT NULL COMMENT ''璁よ瘉淇℃伅'' AFTER `auth_type`',
+ 'SELECT 1'
+ )
+ FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @table_schema
+ AND TABLE_NAME = 'sys_ai_mcp_mount'
+ AND COLUMN_NAME = 'auth_value'
+);
+PREPARE stmt FROM @add_auth_value_sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+SET @add_usage_scope_sql = (
+ SELECT IF(
+ COUNT(*) = 0,
+ 'ALTER TABLE `sys_ai_mcp_mount` ADD COLUMN `usage_scope` varchar(32) DEFAULT ''DIAGNOSE_ONLY'' COMMENT ''鐢ㄩ�旇寖鍥�'' AFTER `auth_value`',
+ 'SELECT 1'
+ )
+ FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @table_schema
+ AND TABLE_NAME = 'sys_ai_mcp_mount'
+ AND COLUMN_NAME = 'usage_scope'
+);
+PREPARE stmt FROM @add_usage_scope_sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+UPDATE `sys_ai_mcp_mount`
+SET `auth_type` = COALESCE(NULLIF(`auth_type`, ''), 'NONE'),
+ `usage_scope` = CASE
+ WHEN `transport_type` = 'INTERNAL' THEN 'CHAT_AND_DIAGNOSE'
+ ELSE COALESCE(NULLIF(`usage_scope`, ''), 'DIAGNOSE_ONLY')
+ END
+WHERE 1 = 1;
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- >>> 20260316_ai_phase7_tool_usage_scope.sql
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+SET @table_schema = DATABASE();
+SET @add_usage_scope_sql = (
+ SELECT IF(
+ COUNT(*) = 0,
+ 'ALTER TABLE `sys_ai_diagnostic_tool_config` ADD COLUMN `usage_scope` varchar(32) DEFAULT ''DIAGNOSE_ONLY'' COMMENT ''鐢ㄩ�旇寖鍥�'' AFTER `tool_prompt`',
+ 'SELECT 1'
+ )
+ FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @table_schema
+ AND TABLE_NAME = 'sys_ai_diagnostic_tool_config'
+ AND COLUMN_NAME = 'usage_scope'
+);
+PREPARE stmt FROM @add_usage_scope_sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+UPDATE `sys_ai_diagnostic_tool_config`
+SET `usage_scope` = CASE
+ WHEN `enabled_flag` IS NOT NULL AND `enabled_flag` <> 1 THEN 'DISABLED'
+ WHEN `scene_code` IS NULL OR TRIM(`scene_code`) = '' THEN 'CHAT_AND_DIAGNOSE'
+ ELSE 'DIAGNOSE_ONLY'
+END
+WHERE `usage_scope` IS NULL OR TRIM(`usage_scope`) = '';
+
+SET FOREIGN_KEY_CHECKS = 1;
+
diff --git a/version/db/init.sql b/version/db/init.sql
index c84ebee..0a749c5 100644
--- a/version/db/init.sql
+++ b/version/db/init.sql
@@ -77,14 +77,13 @@
PRIMARY KEY (`id`),
KEY `idx_ai_param_model_code` (`model_code`),
KEY `idx_ai_param_deleted_code` (`deleted`,`model_code`)
-) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_ai_param
-- ----------------------------
BEGIN;
-INSERT INTO `sys_ai_param` (`id`, `uuid`, `name`, `model_code`, `provider`, `chat_url`, `api_key`, `model_name`, `system_prompt`, `max_context_messages`, `default_flag`, `sort`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`) VALUES (1, '6702082748514305', '閫氱敤鍔╂墜', 'mock-general', 'mock', NULL, NULL, 'mock-general', '浣犳槸WMS绯荤粺鍐呯殑鏅鸿兘鍔╂墜锛屽洖绛旀椂浼樺厛淇濇寔鍑嗙‘銆佺畝娲侊紝骞剁粨鍚堜笂涓嬫枃甯姪鐢ㄦ埛鐞嗚В浠撳偍涓氬姟銆�', 12, 1, 1, 1, 0, 1, 2, '2025-02-05 14:16:51', 2, '2025-02-05 14:16:51', '榛樿婕旂ず妯″瀷');
-INSERT INTO `sys_ai_param` (`id`, `uuid`, `name`, `model_code`, `provider`, `chat_url`, `api_key`, `model_name`, `system_prompt`, `max_context_messages`, `default_flag`, `sort`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`) VALUES (2, '6702082748514306', '鍒涙剰鍔╂墜', 'mock-creative', 'mock', NULL, NULL, 'mock-creative', '浣犳槸WMS绯荤粺鍐呯殑鏅鸿兘鍔╂墜锛屽洖绛旀椂鍙互鏇寸伒娲诲湴缁勭粐琛ㄨ揪锛屼絾缁撹蹇呴』鍑嗙‘銆�', 12, 0, 2, 1, 0, 1, 2, '2025-02-05 14:16:51', 2, '2025-02-05 14:16:51', '婕旂ず鍒涙剰妯″瀷');
+INSERT INTO `sys_ai_param` (`id`, `uuid`, `name`, `model_code`, `provider`, `chat_url`, `api_key`, `model_name`, `system_prompt`, `max_context_messages`, `default_flag`, `sort`, `status`, `deleted`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `memo`) VALUES (1, '6702082748514305', 'DEEPSEEK', 'deepseek-ai/DeepSeek-V3.2', 'openai', 'https://api.siliconflow.cn', NULL, 'deepseek-ai/DeepSeek-V3.2', '浣犳槸WMS绯荤粺鍐呯殑鏅鸿兘鍔╂墜锛屽洖绛旀椂浼樺厛淇濇寔鍑嗙‘銆佺畝娲侊紝骞剁粨鍚堜笂涓嬫枃甯姪鐢ㄦ埛鐞嗚В浠撳偍涓氬姟銆�', 12, 1, 1, 1, 0, 1, 2, '2026-03-11 14:13:22', 2, '2026-03-11 15:03:30', '榛樿婕旂ず妯″瀷');
COMMIT;
-- ----------------------------
--
Gitblit v1.9.1