From c906da018dbf0e4fa3125508998b5fd8f690ca2b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 10:34:37 +0000 Subject: [PATCH 01/19] Initial plan From 3f1bb8bbfbcfe61a911b66d30a1f1adb591b38e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 10:52:27 +0000 Subject: [PATCH 02/19] Implement three features: 3D map improvements, XNCF auto-attach, create agent from PromptCode Agent-Logs-Url: https://github.com/NeuCharFramework/NcfPackageSources/sessions/91362d99-f232-4869-a053-d7259b98d07a Co-authored-by: JeffreySu <2281927+JeffreySu@users.noreply.github.com> --- .../Admin/Pages/AgentsManager/Index.cshtml | 22 + .../AppService/AgentTemplateAppService.cs | 69 + .../OHS/Local/PL/AgentTemplateRequest.cs | 41 + .../wwwroot/js/AgentsManager/index.js | 21 + .../Admin/Pages/PromptRange/Prompt.cshtml | 88 +- .../Local/AppService/PromptRangeAppService.cs | 34 + .../PromptRange_ViewPromptCodeRequest.cs | 13 + .../wwwroot/js/PromptRange/prompt.js | 239 +- .../BuildXncfAppService.Generated.cs | 2728 ++++++++--------- 9 files changed, 1889 insertions(+), 1366 deletions(-) create mode 100644 src/Extensions/Senparc.Xncf.PromptRange/OHS/Local/PL/Request/PromptRange_ViewPromptCodeRequest.cs diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Areas/Admin/Pages/AgentsManager/Index.cshtml b/src/Extensions/Senparc.Xncf.AgentsManager/Areas/Admin/Pages/AgentsManager/Index.cshtml index d0d1ba497..31454de15 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Areas/Admin/Pages/AgentsManager/Index.cshtml +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Areas/Admin/Pages/AgentsManager/Index.cshtml @@ -1165,6 +1165,17 @@
+
+ + + + + +
+
+ + + + + +
AgentTemplateManage(AgentTemplate_ManageReq }); } + //[ApiBind] + [FunctionRender("从 PromptCode 快速创建智能体", "根据 PromptCode 快速创建智能体。支持靶场级别(如:RangeName)、靶道级别(如:RangeName-T1)、完整定位(如:RangeName-T1-A1)", typeof(Register))] + public async Task CreateAgentFromPromptCode(AgentTemplate_CreateFromPromptCodeRequest request) + { + return await this.GetStringResponseAsync(async (response, logger) => + { + var promptCode = request.GetPromptCode(); + + if (string.IsNullOrEmpty(promptCode)) + { + return "请选择或手动输入 PromptCode"; + } + + if (string.IsNullOrEmpty(request.Name)) + { + return "请输入智能体名称"; + } + + // 检查是否已有使用该 PromptCode 前缀的智能体 + var existingAgents = await this._agentsTemplateService.GetObjectListAsync(0, 0, + z => z.PromptCode != null && z.PromptCode.StartsWith(promptCode), + z => z.Id, Ncf.Core.Enums.OrderingType.Descending); + + if (existingAgents.TotalCount > 0) + { + var existingNames = string.Join("、", existingAgents.Select(z => z.Name)); + logger.Append($"⚠️ 注意:当前 PromptCode({promptCode})已有 {existingAgents.TotalCount} 个智能体使用:{existingNames}"); + logger.Append("已继续创建新智能体。"); + } + + var agentTemplateDto = new AgentTemplateDto(request.Name, promptCode, true, + request.Description ?? "", promptCode, + HookRobotType.None, "", null, request.FunctionCallNames); + + await this._agentsTemplateService.UpdateAgentTemplateAsync(0, agentTemplateDto); + + logger.Append($"✅ 智能体「{request.Name}」创建成功!"); + logger.Append($"使用的 PromptCode:{promptCode}"); + + return logger.ToString(); + }); + } + /// /// 获取 AgentTemplate 的列表 @@ -230,6 +273,32 @@ public async Task> Enable(int id, bool enable) }); } + /// + /// 根据 PromptCode 前缀获取匹配的 AgentTemplate 列表 + /// + /// PromptCode(支持前缀匹配,如"RangeName"、"RangeName-T1"、"RangeName-T1-A1") + /// + [ApiBind] + public async Task>> GetListByPromptCode(string promptCode) + { + return await this.GetResponseAsync>(async (response, logger) => + { + if (string.IsNullOrEmpty(promptCode)) + { + return new List(); + } + + var list = await this._agentsTemplateService.GetObjectListAsync(0, 0, + z => z.PromptCode != null && z.PromptCode.StartsWith(promptCode), + z => z.Id, Ncf.Core.Enums.OrderingType.Descending); + + var result = list.Select(z => + _agentsTemplateService.Mapping(z)).ToList(); + + return result; + }); + } + /// /// 获取所有已注册的 AI Plugin 类型 /// diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/OHS/Local/PL/AgentTemplateRequest.cs b/src/Extensions/Senparc.Xncf.AgentsManager/OHS/Local/PL/AgentTemplateRequest.cs index 589ad72e5..8d911f1b8 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/OHS/Local/PL/AgentTemplateRequest.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/OHS/Local/PL/AgentTemplateRequest.cs @@ -86,4 +86,45 @@ public override async Task LoadData(IServiceProvider serviceProvider) } + + /// + /// 从 PromptCode 快速创建智能体的请求 + /// + public class AgentTemplate_CreateFromPromptCodeRequest : FunctionAppRequestBase + { + [Required] + [MaxLength(50)] + [Description("智能体名称||新智能体的名称")] + public string Name { get; set; } + + [Required] + [Description("PromptCode 作用范围||选择 PromptCode 覆盖范围(靶场级别/靶道级别/完整定位)。提示:可选择靶场名称(Range级别)、靶道前缀(Tactic级别)或完整版本号(精确定位)")] + public SelectionList ScopeSelection { get; set; } = new SelectionList(SelectionType.DropDownList); + + [Description("手动输入 PromptCode||手动输入 PromptCode(支持靶场名称、靶道前缀或完整版本号),当选择[手动输入 SystemMessage]时必须在此处输入")] + public string ManualPromptCode { get; set; } + + [Description("说明||对新智能体的说明(可选)")] + public string Description { get; set; } + + [Description("Function Calls||Function Calls 名称列表,多个用逗号分隔(可选)")] + public string FunctionCallNames { get; set; } + + public string GetPromptCode() + { + var selectionValue = ScopeSelection.SelectedValues.FirstOrDefault(); + if (!string.IsNullOrEmpty(selectionValue) && selectionValue != "0") + { + return selectionValue; + } + return ManualPromptCode; + } + + public override async Task LoadData(IServiceProvider serviceProvider) + { + await base.LoadData(serviceProvider); + + await PromptRangeItemHelper.LoadPromptRangeItemSelection(serviceProvider, ScopeSelection); + } + } } diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/index.js b/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/index.js index 9d84dbd3e..9b3e04ac5 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/index.js +++ b/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/index.js @@ -383,6 +383,7 @@ var app = new Vue({ functionCallInputValue: '', functionCallTags: [], // 用于编辑时临时存储标签 pluginTypes: [], // 存储所有可用的插件类型 + agentAutoAttachXncf: false, // 是否自动附加所有 XNCF 功能插件 // MCP Endpoints相关 mcpEndpointInputVisible: false, mcpEndpointNameValue: '', @@ -1440,6 +1441,7 @@ var app = new Vue({ this.functionCallTags = [] this.functionCallInputVisible = false this.functionCallInputValue = '' + this.agentAutoAttachXncf = false } }) .catch(_ => { }); @@ -2529,6 +2531,25 @@ var app = new Vue({ } this.functionCallTags = currentNames; }, + + // 自动附加所有 XNCF 功能插件 + handleAutoAttachXncfChange(val) { + if (val) { + // 开启时:将所有可用插件类型合并到 functionCallNames + const currentNames = this.agentForm.functionCallNames + ? this.agentForm.functionCallNames.split(',').filter(x => x) + : []; + const allNames = [...new Set([...currentNames, ...this.pluginTypes])]; + this.agentForm.functionCallNames = allNames.join(','); + } else { + // 关闭时:移除所有自动添加的插件类型(保留用户手动添加的) + const currentNames = this.agentForm.functionCallNames + ? this.agentForm.functionCallNames.split(',').filter(x => x) + : []; + const manualNames = currentNames.filter(name => !this.pluginTypes.includes(name)); + this.agentForm.functionCallNames = manualNames.join(','); + } + }, // 测试MCP Endpoint连接 async testMcpEndpoint(name, endpoint) { diff --git a/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml b/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml index 7255ed5a0..4cec99068 100644 --- a/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml +++ b/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml @@ -69,6 +69,10 @@ style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; font-weight: bold;"> 导图 + + 创建智能体 +
@@ -771,7 +775,89 @@ :close-on-click-modal="false" center @@close="mapDialogClose"> -
+ @*工具栏*@ +
+ 重置视角 + 全屏 + 适应视图 + +
+
🖱️ 鼠标操作
+
左键拖拽:旋转视角
+
右键拖拽:平移场景
+
滚轮:缩放
+
单击节点:高亮
+
双击节点:跳转靶道
+
⌨️ 键盘快捷键
+
W / A / S / D:前后左右平移
+
Q / E:上下平移
+
R:重置视角
+
F:适应视图
+
+/-:放大/缩小
+
+ 快捷键 +
+
+ 共 {{ map3dNodes.length }} 个节点 +
+
+
+ + @*快速创建智能体 dialog*@ + + + + + + + + + 靶场级别 + + + + + + 靶道级别 + + + + + + 完整定位 + + + + + + + + {{ createAgentPromptCode }} + + + + + + + + + @*已有智能体提示*@ + + + @*新增模型 dialog*@ diff --git a/src/Extensions/Senparc.Xncf.PromptRange/OHS/Local/AppService/PromptRangeAppService.cs b/src/Extensions/Senparc.Xncf.PromptRange/OHS/Local/AppService/PromptRangeAppService.cs index c840130b8..1c85d64ee 100644 --- a/src/Extensions/Senparc.Xncf.PromptRange/OHS/Local/AppService/PromptRangeAppService.cs +++ b/src/Extensions/Senparc.Xncf.PromptRange/OHS/Local/AppService/PromptRangeAppService.cs @@ -6,9 +6,11 @@ using Senparc.CO2NET.WebApi; using Senparc.Ncf.Core.AppServices; using Senparc.Ncf.Core.Exceptions; +using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Xncf.PromptRange.Domain.Models.Entities; using Senparc.Xncf.PromptRange.Domain.Services; using Senparc.Xncf.PromptRange.Models.DatabaseModel.Dto; +using Senparc.Xncf.PromptRange.OHS.Local.PL.Request; namespace Senparc.Xncf.PromptRange.OHS.Local.AppService; @@ -118,4 +120,36 @@ public async Task DeleteAsync(int rangeId) } ); } + + //[ApiBind] + /// + /// FunctionRender:查看靶场 PromptCode 列表(用于创建智能体) + /// + [FunctionRender("查看 PromptCode 列表", "查看所有靶场和靶道的 PromptCode,可用于在 AgentsManager 中快速创建智能体", typeof(Register))] + public async Task ViewPromptCodeList(PromptRange_ViewPromptCodeRequest request) + { + return await this.GetStringResponseAsync(async (response, logger) => + { + var promptItemService = base.GetService(); + var tree = await promptItemService.GetPromptRangeTreeList(true, true); + + logger.Append("=== PromptCode 列表(可用于在 AgentsManager 中创建智能体)==="); + logger.Append(""); + logger.Append("覆盖范围说明:"); + logger.Append(" 靶场级别(Range):使用靶场名称作为 PromptCode,匹配该靶场下的最优 Prompt"); + logger.Append(" 靶道级别(Tactic):使用「靶场名称-T战术编号」,匹配该战术下的最优 Prompt"); + logger.Append(" 完整定位(Full):使用完整版本号,精确匹配指定 Prompt"); + logger.Append(""); + + foreach (var item in tree) + { + logger.Append($"[{item.Level}] {item.Text} → PromptCode: {item.Value}"); + } + + logger.Append(""); + logger.Append("提示:在 AgentsManager 模块中,使用[从 PromptCode 快速创建智能体]功能可基于以上 PromptCode 快速创建智能体。"); + + return logger.ToString(); + }); + } } \ No newline at end of file diff --git a/src/Extensions/Senparc.Xncf.PromptRange/OHS/Local/PL/Request/PromptRange_ViewPromptCodeRequest.cs b/src/Extensions/Senparc.Xncf.PromptRange/OHS/Local/PL/Request/PromptRange_ViewPromptCodeRequest.cs new file mode 100644 index 000000000..e3b1cb360 --- /dev/null +++ b/src/Extensions/Senparc.Xncf.PromptRange/OHS/Local/PL/Request/PromptRange_ViewPromptCodeRequest.cs @@ -0,0 +1,13 @@ +using Senparc.Ncf.XncfBase.FunctionRenders; +using System.ComponentModel; + +namespace Senparc.Xncf.PromptRange.OHS.Local.PL.Request; + +/// +/// 查看 PromptCode 列表的请求(用于 FunctionRender) +/// +public class PromptRange_ViewPromptCodeRequest : FunctionAppRequestBase +{ + [Description("筛选靶场名称||输入靶场名称关键字进行过滤(可为空,表示显示所有靶场)")] + public string FilterRangeName { get; set; } +} diff --git a/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js b/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js index cc45cf110..81f9cc532 100644 --- a/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js +++ b/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js @@ -162,6 +162,7 @@ var app = new Vue({ map3dNodeMap: new Map(), // 节点映射,用于快速查找 map3dLastAnimationTime: 0, // 上次动画更新时间(用于节流) map3dCurrentNodes: [], // 缓存当前选中的节点(性能优化) + _map3dKeydownHandler: null, // 键盘事件处理器 // 靶场 fieldFormVisible: false, fieldFormSubmitLoading: false, @@ -311,6 +312,17 @@ var app = new Vue({ comparePromptBId: null, // 对比的Prompt B的ID comparePromptA: null, // 对比的Prompt A的完整数据 comparePromptB: null, // 对比的Prompt B的完整数据 + // 快速创建智能体 + createAgentDialogVisible: false, // 创建智能体对话框显示状态 + createAgentLoading: false, // 创建中加载状态 + createAgentForm: { + name: '', // 智能体名称 + scopeType: 'full', // 覆盖范围: 'range'=靶场级别, 'tactic'=靶道级别, 'full'=完整定位 + promptCode: '', // 最终使用的 PromptCode + description: '', // 说明 + functionCallNames: '' // Function Calls + }, + createAgentExistingList: [], // 当前 PromptCode 下已有的智能体列表 // 自定义滚动条缩略图 showScrollbarThumbnails: false, scrollInfo: { @@ -326,6 +338,27 @@ var app = new Vue({ return result }, + // 计算创建智能体时使用的 PromptCode(根据范围选择) + createAgentPromptCode() { + if (!this.promptDetail || !this.promptDetail.fullVersion) { + return '' + } + const fullVersion = this.promptDetail.fullVersion + const parts = fullVersion.split('-') + const rangeName = parts[0] || '' + const tacticPart = parts[1] || '' + + switch (this.createAgentForm.scopeType) { + case 'range': + return rangeName + case 'tactic': + return tacticPart ? `${rangeName}-${tacticPart}` : rangeName + case 'full': + default: + return fullVersion + } + }, + // 获取可选择的Prompt列表(用于对比对话框) availablePrompts() { return this.promptOpt || []; @@ -2741,6 +2774,112 @@ var app = new Vue({ } }, + // 打开创建智能体对话框 + openCreateAgentDialog() { + if (!this.promptid || !this.promptDetail || !this.promptDetail.fullVersion) { + this.$message({ + message: '请先选择一个靶道', + type: 'warning' + }) + return + } + // 重置表单 + this.createAgentForm = { + name: this.promptDetail.nickName || this.promptDetail.fullVersion, + scopeType: 'full', + promptCode: '', + description: '', + functionCallNames: '' + } + this.createAgentExistingList = [] + this.createAgentDialogVisible = true + // 检查是否已有智能体使用该 PromptCode + this.$nextTick(() => { + this.checkExistingAgentsByPromptCode() + }) + }, + + // 切换 PromptCode 范围时重新检查 + onCreateAgentScopeChange() { + this.checkExistingAgentsByPromptCode() + }, + + // 检查当前 PromptCode 下已有的智能体 + async checkExistingAgentsByPromptCode() { + const promptCode = this.createAgentPromptCode + if (!promptCode) return + try { + const res = await servicePR.get( + `/api/Senparc.Xncf.AgentsManager/AgentTemplateAppService/Xncf.AgentsManager_AgentTemplateAppService.GetListByPromptCode?promptCode=${encodeURIComponent(promptCode)}` + ) + if (res.data && res.data.success) { + this.createAgentExistingList = res.data.data || [] + } + } catch (e) { + // 忽略错误(AgentsManager 模块可能未安装) + console.warn('检查智能体时出错(请确认 AgentsManager 模块已安装):', e) + this.createAgentExistingList = [] + } + }, + + // 提交创建智能体 + async submitCreateAgent() { + if (!this.createAgentForm.name) { + this.$message({ message: '请输入智能体名称', type: 'warning' }) + return + } + const promptCode = this.createAgentPromptCode + if (!promptCode) { + this.$message({ message: '无效的 PromptCode', type: 'error' }) + return + } + + // 如果已有智能体,需确认 + if (this.createAgentExistingList.length > 0) { + try { + await this.$confirm( + `当前 PromptCode(${promptCode})已有 ${this.createAgentExistingList.length} 个智能体使用。是否继续创建新智能体?`, + '确认创建', + { confirmButtonText: '继续创建', cancelButtonText: '取消', type: 'warning' } + ) + } catch { + return + } + } + + this.createAgentLoading = true + try { + const agentData = { + id: 0, + name: this.createAgentForm.name, + systemMessage: promptCode, + promptCode: promptCode, + enable: true, + description: this.createAgentForm.description || '', + hookRobotType: 0, + hookRobotParameter: '', + avastar: '/images/AgentsManager/avatar/avatar1.png', + functionCallNames: this.createAgentForm.functionCallNames || '', + mcpEndpoints: '' + } + const res = await servicePR.post( + '/api/Senparc.Xncf.AgentsManager/AgentTemplateAppService/Xncf.AgentsManager_AgentTemplateAppService.SetItem', + agentData + ) + if (res.data && res.data.success) { + this.$message({ message: `智能体「${this.createAgentForm.name}」创建成功!`, type: 'success' }) + this.createAgentDialogVisible = false + } else { + this.$message({ message: '创建失败:' + (res.data?.msg || '未知错误'), type: 'error' }) + } + } catch (e) { + console.error('创建智能体失败:', e) + this.$message({ message: '创建失败,请确认 AgentsManager 模块已安装', type: 'error' }) + } finally { + this.createAgentLoading = false + } + }, + // 打开导图对话框 openMapDialog() { if (!this.promptField) { @@ -2770,8 +2909,92 @@ var app = new Vue({ // 关闭导图对话框 mapDialogClose() { this.destroyMap3D() + // 退出全屏(如果全屏状态) + if (document.fullscreenElement) { + document.exitFullscreen() + } }, - + + // 重置 3D 视角到初始位置 + resetMap3DView() { + if (!this.map3dCamera || !this.map3dControls) return + this.map3dCamera.position.set(30, 30, 50) + this.map3dControls.target.set(0, 0, 0) + this.map3dControls.update() + this.map3dNeedsAnimationUpdate = true + }, + + // 适应视图:将所有节点都纳入视野 + fitMap3DView() { + if (!this.map3dCamera || !this.map3dControls || !this.map3dNodes || this.map3dNodes.length === 0) return + // 计算所有节点的包围盒中心 + let minX = Infinity, maxX = -Infinity + let minY = Infinity, maxY = -Infinity + let minZ = Infinity, maxZ = -Infinity + this.map3dNodes.forEach(node => { + if (node.position) { + minX = Math.min(minX, node.position.x) + maxX = Math.max(maxX, node.position.x) + minY = Math.min(minY, node.position.y) + maxY = Math.max(maxY, node.position.y) + minZ = Math.min(minZ, node.position.z) + maxZ = Math.max(maxZ, node.position.z) + } + }) + const centerX = (minX + maxX) / 2 + const centerY = (minY + maxY) / 2 + const centerZ = (minZ + maxZ) / 2 + const size = Math.max(maxX - minX, maxY - minY, maxZ - minZ) + const distance = size * 1.5 + 50 + this.map3dCamera.position.set(centerX + distance * 0.4, centerY + distance * 0.4, centerZ + distance) + this.map3dControls.target.set(centerX, centerY, centerZ) + this.map3dControls.update() + this.map3dNeedsAnimationUpdate = true + }, + + // 切换全屏模式 + toggleMap3DFullscreen() { + const container = document.getElementById('map3dContainer') + if (!container) return + if (!document.fullscreenElement) { + container.requestFullscreen().catch(e => { + console.warn('全屏请求失败:', e) + }) + } else { + document.exitFullscreen() + } + }, + + // 注册 3D 键盘快捷键 + registerMap3DKeyboard() { + if (this._map3dKeydownHandler) return + const SPEED = 3 + this._map3dKeydownHandler = (e) => { + if (!this.mapDialogVisible || !this.map3dCamera || !this.map3dControls) return + let moved = false + // WASD + QE 平移 + switch (e.key.toLowerCase()) { + case 'w': this.map3dCamera.position.y += SPEED; this.map3dControls.target.y += SPEED; moved = true; break + case 's': this.map3dCamera.position.y -= SPEED; this.map3dControls.target.y -= SPEED; moved = true; break + case 'a': this.map3dCamera.position.x -= SPEED; this.map3dControls.target.x -= SPEED; moved = true; break + case 'd': this.map3dCamera.position.x += SPEED; this.map3dControls.target.x += SPEED; moved = true; break + case 'q': this.map3dCamera.position.z += SPEED; moved = true; break + case 'e': this.map3dCamera.position.z -= SPEED; moved = true; break + case 'r': this.resetMap3DView(); moved = true; break + case 'f': this.fitMap3DView(); moved = true; break + case '+': + case '=': this.map3dCamera.position.z -= SPEED * 2; moved = true; break + case '-': this.map3dCamera.position.z += SPEED * 2; moved = true; break + } + if (moved) { + this.map3dControls.update() + this.map3dNeedsAnimationUpdate = true + e.preventDefault() + } + } + window.addEventListener('keydown', this._map3dKeydownHandler) + }, + // 初始化 3D 导图 initMap3D() { const container = document.getElementById('map3dContainer') @@ -2886,6 +3109,14 @@ var app = new Vue({ // 处理窗口大小变化 window.addEventListener('resize', this.handleMap3DResize) + + // 注册键盘快捷键 + this.registerMap3DKeyboard() + + // 自动适应视图(延迟一点等节点渲染完成) + this.$nextTick(() => { + setTimeout(() => this.fitMap3DView(), 200) + }) }, // 构建树状结构数据 @@ -5068,6 +5299,12 @@ var app = new Vue({ destroyMap3D() { window.removeEventListener('resize', this.handleMap3DResize) + // 移除键盘事件处理器 + if (this._map3dKeydownHandler) { + window.removeEventListener('keydown', this._map3dKeydownHandler) + this._map3dKeydownHandler = null + } + // 移除点击事件 if (this.map3dRenderer && this.map3dRenderer.domElement && this.map3dClickHandler) { this.map3dRenderer.domElement.removeEventListener('click', this.map3dClickHandler) diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs index fbd7b9527..f09d62519 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs @@ -14,73 +14,73 @@ namespace Senparc.Xncf.XncfBuilder.OHS.Local /// public partial class BuildXncfAppService { -public const string BackendTemplate = @$" -## Database EntityFramework DbContext class sample -File Name: Template_XncfNameSenparcEntities.cs -File Path: /Domain/Models/DatabaseModel -Code: -```csharp -{SenparcEntitiesTemplate} -``` - -## Database Entity class sample -File Name: Color.cs -File Path: /Domain/Models/DatabaseModel -Code: -```csharp -{ColorModelTemplate} -``` - -## Database Entity DTO class sample -File Name: ColorDto.cs -File Path: /Domain/Models/DatabaseModel/Dto -Code: -```csharp -{ColorDtoTemplate} -``` - -## Service class sample -File Name: Template_XncfNameService.cs -File Path: /Domain/Services -Code: -```csharp -{ColorServiceTemplate} -``` -"; - -public const string FrontendTemplate = @$" -## Page UI sample (front-end) -File Name: DatabaseSampleIndex.cshtml -File Path: < ModuleRootPath >/ Areas / Admin / Pages / Template_XncfName -Code: -```razorpage -{DatabaseSampleIndexViewTemplate} -``` - -## Page UI sample (back-end) -File Name: DatabaseSampleIndex.cshtml.cs -File Path: < ModuleRootPath >/ Areas / Admin / Pages / Template_XncfName -Code: -```csharp -{DatabaseSampleIndexCodeBehindTemplate} -``` - -## Page JavaScript file sample -File Name: databaseSampleIndex.js -File Path: < ModuleRootPath >/ wwwroot / js / Admin / Template_XncfName -Code: -```javascript -{DatabaseSampleIndexJsTemplate} -``` - -## Page CSS file sample -File Name: databaseSampleIndex.css -File Path: < ModuleRootPath >/ wwwroot / css / Admin / Template_XncfName -Code: -```css -{DatabaseSampleIndexCssTemplate} -``` -"; +public const string BackendTemplate = @$" +## Database EntityFramework DbContext class sample +File Name: Template_XncfNameSenparcEntities.cs +File Path: /Domain/Models/DatabaseModel +Code: +```csharp +{SenparcEntitiesTemplate} +``` + +## Database Entity class sample +File Name: Color.cs +File Path: /Domain/Models/DatabaseModel +Code: +```csharp +{ColorModelTemplate} +``` + +## Database Entity DTO class sample +File Name: ColorDto.cs +File Path: /Domain/Models/DatabaseModel/Dto +Code: +```csharp +{ColorDtoTemplate} +``` + +## Service class sample +File Name: Template_XncfNameService.cs +File Path: /Domain/Services +Code: +```csharp +{ColorServiceTemplate} +``` +"; + +public const string FrontendTemplate = @$" +## Page UI sample (front-end) +File Name: DatabaseSampleIndex.cshtml +File Path: < ModuleRootPath >/ Areas / Admin / Pages / Template_XncfName +Code: +```razorpage +{DatabaseSampleIndexViewTemplate} +``` + +## Page UI sample (back-end) +File Name: DatabaseSampleIndex.cshtml.cs +File Path: < ModuleRootPath >/ Areas / Admin / Pages / Template_XncfName +Code: +```csharp +{DatabaseSampleIndexCodeBehindTemplate} +``` + +## Page JavaScript file sample +File Name: databaseSampleIndex.js +File Path: < ModuleRootPath >/ wwwroot / js / Admin / Template_XncfName +Code: +```javascript +{DatabaseSampleIndexJsTemplate} +``` + +## Page CSS file sample +File Name: databaseSampleIndex.css +File Path: < ModuleRootPath >/ wwwroot / css / Admin / Template_XncfName +Code: +```css +{DatabaseSampleIndexCssTemplate} +``` +"; #region CODE Templates @@ -89,26 +89,26 @@ public partial class BuildXncfAppService /// 请求类代码 /// 类型: code /// - public const string RequestCode = @"using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Senparc.Xncf.XncfBuilder -{ - public class Request - { - public string? Method { get; set; } - public string? Path { get; set; } - public string? Body { get; set; } - - // 新增字段测试动态更新 - public Dictionary? Headers { get; set; } - public DateTime Timestamp { get; set; } = DateTime.Now; - } - -} + public const string RequestCode = @"using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Senparc.Xncf.XncfBuilder +{ + public class Request + { + public string? Method { get; set; } + public string? Path { get; set; } + public string? Body { get; set; } + + // 新增字段测试动态更新 + public Dictionary? Headers { get; set; } + public DateTime Timestamp { get; set; } = DateTime.Now; + } + +} "; #endregion @@ -119,237 +119,237 @@ public class Request /// Senparc实体类模板 /// 类型: backend_template /// - public const string SenparcEntitiesTemplate = @"using Microsoft.EntityFrameworkCore; -using Senparc.Ncf.Database; -using Senparc.Ncf.Core.Models; -using Senparc.Ncf.XncfBase.Database; - -namespace Template_OrgName.Xncf.Template_XncfName.Models -{ - public class Template_XncfNameSenparcEntities : XncfDatabaseDbContext - { - public Template_XncfNameSenparcEntities(DbContextOptions dbContextOptions) : base(dbContextOptions) - { - } - - public DbSet Colors { get; set; } - - //DOT REMOVE OR MODIFY THIS LINE 请勿移除或修改本行 - Entities Point - //ex. public DbSet Colors { get; set; } - - //如无特殊需需要,OnModelCreating 方法可以不用写,已经在 Register 中要求注册 - //protected override void OnModelCreating(ModelBuilder modelBuilder) - //{ - //} - } -} + public const string SenparcEntitiesTemplate = @"using Microsoft.EntityFrameworkCore; +using Senparc.Ncf.Database; +using Senparc.Ncf.Core.Models; +using Senparc.Ncf.XncfBase.Database; + +namespace Template_OrgName.Xncf.Template_XncfName.Models +{ + public class Template_XncfNameSenparcEntities : XncfDatabaseDbContext + { + public Template_XncfNameSenparcEntities(DbContextOptions dbContextOptions) : base(dbContextOptions) + { + } + + public DbSet Colors { get; set; } + + //DOT REMOVE OR MODIFY THIS LINE 请勿移除或修改本行 - Entities Point + //ex. public DbSet Colors { get; set; } + + //如无特殊需需要,OnModelCreating 方法可以不用写,已经在 Register 中要求注册 + //protected override void OnModelCreating(ModelBuilder modelBuilder) + //{ + //} + } +} "; /// /// 颜色模型模板 /// 类型: backend_template /// - public const string ColorModelTemplate = @"using Senparc.Ncf.Core.Models; -using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; -using System; -using System.ComponentModel.DataAnnotations.Schema; -using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; - -namespace Template_OrgName.Xncf.Template_XncfName -{ - /// - /// Color 实体类 - /// - [Table(Register.DATABASE_PREFIX + nameof(Color))]//必须添加前缀,防止全系统中发生冲突 - [Serializable] - public class Color : EntityBase - { - /// - /// 颜色码,0-255 - /// - public int Red { get; private set; } - /// - /// 颜色码,0-255 - /// - public int Green { get; private set; } - - /// - /// 颜色码,0-255 - /// - public int Blue { get; private set; } - - /// - /// 附加列,测试多次数据库 Migrate - /// - public string AdditionNote { get; private set; } - - private Color() { } - - public Color(int red, int green, int blue) - { - if (red < 0 || green < 0 || blue < 0) - { - Random();//随机 - } - else - { - Red = red; - Green = green; - Blue = blue; - } - } - - public Color(int red, int green, int blue, string additionNote) : this(red, green, blue) - { - AdditionNote = additionNote; - } - - public Color(ColorDto colorDto) - { - Red = colorDto.Red; - Green = colorDto.Green; - Blue = colorDto.Blue; - } - - public void Random() - { - //随机产生颜色代码 - var radom = new Random(); - Func getRadomColorCode = () => radom.Next(0, 255); - Red = getRadomColorCode(); - Green = getRadomColorCode(); - Blue = getRadomColorCode(); - } - - public void Brighten() - { - Red = Math.Min(255, Red + 10); - Green = Math.Min(255, Green + 10); - Blue = Math.Min(255, Blue + 10); - } - - public void Darken() - { - Red = Math.Max(0, Red - 10); - Green = Math.Max(0, Green - 10); - Blue = Math.Max(0, Blue - 10); - } - - public void Update(UpdateColorRequestDto dto) - { - Red = dto.Red; - Green = dto.Green; - Blue = dto.Blue; - AdditionNote = dto.AdditionNote; - } - } -} + public const string ColorModelTemplate = @"using Senparc.Ncf.Core.Models; +using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; + +namespace Template_OrgName.Xncf.Template_XncfName +{ + /// + /// Color 实体类 + /// + [Table(Register.DATABASE_PREFIX + nameof(Color))]//必须添加前缀,防止全系统中发生冲突 + [Serializable] + public class Color : EntityBase + { + /// + /// 颜色码,0-255 + /// + public int Red { get; private set; } + /// + /// 颜色码,0-255 + /// + public int Green { get; private set; } + + /// + /// 颜色码,0-255 + /// + public int Blue { get; private set; } + + /// + /// 附加列,测试多次数据库 Migrate + /// + public string AdditionNote { get; private set; } + + private Color() { } + + public Color(int red, int green, int blue) + { + if (red < 0 || green < 0 || blue < 0) + { + Random();//随机 + } + else + { + Red = red; + Green = green; + Blue = blue; + } + } + + public Color(int red, int green, int blue, string additionNote) : this(red, green, blue) + { + AdditionNote = additionNote; + } + + public Color(ColorDto colorDto) + { + Red = colorDto.Red; + Green = colorDto.Green; + Blue = colorDto.Blue; + } + + public void Random() + { + //随机产生颜色代码 + var radom = new Random(); + Func getRadomColorCode = () => radom.Next(0, 255); + Red = getRadomColorCode(); + Green = getRadomColorCode(); + Blue = getRadomColorCode(); + } + + public void Brighten() + { + Red = Math.Min(255, Red + 10); + Green = Math.Min(255, Green + 10); + Blue = Math.Min(255, Blue + 10); + } + + public void Darken() + { + Red = Math.Max(0, Red - 10); + Green = Math.Max(0, Green - 10); + Blue = Math.Max(0, Blue - 10); + } + + public void Update(UpdateColorRequestDto dto) + { + Red = dto.Red; + Green = dto.Green; + Blue = dto.Blue; + AdditionNote = dto.AdditionNote; + } + } +} "; /// /// 颜色DTO模板 /// 类型: backend_template /// - public const string ColorDtoTemplate = @"using Senparc.Ncf.Core.Models; - -namespace Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto -{ - public class ColorDto : DtoBase - { - /// - /// 颜色码,0-255 - /// - public int Red { get; set; } - /// - /// 颜色码,0-255 - /// - public int Green { get; set; } - /// - /// 颜色码,0-255 - /// - public int Blue { get; set; } - - /// - /// 附加列,测试多次数据库 Migrate - /// - public string AdditionNote { get; set; } - - public ColorDto() { } - } -} + public const string ColorDtoTemplate = @"using Senparc.Ncf.Core.Models; + +namespace Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto +{ + public class ColorDto : DtoBase + { + /// + /// 颜色码,0-255 + /// + public int Red { get; set; } + /// + /// 颜色码,0-255 + /// + public int Green { get; set; } + /// + /// 颜色码,0-255 + /// + public int Blue { get; set; } + + /// + /// 附加列,测试多次数据库 Migrate + /// + public string AdditionNote { get; set; } + + public ColorDto() { } + } +} "; /// /// 颜色服务模板 /// 类型: backend_template /// - public const string ColorServiceTemplate = @"using Senparc.Ncf.Core.Enums; -using Senparc.Ncf.Repository; -using Senparc.Ncf.Service; -using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; -using System; -using System.Threading.Tasks; - -namespace Template_OrgName.Xncf.Template_XncfName.Domain.Services -{ - public class ColorService : ServiceBase - { - public ColorService(IRepositoryBase repo, IServiceProvider serviceProvider) - : base(repo, serviceProvider) - { - } - - public async Task CreateNewColor() - { - Color color = new Color(-1, -1, -1); - await base.SaveObjectAsync(color).ConfigureAwait(false); - ColorDto colorDto = base.Mapper.Map(color); - return colorDto; - } - - public async Task GetOrInitColor() - { - var color = await base.GetObjectAsync(z => true); - if (color == null)//如果是纯第一次安装,理论上不会有残留数据 - { - //创建默认颜色 - ColorDto colorDto = await this.CreateNewColor().ConfigureAwait(false); - return colorDto; - } - - return base.Mapper.Map(color); - } - - public async Task Brighten() - { - //TODO:异步方法需要添加排序功能 - var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); - obj.Brighten(); - await base.SaveObjectAsync(obj).ConfigureAwait(false); - return base.Mapper.Map(obj); - } - - public async Task Darken() - { - //TODO:异步方法需要添加排序功能 - var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); - obj.Darken(); - await base.SaveObjectAsync(obj).ConfigureAwait(false); - return base.Mapper.Map(obj); - } - - public async Task Random() - { - //TODO:异步方法需要添加排序功能 - var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); - obj.Random(); - await base.SaveObjectAsync(obj).ConfigureAwait(false); - return base.Mapper.Map(obj); - } - - //TODO: 更多业务方法可以写到这里 - } -} + public const string ColorServiceTemplate = @"using Senparc.Ncf.Core.Enums; +using Senparc.Ncf.Repository; +using Senparc.Ncf.Service; +using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; +using System; +using System.Threading.Tasks; + +namespace Template_OrgName.Xncf.Template_XncfName.Domain.Services +{ + public class ColorService : ServiceBase + { + public ColorService(IRepositoryBase repo, IServiceProvider serviceProvider) + : base(repo, serviceProvider) + { + } + + public async Task CreateNewColor() + { + Color color = new Color(-1, -1, -1); + await base.SaveObjectAsync(color).ConfigureAwait(false); + ColorDto colorDto = base.Mapper.Map(color); + return colorDto; + } + + public async Task GetOrInitColor() + { + var color = await base.GetObjectAsync(z => true); + if (color == null)//如果是纯第一次安装,理论上不会有残留数据 + { + //创建默认颜色 + ColorDto colorDto = await this.CreateNewColor().ConfigureAwait(false); + return colorDto; + } + + return base.Mapper.Map(color); + } + + public async Task Brighten() + { + //TODO:异步方法需要添加排序功能 + var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); + obj.Brighten(); + await base.SaveObjectAsync(obj).ConfigureAwait(false); + return base.Mapper.Map(obj); + } + + public async Task Darken() + { + //TODO:异步方法需要添加排序功能 + var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); + obj.Darken(); + await base.SaveObjectAsync(obj).ConfigureAwait(false); + return base.Mapper.Map(obj); + } + + public async Task Random() + { + //TODO:异步方法需要添加排序功能 + var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); + obj.Random(); + await base.SaveObjectAsync(obj).ConfigureAwait(false); + return base.Mapper.Map(obj); + } + + //TODO: 更多业务方法可以写到这里 + } +} "; #endregion @@ -360,431 +360,431 @@ public async Task Random() /// 数据库示例索引页面视图模板 /// 类型: frontend_template /// - public const string DatabaseSampleIndexViewTemplate = @"@page -@model Template_OrgName.Xncf.Template_XncfName.Areas.Template_XncfName.Pages.DatabaseSampleIndex -@{ - ViewData[""Title""] = ""Color 数据库管理""; - Layout = ""_Layout_Vue""; -} - -@section Style { - -} - -@section breadcrumbs { - 扩展模块 - Template_MenuName - Color 数据库管理 -} - -
-
- 添加颜色 - 刷新 - 调试信息 -
- - -
-

调试信息:

-

tableData长度: {{ tableData ? tableData.length : 'null/undefined' }}

-

total: {{ total }}

-

tableLoading: {{ tableLoading }}

-

Vue实例是否正常: {{ $el ? '是' : '否' }}

-
0""> - 第一条数据: -
{{ JSON.stringify(tableData[0], null, 2) }}
-
- - -
-
简化数据显示测试:
-
- ID: {{item.id}} | - RGB: {{item.red}},{{item.green}},{{item.blue}} | - 时间: {{item.addTime}} -
-

- 没有数据显示! -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- - @* dialog for 添加颜色 *@ - - - - - - - - - - - - -
- RGB({{addForm.red}}, {{addForm.green}}, {{addForm.blue}}) -
-
- - - - - 随机颜色 - -
- - 取 消 - 确 定 - -
- - @* dialog for 编辑颜色 *@ - - - - - - - - - - - - -
- RGB({{editForm.red}}, {{editForm.green}}, {{editForm.blue}}) -
-
- - - - - 随机颜色 - -
- - 取 消 - 确 定 - -
-
- -@section scripts{ - + public const string DatabaseSampleIndexViewTemplate = @"@page +@model Template_OrgName.Xncf.Template_XncfName.Areas.Template_XncfName.Pages.DatabaseSampleIndex +@{ + ViewData[""Title""] = ""Color 数据库管理""; + Layout = ""_Layout_Vue""; +} + +@section Style { + +} + +@section breadcrumbs { + 扩展模块 + Template_MenuName + Color 数据库管理 +} + +
+
+ 添加颜色 + 刷新 + 调试信息 +
+ + +
+

调试信息:

+

tableData长度: {{ tableData ? tableData.length : 'null/undefined' }}

+

total: {{ total }}

+

tableLoading: {{ tableLoading }}

+

Vue实例是否正常: {{ $el ? '是' : '否' }}

+
0""> + 第一条数据: +
{{ JSON.stringify(tableData[0], null, 2) }}
+
+ + +
+
简化数据显示测试:
+
+ ID: {{item.id}} | + RGB: {{item.red}},{{item.green}},{{item.blue}} | + 时间: {{item.addTime}} +
+

+ 没有数据显示! +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + @* dialog for 添加颜色 *@ + + + + + + + + + + + + +
+ RGB({{addForm.red}}, {{addForm.green}}, {{addForm.blue}}) +
+
+ + + + + 随机颜色 + +
+ + 取 消 + 确 定 + +
+ + @* dialog for 编辑颜色 *@ + + + + + + + + + + + + +
+ RGB({{editForm.red}}, {{editForm.green}}, {{editForm.blue}}) +
+
+ + + + + 随机颜色 + +
+ + 取 消 + 确 定 + +
+
+ +@section scripts{ + } "; /// /// 数据库示例索引页面代码后置模板 /// 类型: frontend_template /// - public const string DatabaseSampleIndexCodeBehindTemplate = @"using Microsoft.AspNetCore.Mvc; -using Senparc.Ncf.Service; -using Senparc.Ncf.Utility; -using Template_OrgName.Xncf.Template_XncfName.Domain.Services; -using System; -using System.Linq; -using System.Threading.Tasks; -using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; - -namespace Template_OrgName.Xncf.Template_XncfName.Areas.Template_XncfName.Pages -{ - public class DatabaseSampleIndex : Senparc.Ncf.AreaBase.Admin.AdminXncfModulePageModelBase - { - private readonly ColorService _colorService; - - public DatabaseSampleIndex(Lazy xncfModuleService, ColorService colorService) : base(xncfModuleService) - { - _colorService = colorService; - } - - public void OnGet() - { - } - - /// - /// 获取颜色列表(分页) - /// - /// 关键词 - /// 排序字段 - /// 页码 - /// 页大小 - /// - public async Task OnGetColorListAsync(string keyword, string orderField, int pageIndex, int pageSize) - { - try - { - // 调试信息 - System.Diagnostics.Debug.WriteLine($""ColorList API Called - PageIndex: {pageIndex}, PageSize: {pageSize}, OrderField: {orderField}""); - - var seh = new SenparcExpressionHelper(); - // 可以根据需要添加搜索条件 - // seh.ValueCompare.AndAlso(!string.IsNullOrEmpty(keyword), _ => _.Remark.Contains(keyword)); - var where = seh.BuildWhereExpression(); - var response = await _colorService.GetObjectListAsync(pageIndex, pageSize, where, orderField ?? ""Id desc""); - - // 调试信息 - System.Diagnostics.Debug.WriteLine($""Database Query Result - TotalCount: {response.TotalCount}, ItemCount: {response.Count()}""); - - var result = new - { - success = true, - message = ""数据获取成功"", - data = new { - totalCount = response.TotalCount, - pageIndex = response.PageIndex, - list = response.Select(_ => new - { - id = _.Id, - red = _.Red, - green = _.Green, - blue = _.Blue, - additionNote = _.AdditionNote, - addTime = _.AddTime, - lastUpdateTime = _.LastUpdateTime, - remark = _.Remark - }).ToList() - } - }; - - // 调试信息 - System.Diagnostics.Debug.WriteLine($""API Response - ListCount: {result.data.list.Count}""); - - return Ok(result); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($""ColorList API Error: {ex.Message}""); - return Ok(new { - success = false, - message = ""获取数据失败: "" + ex.Message, - totalCount = 0, - pageIndex = pageIndex, - list = new object[0] - }); - } - } - - /// - /// 创建新颜色 - /// - /// 创建颜色请求 - /// - public async Task OnPostCreateColorAsync([FromBody] CreateColorRequestDto request) - { - try - { - // 调试信息 - System.Diagnostics.Debug.WriteLine($""CreateColor API Called - Red: {request.Red}, Green: {request.Green}, Blue: {request.Blue}, AdditionNote: {request.AdditionNote}""); - - if (request == null) - { - return Ok(new { success = false, message = ""请求参数不能为空"" }); - } - - var color = new Color(request.Red, request.Green, request.Blue, request.AdditionNote); - await _colorService.SaveObjectAsync(color); - - return Ok(new { success = true, message = ""颜色创建成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.AddTime, color.LastUpdateTime } }); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($""CreateColor API Error: {ex.Message}""); - return Ok(new { success = false, message = ""创建失败:"" + ex.Message }); - } - } - - /// - /// 更新颜色 - /// - /// 更新颜色请求 - /// - public async Task OnPostUpdateColorAsync([FromBody] UpdateColorRequestDto request) - { - try - { - // 调试信息 - System.Diagnostics.Debug.WriteLine($""UpdateColor API Called - Id: {request.Id}, Red: {request.Red}, Green: {request.Green}, Blue: {request.Blue}, AdditionNote: {request.AdditionNote}""); - - if (request == null) - { - return Ok(new { success = false, message = ""请求参数不能为空"" }); - } - - var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); - if (color == null) - { - return Ok(new { success = false, message = ""颜色不存在"" }); - } - - // 更新 - color.Update(request); - - await _colorService.SaveObjectAsync(color); - - return Ok(new { success = true, message = ""颜色更新成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.LastUpdateTime } }); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($""UpdateColor API Error: {ex.Message}""); - return Ok(new { success = false, message = ""更新失败:"" + ex.Message }); - } - } - - /// - /// 删除颜色 - /// - /// 删除颜色请求 - /// - public async Task OnPostDeleteColorAsync([FromBody] DeleteColorRequestDto request) - { - try - { - // 调试信息 - System.Diagnostics.Debug.WriteLine($""DeleteColor API Called - Id: {request.Id}""); - - if (request == null) - { - return Ok(new { success = false, message = ""请求参数不能为空"" }); - } - - var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); - if (color == null) - { - return Ok(new { success = false, message = ""颜色不存在"" }); - } - - await _colorService.DeleteObjectAsync(color); - return Ok(new { success = true, message = ""颜色删除成功"" }); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($""DeleteColor API Error: {ex.Message}""); - return Ok(new { success = false, message = ""删除失败:"" + ex.Message }); - } - } - - /// - /// 随机化指定颜色 - /// - /// 随机化颜色请求 - /// - public async Task OnPostRandomizeColorAsync([FromBody] RandomizeColorRequestDto request) - { - try - { - // 调试信息 - System.Diagnostics.Debug.WriteLine($""RandomizeColor API Called - Id: {request.Id}""); - - if (request == null) - { - return Ok(new { success = false, message = ""请求参数不能为空"" }); - } - - var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); - if (color == null) - { - return Ok(new { success = false, message = ""颜色不存在"" }); - } - - color.Random(); - await _colorService.SaveObjectAsync(color); - - return Ok(new { success = true, message = ""颜色随机化成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.LastUpdateTime } }); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($""RandomizeColor API Error: {ex.Message}""); - return Ok(new { success = false, message = ""随机化失败:"" + ex.Message }); - } - } - - /// - /// 获取颜色详情 - /// - /// 颜色ID - /// - public async Task OnGetColorDetailAsync(int id) - { - try - { - var color = await _colorService.GetObjectAsync(c => c.Id == id); - if (color == null) - { - return Ok(new { success = false, message = ""颜色不存在"" }); - } - - return Ok(new { success = true, data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.AddTime, color.LastUpdateTime, color.Remark } }); - } - catch (Exception ex) - { - return Ok(new { success = false, message = ""获取失败:"" + ex.Message }); - } - } - } + public const string DatabaseSampleIndexCodeBehindTemplate = @"using Microsoft.AspNetCore.Mvc; +using Senparc.Ncf.Service; +using Senparc.Ncf.Utility; +using Template_OrgName.Xncf.Template_XncfName.Domain.Services; +using System; +using System.Linq; +using System.Threading.Tasks; +using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; + +namespace Template_OrgName.Xncf.Template_XncfName.Areas.Template_XncfName.Pages +{ + public class DatabaseSampleIndex : Senparc.Ncf.AreaBase.Admin.AdminXncfModulePageModelBase + { + private readonly ColorService _colorService; + + public DatabaseSampleIndex(Lazy xncfModuleService, ColorService colorService) : base(xncfModuleService) + { + _colorService = colorService; + } + + public void OnGet() + { + } + + /// + /// 获取颜色列表(分页) + /// + /// 关键词 + /// 排序字段 + /// 页码 + /// 页大小 + /// + public async Task OnGetColorListAsync(string keyword, string orderField, int pageIndex, int pageSize) + { + try + { + // 调试信息 + System.Diagnostics.Debug.WriteLine($""ColorList API Called - PageIndex: {pageIndex}, PageSize: {pageSize}, OrderField: {orderField}""); + + var seh = new SenparcExpressionHelper(); + // 可以根据需要添加搜索条件 + // seh.ValueCompare.AndAlso(!string.IsNullOrEmpty(keyword), _ => _.Remark.Contains(keyword)); + var where = seh.BuildWhereExpression(); + var response = await _colorService.GetObjectListAsync(pageIndex, pageSize, where, orderField ?? ""Id desc""); + + // 调试信息 + System.Diagnostics.Debug.WriteLine($""Database Query Result - TotalCount: {response.TotalCount}, ItemCount: {response.Count()}""); + + var result = new + { + success = true, + message = ""数据获取成功"", + data = new { + totalCount = response.TotalCount, + pageIndex = response.PageIndex, + list = response.Select(_ => new + { + id = _.Id, + red = _.Red, + green = _.Green, + blue = _.Blue, + additionNote = _.AdditionNote, + addTime = _.AddTime, + lastUpdateTime = _.LastUpdateTime, + remark = _.Remark + }).ToList() + } + }; + + // 调试信息 + System.Diagnostics.Debug.WriteLine($""API Response - ListCount: {result.data.list.Count}""); + + return Ok(result); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($""ColorList API Error: {ex.Message}""); + return Ok(new { + success = false, + message = ""获取数据失败: "" + ex.Message, + totalCount = 0, + pageIndex = pageIndex, + list = new object[0] + }); + } + } + + /// + /// 创建新颜色 + /// + /// 创建颜色请求 + /// + public async Task OnPostCreateColorAsync([FromBody] CreateColorRequestDto request) + { + try + { + // 调试信息 + System.Diagnostics.Debug.WriteLine($""CreateColor API Called - Red: {request.Red}, Green: {request.Green}, Blue: {request.Blue}, AdditionNote: {request.AdditionNote}""); + + if (request == null) + { + return Ok(new { success = false, message = ""请求参数不能为空"" }); + } + + var color = new Color(request.Red, request.Green, request.Blue, request.AdditionNote); + await _colorService.SaveObjectAsync(color); + + return Ok(new { success = true, message = ""颜色创建成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.AddTime, color.LastUpdateTime } }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($""CreateColor API Error: {ex.Message}""); + return Ok(new { success = false, message = ""创建失败:"" + ex.Message }); + } + } + + /// + /// 更新颜色 + /// + /// 更新颜色请求 + /// + public async Task OnPostUpdateColorAsync([FromBody] UpdateColorRequestDto request) + { + try + { + // 调试信息 + System.Diagnostics.Debug.WriteLine($""UpdateColor API Called - Id: {request.Id}, Red: {request.Red}, Green: {request.Green}, Blue: {request.Blue}, AdditionNote: {request.AdditionNote}""); + + if (request == null) + { + return Ok(new { success = false, message = ""请求参数不能为空"" }); + } + + var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); + if (color == null) + { + return Ok(new { success = false, message = ""颜色不存在"" }); + } + + // 更新 + color.Update(request); + + await _colorService.SaveObjectAsync(color); + + return Ok(new { success = true, message = ""颜色更新成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.LastUpdateTime } }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($""UpdateColor API Error: {ex.Message}""); + return Ok(new { success = false, message = ""更新失败:"" + ex.Message }); + } + } + + /// + /// 删除颜色 + /// + /// 删除颜色请求 + /// + public async Task OnPostDeleteColorAsync([FromBody] DeleteColorRequestDto request) + { + try + { + // 调试信息 + System.Diagnostics.Debug.WriteLine($""DeleteColor API Called - Id: {request.Id}""); + + if (request == null) + { + return Ok(new { success = false, message = ""请求参数不能为空"" }); + } + + var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); + if (color == null) + { + return Ok(new { success = false, message = ""颜色不存在"" }); + } + + await _colorService.DeleteObjectAsync(color); + return Ok(new { success = true, message = ""颜色删除成功"" }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($""DeleteColor API Error: {ex.Message}""); + return Ok(new { success = false, message = ""删除失败:"" + ex.Message }); + } + } + + /// + /// 随机化指定颜色 + /// + /// 随机化颜色请求 + /// + public async Task OnPostRandomizeColorAsync([FromBody] RandomizeColorRequestDto request) + { + try + { + // 调试信息 + System.Diagnostics.Debug.WriteLine($""RandomizeColor API Called - Id: {request.Id}""); + + if (request == null) + { + return Ok(new { success = false, message = ""请求参数不能为空"" }); + } + + var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); + if (color == null) + { + return Ok(new { success = false, message = ""颜色不存在"" }); + } + + color.Random(); + await _colorService.SaveObjectAsync(color); + + return Ok(new { success = true, message = ""颜色随机化成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.LastUpdateTime } }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($""RandomizeColor API Error: {ex.Message}""); + return Ok(new { success = false, message = ""随机化失败:"" + ex.Message }); + } + } + + /// + /// 获取颜色详情 + /// + /// 颜色ID + /// + public async Task OnGetColorDetailAsync(int id) + { + try + { + var color = await _colorService.GetObjectAsync(c => c.Id == id); + if (color == null) + { + return Ok(new { success = false, message = ""颜色不存在"" }); + } + + return Ok(new { success = true, data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.AddTime, color.LastUpdateTime, color.Remark } }); + } + catch (Exception ex) + { + return Ok(new { success = false, message = ""获取失败:"" + ex.Message }); + } + } + } } "; #endregion @@ -795,433 +795,433 @@ public async Task OnGetColorDetailAsync(int id) /// 数据库示例索引页面JavaScript模板 /// 类型: frontend_script /// - public const string DatabaseSampleIndexJsTemplate = @"var app = new Vue({ - el: ""#app"", - data() { - return { - page: { - page: 1, - size: 10 - }, - tableLoading: true, - tableData: [], - showDebug: false, - addFormDialogVisible: false, - addForm: { - red: 128, - green: 128, - blue: 128, - additionNote: '' - }, - editFormDialogVisible: false, - editForm: { - id: 0, - red: 128, - green: 128, - blue: 128, - additionNote: '' - }, - total: 0, - addRules: { - red: [ - { required: true, message: '请设置红色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '红色值范围为0-255', trigger: 'change' } - ], - green: [ - { required: true, message: '请设置绿色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '绿色值范围为0-255', trigger: 'change' } - ], - blue: [ - { required: true, message: '请设置蓝色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '蓝色值范围为0-255', trigger: 'change' } - ] - }, - editRules: { - red: [ - { required: true, message: '请设置红色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '红色值范围为0-255', trigger: 'change' } - ], - green: [ - { required: true, message: '请设置绿色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '绿色值范围为0-255', trigger: 'change' } - ], - blue: [ - { required: true, message: '请设置蓝色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '蓝色值范围为0-255', trigger: 'change' } - ] - } - } - }, - mounted() { - //wait page load - setTimeout(async () => { - await this.init(); - }, 100) - }, - methods: { - async init() { - await this.getDataList(); - }, - async handleSizeChange(val) { - this.page.size = val; - await this.getDataList(); - }, - async handleCurrentChange(val) { - this.page.page = val; - await this.getDataList(); - }, - async getDataList() { - this.tableLoading = true - await service.get('/Admin/Template_XncfName/DatabaseSampleIndex?handler=ColorList', { - params: { - pageIndex: this.page.page, - pageSize: this.page.size, - orderField: ""Id desc"", - keyword: """" - } - }) - .then(res => { - console.log('=== API Response Debug ==='); - console.log('Complete Response:', res); - console.log('Response Data:', res.data); - console.log('Response Data Type:', typeof res.data); - console.log('Has res.data.data?:', res.data && res.data.data); - console.log('Has res.data.data.list?:', res.data && res.data.data && res.data.data.list); - console.log('res.data.data.list value:', res.data && res.data.data ? res.data.data.list : 'nested data not found'); - console.log('=================='); - - // 尝试多种可能的数据结构 - let dataList = null; - let totalCount = 0; - let dataSource = ''; - - if (res.data && res.data.data && res.data.data.data && res.data.data.data.list) { - // NCF框架标准格式 + 新的API格式: {data: {data: {success, message, data: {list, totalCount}}}} - dataList = res.data.data.data.list; - totalCount = res.data.data.data.totalCount || 0; - dataSource = 'NCF标准格式: res.data.data.data.list'; - console.log('✅ 使用NCF标准格式: res.data.data.data.list'); - console.log('✅ List数据:', dataList); - console.log('✅ TotalCount:', totalCount); - } else if (res.data && res.data.data && res.data.data.list) { - // 简单格式: {data: {list, totalCount}} - dataList = res.data.data.list; - totalCount = res.data.data.totalCount || 0; - dataSource = '简单格式: res.data.data.list'; - console.log('✅ 使用简单格式: res.data.data.list'); - } else if (res.data && Array.isArray(res.data)) { - // 如果data直接是数组 - dataList = res.data; - totalCount = res.data.length; - dataSource = '数组格式: res.data (array)'; - console.log('✅ 使用数组格式: res.data (array)'); - } else if (res && res.list) { - // 如果list在顶层 - dataList = res.list; - totalCount = res.totalCount || 0; - dataSource = '顶层格式: res.list'; - console.log('✅ 使用顶层格式: res.list'); - } else { - console.error('❌ 无法识别的数据格式:', res); - console.log('🔍 尝试的路径:'); - console.log('- res.data.data.list:', res.data && res.data.data ? res.data.data.list : 'not found'); - console.log('- res.data.list:', res.data ? res.data.list : 'not found'); - console.log('- res.data (array):', res.data && Array.isArray(res.data) ? 'is array' : 'not array'); - console.log('- res.list:', res.list ? res.list : 'not found'); - dataList = []; - totalCount = 0; - dataSource = '无法识别格式'; - } - - console.log('🎯 Final dataList:', dataList); - console.log('🎯 Final totalCount:', totalCount); - console.log('🎯 Data source:', dataSource); - - // 数据赋值前的状态 - console.log('📋 赋值前 tableData:', this.tableData); - console.log('📋 赋值前 total:', this.total); - - this.tableData = dataList || []; - this.total = totalCount; - - // 数据赋值后的状态 - console.log('📋 赋值后 tableData:', this.tableData); - console.log('📋 赋值后 tableData.length:', this.tableData.length); - console.log('📋 赋值后 total:', this.total); - - // 强制Vue更新 - this.$forceUpdate(); - console.log('🔄 Vue已强制更新'); - - // 延迟检查数据是否正确绑定 - setTimeout(() => { - console.log('⏰ 延迟检查 tableData:', this.tableData); - console.log('⏰ 延迟检查 tableData.length:', this.tableData ? this.tableData.length : 'null'); - }, 100); - - this.tableLoading = false - }) - .catch(error => { - console.error('获取数据失败:', error); - this.tableLoading = false; - this.$message.error('获取数据失败: ' + (error.message || error)); - }); - }, - addColor() { - this.addFormDialogVisible = true; - }, - refreshList() { - this.getDataList(); - }, - async addColorSubmit() { - this.$refs.addForm.validate(async (valid) => { - if (valid) { - console.log('📤 发送创建请求:', { - red: this.addForm.red, - green: this.addForm.green, - blue: this.addForm.blue, - additionNote: this.addForm.additionNote - }); - - await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=CreateColor', { - red: this.addForm.red, - green: this.addForm.green, - blue: this.addForm.blue, - additionNote: this.addForm.additionNote - }, { - headers: { - 'Content-Type': 'application/json' - } - }) - .then(res => { - console.log('📥 创建响应:', res); - // 兼容NCF框架的嵌套响应格式 - const responseData = res.data.data || res.data; - this.$message({ - type: responseData.success ? 'success' : 'error', - message: responseData.message || '操作完成' - }); - if (responseData.success) { - this.getDataList() - this.clearAddForm() - this.addFormDialogVisible = false; - } - }) - .catch(error => { - console.error('创建失败:', error); - this.$message.error('创建失败'); - }); - } else { - return false; - } - }); - }, - clearAddForm() { - this.addForm = { - red: 128, - green: 128, - blue: 128, - additionNote: '' - }; - if (this.$refs.addForm) { - this.$refs.addForm.resetFields(); - } - }, - clearEditForm() { - this.editForm = { - id: 0, - red: 128, - green: 128, - blue: 128, - additionNote: '' - }; - if (this.$refs.editForm) { - this.$refs.editForm.resetFields(); - } - }, - async editColorSubmit() { - this.$refs.editForm.validate(async (valid) => { - if (valid) { - console.log('📤 发送更新请求:', { - id: this.editForm.id, - red: this.editForm.red, - green: this.editForm.green, - blue: this.editForm.blue, - additionNote: this.editForm.additionNote - }); - - await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=UpdateColor', { - id: this.editForm.id, - red: this.editForm.red, - green: this.editForm.green, - blue: this.editForm.blue, - additionNote: this.editForm.additionNote - }, { - headers: { - 'Content-Type': 'application/json' - } - }) - .then(res => { - console.log('📥 更新响应:', res); - // 兼容NCF框架的嵌套响应格式 - const responseData = res.data.data || res.data; - this.$message({ - type: responseData.success ? 'success' : 'error', - message: responseData.message || '操作完成' - }); - if (responseData.success) { - this.getDataList() - this.clearEditForm() - this.editFormDialogVisible = false; - } - }) - .catch(error => { - console.error('更新失败:', error); - this.$message.error('更新失败'); - }); - } else { - return false; - } - }); - }, - dateformatter(date) { - if (!date) return ''; - - try { - // 使用原生JavaScript格式化日期 - const d = new Date(date); - - // 检查日期是否有效 - if (isNaN(d.getTime())) { - return date; // 如果无法解析,返回原始值 - } - - // 格式化为 YYYY-MM-DD HH:mm:ss - const year = d.getFullYear(); - const month = String(d.getMonth() + 1).padStart(2, '0'); - const day = String(d.getDate()).padStart(2, '0'); - const hours = String(d.getHours()).padStart(2, '0'); - const minutes = String(d.getMinutes()).padStart(2, '0'); - const seconds = String(d.getSeconds()).padStart(2, '0'); - - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; - } catch (error) { - console.warn('日期格式化错误:', error, '原始值:', date); - return date; // 如果格式化失败,返回原始值 - } - }, - editColor(row) { - this.editForm = { - id: row.id, - red: row.red, - green: row.green, - blue: row.blue, - additionNote: row.additionNote || '' - }; - this.editFormDialogVisible = true; - }, - deleteColor(row) { - this.$confirm('此操作将永久删除该颜色, 是否继续?', '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning' - }).then(async () => { - console.log('📤 发送删除请求:', { id: row.id }); - - await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=DeleteColor', { - id: row.id - }, { - headers: { - 'Content-Type': 'application/json' - } - }) - .then(res => { - console.log('📥 删除响应:', res); - // 兼容NCF框架的嵌套响应格式 - const responseData = res.data.data || res.data; - this.$message({ - type: responseData.success ? 'success' : 'error', - message: responseData.message || '操作完成' - }); - if (responseData.success) { - this.getDataList(); - } - }) - .catch(error => { - console.error('删除失败:', error); - this.$message.error('删除失败'); - }); - }).catch(() => { - this.$message({ - type: 'info', - message: '已取消删除' - }); - }); - }, - async randomizeColor(row) { - console.log('📤 发送随机化请求:', { id: row.id }); - - await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=RandomizeColor', { - id: row.id - }, { - headers: { - 'Content-Type': 'application/json' - } - }) - .then(res => { - console.log('📥 随机化响应:', res); - // 兼容NCF框架的嵌套响应格式 - const responseData = res.data.data || res.data; - this.$message({ - type: responseData.success ? 'success' : 'error', - message: responseData.message || '操作完成' - }); - if (responseData.success) { - this.getDataList(); - } - }) - .catch(error => { - console.error('随机化失败:', error); - this.$message.error('随机化失败'); - }); - }, - randomizeForm() { - this.addForm.red = Math.floor(Math.random() * 256); - this.addForm.green = Math.floor(Math.random() * 256); - this.addForm.blue = Math.floor(Math.random() * 256); - }, - randomizeEditForm() { - this.editForm.red = Math.floor(Math.random() * 256); - this.editForm.green = Math.floor(Math.random() * 256); - this.editForm.blue = Math.floor(Math.random() * 256); - }, - debugInfo() { - this.showDebug = !this.showDebug; - console.log('=== Vue Component Debug Info ==='); - console.log('Current tableData:', this.tableData); - console.log('tableData length:', this.tableData ? this.tableData.length : 'null/undefined'); - console.log('Total:', this.total); - console.log('Page:', this.page); - console.log('Table Loading:', this.tableLoading); - console.log('Show Debug:', this.showDebug); - console.log('Vue instance $el:', this.$el); - console.log('================================'); - - // 测试Vue响应性 - if (this.tableData && this.tableData.length === 0) { - console.log('测试:添加假数据'); - this.tableData = [ - {id: 999, red: 255, green: 0, blue: 0, addTime: new Date().toISOString(), lastUpdateTime: new Date().toISOString(), remark: 'test'} - ]; - this.total = 1; - setTimeout(() => { - console.log('2秒后清除假数据'); - this.tableData = []; - this.total = 0; - }, 2000); - } - } - } + public const string DatabaseSampleIndexJsTemplate = @"var app = new Vue({ + el: ""#app"", + data() { + return { + page: { + page: 1, + size: 10 + }, + tableLoading: true, + tableData: [], + showDebug: false, + addFormDialogVisible: false, + addForm: { + red: 128, + green: 128, + blue: 128, + additionNote: '' + }, + editFormDialogVisible: false, + editForm: { + id: 0, + red: 128, + green: 128, + blue: 128, + additionNote: '' + }, + total: 0, + addRules: { + red: [ + { required: true, message: '请设置红色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '红色值范围为0-255', trigger: 'change' } + ], + green: [ + { required: true, message: '请设置绿色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '绿色值范围为0-255', trigger: 'change' } + ], + blue: [ + { required: true, message: '请设置蓝色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '蓝色值范围为0-255', trigger: 'change' } + ] + }, + editRules: { + red: [ + { required: true, message: '请设置红色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '红色值范围为0-255', trigger: 'change' } + ], + green: [ + { required: true, message: '请设置绿色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '绿色值范围为0-255', trigger: 'change' } + ], + blue: [ + { required: true, message: '请设置蓝色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '蓝色值范围为0-255', trigger: 'change' } + ] + } + } + }, + mounted() { + //wait page load + setTimeout(async () => { + await this.init(); + }, 100) + }, + methods: { + async init() { + await this.getDataList(); + }, + async handleSizeChange(val) { + this.page.size = val; + await this.getDataList(); + }, + async handleCurrentChange(val) { + this.page.page = val; + await this.getDataList(); + }, + async getDataList() { + this.tableLoading = true + await service.get('/Admin/Template_XncfName/DatabaseSampleIndex?handler=ColorList', { + params: { + pageIndex: this.page.page, + pageSize: this.page.size, + orderField: ""Id desc"", + keyword: """" + } + }) + .then(res => { + console.log('=== API Response Debug ==='); + console.log('Complete Response:', res); + console.log('Response Data:', res.data); + console.log('Response Data Type:', typeof res.data); + console.log('Has res.data.data?:', res.data && res.data.data); + console.log('Has res.data.data.list?:', res.data && res.data.data && res.data.data.list); + console.log('res.data.data.list value:', res.data && res.data.data ? res.data.data.list : 'nested data not found'); + console.log('=================='); + + // 尝试多种可能的数据结构 + let dataList = null; + let totalCount = 0; + let dataSource = ''; + + if (res.data && res.data.data && res.data.data.data && res.data.data.data.list) { + // NCF框架标准格式 + 新的API格式: {data: {data: {success, message, data: {list, totalCount}}}} + dataList = res.data.data.data.list; + totalCount = res.data.data.data.totalCount || 0; + dataSource = 'NCF标准格式: res.data.data.data.list'; + console.log('✅ 使用NCF标准格式: res.data.data.data.list'); + console.log('✅ List数据:', dataList); + console.log('✅ TotalCount:', totalCount); + } else if (res.data && res.data.data && res.data.data.list) { + // 简单格式: {data: {list, totalCount}} + dataList = res.data.data.list; + totalCount = res.data.data.totalCount || 0; + dataSource = '简单格式: res.data.data.list'; + console.log('✅ 使用简单格式: res.data.data.list'); + } else if (res.data && Array.isArray(res.data)) { + // 如果data直接是数组 + dataList = res.data; + totalCount = res.data.length; + dataSource = '数组格式: res.data (array)'; + console.log('✅ 使用数组格式: res.data (array)'); + } else if (res && res.list) { + // 如果list在顶层 + dataList = res.list; + totalCount = res.totalCount || 0; + dataSource = '顶层格式: res.list'; + console.log('✅ 使用顶层格式: res.list'); + } else { + console.error('❌ 无法识别的数据格式:', res); + console.log('🔍 尝试的路径:'); + console.log('- res.data.data.list:', res.data && res.data.data ? res.data.data.list : 'not found'); + console.log('- res.data.list:', res.data ? res.data.list : 'not found'); + console.log('- res.data (array):', res.data && Array.isArray(res.data) ? 'is array' : 'not array'); + console.log('- res.list:', res.list ? res.list : 'not found'); + dataList = []; + totalCount = 0; + dataSource = '无法识别格式'; + } + + console.log('🎯 Final dataList:', dataList); + console.log('🎯 Final totalCount:', totalCount); + console.log('🎯 Data source:', dataSource); + + // 数据赋值前的状态 + console.log('📋 赋值前 tableData:', this.tableData); + console.log('📋 赋值前 total:', this.total); + + this.tableData = dataList || []; + this.total = totalCount; + + // 数据赋值后的状态 + console.log('📋 赋值后 tableData:', this.tableData); + console.log('📋 赋值后 tableData.length:', this.tableData.length); + console.log('📋 赋值后 total:', this.total); + + // 强制Vue更新 + this.$forceUpdate(); + console.log('🔄 Vue已强制更新'); + + // 延迟检查数据是否正确绑定 + setTimeout(() => { + console.log('⏰ 延迟检查 tableData:', this.tableData); + console.log('⏰ 延迟检查 tableData.length:', this.tableData ? this.tableData.length : 'null'); + }, 100); + + this.tableLoading = false + }) + .catch(error => { + console.error('获取数据失败:', error); + this.tableLoading = false; + this.$message.error('获取数据失败: ' + (error.message || error)); + }); + }, + addColor() { + this.addFormDialogVisible = true; + }, + refreshList() { + this.getDataList(); + }, + async addColorSubmit() { + this.$refs.addForm.validate(async (valid) => { + if (valid) { + console.log('📤 发送创建请求:', { + red: this.addForm.red, + green: this.addForm.green, + blue: this.addForm.blue, + additionNote: this.addForm.additionNote + }); + + await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=CreateColor', { + red: this.addForm.red, + green: this.addForm.green, + blue: this.addForm.blue, + additionNote: this.addForm.additionNote + }, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(res => { + console.log('📥 创建响应:', res); + // 兼容NCF框架的嵌套响应格式 + const responseData = res.data.data || res.data; + this.$message({ + type: responseData.success ? 'success' : 'error', + message: responseData.message || '操作完成' + }); + if (responseData.success) { + this.getDataList() + this.clearAddForm() + this.addFormDialogVisible = false; + } + }) + .catch(error => { + console.error('创建失败:', error); + this.$message.error('创建失败'); + }); + } else { + return false; + } + }); + }, + clearAddForm() { + this.addForm = { + red: 128, + green: 128, + blue: 128, + additionNote: '' + }; + if (this.$refs.addForm) { + this.$refs.addForm.resetFields(); + } + }, + clearEditForm() { + this.editForm = { + id: 0, + red: 128, + green: 128, + blue: 128, + additionNote: '' + }; + if (this.$refs.editForm) { + this.$refs.editForm.resetFields(); + } + }, + async editColorSubmit() { + this.$refs.editForm.validate(async (valid) => { + if (valid) { + console.log('📤 发送更新请求:', { + id: this.editForm.id, + red: this.editForm.red, + green: this.editForm.green, + blue: this.editForm.blue, + additionNote: this.editForm.additionNote + }); + + await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=UpdateColor', { + id: this.editForm.id, + red: this.editForm.red, + green: this.editForm.green, + blue: this.editForm.blue, + additionNote: this.editForm.additionNote + }, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(res => { + console.log('📥 更新响应:', res); + // 兼容NCF框架的嵌套响应格式 + const responseData = res.data.data || res.data; + this.$message({ + type: responseData.success ? 'success' : 'error', + message: responseData.message || '操作完成' + }); + if (responseData.success) { + this.getDataList() + this.clearEditForm() + this.editFormDialogVisible = false; + } + }) + .catch(error => { + console.error('更新失败:', error); + this.$message.error('更新失败'); + }); + } else { + return false; + } + }); + }, + dateformatter(date) { + if (!date) return ''; + + try { + // 使用原生JavaScript格式化日期 + const d = new Date(date); + + // 检查日期是否有效 + if (isNaN(d.getTime())) { + return date; // 如果无法解析,返回原始值 + } + + // 格式化为 YYYY-MM-DD HH:mm:ss + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + const hours = String(d.getHours()).padStart(2, '0'); + const minutes = String(d.getMinutes()).padStart(2, '0'); + const seconds = String(d.getSeconds()).padStart(2, '0'); + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + } catch (error) { + console.warn('日期格式化错误:', error, '原始值:', date); + return date; // 如果格式化失败,返回原始值 + } + }, + editColor(row) { + this.editForm = { + id: row.id, + red: row.red, + green: row.green, + blue: row.blue, + additionNote: row.additionNote || '' + }; + this.editFormDialogVisible = true; + }, + deleteColor(row) { + this.$confirm('此操作将永久删除该颜色, 是否继续?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(async () => { + console.log('📤 发送删除请求:', { id: row.id }); + + await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=DeleteColor', { + id: row.id + }, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(res => { + console.log('📥 删除响应:', res); + // 兼容NCF框架的嵌套响应格式 + const responseData = res.data.data || res.data; + this.$message({ + type: responseData.success ? 'success' : 'error', + message: responseData.message || '操作完成' + }); + if (responseData.success) { + this.getDataList(); + } + }) + .catch(error => { + console.error('删除失败:', error); + this.$message.error('删除失败'); + }); + }).catch(() => { + this.$message({ + type: 'info', + message: '已取消删除' + }); + }); + }, + async randomizeColor(row) { + console.log('📤 发送随机化请求:', { id: row.id }); + + await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=RandomizeColor', { + id: row.id + }, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(res => { + console.log('📥 随机化响应:', res); + // 兼容NCF框架的嵌套响应格式 + const responseData = res.data.data || res.data; + this.$message({ + type: responseData.success ? 'success' : 'error', + message: responseData.message || '操作完成' + }); + if (responseData.success) { + this.getDataList(); + } + }) + .catch(error => { + console.error('随机化失败:', error); + this.$message.error('随机化失败'); + }); + }, + randomizeForm() { + this.addForm.red = Math.floor(Math.random() * 256); + this.addForm.green = Math.floor(Math.random() * 256); + this.addForm.blue = Math.floor(Math.random() * 256); + }, + randomizeEditForm() { + this.editForm.red = Math.floor(Math.random() * 256); + this.editForm.green = Math.floor(Math.random() * 256); + this.editForm.blue = Math.floor(Math.random() * 256); + }, + debugInfo() { + this.showDebug = !this.showDebug; + console.log('=== Vue Component Debug Info ==='); + console.log('Current tableData:', this.tableData); + console.log('tableData length:', this.tableData ? this.tableData.length : 'null/undefined'); + console.log('Total:', this.total); + console.log('Page:', this.page); + console.log('Table Loading:', this.tableLoading); + console.log('Show Debug:', this.showDebug); + console.log('Vue instance $el:', this.$el); + console.log('================================'); + + // 测试Vue响应性 + if (this.tableData && this.tableData.length === 0) { + console.log('测试:添加假数据'); + this.tableData = [ + {id: 999, red: 255, green: 0, blue: 0, addTime: new Date().toISOString(), lastUpdateTime: new Date().toISOString(), remark: 'test'} + ]; + this.total = 1; + setTimeout(() => { + console.log('2秒后清除假数据'); + this.tableData = []; + this.total = 0; + }, 2000); + } + } + } }); "; #endregion @@ -1232,224 +1232,224 @@ await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=Randomi /// 数据库示例索引页面CSS模板 /// 类型: frontend_style /// - public const string DatabaseSampleIndexCssTemplate = @"/* 通用样式 */ -.d-flex{ - display: flex; -} -.justify-content-between{ - justify-content: space-between; -} -.align-items-center{ - align-items: center; -} - -/* 过滤器容器样式 */ -.filter-container { - margin-bottom: 20px; - padding: 10px 0; -} - -.filter-container .el-button { - margin-right: 10px; -} - -/* 颜色预览样式 */ -.color-preview { - width: 100%; - height: 40px; - border-radius: 4px; - border: 1px solid #dcdfe6; - display: flex; - align-items: center; - justify-content: center; - color: white; - font-size: 12px; - font-weight: bold; - text-shadow: 1px 1px 2px rgba(0,0,0,0.5); -} - -.color-preview-large { - width: 100%; - height: 80px; - border-radius: 8px; - border: 2px solid #dcdfe6; - display: flex; - align-items: center; - justify-content: center; - color: white; - font-size: 16px; - font-weight: bold; - text-shadow: 2px 2px 4px rgba(0,0,0,0.7); - margin: 10px 0; - transition: all 0.3s ease; -} - -.color-preview-large:hover { - transform: scale(1.02); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); -} - -/* 分页容器样式 */ -.pagination-container { - margin-top: 20px; - text-align: center; -} - -/* 表格样式增强 */ -.el-table { - border-radius: 8px; - overflow: hidden; - box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1); -} - -.el-table th { - background-color: #fafafa; - color: #333; - font-weight: 600; -} - -/* 颜色标签样式 */ -.el-tag { - min-width: 50px; - text-align: center; - font-weight: bold; - border: none !important; - text-shadow: 1px 1px 2px rgba(0,0,0,0.5); -} - -/* 对话框样式 */ -.el-dialog { - border-radius: 12px; - overflow: hidden; -} - -.el-dialog__header { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 20px 20px 0 20px; -} - -.el-dialog__title { - color: white; - font-weight: 600; -} - -.el-dialog__body { - padding: 30px 20px; -} - -/* 滑块样式 */ -.el-slider { - margin: 20px 0; -} - -.el-slider__runway { - height: 6px; - background-color: #e4e7ed; - border-radius: 3px; -} - -.el-slider__button { - width: 20px; - height: 20px; - border: 2px solid #409eff; -} - -/* 按钮样式增强 */ -.el-button--mini { - padding: 5px 10px; - font-size: 12px; - border-radius: 4px; -} - -.el-button--primary { - background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%); - border: none; -} - -.el-button--success { - background: linear-gradient(135deg, #67c23a 0%, #5daf34 100%); - border: none; -} - -.el-button--warning { - background: linear-gradient(135deg, #e6a23c 0%, #cf9236 100%); - border: none; -} - -.el-button--danger { - background: linear-gradient(135deg, #f56c6c 0%, #f25c5c 100%); - border: none; -} - -.el-button--info { - background: linear-gradient(135deg, #909399 0%, #82848a 100%); - border: none; -} - -/* 表单项样式 */ -.el-form-item { - margin-bottom: 22px; -} - -.el-form-item__label { - font-weight: 600; - color: #333; -} - -/* 加载动画样式 */ -.el-loading-mask { - background-color: rgba(255, 255, 255, 0.9); -} - -/* 响应式设计 */ -@media (max-width: 768px) { - .filter-container { - text-align: center; - } - - .filter-container .el-button { - margin: 5px; - width: auto; - } - - .color-preview { - height: 30px; - font-size: 10px; - } - - .color-preview-large { - height: 60px; - font-size: 14px; - } -} - -/* 动画效果 */ -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.el-table tbody tr { - animation: fadeIn 0.3s ease-out; -} - -/* 鼠标悬停效果 */ -.el-table tbody tr:hover { - background-color: #f5f7fa !important; - transition: background-color 0.3s ease; -} - -.el-button:hover { - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(0,0,0,0.15); - transition: all 0.3s ease; + public const string DatabaseSampleIndexCssTemplate = @"/* 通用样式 */ +.d-flex{ + display: flex; +} +.justify-content-between{ + justify-content: space-between; +} +.align-items-center{ + align-items: center; +} + +/* 过滤器容器样式 */ +.filter-container { + margin-bottom: 20px; + padding: 10px 0; +} + +.filter-container .el-button { + margin-right: 10px; +} + +/* 颜色预览样式 */ +.color-preview { + width: 100%; + height: 40px; + border-radius: 4px; + border: 1px solid #dcdfe6; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 12px; + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); +} + +.color-preview-large { + width: 100%; + height: 80px; + border-radius: 8px; + border: 2px solid #dcdfe6; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 16px; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.7); + margin: 10px 0; + transition: all 0.3s ease; +} + +.color-preview-large:hover { + transform: scale(1.02); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); +} + +/* 分页容器样式 */ +.pagination-container { + margin-top: 20px; + text-align: center; +} + +/* 表格样式增强 */ +.el-table { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1); +} + +.el-table th { + background-color: #fafafa; + color: #333; + font-weight: 600; +} + +/* 颜色标签样式 */ +.el-tag { + min-width: 50px; + text-align: center; + font-weight: bold; + border: none !important; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); +} + +/* 对话框样式 */ +.el-dialog { + border-radius: 12px; + overflow: hidden; +} + +.el-dialog__header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 20px 20px 0 20px; +} + +.el-dialog__title { + color: white; + font-weight: 600; +} + +.el-dialog__body { + padding: 30px 20px; +} + +/* 滑块样式 */ +.el-slider { + margin: 20px 0; +} + +.el-slider__runway { + height: 6px; + background-color: #e4e7ed; + border-radius: 3px; +} + +.el-slider__button { + width: 20px; + height: 20px; + border: 2px solid #409eff; +} + +/* 按钮样式增强 */ +.el-button--mini { + padding: 5px 10px; + font-size: 12px; + border-radius: 4px; +} + +.el-button--primary { + background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%); + border: none; +} + +.el-button--success { + background: linear-gradient(135deg, #67c23a 0%, #5daf34 100%); + border: none; +} + +.el-button--warning { + background: linear-gradient(135deg, #e6a23c 0%, #cf9236 100%); + border: none; +} + +.el-button--danger { + background: linear-gradient(135deg, #f56c6c 0%, #f25c5c 100%); + border: none; +} + +.el-button--info { + background: linear-gradient(135deg, #909399 0%, #82848a 100%); + border: none; +} + +/* 表单项样式 */ +.el-form-item { + margin-bottom: 22px; +} + +.el-form-item__label { + font-weight: 600; + color: #333; +} + +/* 加载动画样式 */ +.el-loading-mask { + background-color: rgba(255, 255, 255, 0.9); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .filter-container { + text-align: center; + } + + .filter-container .el-button { + margin: 5px; + width: auto; + } + + .color-preview { + height: 30px; + font-size: 10px; + } + + .color-preview-large { + height: 60px; + font-size: 14px; + } +} + +/* 动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.el-table tbody tr { + animation: fadeIn 0.3s ease-out; +} + +/* 鼠标悬停效果 */ +.el-table tbody tr:hover { + background-color: #f5f7fa !important; + transition: background-color 0.3s ease; +} + +.el-button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0,0,0,0.15); + transition: all 0.3s ease; } "; #endregion From cf095fb2046b1124755a63cae79b02ee0ecf5408 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 10:59:12 +0000 Subject: [PATCH 03/19] Address code review feedback: fix error message, simplify description, extract helper method Agent-Logs-Url: https://github.com/NeuCharFramework/NcfPackageSources/sessions/91362d99-f232-4869-a053-d7259b98d07a Co-authored-by: JeffreySu <2281927+JeffreySu@users.noreply.github.com> --- .../OHS/Local/PL/AgentTemplateRequest.cs | 2 +- .../wwwroot/js/AgentsManager/index.js | 17 ++++++++++------- .../wwwroot/js/PromptRange/prompt.js | 9 ++++++++- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/OHS/Local/PL/AgentTemplateRequest.cs b/src/Extensions/Senparc.Xncf.AgentsManager/OHS/Local/PL/AgentTemplateRequest.cs index 8d911f1b8..07331f88b 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/OHS/Local/PL/AgentTemplateRequest.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/OHS/Local/PL/AgentTemplateRequest.cs @@ -98,7 +98,7 @@ public class AgentTemplate_CreateFromPromptCodeRequest : FunctionAppRequestBase public string Name { get; set; } [Required] - [Description("PromptCode 作用范围||选择 PromptCode 覆盖范围(靶场级别/靶道级别/完整定位)。提示:可选择靶场名称(Range级别)、靶道前缀(Tactic级别)或完整版本号(精确定位)")] + [Description("PromptCode 作用范围||选择覆盖范围:靶场名称(Range级别)、靶道前缀(Tactic级别)或完整版本号(精确定位)")] public SelectionList ScopeSelection { get; set; } = new SelectionList(SelectionType.DropDownList); [Description("手动输入 PromptCode||手动输入 PromptCode(支持靶场名称、靶道前缀或完整版本号),当选择[手动输入 SystemMessage]时必须在此处输入")] diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/index.js b/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/index.js index 9b3e04ac5..c6f4ef202 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/index.js +++ b/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/index.js @@ -2523,7 +2523,7 @@ var app = new Vue({ // 删除 Function Call 标签 handleFunctionCallClose(tag) { - const currentNames = this.agentForm.functionCallNames.split(',').filter(x => x); + const currentNames = this.getFunctionCallNamesList(); const index = currentNames.indexOf(tag); if (index > -1) { currentNames.splice(index, 1); @@ -2532,20 +2532,23 @@ var app = new Vue({ this.functionCallTags = currentNames; }, + // 获取当前 functionCallNames 的数组形式 + getFunctionCallNamesList() { + return this.agentForm.functionCallNames + ? this.agentForm.functionCallNames.split(',').filter(x => x) + : []; + }, + // 自动附加所有 XNCF 功能插件 handleAutoAttachXncfChange(val) { if (val) { // 开启时:将所有可用插件类型合并到 functionCallNames - const currentNames = this.agentForm.functionCallNames - ? this.agentForm.functionCallNames.split(',').filter(x => x) - : []; + const currentNames = this.getFunctionCallNamesList(); const allNames = [...new Set([...currentNames, ...this.pluginTypes])]; this.agentForm.functionCallNames = allNames.join(','); } else { // 关闭时:移除所有自动添加的插件类型(保留用户手动添加的) - const currentNames = this.agentForm.functionCallNames - ? this.agentForm.functionCallNames.split(',').filter(x => x) - : []; + const currentNames = this.getFunctionCallNamesList(); const manualNames = currentNames.filter(name => !this.pluginTypes.includes(name)); this.agentForm.functionCallNames = manualNames.join(','); } diff --git a/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js b/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js index 81f9cc532..3e8332cea 100644 --- a/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js +++ b/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js @@ -2776,9 +2776,16 @@ var app = new Vue({ // 打开创建智能体对话框 openCreateAgentDialog() { + if (!this.promptField) { + this.$message({ + message: '请先选择靶场', + type: 'warning' + }) + return + } if (!this.promptid || !this.promptDetail || !this.promptDetail.fullVersion) { this.$message({ - message: '请先选择一个靶道', + message: '请先选择靶道以确定 PromptCode', type: 'warning' }) return From ab89ea3e847b6b761a83285fd86580842af5b352 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 17:22:00 +0000 Subject: [PATCH 04/19] Fix: 3D map keyboard/toolbar not rendering due to missing map3dNeedsAnimationUpdate check in animation loop Agent-Logs-Url: https://github.com/NeuCharFramework/NcfPackageSources/sessions/70347c9b-5127-4df7-bfc8-cedf25dd82c3 Co-authored-by: JeffreySu <2281927+JeffreySu@users.noreply.github.com> --- .../Areas/Admin/Pages/PromptRange/Prompt.cshtml | 2 +- .../wwwroot/js/PromptRange/prompt.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml b/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml index 4cec99068..cdf151ee7 100644 --- a/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml +++ b/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml @@ -801,7 +801,7 @@ 共 {{ map3dNodes.length }} 个节点
-
+
@*快速创建智能体 dialog*@ Date: Thu, 9 Apr 2026 00:18:34 +0800 Subject: [PATCH 05/19] Enhance textSprite function: improve line handling and adjust sprite dimensions for better rendering --- .../wwwroot/js/AgentsManager/agent-3d.js | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/agent-3d.js b/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/agent-3d.js index 50265e149..52c9d9f61 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/agent-3d.js +++ b/src/Extensions/Senparc.Xncf.AgentsManager/wwwroot/js/AgentsManager/agent-3d.js @@ -15,6 +15,7 @@ const scaleDivisor = options.scaleDivisor || 26; const sizeAttenuation = typeof options.sizeAttenuation === 'boolean' ? options.sizeAttenuation : true; const maxLineLength = options.maxLineLength || 30; + const maxLines = options.maxLines || 8; const maxWorldWidth = options.maxWorldWidth || 15; const maxWorldHeight = options.maxWorldHeight || 9; const bg = options.background || 'rgba(10,20,30,0.82)'; @@ -25,13 +26,38 @@ const ctx = canvas.getContext('2d'); ctx.font = 'bold ' + fontSize + 'px sans-serif'; - const rows = String(text || '').split('\n').map(function (line) { + const rows = []; + String(text || '').split('\n').forEach(function (line) { const lineText = String(line || ''); - if (lineText.length <= maxLineLength) { - return lineText; + if (!lineText) { + rows.push(''); + return; + } + let start = 0; + while (start < lineText.length) { + rows.push(lineText.slice(start, start + maxLineLength)); + start += maxLineLength; + if (rows.length >= maxLines) { + break; + } } - return lineText.slice(0, maxLineLength - 1) + '…'; }); + + if (rows.length === 0) { + rows.push(''); + } + + if (rows.length > maxLines) { + rows.length = maxLines; + } + + if (rows.length >= maxLines) { + const last = rows[maxLines - 1] || ''; + if (last.length >= maxLineLength) { + rows[maxLines - 1] = last.slice(0, maxLineLength - 1) + '…'; + } + } + const width = Math.max.apply(null, rows.map(function (line) { return ctx.measureText(line).width; })) + padding * 2; const rowHeight = Math.ceil(fontSize * 1.45); const height = rowHeight * rows.length + padding * 2; @@ -47,8 +73,8 @@ const x = 2; const y = 2; - const w = canvas.width - 4; - const h = canvas.height - 4; + const w = width - 4; + const h = height - 4; const r = 10; ctx.beginPath(); @@ -83,8 +109,11 @@ sizeAttenuation: sizeAttenuation }); const sprite = new THREE.Sprite(material); - const worldWidth = Math.min(canvas.width / dpr / scaleDivisor, maxWorldWidth); - const worldHeight = Math.min(canvas.height / dpr / scaleDivisor, maxWorldHeight); + const rawWorldWidth = width / scaleDivisor; + const rawWorldHeight = height / scaleDivisor; + const fitRatio = Math.min(1, maxWorldWidth / rawWorldWidth, maxWorldHeight / rawWorldHeight); + const worldWidth = rawWorldWidth * fitRatio; + const worldHeight = rawWorldHeight * fitRatio; sprite.scale.set(worldWidth, worldHeight, 1); return sprite; } From 91f52268d5bcd8a3300624610e712c08d91fc6bf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Apr 2026 16:32:21 +0000 Subject: [PATCH 06/19] Fix Q/E depth pan to sync orbit target Co-authored-by: Jeffrey Su --- .../Areas/Admin/Pages/PromptRange/Prompt.cshtml | 2 +- .../Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml b/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml index 9e1d31f29..0f1869a09 100644 --- a/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml +++ b/src/Extensions/Senparc.Xncf.PromptRange/Areas/Admin/Pages/PromptRange/Prompt.cshtml @@ -794,7 +794,7 @@
双击节点:跳转靶道
⌨️ 键盘快捷键
W / A / S / D:前后左右平移
-
Q / E:上下平移
+
Q / E:深度平移(前后)
R:重置视角
F:适应视图
+/-:放大/缩小
diff --git a/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js b/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js index d2570fa6d..c06986d9c 100644 --- a/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js +++ b/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js @@ -3130,8 +3130,8 @@ var app = new Vue({ case 's': this.map3dCamera.position.y -= SPEED; this.map3dControls.target.y -= SPEED; moved = true; break case 'a': this.map3dCamera.position.x -= SPEED; this.map3dControls.target.x -= SPEED; moved = true; break case 'd': this.map3dCamera.position.x += SPEED; this.map3dControls.target.x += SPEED; moved = true; break - case 'q': this.map3dCamera.position.z += SPEED; moved = true; break - case 'e': this.map3dCamera.position.z -= SPEED; moved = true; break + case 'q': this.map3dCamera.position.z += SPEED; this.map3dControls.target.z += SPEED; moved = true; break + case 'e': this.map3dCamera.position.z -= SPEED; this.map3dControls.target.z -= SPEED; moved = true; break case 'r': this.resetMap3DView(); moved = true; break case 'f': this.fitMap3DView(); moved = true; break case '+': From 0a6e566a5c3eeb939aa233b14d39d65e8e7ff206 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Apr 2026 16:33:33 +0000 Subject: [PATCH 07/19] fix(prompt-range): apply FilterRangeName in prompt code list Co-authored-by: Jeffrey Su --- .../Application/AppServices/PromptRangeAppService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Extensions/Senparc.Xncf.PromptRange/Application/AppServices/PromptRangeAppService.cs b/src/Extensions/Senparc.Xncf.PromptRange/Application/AppServices/PromptRangeAppService.cs index e8dfa6a80..e37c95677 100644 --- a/src/Extensions/Senparc.Xncf.PromptRange/Application/AppServices/PromptRangeAppService.cs +++ b/src/Extensions/Senparc.Xncf.PromptRange/Application/AppServices/PromptRangeAppService.cs @@ -136,10 +136,13 @@ public async Task ViewPromptCodeList(PromptRange_ViewPromptCo return await this.GetStringResponseAsync(async (response, logger) => { var promptItemService = base.GetService(); - var tree = await promptItemService.GetPromptRangeTreeList(true, true); + var filterRangeName = request?.FilterRangeName?.Trim(); + var tree = await promptItemService.GetPromptRangeTreeList(true, true, filterRangeName); logger.Append("=== PromptCode 列表(可用于在 AgentsManager 中创建智能体)==="); logger.Append(""); + logger.Append($"筛选条件:{(filterRangeName.IsNullOrEmpty() ? "全部靶场" : filterRangeName)}"); + logger.Append(""); logger.Append("覆盖范围说明:"); logger.Append(" 靶场级别(Range):使用靶场名称作为 PromptCode,匹配该靶场下的最优 Prompt"); logger.Append(" 靶道级别(Tactic):使用「靶场名称-T战术编号」,匹配该战术下的最优 Prompt"); From 14e091b9af980cffc508cad5131f61b7d2b87e58 Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Thu, 9 Apr 2026 00:45:57 +0800 Subject: [PATCH 08/19] fix: add missing using directive for Senparc.CO2NET.Extensions --- .../Application/AppServices/PromptRangeAppService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Extensions/Senparc.Xncf.PromptRange/Application/AppServices/PromptRangeAppService.cs b/src/Extensions/Senparc.Xncf.PromptRange/Application/AppServices/PromptRangeAppService.cs index e37c95677..3bd6777c7 100644 --- a/src/Extensions/Senparc.Xncf.PromptRange/Application/AppServices/PromptRangeAppService.cs +++ b/src/Extensions/Senparc.Xncf.PromptRange/Application/AppServices/PromptRangeAppService.cs @@ -11,6 +11,7 @@ using Senparc.Xncf.PromptRange.Domain.Models.Entities; using Senparc.Xncf.PromptRange.Domain.Services; using Senparc.Xncf.PromptRange.Models.DatabaseModel.Dto; +using Senparc.CO2NET.Extensions; namespace Senparc.Xncf.PromptRange.OHS.Local.AppService; From 8e12f63f5daaee77173e1c581823e76cf16ecdd5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Apr 2026 16:51:35 +0000 Subject: [PATCH 09/19] Fix Map3D keyboard shortcuts under IME/layout Co-authored-by: Jeffrey Su --- .../wwwroot/js/PromptRange/prompt.js | 71 ++++++++++++++++--- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js b/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js index c06986d9c..bd8802a2e 100644 --- a/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js +++ b/src/Extensions/Senparc.Xncf.PromptRange/wwwroot/js/PromptRange/prompt.js @@ -3122,16 +3122,47 @@ var app = new Vue({ if (this._map3dKeydownHandler) return const SPEED = 3 this._map3dKeydownHandler = (e) => { - if (!this.mapDialogVisible || !this.map3dCamera || !this.map3dControls) return + if (!this.mapDialogVisible || !this.map3dCamera) return + if (e.ctrlKey || e.metaKey || e.altKey) return + + // 优先使用物理键位(e.code)避免中文输入法/非英文布局下 e.key 不稳定导致快捷键失效 + const key = (e.key || '').toLowerCase() + const code = e.code || '' + let actionKey = key + + if (code.startsWith('Key') && code.length === 4) { + actionKey = code.slice(3).toLowerCase() + } else if (code === 'NumpadAdd') { + actionKey = '+' + } else if (code === 'NumpadSubtract') { + actionKey = '-' + } else if (code === 'Equal' && (key === '' || key === 'unidentified' || key === 'process')) { + actionKey = '=' + } else if (code === 'Minus' && (key === '' || key === 'unidentified' || key === 'process')) { + actionKey = '-' + } + + const hasControls = !!(this.map3dControls && this.map3dControls.target) + const moveBy = (x, y, z) => { + this.map3dCamera.position.x += x + this.map3dCamera.position.y += y + this.map3dCamera.position.z += z + if (hasControls) { + this.map3dControls.target.x += x + this.map3dControls.target.y += y + this.map3dControls.target.z += z + } + } + let moved = false // WASD + QE 平移 - switch (e.key.toLowerCase()) { - case 'w': this.map3dCamera.position.y += SPEED; this.map3dControls.target.y += SPEED; moved = true; break - case 's': this.map3dCamera.position.y -= SPEED; this.map3dControls.target.y -= SPEED; moved = true; break - case 'a': this.map3dCamera.position.x -= SPEED; this.map3dControls.target.x -= SPEED; moved = true; break - case 'd': this.map3dCamera.position.x += SPEED; this.map3dControls.target.x += SPEED; moved = true; break - case 'q': this.map3dCamera.position.z += SPEED; this.map3dControls.target.z += SPEED; moved = true; break - case 'e': this.map3dCamera.position.z -= SPEED; this.map3dControls.target.z -= SPEED; moved = true; break + switch (actionKey) { + case 'w': moveBy(0, SPEED, 0); moved = true; break + case 's': moveBy(0, -SPEED, 0); moved = true; break + case 'a': moveBy(-SPEED, 0, 0); moved = true; break + case 'd': moveBy(SPEED, 0, 0); moved = true; break + case 'q': moveBy(0, 0, SPEED); moved = true; break + case 'e': moveBy(0, 0, -SPEED); moved = true; break case 'r': this.resetMap3DView(); moved = true; break case 'f': this.fitMap3DView(); moved = true; break case '+': @@ -3139,12 +3170,30 @@ var app = new Vue({ case '-': this.map3dCamera.position.z += SPEED * 2; moved = true; break } if (moved) { - this.map3dControls.update() + if (this.map3dControls) { + this.map3dControls.update() + } this.map3dNeedsAnimationUpdate = true + console.debug('[Map3D Keyboard] key:', actionKey, { + rawKey: e.key, + code: e.code, + camera: { + x: this.map3dCamera.position.x, + y: this.map3dCamera.position.y, + z: this.map3dCamera.position.z + }, + target: hasControls ? { + x: this.map3dControls.target.x, + y: this.map3dControls.target.y, + z: this.map3dControls.target.z + } : null + }) e.preventDefault() + e.stopPropagation() } } - window.addEventListener('keydown', this._map3dKeydownHandler) + window.addEventListener('keydown', this._map3dKeydownHandler, true) + console.debug('[Map3D Keyboard] shortcut listener registered') }, // 初始化 3D 导图 @@ -5935,7 +5984,7 @@ var app = new Vue({ // 移除键盘事件处理器 if (this._map3dKeydownHandler) { - window.removeEventListener('keydown', this._map3dKeydownHandler) + window.removeEventListener('keydown', this._map3dKeydownHandler, true) this._map3dKeydownHandler = null } From 822d9951cf4091037ba2dded8ab77b251d76bd1c Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Thu, 9 Apr 2026 00:54:46 +0800 Subject: [PATCH 10/19] fix: normalize prompt code to avoid storing alias in SystemMessage --- .../AppService/AgentTemplateAppService.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs index 2cdf16047..a5497a901 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs @@ -46,7 +46,7 @@ public async Task AgentTemplateManage(AgentTemplate_ManageReq return await this.GetStringResponseAsync(async (response, logger) => { SenparcAI_GetByVersionResponse promptResult; - var promptCode = request.GetySystemMessagePromptCode(); + var promptCode = await NormalizePromptCodeAsync(request.GetySystemMessagePromptCode()); try { @@ -80,7 +80,7 @@ public async Task CreateAgentFromPromptCode(AgentTemplate_Cre { return await this.GetStringResponseAsync(async (response, logger) => { - var promptCode = request.GetPromptCode(); + var promptCode = await NormalizePromptCodeAsync(request.GetPromptCode()); if (string.IsNullOrEmpty(promptCode)) { @@ -117,6 +117,30 @@ public async Task CreateAgentFromPromptCode(AgentTemplate_Cre }); } + /// + /// 将靶场别称开头的 PromptCode 归一化为 RangeName 开头,避免把 Alias 存入 SystemMessage。 + /// + private async Task NormalizePromptCodeAsync(string promptCode) + { + if (string.IsNullOrWhiteSpace(promptCode)) + { + return promptCode; + } + + var normalizedPromptCode = promptCode.Trim(); + var splitIndex = normalizedPromptCode.IndexOf('-'); + var rangePrefix = splitIndex >= 0 ? normalizedPromptCode.Substring(0, splitIndex) : normalizedPromptCode; + var suffix = splitIndex >= 0 ? normalizedPromptCode.Substring(splitIndex) : string.Empty; + + var promptRange = await _promptRangeService.GetObjectAsync(z => z.RangeName == rangePrefix || z.Alias == rangePrefix); + if (promptRange == null || string.IsNullOrWhiteSpace(promptRange.RangeName)) + { + return normalizedPromptCode; + } + + return promptRange.RangeName + suffix; + } + /// /// 获取 AgentTemplate 的列表 /// From c6d8a26313e242f795fdb0ceb528b6c681f48597 Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Thu, 9 Apr 2026 21:59:40 +0800 Subject: [PATCH 11/19] feat: add FindAgentTemplate function and enhance ChatGroupRequest for manual input support --- .../AppService/AgentTemplateAppService.cs | 68 +++++++++ .../AppService/ChatGroupAppService.cs | 136 ++++++++++++++++-- .../Application/DTOs/AgentTemplateRequest.cs | 13 ++ .../Application/DTOs/ChatGroupRequest.cs | 9 ++ 4 files changed, 213 insertions(+), 13 deletions(-) diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs index a5497a901..8337b47bb 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs @@ -117,6 +117,74 @@ public async Task CreateAgentFromPromptCode(AgentTemplate_Cre }); } + [FunctionRender("搜索 Agent 模板并返回 ID", "根据名称或 PromptCode 搜索最匹配的 AgentTemplate,并返回可选 ID。支持多个关键词。", typeof(Register))] + public async Task FindAgentTemplate(AgentTemplate_FindByNameRequest request) + { + return await this.GetStringResponseAsync(async (response, logger) => + { + if (string.IsNullOrWhiteSpace(request.Query)) + { + return "请输入搜索词(名称、PromptCode 或关键字)"; + } + + var topN = request.TopN <= 0 ? 5 : Math.Min(request.TopN, 20); + var aliasMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["提示词优化器"] = "PromptCatalyzer", + ["优化器"] = "PromptCatalyzer" + }; + + var keywords = request.Query + .Split(new[] { ',', ',', ';', ';', '\n', '\r', '|' }, StringSplitOptions.RemoveEmptyEntries) + .Select(z => z.Trim()) + .Where(z => !string.IsNullOrWhiteSpace(z)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (keywords.Count == 0) + { + return "请输入有效搜索词"; + } + + var enabledAgents = await _agentsTemplateService.GetFullListAsync(z => z.Enable, z => z.Id, Ncf.Core.Enums.OrderingType.Descending); + + foreach (var keywordRaw in keywords) + { + var keyword = aliasMap.TryGetValue(keywordRaw, out var alias) ? alias : keywordRaw; + var exact = enabledAgents + .Where(z => string.Equals(z.Name, keyword, StringComparison.OrdinalIgnoreCase) + || string.Equals(z.PromptCode, keyword, StringComparison.OrdinalIgnoreCase)) + .OrderByDescending(z => z.Id) + .ToList(); + + var fuzzy = enabledAgents + .Where(z => + (!string.IsNullOrWhiteSpace(z.Name) && z.Name.Contains(keyword, StringComparison.OrdinalIgnoreCase)) + || (!string.IsNullOrWhiteSpace(z.PromptCode) && z.PromptCode.Contains(keyword, StringComparison.OrdinalIgnoreCase))) + .OrderByDescending(z => z.Id) + .ToList(); + + var candidates = exact.Count > 0 + ? exact + : fuzzy; + + logger.Append($"关键词:{keywordRaw}"); + if (candidates.Count == 0) + { + logger.Append(" 未找到可用 AgentTemplate"); + continue; + } + + foreach (var c in candidates.Take(topN)) + { + logger.Append($" ID={c.Id} | 名称={c.Name} | PromptCode={c.PromptCode}"); + } + } + + return logger.ToString(); + }); + } + /// /// 将靶场别称开头的 PromptCode 归一化为 RangeName 开头,避免把 Alias 存入 SystemMessage。 /// diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs index 86872bec0..fb2ecc488 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs @@ -53,20 +53,25 @@ public async Task ManageChatGroupManage(ChatGroup_ManageChatG { return await this.GetStringResponseAsync(async (response, logger) => { - //群主 - if (request.Admin.SelectedValues.Count() == 0 || !int.TryParse(request.Admin.SelectedValues.First(), out int adminId)) + var enabledAgents = await _agentsTemplateService.GetFullListAsync(z => z.Enable, z => z.Id, Ncf.Core.Enums.OrderingType.Descending); + + // 群主:支持传 Agent ID、名称、PromptCode + var adminInput = request.Admin.SelectedValues.FirstOrDefault() ?? request.AdminNameOrId; + var adminResult = ResolveAgentId(enabledAgents, adminInput); + if (!adminResult.Success) { - return "必须选择一位群主,请到 AgentTemplate 中设置!"; + return $"群主选择失败:{adminResult.ErrorMessage}"; } + var adminId = adminResult.AgentId; - //对接人 - if (request.EnterAgent.SelectedValues.Count() == 0 || !int.TryParse(request.EnterAgent.SelectedValues.First(), out int enterAgentId)) + // 对接人:支持传 Agent ID、名称、PromptCode + var enterInput = request.EnterAgent.SelectedValues.FirstOrDefault() ?? request.EnterAgentNameOrId; + var enterResult = ResolveAgentId(enabledAgents, enterInput); + if (!enterResult.Success) { - return "必须选择一位对接人,请到 AgentTemplate 中设置!"; + return $"对接人选择失败:{enterResult.ErrorMessage}"; } - - //var agentsTemplateAdmin = await _agentsTemplateService.GetAgentTemplateAsync(adminId); - //var agentsTemplateEnterAgent = await _agentsTemplateService.GetAgentTemplateAsync(enterAgent); + var enterAgentId = enterResult.AgentId; //TODO:封装到 Service 中 ChatGroup chatGroup = null; @@ -76,13 +81,18 @@ public async Task ManageChatGroupManage(ChatGroup_ManageChatG { //新建 chatGroup = new ChatGroup(chatGroupDto); - isNew = false; + isNew = true; } else { - int.TryParse(request.ChatGroup.SelectedValues.First(), out int chatGroupId); + int.TryParse(request.ChatGroup.SelectedValues.FirstOrDefault(), out int chatGroupId); chatGroup = await _chatGroupService.GetObjectAsync(z => z.Id == chatGroupId); - _chatGroupService.Mapper.Map(chatGroupDto); + if (chatGroup == null) + { + return $"未找到需要编辑的 ChatGroup,ID:{chatGroupId}"; + } + + chatGroup.Update(chatGroupDto); } await _chatGroupService.SaveObjectAsync(chatGroup); @@ -91,7 +101,30 @@ public async Task ManageChatGroupManage(ChatGroup_ManageChatG //添加成员 var memberList = new List(); - var memberIdList = request.Members.SelectedValues.Select(z => int.Parse(z)).ToList(); + var rawMemberInputs = new List(); + rawMemberInputs.AddRange(request.Members.SelectedValues.Where(z => !string.IsNullOrWhiteSpace(z))); + rawMemberInputs.AddRange(SplitInputs(request.MemberNamesOrIds)); + + var memberIdList = new List(); + foreach (var memberInput in rawMemberInputs.Distinct(StringComparer.OrdinalIgnoreCase)) + { + var memberResult = ResolveAgentId(enabledAgents, memberInput); + if (!memberResult.Success) + { + return $"群成员选择失败:{memberResult.ErrorMessage}"; + } + + if (!memberIdList.Contains(memberResult.AgentId)) + { + memberIdList.Add(memberResult.AgentId); + } + } + + if (memberIdList.Count == 0) + { + return "请至少提供一个群成员(支持 ID 或名称)"; + } + //合并“对接人”为成员 if (!memberIdList.Contains(chatGroupDto.EnterAgentTemplateId)) { @@ -113,6 +146,83 @@ public async Task ManageChatGroupManage(ChatGroup_ManageChatG }); } + private static readonly Dictionary AgentNameAliasMap = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["提示词优化器"] = "PromptCatalyzer", + ["优化器"] = "PromptCatalyzer" + }; + + private static List SplitInputs(string rawInput) + { + if (string.IsNullOrWhiteSpace(rawInput)) + { + return new List(); + } + + return rawInput + .Split(new[] { ',', ',', ';', ';', '\n', '\r', '|' }, StringSplitOptions.RemoveEmptyEntries) + .Select(z => z.Trim()) + .Where(z => !string.IsNullOrWhiteSpace(z)) + .ToList(); + } + + private (bool Success, int AgentId, string ErrorMessage) ResolveAgentId(List enabledAgents, string rawInput) + { + if (string.IsNullOrWhiteSpace(rawInput)) + { + return (false, 0, "输入为空,请传入 Agent ID、名称或 PromptCode"); + } + + var input = rawInput.Trim(); + + if (int.TryParse(input, out var agentId)) + { + var byId = enabledAgents.FirstOrDefault(z => z.Id == agentId); + if (byId == null) + { + return (false, 0, $"未找到启用状态的 Agent,ID:{agentId}"); + } + return (true, byId.Id, string.Empty); + } + + if (AgentNameAliasMap.TryGetValue(input, out var aliasName)) + { + input = aliasName; + } + + var exactName = enabledAgents.Where(z => string.Equals(z.Name, input, StringComparison.OrdinalIgnoreCase)).ToList(); + if (exactName.Count == 1) + { + return (true, exactName[0].Id, string.Empty); + } + + var exactPromptCode = enabledAgents.Where(z => string.Equals(z.PromptCode, input, StringComparison.OrdinalIgnoreCase)).ToList(); + if (exactPromptCode.Count == 1) + { + return (true, exactPromptCode[0].Id, string.Empty); + } + + var fuzzy = enabledAgents.Where(z => + (!string.IsNullOrWhiteSpace(z.Name) && z.Name.Contains(input, StringComparison.OrdinalIgnoreCase)) + || (!string.IsNullOrWhiteSpace(z.PromptCode) && z.PromptCode.Contains(input, StringComparison.OrdinalIgnoreCase))) + .OrderByDescending(z => z.Id) + .ToList(); + + if (fuzzy.Count == 1) + { + return (true, fuzzy[0].Id, string.Empty); + } + + if (fuzzy.Count > 1) + { + var options = string.Join(";", fuzzy.Take(5).Select(z => $"{z.Id}:{z.Name}({z.PromptCode})")); + return (false, 0, $"输入“{rawInput}”匹配到多个 Agent,请指定 ID:{options}"); + } + + return (false, 0, $"未找到匹配的 Agent:{rawInput}"); + } + [FunctionRender("启动 ChatGroup", "启动 ChatGroup", typeof(Register))] public async Task RunChatGroup(ChatGroup_RunChatGroupRequest request) { diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/AgentTemplateRequest.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/AgentTemplateRequest.cs index 7a93b3f37..baf55a47c 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/AgentTemplateRequest.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/AgentTemplateRequest.cs @@ -127,4 +127,17 @@ public override async Task LoadData(IServiceProvider serviceProvider) await PromptRangeItemHelper.LoadPromptRangeItemSelection(serviceProvider, ScopeSelection); } } + + /// + /// 搜索 AgentTemplate 并返回可用 ID 的请求 + /// + public class AgentTemplate_FindByNameRequest : FunctionAppRequestBase + { + [Required] + [Description("搜索词||支持名称、PromptCode 或关键字,可输入多个,使用逗号、分号、换行分隔")] + public string Query { get; set; } + + [Description("最大返回数量||每个搜索词的最大候选数,默认 5")] + public int TopN { get; set; } = 5; + } } diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs index 77a6e57fc..769aa7bc2 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs @@ -33,14 +33,23 @@ public class ChatGroup_ManageChatGroupRequest : FunctionAppRequestBase [Description("群成员||群成员")] public SelectionList Members { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List()); + [Description("群成员(手动输入)||可选。支持名称、ID、PromptCode,多个值可用逗号、分号、换行分隔")] + public string MemberNamesOrIds { get; set; } + [Required] [Description("群主||群管理员,群管理员不会被合并到“群成员”中,通常不参与显式的发言。")] public SelectionList Admin { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); + [Description("群主(手动输入)||可选。支持名称、ID 或 PromptCode")] + public string AdminNameOrId { get; set; } + [Required] [Description("对接人||对接人,即接受命令的人,通常也是期待返回期望结果的人。对接人也会被合并到“群成员”中")] public SelectionList EnterAgent { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); + [Description("对接人(手动输入)||可选。支持名称、ID 或 PromptCode")] + public string EnterAgentNameOrId { get; set; } + [MaxLength(200)] [Description("说明||说明")] From 1ea003f3cd002b5a84ad10ae37f6591493ea290a Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Mon, 13 Apr 2026 08:49:20 +0800 Subject: [PATCH 12/19] feat: enhance ChatGroup functionality with automatic host agent selection and improved request descriptions --- .../AppService/ChatGroupAppService.cs | 168 ++++++++++++++---- .../Application/DTOs/ChatGroupRequest.cs | 9 +- .../Domain/Services/ChatGroupService.cs | 22 +++ 3 files changed, 155 insertions(+), 44 deletions(-) diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs index fb2ecc488..461a5be88 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs @@ -14,7 +14,9 @@ using Senparc.Xncf.AgentsManager.OHS.Local.PL; using Senparc.Xncf.AIKernel.Domain.Models.DatabaseModel.Dto; using Senparc.Xncf.AIKernel.Domain.Services; +using Senparc.Xncf.PromptRange.Domain.Models; using Senparc.Xncf.PromptRange.Domain.Services; +using Senparc.Xncf.PromptRange.OHS.Local.PL.Request; using Senparc.Xncf.PromptRange.OHS.Local.PL.Response; using System; using System.Collections.Generic; @@ -31,6 +33,7 @@ public class ChatGroupAppService : AppServiceBase private readonly AIModelService _aIModelService; private readonly ChatTaskService _chatTaskService; private readonly PromptItemService _promptItemService; + private readonly PromptRangeService _promptRangeService; public ChatGroupAppService(IServiceProvider serviceProvider, ChatGroupService chatGroupService, @@ -38,7 +41,8 @@ public ChatGroupAppService(IServiceProvider serviceProvider, AgentsTemplateService agentsTemplateService, AIModelService aIModelService, ChatTaskService chatTaskService, - PromptItemService promptItemService) : base(serviceProvider) + PromptItemService promptItemService, + PromptRangeService promptRangeService) : base(serviceProvider) { this._chatGroupService = chatGroupService; this._chatGroupMemeberService = chatGroupMemeberService; @@ -46,6 +50,7 @@ public ChatGroupAppService(IServiceProvider serviceProvider, this._aIModelService = aIModelService; this._chatTaskService = chatTaskService; this._promptItemService = promptItemService; + this._promptRangeService = promptRangeService; } [FunctionRender("管理 ChatGroup", "管理 ChatGroup", typeof(Register))] @@ -55,23 +60,10 @@ public async Task ManageChatGroupManage(ChatGroup_ManageChatG { var enabledAgents = await _agentsTemplateService.GetFullListAsync(z => z.Enable, z => z.Id, Ncf.Core.Enums.OrderingType.Descending); - // 群主:支持传 Agent ID、名称、PromptCode - var adminInput = request.Admin.SelectedValues.FirstOrDefault() ?? request.AdminNameOrId; - var adminResult = ResolveAgentId(enabledAgents, adminInput); - if (!adminResult.Success) - { - return $"群主选择失败:{adminResult.ErrorMessage}"; - } - var adminId = adminResult.AgentId; - - // 对接人:支持传 Agent ID、名称、PromptCode - var enterInput = request.EnterAgent.SelectedValues.FirstOrDefault() ?? request.EnterAgentNameOrId; - var enterResult = ResolveAgentId(enabledAgents, enterInput); - if (!enterResult.Success) - { - return $"对接人选择失败:{enterResult.ErrorMessage}"; - } - var enterAgentId = enterResult.AgentId; + // 固定逻辑:群主和对接人优先使用“主持人”名称中评分最高的 Agent;不存在则自动创建。 + var preferredHost = await EnsurePreferredHostAgentAsync(enabledAgents, logger); + var adminId = preferredHost.Id; + var enterAgentId = preferredHost.Id; //TODO:封装到 Service 中 ChatGroup chatGroup = null; @@ -146,6 +138,103 @@ public async Task ManageChatGroupManage(ChatGroup_ManageChatG }); } + private async Task EnsurePreferredHostAgentAsync(List enabledAgents, AppServiceLogger logger) + { + var hostCandidates = enabledAgents + .Where(z => !string.IsNullOrWhiteSpace(z.Name) + && z.Name.Contains("主持人", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (hostCandidates.Count > 0) + { + var scored = new List<(AgentTemplate Agent, float Score)>(); + foreach (var candidate in hostCandidates) + { + var score = await GetAgentScoreByPromptCodeAsync(candidate.PromptCode); + scored.Add((candidate, score)); + } + + var selected = scored + .OrderByDescending(z => z.Score) + .ThenByDescending(z => z.Agent.Id) + .First().Agent; + + logger.Append($"已自动选择主持人 Agent:{selected.Name}(ID:{selected.Id})"); + return selected; + } + + var promptRange = await _promptRangeService.AddAsync("智能主持人"); + + var chatModel = await _aIModelService.GetObjectAsync( + z => z.Show && z.ConfigModelType == Senparc.Xncf.AIKernel.Domain.Models.ConfigModelType.Chat) + ?? await _aIModelService.GetObjectAsync( + z => z.ConfigModelType == Senparc.Xncf.AIKernel.Domain.Models.ConfigModelType.Chat); + + if (chatModel == null) + { + throw new NcfExceptionBase("未找到可用的 Chat 类型 AI 模型,无法自动创建主持人 Agent"); + } + + var promptItem = await _promptItemService.AddPromptItemAsync(new PromptItem_AddRequest + { + RangeId = promptRange.Id, + ModelId = chatModel.Id, + Content = "你是一个多智能体小组的主持人(群主)。职责:维护讨论秩序、拆解任务、分配发言顺序、在信息不足时追问澄清、在出现分歧时推动收敛,并输出清晰的阶段结论。你应保持中立、简洁、可执行,不直接替代成员完成专业内容。", + IsTopTactic = true, + IsNewTactic = false, + IsNewSubTactic = false, + IsNewAiming = false, + NumsOfResults = 0, + MaxToken = 3000, + Temperature = 0.4f, + TopP = 0.8f, + FrequencyPenalty = 0, + PresencePenalty = 0, + StopSequences = null, + IsDraft = true, + Note = "自动主持人", + ExpectedResultsJson = string.Empty, + Prefix = string.Empty, + Suffix = string.Empty, + VariableDictJson = string.Empty + }); + + var hostAgent = new AgentTemplate( + name: "智能主持人", + systemMessage: promptItem.FullVersion, + enable: true, + description: "系统自动创建的主持人 Agent(用于群主与对接人)", + promptCode: promptItem.FullVersion, + hookRobotType: HookRobotType.None, + hookRobotParameter: string.Empty, + avastar: null, + functionCallNames: null, + mcpEndpoints: null); + + await _agentsTemplateService.SaveObjectAsync(hostAgent); + + logger.Append($"未找到主持人 Agent,已自动创建:{hostAgent.Name}(ID:{hostAgent.Id}),PromptCode:{hostAgent.PromptCode}"); + return hostAgent; + } + + private async Task GetAgentScoreByPromptCodeAsync(string promptCode) + { + if (string.IsNullOrWhiteSpace(promptCode)) + { + return -1; + } + + try + { + var promptItem = await _promptItemService.GetBestPromptAsync(promptCode, true); + return promptItem == null ? -1 : (float)promptItem.EvalAvgScore; + } + catch + { + return -1; + } + } + private static readonly Dictionary AgentNameAliasMap = new Dictionary(StringComparer.OrdinalIgnoreCase) { @@ -235,32 +324,35 @@ public async Task RunChatGroup(ChatGroup_RunChatGroupRequest } var aiModelSelected = request.AIModel.SelectedValues.FirstOrDefault(); - var aiSetting = Senparc.AI.Config.SenparcAiSetting; - if (aiModelSelected != "Default") - { - int.TryParse(aiModelSelected, out int aiModelId); - var aiModel = await _aIModelService.GetObjectAsync(z => z.Id == aiModelId); - if (aiModel == null) - { - throw new NcfExceptionBase($"当前选择的 AI 模型不存在:{aiModelSelected}"); - } + var aiModelId = 0; - var aiModelDto = _aIModelService.Mapper.Map(aiModel); - - aiSetting = _aIModelService.BuildSenparcAiSetting(aiModelDto); + if (!string.IsNullOrWhiteSpace(aiModelSelected) + && !string.Equals(aiModelSelected, "Default", StringComparison.OrdinalIgnoreCase) + && int.TryParse(aiModelSelected, out var selectedModelId) + && selectedModelId > 0) + { + aiModelId = selectedModelId; } - List tasks = new List(); - foreach (var chatGroupId in request.ChatGroups.SelectedValues.Select(z => int.Parse(z))) { - var task = _chatGroupService.RunChatGroup(logger, chatGroupId, request.Command, aiSetting, request.Individuation.IsSelected("1")); - tasks.Add(task); - } - - Task.WaitAll(tasks.ToArray()); + var runRequest = new ChatGroup_RunGroupRequest + { + Name = $"ChatGroup-{chatGroupId}-{DateTime.Now:yyyyMMddHHmmss}", + ChatGroupId = chatGroupId, + AiModelId = aiModelId, + PromptCommand = request.Command, + Description = "由 FunctionRender 启动", + Personality = request.Individuation.IsSelected("1"), + HookPlatform = HookPlatform.None, + HookParameter = string.Empty, + ChatMaxRound = ChatGroupService.ChatMaxRound + }; + + await _chatGroupService.RunChatGroupInThread(runRequest); + } - return logger.ToString(); + return "已创建并启动任务,请到 ChatTask 列表查看执行状态。"; }); } diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs index 769aa7bc2..b3b4b8e01 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs @@ -29,22 +29,19 @@ public class ChatGroup_ManageChatGroupRequest : FunctionAppRequestBase [Description("群名称||群名称")] public string Name { get; set; } - [Required] - [Description("群成员||群成员")] + [Description("群成员||群成员(可不选,改用“群成员(手动输入)”)")] public SelectionList Members { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List()); [Description("群成员(手动输入)||可选。支持名称、ID、PromptCode,多个值可用逗号、分号、换行分隔")] public string MemberNamesOrIds { get; set; } - [Required] - [Description("群主||群管理员,群管理员不会被合并到“群成员”中,通常不参与显式的发言。")] + [Description("群主||优先自动使用评分最高的“主持人”Agent;手动输入仅作兼容")] public SelectionList Admin { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); [Description("群主(手动输入)||可选。支持名称、ID 或 PromptCode")] public string AdminNameOrId { get; set; } - [Required] - [Description("对接人||对接人,即接受命令的人,通常也是期待返回期望结果的人。对接人也会被合并到“群成员”中")] + [Description("对接人||优先自动使用评分最高的“主持人”Agent;手动输入仅作兼容")] public SelectionList EnterAgent { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); [Description("对接人(手动输入)||可选。支持名称、ID 或 PromptCode")] diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Domain/Services/ChatGroupService.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Domain/Services/ChatGroupService.cs index 19b889b61..07574dfd5 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Domain/Services/ChatGroupService.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Domain/Services/ChatGroupService.cs @@ -302,6 +302,28 @@ private async Task RunChatGroupExecutionCoreAsync(ChatGroup_RunGroupRequest requ var chatGroupService = services.GetService(); var chatGroup = await chatGroupService.GetObjectAsync(x => x.Id == groupId); + + // 默认模型继承规则:未显式指定模型时,优先继承当前群主使用的 Prompt 模型。 + if (aiModelId <= 0 && chatGroup != null) + { + var adminAgent = await agentTemplateService.GetObjectAsync(z => z.Id == chatGroup.AdminAgentTemplateId); + if (adminAgent != null && !adminAgent.PromptCode.IsNullOrEmpty()) + { + try + { + var adminPrompt = await promptItemService.GetBestPromptAsync(adminAgent.PromptCode, true); + if (adminPrompt != null && adminPrompt.ModelId > 0) + { + aiModelId = adminPrompt.ModelId; + } + } + catch + { + // 保持 aiModelId=0,继续走系统默认模型。 + } + } + } + var chatGroupDto = chatGroupService.Mapping(chatGroup); var chatTaskDto = new ChatTaskDto(request.Name, groupId, aiModelId, ChatTask_Status.Waiting, userCommand, request.Description, personality, request.HookPlatform, request.HookParameter, false, From 42955881b3fc7f7e5256031e5e2822df25726baa Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Fri, 24 Apr 2026 17:08:46 +0800 Subject: [PATCH 13/19] feat: Enhance parameter handling in XncfModule - Updated Start.cshtml to include filterable and create options for select inputs. - Normalized JSON parameters in Start.cshtml.cs and ModuleAppService.cs for consistent request handling. - Introduced normalizeMultiValue method in start.js to handle multi-value inputs and ensure proper array formatting. - Adjusted logic for setting default selected values in multi-select and dropdowns to improve user experience. --- .../Functions/FunctionHelperTests.cs | 58 +- .../FunctionRequestParameterNormalizer.cs | 225 +++ .../Functions/FunctionHelper.cs | 41 +- .../Parameters/FunctionParameterInfo.cs | 15 +- .../FunctionParameterUiAttribute.cs | 25 + .../AppService/AgentTemplateAppService.cs | 13 +- .../Application/DTOs/AgentTemplateRequest.cs | 62 +- .../BuildXncfAppService.Generated.cs | 1525 ----------------- .../Areas/Admin/Pages/XncfModule/Start.cshtml | 6 +- .../Admin/Pages/XncfModule/Start.cshtml.cs | 3 +- .../OHS/Local/AppService/ModuleAppService.cs | 3 +- .../js/Admin/Pages/XncfModule/start.js | 33 +- 12 files changed, 431 insertions(+), 1578 deletions(-) create mode 100644 src/Basic/Senparc.Ncf.XncfBase/FunctionRenders/FunctionRequestParameterNormalizer.cs create mode 100644 src/Basic/Senparc.Ncf.XncfBase/Functions/Parameters/FunctionParameterUiAttribute.cs delete mode 100644 src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs diff --git a/src/Basic/Senparc.Ncf.XncfBase.Tests/Functions/FunctionHelperTests.cs b/src/Basic/Senparc.Ncf.XncfBase.Tests/Functions/FunctionHelperTests.cs index 23e8eb6ad..9fb9a866a 100644 --- a/src/Basic/Senparc.Ncf.XncfBase.Tests/Functions/FunctionHelperTests.cs +++ b/src/Basic/Senparc.Ncf.XncfBase.Tests/Functions/FunctionHelperTests.cs @@ -5,8 +5,10 @@ using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Tests; using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Text.Json.Serialization; using System.Threading.Tasks; namespace Senparc.Ncf.XncfBase.Functions.Tests @@ -36,7 +38,25 @@ public override async Task LoadData(IServiceProvider serviceProvider) } } - [FunctionRender("设置参数", "设置备份间隔时间、备份文件路径等参数", typeof(Register))] + public class SetSelectionConfigFunctionAppRequest : FunctionAppRequestBase + { + [Required] + [System.ComponentModel.Description("智能体||从下拉中选择,或直接手动输入")] + [FunctionParameterUi(ParameterType.DropDownList, nameof(AgentOptions), Filterable = true, AllowCreate = true)] + public string AgentName { get; set; } + + [JsonIgnore] + public SelectionList AgentOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); + + public override Task LoadData(IServiceProvider serviceProvider) + { + AgentOptions.Items.Add(new SelectionItem("PromptCatalyzer", "PromptCatalyzer", defaultSelected: true)); + AgentOptions.Items.Add(new SelectionItem("Scorer", "Scorer")); + return Task.CompletedTask; + } + } + + [FunctionRender("设置参数", "设置备份间隔时间、备份文件路径等参数", typeof(TestModuleRegister))] public async Task SetConfig(SetConfigFunctionAppRequest request) { return await this.GetStringResponseAsync(async (response, logger) => @@ -44,6 +64,15 @@ public async Task SetConfig(SetConfigFunctionAppRequest reque return request.BackupPath; }); } + + [FunctionRender("设置智能体", "测试简单值 + 下拉元数据", typeof(TestModuleRegister))] + public async Task SetSelectionConfig(SetSelectionConfigFunctionAppRequest request) + { + return await this.GetStringResponseAsync(async (response, logger) => + { + return request.AgentName; + }); + } } @@ -64,5 +93,32 @@ public void GetFunctionParameterInfoAsyncTest() Assert.IsTrue(result.Count > 0); Console.WriteLine(result.ToJson(true)); } + + [TestMethod()] + public void GetFunctionParameterInfoWithUiAttributeTest() + { + var functionBag = Senparc.Ncf.XncfBase.Register.FunctionRenderCollection[typeof(TestModuleRegister)].Values + .First(z => z.MethodInfo.Name == nameof(TestFunctionAppService.SetSelectionConfig)); + + var result = FunctionHelper.GetFunctionParameterInfoAsync(base._serviceProvider, functionBag, true).GetAwaiter().GetResult(); + var agentName = result.First(z => z.Name == nameof(TestFunctionAppService.SetSelectionConfigFunctionAppRequest.AgentName)); + + Assert.AreEqual(ParameterType.DropDownList, agentName.ParameterType); + Assert.AreEqual("String", agentName.SystemType); + Assert.AreEqual(2, agentName.SelectionList.Items.Count); + Assert.IsTrue(agentName.Filterable); + Assert.IsTrue(agentName.AllowCreate); + } + + [TestMethod()] + public void NormalizeLegacySelectionListJsonToStringRequestTest() + { + var rawJson = "{\"AgentName\":{\"SelectedValues\":[\"PromptCatalyzer\"]}}"; + var normalizedJson = FunctionRequestParameterNormalizer.NormalizeJson(rawJson, typeof(TestFunctionAppService.SetSelectionConfigFunctionAppRequest)); + + var result = Senparc.CO2NET.Helpers.SerializerHelper.GetObject(normalizedJson, typeof(TestFunctionAppService.SetSelectionConfigFunctionAppRequest)) as TestFunctionAppService.SetSelectionConfigFunctionAppRequest; + + Assert.AreEqual("PromptCatalyzer", result.AgentName); + } } } \ No newline at end of file diff --git a/src/Basic/Senparc.Ncf.XncfBase/FunctionRenders/FunctionRequestParameterNormalizer.cs b/src/Basic/Senparc.Ncf.XncfBase/FunctionRenders/FunctionRequestParameterNormalizer.cs new file mode 100644 index 000000000..3cfb81ad5 --- /dev/null +++ b/src/Basic/Senparc.Ncf.XncfBase/FunctionRenders/FunctionRequestParameterNormalizer.cs @@ -0,0 +1,225 @@ +using Senparc.Ncf.XncfBase.Functions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace Senparc.Ncf.XncfBase.FunctionRenders +{ + /// + /// Normalizes request payloads so legacy SelectionList JSON and simple string JSON + /// can both bind to FunctionRender request types. + /// + public static class FunctionRequestParameterNormalizer + { + public static string NormalizeJson(string rawJson, Type requestType) + { + if (string.IsNullOrWhiteSpace(rawJson) || requestType == null) + { + return rawJson; + } + + JsonNode rootNode; + try + { + rootNode = JsonNode.Parse(rawJson); + } + catch + { + return rawJson; + } + + if (rootNode is not JsonObject jsonObject) + { + return rawJson; + } + + var properties = requestType.GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(z => z.CanWrite) + .ToList(); + + foreach (var property in properties) + { + if (!TryGetJsonProperty(jsonObject, property.Name, out var jsonKey, out var currentNode)) + { + continue; + } + + if (property.PropertyType == typeof(SelectionList)) + { + jsonObject[jsonKey] = NormalizeSelectionListNode(currentNode); + continue; + } + + var uiAttr = property.GetCustomAttribute(); + jsonObject[jsonKey] = NormalizeSimpleNode(property.PropertyType, currentNode, uiAttr); + } + + return jsonObject.ToJsonString(new JsonSerializerOptions + { + WriteIndented = false + }); + } + + private static JsonNode NormalizeSelectionListNode(JsonNode currentNode) + { + if (currentNode is JsonObject currentObject && TryGetJsonProperty(currentObject, "SelectedValues", out _, out _)) + { + return currentNode; + } + + var values = ExtractValues(currentNode, forMultiple: true); + var selectedValues = new JsonArray(values.Select(z => JsonValue.Create(z)).ToArray()); + + return new JsonObject + { + ["SelectedValues"] = selectedValues + }; + } + + private static JsonNode NormalizeSimpleNode(Type targetType, JsonNode currentNode, FunctionParameterUiAttribute uiAttr) + { + var forMultiple = uiAttr?.ParameterType == ParameterType.CheckBoxList || targetType == typeof(string[]); + var values = ExtractValues(currentNode, forMultiple); + + if (targetType == typeof(string[])) + { + return new JsonArray(values.Select(z => JsonValue.Create(z)).ToArray()); + } + + if (targetType == typeof(string)) + { + if (currentNode is JsonValue valueNode && TryGetString(valueNode, out var stringValue)) + { + return JsonValue.Create(stringValue); + } + + return JsonValue.Create(forMultiple ? string.Join("\n", values) : values.FirstOrDefault()); + } + + var firstValue = values.FirstOrDefault(); + if (firstValue == null) + { + return null; + } + + if (IsNullableType(targetType, typeof(int)) && int.TryParse(firstValue, out var intValue)) + { + return JsonValue.Create(intValue); + } + + if (IsNullableType(targetType, typeof(long)) && long.TryParse(firstValue, out var longValue)) + { + return JsonValue.Create(longValue); + } + + if (IsNullableType(targetType, typeof(bool)) && bool.TryParse(firstValue, out var boolValue)) + { + return JsonValue.Create(boolValue); + } + + return JsonValue.Create(firstValue); + } + + private static List ExtractValues(JsonNode currentNode, bool forMultiple) + { + if (currentNode == null) + { + return new List(); + } + + if (currentNode is JsonObject currentObject) + { + if (TryGetJsonProperty(currentObject, "SelectedValues", out _, out var selectedValuesNode)) + { + return ExtractValues(selectedValuesNode, true); + } + + return new List(); + } + + if (currentNode is JsonArray currentArray) + { + return currentArray.SelectMany(z => ExtractValues(z, true)) + .Where(z => !string.IsNullOrWhiteSpace(z)) + .Distinct(StringComparer.Ordinal) + .ToList(); + } + + if (currentNode is JsonValue currentValue && TryGetString(currentValue, out var stringValue)) + { + return SplitInput(stringValue, forMultiple); + } + + return new List(); + } + + private static List SplitInput(string input, bool forMultiple) + { + if (string.IsNullOrWhiteSpace(input)) + { + return new List(); + } + + if (!forMultiple) + { + return new List { input.Trim() }; + } + + return input.Split(new[] { ',', ',', ';', ';', '\n', '\r', '|' }, StringSplitOptions.RemoveEmptyEntries) + .Select(z => z.Trim()) + .Where(z => !string.IsNullOrWhiteSpace(z)) + .Distinct(StringComparer.Ordinal) + .ToList(); + } + + private static bool TryGetJsonProperty(JsonObject jsonObject, string propertyName, out string actualKey, out JsonNode currentNode) + { + actualKey = null; + currentNode = null; + + foreach (var item in jsonObject) + { + if (string.Equals(item.Key, propertyName, StringComparison.OrdinalIgnoreCase)) + { + actualKey = item.Key; + currentNode = item.Value; + return true; + } + } + + return false; + } + + private static bool TryGetString(JsonValue jsonValue, out string result) + { + result = null; + + try + { + result = jsonValue.GetValue(); + return true; + } + catch + { + try + { + result = jsonValue.ToString(); + return true; + } + catch + { + return false; + } + } + } + + private static bool IsNullableType(Type actualType, Type targetType) + { + var underlyingType = Nullable.GetUnderlyingType(actualType) ?? actualType; + return underlyingType == targetType; + } + } +} \ No newline at end of file diff --git a/src/Basic/Senparc.Ncf.XncfBase/Functions/FunctionHelper.cs b/src/Basic/Senparc.Ncf.XncfBase/Functions/FunctionHelper.cs index d6f8ae4ea..cbbf06358 100644 --- a/src/Basic/Senparc.Ncf.XncfBase/Functions/FunctionHelper.cs +++ b/src/Basic/Senparc.Ncf.XncfBase/Functions/FunctionHelper.cs @@ -12,6 +12,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Text.Json.Serialization; using System.Text; using System.Threading.Tasks; @@ -69,10 +70,33 @@ public static async Task> GetFunctionParameterInfoAs List result = new List(); foreach (var prop in props) { + if (ShouldIgnoreProperty(prop)) + { + continue; + } + SelectionList selectionList = null; + var filterable = false; + var allowCreate = false; parameterType = ParameterType.Text;//默认为文本内容 - //判断是否存在选项 - if (prop.PropertyType == typeof(SelectionList)) + + var functionParameterUi = prop.GetCustomAttribute(); + if (functionParameterUi != null) + { + parameterType = functionParameterUi.ParameterType; + filterable = functionParameterUi.Filterable; + allowCreate = functionParameterUi.AllowCreate; + + if (!functionParameterUi.SelectionListPropertyName.IsNullOrEmpty() && paraObj != null) + { + var selectionProp = functionParameterType.GetProperty(functionParameterUi.SelectionListPropertyName); + if (selectionProp?.PropertyType == typeof(SelectionList)) + { + selectionList = selectionProp.GetValue(paraObj, null) as SelectionList; + } + } + } + else if (prop.PropertyType == typeof(SelectionList)) { var selections = prop.GetValue(paraObj, null) as SelectionList; switch (selections.SelectionType) @@ -130,12 +154,23 @@ public static async Task> GetFunctionParameterInfoAs } var functionParamInfo = new FunctionParameterInfo(name, title, description, isRequired, systemType, parameterType, - selectionList ?? new SelectionList(SelectionType.Unknown), value, maxLength); + selectionList ?? new SelectionList(SelectionType.Unknown), value, maxLength, + filterable, allowCreate); result.Add(functionParamInfo); } return result; } + private static bool ShouldIgnoreProperty(PropertyInfo propertyInfo) + { + if (propertyInfo.GetCustomAttribute() != null) + { + return true; + } + + return propertyInfo.CustomAttributes.Any(z => z.AttributeType.FullName == "Newtonsoft.Json.JsonIgnoreAttribute"); + } + /// /// 给 SelectionList 对象添加当前解决方案中的 XNCF 项目 /// (扫描当前解决方案包含的所有领域项目) diff --git a/src/Basic/Senparc.Ncf.XncfBase/Functions/Parameters/FunctionParameterInfo.cs b/src/Basic/Senparc.Ncf.XncfBase/Functions/Parameters/FunctionParameterInfo.cs index 9dc8a7dde..83d96634b 100644 --- a/src/Basic/Senparc.Ncf.XncfBase/Functions/Parameters/FunctionParameterInfo.cs +++ b/src/Basic/Senparc.Ncf.XncfBase/Functions/Parameters/FunctionParameterInfo.cs @@ -61,12 +61,23 @@ public class FunctionParameterInfo /// public SelectionList SelectionList { get; set; } + /// + /// 下拉框是否允许搜索 + /// + public bool Filterable { get; set; } + + /// + /// 下拉框是否允许创建自定义值 + /// + public bool AllowCreate { get; set; } + public FunctionParameterInfo() { } public FunctionParameterInfo(string name, string title, string description, - bool isRequired, string systemType, ParameterType parameterType, SelectionList selectionList, object value, int maxLength) + bool isRequired, string systemType, ParameterType parameterType, SelectionList selectionList, object value, int maxLength, + bool filterable = false, bool allowCreate = false) { Name = name; Title = title; @@ -77,6 +88,8 @@ public FunctionParameterInfo(string name, string title, string description, ParameterType = parameterType; Value = value; MaxLength = maxLength; + Filterable = filterable; + AllowCreate = allowCreate; } } } diff --git a/src/Basic/Senparc.Ncf.XncfBase/Functions/Parameters/FunctionParameterUiAttribute.cs b/src/Basic/Senparc.Ncf.XncfBase/Functions/Parameters/FunctionParameterUiAttribute.cs new file mode 100644 index 000000000..51e019dd7 --- /dev/null +++ b/src/Basic/Senparc.Ncf.XncfBase/Functions/Parameters/FunctionParameterUiAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Senparc.Ncf.XncfBase +{ + /// + /// Declares how a simple request property should be rendered in the Function UI. + /// + [AttributeUsage(AttributeTargets.Property)] + public class FunctionParameterUiAttribute : Attribute + { + public ParameterType ParameterType { get; } + + public string SelectionListPropertyName { get; } + + public bool Filterable { get; set; } + + public bool AllowCreate { get; set; } + + public FunctionParameterUiAttribute(ParameterType parameterType, string selectionListPropertyName = null) + { + ParameterType = parameterType; + SelectionListPropertyName = selectionListPropertyName; + } + } +} \ No newline at end of file diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs index 8337b47bb..6b28b124a 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/AgentTemplateAppService.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Senparc.CO2NET.Extensions; namespace Senparc.Xncf.AgentsManager.OHS.Local.AppService @@ -43,10 +44,11 @@ public AgentTemplateAppService(IServiceProvider serviceProvider, AgentsTemplateS [FunctionRender("Agent 模板管理", "Agent 模板管理", typeof(Register))] public async Task AgentTemplateManage(AgentTemplate_ManageRequest request) { + Console.Write(request.ToJson(true)); return await this.GetStringResponseAsync(async (response, logger) => { SenparcAI_GetByVersionResponse promptResult; - var promptCode = await NormalizePromptCodeAsync(request.GetySystemMessagePromptCode()); + var promptCode = await NormalizePromptCodeAsync(request.GetSystemMessagePromptCode()); try { @@ -63,7 +65,7 @@ public async Task AgentTemplateManage(AgentTemplate_ManageReq var agentTemplateDto = new AgentTemplateDto(request.Name, promptCode, true, request.Description, promptCode, - Enum.Parse(request.HookRobotType.SelectedValues.FirstOrDefault()), request.HookRobotParameter, request.FunctionCallNames); + Enum.Parse(request.HookRobotType), request.HookRobotParameter, request.FunctionCallNames); await this._agentsTemplateService.UpdateAgentTemplateAsync(request.Id, agentTemplateDto); @@ -80,7 +82,9 @@ public async Task CreateAgentFromPromptCode(AgentTemplate_Cre { return await this.GetStringResponseAsync(async (response, logger) => { - var promptCode = await NormalizePromptCodeAsync(request.GetPromptCode()); + try{ + Console.Write(request.ToJson(true)); + var promptCode = request.GetPromptCode();//await NormalizePromptCodeAsync(request.GetPromptCode()); if (string.IsNullOrEmpty(promptCode)) { @@ -112,7 +116,10 @@ public async Task CreateAgentFromPromptCode(AgentTemplate_Cre logger.Append($"✅ 智能体「{request.Name}」创建成功!"); logger.Append($"使用的 PromptCode:{promptCode}"); + }catch(Exception ex){ +logger.Append($"❌ 创建智能体失败:{ex.Message}"); + } return logger.ToString(); }); } diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/AgentTemplateRequest.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/AgentTemplateRequest.cs index baf55a47c..b6446e763 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/AgentTemplateRequest.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/AgentTemplateRequest.cs @@ -3,6 +3,7 @@ using Senparc.CO2NET.Extensions; using Senparc.Ncf.Core.Enums; using Senparc.Ncf.Core.Extensions; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; using Senparc.Xncf.AgentsManager.Models.DatabaseModel; @@ -16,6 +17,7 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; @@ -33,12 +35,13 @@ public class AgentTemplate_ManageRequest : FunctionAppRequestBase public int Id { get; set; } [Required] - [Description("SystemMessage||SystemMessage 的 PromptRangeCode(支持自选模式)")] - public SelectionList SystemMessagePromptCodeSelection { get; set; } = new SelectionList(SelectionType.DropDownList); - - [Description("手动输入 SystemMessage||SystemMessage 的 PromptRangeCode(支持自选模式),当 SystemMessage 选择“手动输入 SystemMessage”时必须在此处输入")] + [Description("SystemMessage||SystemMessage 的 PromptRangeCode(支持搜索、下拉和手动输入)")] + [FunctionParameterUi(ParameterType.DropDownList, nameof(SystemMessagePromptCodeOptions), Filterable = true, AllowCreate = true)] public string SystemMessagePromptCode { get; set; } + [JsonIgnore] + public SelectionList SystemMessagePromptCodeOptions { get; set; } = new SelectionList(SelectionType.DropDownList); + [Description("说明||对 Agent Template 进行说明,此信息不会对模型效果产生影响")] @@ -46,7 +49,11 @@ public class AgentTemplate_ManageRequest : FunctionAppRequestBase [Required] [Description("外接平台||需要对外发布消息的平台")] - public SelectionList HookRobotType { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); + [FunctionParameterUi(ParameterType.DropDownList, nameof(HookRobotTypeOptions))] + public string HookRobotType { get; set; } + + [JsonIgnore] + public SelectionList HookRobotTypeOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); //TODO:可以选择多个通道 @@ -56,18 +63,14 @@ public class AgentTemplate_ManageRequest : FunctionAppRequestBase [Description("Function Calls||Function Calls 名称列表,多个用逗号分隔")] public string FunctionCallNames { get; set; } - public string GetySystemMessagePromptCode() + public string GetSystemMessagePromptCode() { - var selectionValue = SystemMessagePromptCodeSelection.SelectedValues.FirstOrDefault(); - if (selectionValue != "0") - { - return selectionValue; - } - else - { - return SystemMessagePromptCode; - } + return SystemMessagePromptCode?.Trim(); + } + public string GetySystemMessagePromptCode() + { + return GetSystemMessagePromptCode(); } public override async Task LoadData(IServiceProvider serviceProvider) @@ -78,10 +81,10 @@ public override async Task LoadData(IServiceProvider serviceProvider) var hookRobotTypeItems = Enum.GetValues(); foreach (var item in hookRobotTypeItems) { - HookRobotType.Items.Add(new SelectionItem(((int)item).ToString(), item.ToString(), item.ToString(), false)); + HookRobotTypeOptions.Items.Add(new SelectionItem(((int)item).ToString(), item.ToString(), item.ToString(), item == Models.DatabaseModel.HookRobotType.None)); } - await PromptRangeItemHelper.LoadPromptRangeItemSelection(serviceProvider, SystemMessagePromptCodeSelection); + await PromptRangeItemHelper.LoadPromptRangeItemSelection(serviceProvider, SystemMessagePromptCodeOptions); } @@ -97,9 +100,9 @@ public class AgentTemplate_CreateFromPromptCodeRequest : FunctionAppRequestBase [Description("智能体名称||新智能体的名称")] public string Name { get; set; } - [Required] - [Description("PromptCode 作用范围||选择覆盖范围:靶场名称(Range级别)、靶道前缀(Tactic级别)或完整版本号(精确定位)")] - public SelectionList ScopeSelection { get; set; } = new SelectionList(SelectionType.DropDownList); + // [Required] + // [Description("PromptCode 作用范围||选择覆盖范围:靶场名称(Range级别):Range、靶道前缀(Tactic级别):Tactic、或完整版本号(精确定位):PromptCode,只能严格从 Range、Tactic、PromptCode 中选择")] + // public string ScopeSelection { get; set; } [Description("手动输入 PromptCode||手动输入 PromptCode(支持靶场名称、靶道前缀或完整版本号),当选择[手动输入 SystemMessage]时必须在此处输入")] public string ManualPromptCode { get; set; } @@ -112,20 +115,19 @@ public class AgentTemplate_CreateFromPromptCodeRequest : FunctionAppRequestBase public string GetPromptCode() { - var selectionValue = ScopeSelection.SelectedValues.FirstOrDefault(); - if (!string.IsNullOrEmpty(selectionValue) && selectionValue != "0") - { - return selectionValue; - } + // if (!string.IsNullOrEmpty(ScopeSelection)) + // { + // return ScopeSelection; + // } return ManualPromptCode; } - public override async Task LoadData(IServiceProvider serviceProvider) - { - await base.LoadData(serviceProvider); + // public override async Task LoadData(IServiceProvider serviceProvider) + // { + // await base.LoadData(serviceProvider); - await PromptRangeItemHelper.LoadPromptRangeItemSelection(serviceProvider, ScopeSelection); - } + // await PromptRangeItemHelper.LoadPromptRangeItemSelection(serviceProvider, ScopeSelection); + // } } /// diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs deleted file mode 100644 index f09d62519..000000000 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs +++ /dev/null @@ -1,1525 +0,0 @@ -// -// This file was generated by MultiFileCodeGenerator -// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. -// - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Senparc.Xncf.XncfBuilder.OHS.Local -{ - /// - /// 自动生成的NCF模板常量类,包含所有模板文件的内容 - /// - public partial class BuildXncfAppService - { -public const string BackendTemplate = @$" -## Database EntityFramework DbContext class sample -File Name: Template_XncfNameSenparcEntities.cs -File Path: /Domain/Models/DatabaseModel -Code: -```csharp -{SenparcEntitiesTemplate} -``` - -## Database Entity class sample -File Name: Color.cs -File Path: /Domain/Models/DatabaseModel -Code: -```csharp -{ColorModelTemplate} -``` - -## Database Entity DTO class sample -File Name: ColorDto.cs -File Path: /Domain/Models/DatabaseModel/Dto -Code: -```csharp -{ColorDtoTemplate} -``` - -## Service class sample -File Name: Template_XncfNameService.cs -File Path: /Domain/Services -Code: -```csharp -{ColorServiceTemplate} -``` -"; - -public const string FrontendTemplate = @$" -## Page UI sample (front-end) -File Name: DatabaseSampleIndex.cshtml -File Path: < ModuleRootPath >/ Areas / Admin / Pages / Template_XncfName -Code: -```razorpage -{DatabaseSampleIndexViewTemplate} -``` - -## Page UI sample (back-end) -File Name: DatabaseSampleIndex.cshtml.cs -File Path: < ModuleRootPath >/ Areas / Admin / Pages / Template_XncfName -Code: -```csharp -{DatabaseSampleIndexCodeBehindTemplate} -``` - -## Page JavaScript file sample -File Name: databaseSampleIndex.js -File Path: < ModuleRootPath >/ wwwroot / js / Admin / Template_XncfName -Code: -```javascript -{DatabaseSampleIndexJsTemplate} -``` - -## Page CSS file sample -File Name: databaseSampleIndex.css -File Path: < ModuleRootPath >/ wwwroot / css / Admin / Template_XncfName -Code: -```css -{DatabaseSampleIndexCssTemplate} -``` -"; - - - #region CODE Templates - - /// - /// 请求类代码 - /// 类型: code - /// - public const string RequestCode = @"using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Senparc.Xncf.XncfBuilder -{ - public class Request - { - public string? Method { get; set; } - public string? Path { get; set; } - public string? Body { get; set; } - - // 新增字段测试动态更新 - public Dictionary? Headers { get; set; } - public DateTime Timestamp { get; set; } = DateTime.Now; - } - -} -"; - - #endregion - - #region BACKEND_TEMPLATE Templates - - /// - /// Senparc实体类模板 - /// 类型: backend_template - /// - public const string SenparcEntitiesTemplate = @"using Microsoft.EntityFrameworkCore; -using Senparc.Ncf.Database; -using Senparc.Ncf.Core.Models; -using Senparc.Ncf.XncfBase.Database; - -namespace Template_OrgName.Xncf.Template_XncfName.Models -{ - public class Template_XncfNameSenparcEntities : XncfDatabaseDbContext - { - public Template_XncfNameSenparcEntities(DbContextOptions dbContextOptions) : base(dbContextOptions) - { - } - - public DbSet Colors { get; set; } - - //DOT REMOVE OR MODIFY THIS LINE 请勿移除或修改本行 - Entities Point - //ex. public DbSet Colors { get; set; } - - //如无特殊需需要,OnModelCreating 方法可以不用写,已经在 Register 中要求注册 - //protected override void OnModelCreating(ModelBuilder modelBuilder) - //{ - //} - } -} -"; - - /// - /// 颜色模型模板 - /// 类型: backend_template - /// - public const string ColorModelTemplate = @"using Senparc.Ncf.Core.Models; -using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; -using System; -using System.ComponentModel.DataAnnotations.Schema; -using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; - -namespace Template_OrgName.Xncf.Template_XncfName -{ - /// - /// Color 实体类 - /// - [Table(Register.DATABASE_PREFIX + nameof(Color))]//必须添加前缀,防止全系统中发生冲突 - [Serializable] - public class Color : EntityBase - { - /// - /// 颜色码,0-255 - /// - public int Red { get; private set; } - /// - /// 颜色码,0-255 - /// - public int Green { get; private set; } - - /// - /// 颜色码,0-255 - /// - public int Blue { get; private set; } - - /// - /// 附加列,测试多次数据库 Migrate - /// - public string AdditionNote { get; private set; } - - private Color() { } - - public Color(int red, int green, int blue) - { - if (red < 0 || green < 0 || blue < 0) - { - Random();//随机 - } - else - { - Red = red; - Green = green; - Blue = blue; - } - } - - public Color(int red, int green, int blue, string additionNote) : this(red, green, blue) - { - AdditionNote = additionNote; - } - - public Color(ColorDto colorDto) - { - Red = colorDto.Red; - Green = colorDto.Green; - Blue = colorDto.Blue; - } - - public void Random() - { - //随机产生颜色代码 - var radom = new Random(); - Func getRadomColorCode = () => radom.Next(0, 255); - Red = getRadomColorCode(); - Green = getRadomColorCode(); - Blue = getRadomColorCode(); - } - - public void Brighten() - { - Red = Math.Min(255, Red + 10); - Green = Math.Min(255, Green + 10); - Blue = Math.Min(255, Blue + 10); - } - - public void Darken() - { - Red = Math.Max(0, Red - 10); - Green = Math.Max(0, Green - 10); - Blue = Math.Max(0, Blue - 10); - } - - public void Update(UpdateColorRequestDto dto) - { - Red = dto.Red; - Green = dto.Green; - Blue = dto.Blue; - AdditionNote = dto.AdditionNote; - } - } -} -"; - - /// - /// 颜色DTO模板 - /// 类型: backend_template - /// - public const string ColorDtoTemplate = @"using Senparc.Ncf.Core.Models; - -namespace Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto -{ - public class ColorDto : DtoBase - { - /// - /// 颜色码,0-255 - /// - public int Red { get; set; } - /// - /// 颜色码,0-255 - /// - public int Green { get; set; } - /// - /// 颜色码,0-255 - /// - public int Blue { get; set; } - - /// - /// 附加列,测试多次数据库 Migrate - /// - public string AdditionNote { get; set; } - - public ColorDto() { } - } -} -"; - - /// - /// 颜色服务模板 - /// 类型: backend_template - /// - public const string ColorServiceTemplate = @"using Senparc.Ncf.Core.Enums; -using Senparc.Ncf.Repository; -using Senparc.Ncf.Service; -using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; -using System; -using System.Threading.Tasks; - -namespace Template_OrgName.Xncf.Template_XncfName.Domain.Services -{ - public class ColorService : ServiceBase - { - public ColorService(IRepositoryBase repo, IServiceProvider serviceProvider) - : base(repo, serviceProvider) - { - } - - public async Task CreateNewColor() - { - Color color = new Color(-1, -1, -1); - await base.SaveObjectAsync(color).ConfigureAwait(false); - ColorDto colorDto = base.Mapper.Map(color); - return colorDto; - } - - public async Task GetOrInitColor() - { - var color = await base.GetObjectAsync(z => true); - if (color == null)//如果是纯第一次安装,理论上不会有残留数据 - { - //创建默认颜色 - ColorDto colorDto = await this.CreateNewColor().ConfigureAwait(false); - return colorDto; - } - - return base.Mapper.Map(color); - } - - public async Task Brighten() - { - //TODO:异步方法需要添加排序功能 - var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); - obj.Brighten(); - await base.SaveObjectAsync(obj).ConfigureAwait(false); - return base.Mapper.Map(obj); - } - - public async Task Darken() - { - //TODO:异步方法需要添加排序功能 - var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); - obj.Darken(); - await base.SaveObjectAsync(obj).ConfigureAwait(false); - return base.Mapper.Map(obj); - } - - public async Task Random() - { - //TODO:异步方法需要添加排序功能 - var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); - obj.Random(); - await base.SaveObjectAsync(obj).ConfigureAwait(false); - return base.Mapper.Map(obj); - } - - //TODO: 更多业务方法可以写到这里 - } -} -"; - - #endregion - - #region FRONTEND_TEMPLATE Templates - - /// - /// 数据库示例索引页面视图模板 - /// 类型: frontend_template - /// - public const string DatabaseSampleIndexViewTemplate = @"@page -@model Template_OrgName.Xncf.Template_XncfName.Areas.Template_XncfName.Pages.DatabaseSampleIndex -@{ - ViewData[""Title""] = ""Color 数据库管理""; - Layout = ""_Layout_Vue""; -} - -@section Style { - -} - -@section breadcrumbs { - 扩展模块 - Template_MenuName - Color 数据库管理 -} - -
-
- 添加颜色 - 刷新 - 调试信息 -
- - -
-

调试信息:

-

tableData长度: {{ tableData ? tableData.length : 'null/undefined' }}

-

total: {{ total }}

-

tableLoading: {{ tableLoading }}

-

Vue实例是否正常: {{ $el ? '是' : '否' }}

-
0""> - 第一条数据: -
{{ JSON.stringify(tableData[0], null, 2) }}
-
- - -
-
简化数据显示测试:
-
- ID: {{item.id}} | - RGB: {{item.red}},{{item.green}},{{item.blue}} | - 时间: {{item.addTime}} -
-

- 没有数据显示! -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- - @* dialog for 添加颜色 *@ - - - - - - - - - - - - -
- RGB({{addForm.red}}, {{addForm.green}}, {{addForm.blue}}) -
-
- - - - - 随机颜色 - -
- - 取 消 - 确 定 - -
- - @* dialog for 编辑颜色 *@ - - - - - - - - - - - - -
- RGB({{editForm.red}}, {{editForm.green}}, {{editForm.blue}}) -
-
- - - - - 随机颜色 - -
- - 取 消 - 确 定 - -
-
- -@section scripts{ - -} "; - - /// - /// 数据库示例索引页面代码后置模板 - /// 类型: frontend_template - /// - public const string DatabaseSampleIndexCodeBehindTemplate = @"using Microsoft.AspNetCore.Mvc; -using Senparc.Ncf.Service; -using Senparc.Ncf.Utility; -using Template_OrgName.Xncf.Template_XncfName.Domain.Services; -using System; -using System.Linq; -using System.Threading.Tasks; -using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; - -namespace Template_OrgName.Xncf.Template_XncfName.Areas.Template_XncfName.Pages -{ - public class DatabaseSampleIndex : Senparc.Ncf.AreaBase.Admin.AdminXncfModulePageModelBase - { - private readonly ColorService _colorService; - - public DatabaseSampleIndex(Lazy xncfModuleService, ColorService colorService) : base(xncfModuleService) - { - _colorService = colorService; - } - - public void OnGet() - { - } - - /// - /// 获取颜色列表(分页) - /// - /// 关键词 - /// 排序字段 - /// 页码 - /// 页大小 - /// - public async Task OnGetColorListAsync(string keyword, string orderField, int pageIndex, int pageSize) - { - try - { - // 调试信息 - System.Diagnostics.Debug.WriteLine($""ColorList API Called - PageIndex: {pageIndex}, PageSize: {pageSize}, OrderField: {orderField}""); - - var seh = new SenparcExpressionHelper(); - // 可以根据需要添加搜索条件 - // seh.ValueCompare.AndAlso(!string.IsNullOrEmpty(keyword), _ => _.Remark.Contains(keyword)); - var where = seh.BuildWhereExpression(); - var response = await _colorService.GetObjectListAsync(pageIndex, pageSize, where, orderField ?? ""Id desc""); - - // 调试信息 - System.Diagnostics.Debug.WriteLine($""Database Query Result - TotalCount: {response.TotalCount}, ItemCount: {response.Count()}""); - - var result = new - { - success = true, - message = ""数据获取成功"", - data = new { - totalCount = response.TotalCount, - pageIndex = response.PageIndex, - list = response.Select(_ => new - { - id = _.Id, - red = _.Red, - green = _.Green, - blue = _.Blue, - additionNote = _.AdditionNote, - addTime = _.AddTime, - lastUpdateTime = _.LastUpdateTime, - remark = _.Remark - }).ToList() - } - }; - - // 调试信息 - System.Diagnostics.Debug.WriteLine($""API Response - ListCount: {result.data.list.Count}""); - - return Ok(result); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($""ColorList API Error: {ex.Message}""); - return Ok(new { - success = false, - message = ""获取数据失败: "" + ex.Message, - totalCount = 0, - pageIndex = pageIndex, - list = new object[0] - }); - } - } - - /// - /// 创建新颜色 - /// - /// 创建颜色请求 - /// - public async Task OnPostCreateColorAsync([FromBody] CreateColorRequestDto request) - { - try - { - // 调试信息 - System.Diagnostics.Debug.WriteLine($""CreateColor API Called - Red: {request.Red}, Green: {request.Green}, Blue: {request.Blue}, AdditionNote: {request.AdditionNote}""); - - if (request == null) - { - return Ok(new { success = false, message = ""请求参数不能为空"" }); - } - - var color = new Color(request.Red, request.Green, request.Blue, request.AdditionNote); - await _colorService.SaveObjectAsync(color); - - return Ok(new { success = true, message = ""颜色创建成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.AddTime, color.LastUpdateTime } }); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($""CreateColor API Error: {ex.Message}""); - return Ok(new { success = false, message = ""创建失败:"" + ex.Message }); - } - } - - /// - /// 更新颜色 - /// - /// 更新颜色请求 - /// - public async Task OnPostUpdateColorAsync([FromBody] UpdateColorRequestDto request) - { - try - { - // 调试信息 - System.Diagnostics.Debug.WriteLine($""UpdateColor API Called - Id: {request.Id}, Red: {request.Red}, Green: {request.Green}, Blue: {request.Blue}, AdditionNote: {request.AdditionNote}""); - - if (request == null) - { - return Ok(new { success = false, message = ""请求参数不能为空"" }); - } - - var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); - if (color == null) - { - return Ok(new { success = false, message = ""颜色不存在"" }); - } - - // 更新 - color.Update(request); - - await _colorService.SaveObjectAsync(color); - - return Ok(new { success = true, message = ""颜色更新成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.LastUpdateTime } }); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($""UpdateColor API Error: {ex.Message}""); - return Ok(new { success = false, message = ""更新失败:"" + ex.Message }); - } - } - - /// - /// 删除颜色 - /// - /// 删除颜色请求 - /// - public async Task OnPostDeleteColorAsync([FromBody] DeleteColorRequestDto request) - { - try - { - // 调试信息 - System.Diagnostics.Debug.WriteLine($""DeleteColor API Called - Id: {request.Id}""); - - if (request == null) - { - return Ok(new { success = false, message = ""请求参数不能为空"" }); - } - - var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); - if (color == null) - { - return Ok(new { success = false, message = ""颜色不存在"" }); - } - - await _colorService.DeleteObjectAsync(color); - return Ok(new { success = true, message = ""颜色删除成功"" }); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($""DeleteColor API Error: {ex.Message}""); - return Ok(new { success = false, message = ""删除失败:"" + ex.Message }); - } - } - - /// - /// 随机化指定颜色 - /// - /// 随机化颜色请求 - /// - public async Task OnPostRandomizeColorAsync([FromBody] RandomizeColorRequestDto request) - { - try - { - // 调试信息 - System.Diagnostics.Debug.WriteLine($""RandomizeColor API Called - Id: {request.Id}""); - - if (request == null) - { - return Ok(new { success = false, message = ""请求参数不能为空"" }); - } - - var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); - if (color == null) - { - return Ok(new { success = false, message = ""颜色不存在"" }); - } - - color.Random(); - await _colorService.SaveObjectAsync(color); - - return Ok(new { success = true, message = ""颜色随机化成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.LastUpdateTime } }); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($""RandomizeColor API Error: {ex.Message}""); - return Ok(new { success = false, message = ""随机化失败:"" + ex.Message }); - } - } - - /// - /// 获取颜色详情 - /// - /// 颜色ID - /// - public async Task OnGetColorDetailAsync(int id) - { - try - { - var color = await _colorService.GetObjectAsync(c => c.Id == id); - if (color == null) - { - return Ok(new { success = false, message = ""颜色不存在"" }); - } - - return Ok(new { success = true, data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.AddTime, color.LastUpdateTime, color.Remark } }); - } - catch (Exception ex) - { - return Ok(new { success = false, message = ""获取失败:"" + ex.Message }); - } - } - } -} "; - - #endregion - - #region FRONTEND_SCRIPT Templates - - /// - /// 数据库示例索引页面JavaScript模板 - /// 类型: frontend_script - /// - public const string DatabaseSampleIndexJsTemplate = @"var app = new Vue({ - el: ""#app"", - data() { - return { - page: { - page: 1, - size: 10 - }, - tableLoading: true, - tableData: [], - showDebug: false, - addFormDialogVisible: false, - addForm: { - red: 128, - green: 128, - blue: 128, - additionNote: '' - }, - editFormDialogVisible: false, - editForm: { - id: 0, - red: 128, - green: 128, - blue: 128, - additionNote: '' - }, - total: 0, - addRules: { - red: [ - { required: true, message: '请设置红色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '红色值范围为0-255', trigger: 'change' } - ], - green: [ - { required: true, message: '请设置绿色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '绿色值范围为0-255', trigger: 'change' } - ], - blue: [ - { required: true, message: '请设置蓝色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '蓝色值范围为0-255', trigger: 'change' } - ] - }, - editRules: { - red: [ - { required: true, message: '请设置红色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '红色值范围为0-255', trigger: 'change' } - ], - green: [ - { required: true, message: '请设置绿色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '绿色值范围为0-255', trigger: 'change' } - ], - blue: [ - { required: true, message: '请设置蓝色值', trigger: 'change' }, - { type: 'number', min: 0, max: 255, message: '蓝色值范围为0-255', trigger: 'change' } - ] - } - } - }, - mounted() { - //wait page load - setTimeout(async () => { - await this.init(); - }, 100) - }, - methods: { - async init() { - await this.getDataList(); - }, - async handleSizeChange(val) { - this.page.size = val; - await this.getDataList(); - }, - async handleCurrentChange(val) { - this.page.page = val; - await this.getDataList(); - }, - async getDataList() { - this.tableLoading = true - await service.get('/Admin/Template_XncfName/DatabaseSampleIndex?handler=ColorList', { - params: { - pageIndex: this.page.page, - pageSize: this.page.size, - orderField: ""Id desc"", - keyword: """" - } - }) - .then(res => { - console.log('=== API Response Debug ==='); - console.log('Complete Response:', res); - console.log('Response Data:', res.data); - console.log('Response Data Type:', typeof res.data); - console.log('Has res.data.data?:', res.data && res.data.data); - console.log('Has res.data.data.list?:', res.data && res.data.data && res.data.data.list); - console.log('res.data.data.list value:', res.data && res.data.data ? res.data.data.list : 'nested data not found'); - console.log('=================='); - - // 尝试多种可能的数据结构 - let dataList = null; - let totalCount = 0; - let dataSource = ''; - - if (res.data && res.data.data && res.data.data.data && res.data.data.data.list) { - // NCF框架标准格式 + 新的API格式: {data: {data: {success, message, data: {list, totalCount}}}} - dataList = res.data.data.data.list; - totalCount = res.data.data.data.totalCount || 0; - dataSource = 'NCF标准格式: res.data.data.data.list'; - console.log('✅ 使用NCF标准格式: res.data.data.data.list'); - console.log('✅ List数据:', dataList); - console.log('✅ TotalCount:', totalCount); - } else if (res.data && res.data.data && res.data.data.list) { - // 简单格式: {data: {list, totalCount}} - dataList = res.data.data.list; - totalCount = res.data.data.totalCount || 0; - dataSource = '简单格式: res.data.data.list'; - console.log('✅ 使用简单格式: res.data.data.list'); - } else if (res.data && Array.isArray(res.data)) { - // 如果data直接是数组 - dataList = res.data; - totalCount = res.data.length; - dataSource = '数组格式: res.data (array)'; - console.log('✅ 使用数组格式: res.data (array)'); - } else if (res && res.list) { - // 如果list在顶层 - dataList = res.list; - totalCount = res.totalCount || 0; - dataSource = '顶层格式: res.list'; - console.log('✅ 使用顶层格式: res.list'); - } else { - console.error('❌ 无法识别的数据格式:', res); - console.log('🔍 尝试的路径:'); - console.log('- res.data.data.list:', res.data && res.data.data ? res.data.data.list : 'not found'); - console.log('- res.data.list:', res.data ? res.data.list : 'not found'); - console.log('- res.data (array):', res.data && Array.isArray(res.data) ? 'is array' : 'not array'); - console.log('- res.list:', res.list ? res.list : 'not found'); - dataList = []; - totalCount = 0; - dataSource = '无法识别格式'; - } - - console.log('🎯 Final dataList:', dataList); - console.log('🎯 Final totalCount:', totalCount); - console.log('🎯 Data source:', dataSource); - - // 数据赋值前的状态 - console.log('📋 赋值前 tableData:', this.tableData); - console.log('📋 赋值前 total:', this.total); - - this.tableData = dataList || []; - this.total = totalCount; - - // 数据赋值后的状态 - console.log('📋 赋值后 tableData:', this.tableData); - console.log('📋 赋值后 tableData.length:', this.tableData.length); - console.log('📋 赋值后 total:', this.total); - - // 强制Vue更新 - this.$forceUpdate(); - console.log('🔄 Vue已强制更新'); - - // 延迟检查数据是否正确绑定 - setTimeout(() => { - console.log('⏰ 延迟检查 tableData:', this.tableData); - console.log('⏰ 延迟检查 tableData.length:', this.tableData ? this.tableData.length : 'null'); - }, 100); - - this.tableLoading = false - }) - .catch(error => { - console.error('获取数据失败:', error); - this.tableLoading = false; - this.$message.error('获取数据失败: ' + (error.message || error)); - }); - }, - addColor() { - this.addFormDialogVisible = true; - }, - refreshList() { - this.getDataList(); - }, - async addColorSubmit() { - this.$refs.addForm.validate(async (valid) => { - if (valid) { - console.log('📤 发送创建请求:', { - red: this.addForm.red, - green: this.addForm.green, - blue: this.addForm.blue, - additionNote: this.addForm.additionNote - }); - - await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=CreateColor', { - red: this.addForm.red, - green: this.addForm.green, - blue: this.addForm.blue, - additionNote: this.addForm.additionNote - }, { - headers: { - 'Content-Type': 'application/json' - } - }) - .then(res => { - console.log('📥 创建响应:', res); - // 兼容NCF框架的嵌套响应格式 - const responseData = res.data.data || res.data; - this.$message({ - type: responseData.success ? 'success' : 'error', - message: responseData.message || '操作完成' - }); - if (responseData.success) { - this.getDataList() - this.clearAddForm() - this.addFormDialogVisible = false; - } - }) - .catch(error => { - console.error('创建失败:', error); - this.$message.error('创建失败'); - }); - } else { - return false; - } - }); - }, - clearAddForm() { - this.addForm = { - red: 128, - green: 128, - blue: 128, - additionNote: '' - }; - if (this.$refs.addForm) { - this.$refs.addForm.resetFields(); - } - }, - clearEditForm() { - this.editForm = { - id: 0, - red: 128, - green: 128, - blue: 128, - additionNote: '' - }; - if (this.$refs.editForm) { - this.$refs.editForm.resetFields(); - } - }, - async editColorSubmit() { - this.$refs.editForm.validate(async (valid) => { - if (valid) { - console.log('📤 发送更新请求:', { - id: this.editForm.id, - red: this.editForm.red, - green: this.editForm.green, - blue: this.editForm.blue, - additionNote: this.editForm.additionNote - }); - - await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=UpdateColor', { - id: this.editForm.id, - red: this.editForm.red, - green: this.editForm.green, - blue: this.editForm.blue, - additionNote: this.editForm.additionNote - }, { - headers: { - 'Content-Type': 'application/json' - } - }) - .then(res => { - console.log('📥 更新响应:', res); - // 兼容NCF框架的嵌套响应格式 - const responseData = res.data.data || res.data; - this.$message({ - type: responseData.success ? 'success' : 'error', - message: responseData.message || '操作完成' - }); - if (responseData.success) { - this.getDataList() - this.clearEditForm() - this.editFormDialogVisible = false; - } - }) - .catch(error => { - console.error('更新失败:', error); - this.$message.error('更新失败'); - }); - } else { - return false; - } - }); - }, - dateformatter(date) { - if (!date) return ''; - - try { - // 使用原生JavaScript格式化日期 - const d = new Date(date); - - // 检查日期是否有效 - if (isNaN(d.getTime())) { - return date; // 如果无法解析,返回原始值 - } - - // 格式化为 YYYY-MM-DD HH:mm:ss - const year = d.getFullYear(); - const month = String(d.getMonth() + 1).padStart(2, '0'); - const day = String(d.getDate()).padStart(2, '0'); - const hours = String(d.getHours()).padStart(2, '0'); - const minutes = String(d.getMinutes()).padStart(2, '0'); - const seconds = String(d.getSeconds()).padStart(2, '0'); - - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; - } catch (error) { - console.warn('日期格式化错误:', error, '原始值:', date); - return date; // 如果格式化失败,返回原始值 - } - }, - editColor(row) { - this.editForm = { - id: row.id, - red: row.red, - green: row.green, - blue: row.blue, - additionNote: row.additionNote || '' - }; - this.editFormDialogVisible = true; - }, - deleteColor(row) { - this.$confirm('此操作将永久删除该颜色, 是否继续?', '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning' - }).then(async () => { - console.log('📤 发送删除请求:', { id: row.id }); - - await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=DeleteColor', { - id: row.id - }, { - headers: { - 'Content-Type': 'application/json' - } - }) - .then(res => { - console.log('📥 删除响应:', res); - // 兼容NCF框架的嵌套响应格式 - const responseData = res.data.data || res.data; - this.$message({ - type: responseData.success ? 'success' : 'error', - message: responseData.message || '操作完成' - }); - if (responseData.success) { - this.getDataList(); - } - }) - .catch(error => { - console.error('删除失败:', error); - this.$message.error('删除失败'); - }); - }).catch(() => { - this.$message({ - type: 'info', - message: '已取消删除' - }); - }); - }, - async randomizeColor(row) { - console.log('📤 发送随机化请求:', { id: row.id }); - - await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=RandomizeColor', { - id: row.id - }, { - headers: { - 'Content-Type': 'application/json' - } - }) - .then(res => { - console.log('📥 随机化响应:', res); - // 兼容NCF框架的嵌套响应格式 - const responseData = res.data.data || res.data; - this.$message({ - type: responseData.success ? 'success' : 'error', - message: responseData.message || '操作完成' - }); - if (responseData.success) { - this.getDataList(); - } - }) - .catch(error => { - console.error('随机化失败:', error); - this.$message.error('随机化失败'); - }); - }, - randomizeForm() { - this.addForm.red = Math.floor(Math.random() * 256); - this.addForm.green = Math.floor(Math.random() * 256); - this.addForm.blue = Math.floor(Math.random() * 256); - }, - randomizeEditForm() { - this.editForm.red = Math.floor(Math.random() * 256); - this.editForm.green = Math.floor(Math.random() * 256); - this.editForm.blue = Math.floor(Math.random() * 256); - }, - debugInfo() { - this.showDebug = !this.showDebug; - console.log('=== Vue Component Debug Info ==='); - console.log('Current tableData:', this.tableData); - console.log('tableData length:', this.tableData ? this.tableData.length : 'null/undefined'); - console.log('Total:', this.total); - console.log('Page:', this.page); - console.log('Table Loading:', this.tableLoading); - console.log('Show Debug:', this.showDebug); - console.log('Vue instance $el:', this.$el); - console.log('================================'); - - // 测试Vue响应性 - if (this.tableData && this.tableData.length === 0) { - console.log('测试:添加假数据'); - this.tableData = [ - {id: 999, red: 255, green: 0, blue: 0, addTime: new Date().toISOString(), lastUpdateTime: new Date().toISOString(), remark: 'test'} - ]; - this.total = 1; - setTimeout(() => { - console.log('2秒后清除假数据'); - this.tableData = []; - this.total = 0; - }, 2000); - } - } - } -}); "; - - #endregion - - #region FRONTEND_STYLE Templates - - /// - /// 数据库示例索引页面CSS模板 - /// 类型: frontend_style - /// - public const string DatabaseSampleIndexCssTemplate = @"/* 通用样式 */ -.d-flex{ - display: flex; -} -.justify-content-between{ - justify-content: space-between; -} -.align-items-center{ - align-items: center; -} - -/* 过滤器容器样式 */ -.filter-container { - margin-bottom: 20px; - padding: 10px 0; -} - -.filter-container .el-button { - margin-right: 10px; -} - -/* 颜色预览样式 */ -.color-preview { - width: 100%; - height: 40px; - border-radius: 4px; - border: 1px solid #dcdfe6; - display: flex; - align-items: center; - justify-content: center; - color: white; - font-size: 12px; - font-weight: bold; - text-shadow: 1px 1px 2px rgba(0,0,0,0.5); -} - -.color-preview-large { - width: 100%; - height: 80px; - border-radius: 8px; - border: 2px solid #dcdfe6; - display: flex; - align-items: center; - justify-content: center; - color: white; - font-size: 16px; - font-weight: bold; - text-shadow: 2px 2px 4px rgba(0,0,0,0.7); - margin: 10px 0; - transition: all 0.3s ease; -} - -.color-preview-large:hover { - transform: scale(1.02); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); -} - -/* 分页容器样式 */ -.pagination-container { - margin-top: 20px; - text-align: center; -} - -/* 表格样式增强 */ -.el-table { - border-radius: 8px; - overflow: hidden; - box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1); -} - -.el-table th { - background-color: #fafafa; - color: #333; - font-weight: 600; -} - -/* 颜色标签样式 */ -.el-tag { - min-width: 50px; - text-align: center; - font-weight: bold; - border: none !important; - text-shadow: 1px 1px 2px rgba(0,0,0,0.5); -} - -/* 对话框样式 */ -.el-dialog { - border-radius: 12px; - overflow: hidden; -} - -.el-dialog__header { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 20px 20px 0 20px; -} - -.el-dialog__title { - color: white; - font-weight: 600; -} - -.el-dialog__body { - padding: 30px 20px; -} - -/* 滑块样式 */ -.el-slider { - margin: 20px 0; -} - -.el-slider__runway { - height: 6px; - background-color: #e4e7ed; - border-radius: 3px; -} - -.el-slider__button { - width: 20px; - height: 20px; - border: 2px solid #409eff; -} - -/* 按钮样式增强 */ -.el-button--mini { - padding: 5px 10px; - font-size: 12px; - border-radius: 4px; -} - -.el-button--primary { - background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%); - border: none; -} - -.el-button--success { - background: linear-gradient(135deg, #67c23a 0%, #5daf34 100%); - border: none; -} - -.el-button--warning { - background: linear-gradient(135deg, #e6a23c 0%, #cf9236 100%); - border: none; -} - -.el-button--danger { - background: linear-gradient(135deg, #f56c6c 0%, #f25c5c 100%); - border: none; -} - -.el-button--info { - background: linear-gradient(135deg, #909399 0%, #82848a 100%); - border: none; -} - -/* 表单项样式 */ -.el-form-item { - margin-bottom: 22px; -} - -.el-form-item__label { - font-weight: 600; - color: #333; -} - -/* 加载动画样式 */ -.el-loading-mask { - background-color: rgba(255, 255, 255, 0.9); -} - -/* 响应式设计 */ -@media (max-width: 768px) { - .filter-container { - text-align: center; - } - - .filter-container .el-button { - margin: 5px; - width: auto; - } - - .color-preview { - height: 30px; - font-size: 10px; - } - - .color-preview-large { - height: 60px; - font-size: 14px; - } -} - -/* 动画效果 */ -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.el-table tbody tr { - animation: fadeIn 0.3s ease-out; -} - -/* 鼠标悬停效果 */ -.el-table tbody tr:hover { - background-color: #f5f7fa !important; - transition: background-color 0.3s ease; -} - -.el-button:hover { - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(0,0,0,0.15); - transition: all 0.3s ease; -} "; - - #endregion - #region Template Types - - /// - /// 模板类型常量 - /// - public static class TemplateTypes - { - public const string CODE = "code"; - public const string BACKENDTEMPLATE = "backend_template"; - public const string FRONTENDTEMPLATE = "frontend_template"; - public const string FRONTENDSCRIPT = "frontend_script"; - public const string FRONTENDSTYLE = "frontend_style"; - } - - #endregion - - #region File Information - - /// - /// 所有模板文件信息 - /// - public static readonly TemplateFileInfo[] AllTemplateFiles = new TemplateFileInfo[] - { - new TemplateFileInfo("RequestCode", "请求类代码", "code", "Request.cs", RequestCode), - new TemplateFileInfo("SenparcEntitiesTemplate", "Senparc实体类模板", "backend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Domain/Models/DatabaseModel/Template_XncfNameSenparcEntities.cs", SenparcEntitiesTemplate), - new TemplateFileInfo("ColorModelTemplate", "颜色模型模板", "backend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Domain/Models/DatabaseModel/Color.cs", ColorModelTemplate), - new TemplateFileInfo("ColorDtoTemplate", "颜色DTO模板", "backend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Domain/Models/DatabaseModel/Dto/ColorDto.cs", ColorDtoTemplate), - new TemplateFileInfo("ColorServiceTemplate", "颜色服务模板", "backend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Domain/Services/ColorService.cs", ColorServiceTemplate), - new TemplateFileInfo("DatabaseSampleIndexViewTemplate", "数据库示例索引页面视图模板", "frontend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Areas/Admin/Pages/Template_XncfName/DatabaseSampleIndex.cshtml", DatabaseSampleIndexViewTemplate), - new TemplateFileInfo("DatabaseSampleIndexCodeBehindTemplate", "数据库示例索引页面代码后置模板", "frontend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Areas/Admin/Pages/Template_XncfName/DatabaseSampleIndex.cshtml.cs", DatabaseSampleIndexCodeBehindTemplate), - new TemplateFileInfo("DatabaseSampleIndexJsTemplate", "数据库示例索引页面JavaScript模板", "frontend_script", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/wwwroot/js/Admin/Template_XncfName/databaseSampleIndex.js", DatabaseSampleIndexJsTemplate), - new TemplateFileInfo("DatabaseSampleIndexCssTemplate", "数据库示例索引页面CSS模板", "frontend_style", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/wwwroot/css/Admin/Template_XncfName/databaseSampleIndex.css", DatabaseSampleIndexCssTemplate), - }; - - /// - /// 模板文件信息结构 - /// - public record TemplateFileInfo(string Name, string Description, string Type, string Path, string Content); - - #endregion - - #region Helper Methods - - /// - /// 根据类型获取模板文件 - /// - public static TemplateFileInfo[] GetTemplatesByType(string templateType) - { - return AllTemplateFiles.Where(f => f.Type == templateType).ToArray(); - } - - /// - /// 根据名称获取模板内容 - /// - public static string? GetTemplateContent(string templateName) - { - return AllTemplateFiles.FirstOrDefault(f => f.Name == templateName)?.Content; - } - - /// - /// 获取所有模板类型 - /// - public static string[] GetAllTemplateTypes() - { - return AllTemplateFiles.Select(f => f.Type).Distinct().ToArray(); - } - - #endregion - } -} diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/XncfModule/Start.cshtml b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/XncfModule/Start.cshtml index 8da8bfcd5..70b071116 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/XncfModule/Start.cshtml +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/XncfModule/Start.cshtml @@ -197,7 +197,11 @@ v-model="runData[item.name].value" :maxlength="item.maxLength===0 ? 500 : item.maxLength" :type="item.parameterType===3 ? 'password' : 'text'"> - + diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/XncfModule/Start.cshtml.cs b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/XncfModule/Start.cshtml.cs index b084d059e..06d632d9d 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/XncfModule/Start.cshtml.cs +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/XncfModule/Start.cshtml.cs @@ -185,7 +185,8 @@ public async Task OnPostRunFunctionAsync([FromBody] ExecuteFuncPa switch (paramCount) { case 1: - var requestPara = SerializerHelper.GetObject(executeFuncParamDto2.XncfFunctionParams, functionParameterType) as IAppRequest; + var normalizedJson = FunctionRequestParameterNormalizer.NormalizeJson(executeFuncParamDto2.XncfFunctionParams, functionParameterType); + var requestPara = SerializerHelper.GetObject(normalizedJson, functionParameterType) as IAppRequest; paras = new[] { requestPara }; break; case 0: diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/OHS/Local/AppService/ModuleAppService.cs b/tools/NcfSimulatedSite/Senparc.Areas.Admin/OHS/Local/AppService/ModuleAppService.cs index 639542d7f..00e0ab078 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/OHS/Local/AppService/ModuleAppService.cs +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/OHS/Local/AppService/ModuleAppService.cs @@ -438,7 +438,8 @@ public async Task> RunFunctionAsync( switch (paramCount) { case 1: - var requestPara = SerializerHelper.GetObject(executeFuncParamDto2.XncfFunctionParams, functionParameterType) as IAppRequest; + var normalizedJson = FunctionRequestParameterNormalizer.NormalizeJson(executeFuncParamDto2.XncfFunctionParams, functionParameterType); + var requestPara = SerializerHelper.GetObject(normalizedJson, functionParameterType) as IAppRequest; paras = new[] { requestPara }; break; case 0: diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/XncfModule/start.js b/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/XncfModule/start.js index 1eb94fd61..6a1fba17a 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/XncfModule/start.js +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/XncfModule/start.js @@ -49,6 +49,20 @@ this.getList(); }, methods: { + normalizeMultiValue(value) { + if (Array.isArray(value)) { + return value; + } + + if (typeof value !== 'string' || value.length === 0) { + return []; + } + + return value + .split(/[;,,;\n\r|]+/) + .map(item => item.trim()) + .filter(item => item.length > 0); + }, async getList() { const uid = resizeUrl().uid; const res = await service.get(`/Admin/XncfModule/Start?handler=Detail&uid=${uid}`); @@ -92,10 +106,10 @@ // 多选 if (res.parameterType === 2 && res.selectionList.items) { this.runData[res.name] = {}; - this.runData[res.name].value = []; + this.runData[res.name].value = this.normalizeMultiValue(res.value); this.runData[res.name].item = res; res.selectionList.items.map(ele => { - if (ele.defaultSelected) { + if (ele.defaultSelected && this.runData[res.name].value.indexOf(ele.value) < 0) { this.runData[res.name].value.push(ele.value); } }); @@ -103,10 +117,10 @@ // 下拉框value if (res.parameterType === 1 && res.selectionList.items) { this.runData[res.name] = {}; - this.runData[res.name].value = ''; + this.runData[res.name].value = res.value || ''; this.runData[res.name].item = res; res.selectionList.items.map(ele => { - if (ele.defaultSelected) { + if (!this.runData[res.name].value && ele.defaultSelected) { this.runData[res.name].value = ele.value; } }); @@ -172,13 +186,10 @@ }); return; } else { - xncfFunctionParams[i] = {}; - xncfFunctionParams[i].SelectedValues = []; - xncfFunctionParams[i].SelectedValues = this.runData[i].value; - + xncfFunctionParams[i] = this.runData[i].value; } } - // 下拉框value为字符串,但接口要数组 + // 下拉框 if (this.runData[i].item.parameterType === 1) { if (this.runData[i].item.isRequired && this.runData[i].value.length === 0) { this.$notify({ @@ -188,9 +199,7 @@ }); return; } else { - xncfFunctionParams[i] = {}; - xncfFunctionParams[i].SelectedValues = []; - xncfFunctionParams[i].SelectedValues[0] = this.runData[i].value; + xncfFunctionParams[i] = this.runData[i].value; } } // 输入框 From 9725c0079656a8950c59d07c4cc3e75599be1fe1 Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Fri, 24 Apr 2026 17:48:59 +0800 Subject: [PATCH 14/19] Refactor function request and service classes to use simplified data types - Updated MyFunctionRequest.cs to replace SelectionList with string and string[] for Operator and Power properties, enhancing clarity and usability. - Modified BuildXncfAppService.AI.MCP.cs and related service classes to streamline handling of request parameters, removing unnecessary SelectionList usage. - Adjusted BuildXncfRequest.cs and DatabaseMigrationRequest.cs to utilize direct data types for better performance and readability. - Enhanced XncfStateRequest.cs and related classes to improve data loading and handling, ensuring consistent use of simplified types. - Updated simulated site function requests to align with new data structure, ensuring compatibility and ease of use. --- .../Local/AppService/MyFuctionAppService.cs | 4 +- .../OHS/Local/PL/MyFunctionRequest.cs | 14 +++- .../OHS/Local/PL/AIModelStudioRequest.cs | 10 ++- .../OHS/Local/PL/MyFunctionRequest.cs | 14 +++- .../AppService/ChatGroupAppService.cs | 18 ++--- .../Application/DTOs/ChatGroupRequest.cs | 68 ++++++++++++++----- .../OHS/Local/NameSpaceAppService.cs | 2 +- .../OHS/PL/NameSpaceRequest.cs | 8 ++- .../AppService/DatabaseBackupAppService.cs | 5 +- .../OHS/Local/PL/DatabaseBackupRequest.cs | 8 ++- .../Local/AppService/MyFuctionAppService.cs | 4 +- .../OHS/Local/PL/MyFunctionRequest.cs | 14 +++- .../Local/AppService/MyFuctionAppService.cs | 4 +- .../OHS/Local/PL/MyFunctionRequest.cs | 14 +++- .../Local/AppService/MyFuctionAppService.cs | 4 +- .../OHS/Local/PL/MyFunctionRequest.cs | 14 +++- .../Local/AppService/MyFuctionAppService.cs | 6 +- .../OHS/Local/PL/MyFunctionRequest.cs | 25 +++++-- .../Local/AppService/MyFuctionAppService.cs | 4 +- .../OHS/Local/PL/MyFunctionRequest.cs | 14 +++- .../Local/AppService/MyFuctionAppService.cs | 4 +- .../OHS/Local/PL/MyFunctionRequest.cs | 14 +++- .../OHS/Local/BuildXncfAppService.AI.MCP.cs | 25 ++----- .../OHS/Local/BuildXncfAppService.AI.cs | 20 +++--- .../OHS/Local/BuildXncfAppService.cs | 20 +++--- .../OHS/Local/DatabaseMigrationsAppService.cs | 9 +-- .../OHS/PL/BuildXncfRequest.AI.cs | 44 +++++++++--- .../OHS/PL/BuildXncfRequest.cs | 32 +++++++-- .../OHS/PL/DatabaseMigrationRequest.cs | 34 +++++++--- .../Local/AppService/XncfStateAppService.cs | 4 +- .../OHS/Local/PL/XncfStateRequest.cs | 22 ++++-- .../OHS/Local/PL/MyFunctionRequest.cs | 14 +++- .../Local/AppService/MyFuctionAppService.cs | 4 +- .../OHS/Local/PL/MyFunctionRequest.cs | 14 +++- 34 files changed, 367 insertions(+), 147 deletions(-) diff --git a/src/Extensions/Senparc.Xncf.AIAgentsHub/OHS/Local/AppService/MyFuctionAppService.cs b/src/Extensions/Senparc.Xncf.AIAgentsHub/OHS/Local/AppService/MyFuctionAppService.cs index 8a984a5df..cece1506f 100644 --- a/src/Extensions/Senparc.Xncf.AIAgentsHub/OHS/Local/AppService/MyFuctionAppService.cs +++ b/src/Extensions/Senparc.Xncf.AIAgentsHub/OHS/Local/AppService/MyFuctionAppService.cs @@ -37,7 +37,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques */ double calcResult = request.Number1; - var theOperator = request.Operator.SelectedValues.FirstOrDefault(); + var theOperator = request.Operator; switch (theOperator) { case "+": @@ -68,7 +68,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques Action raisePower = power => { - if (request.Power.SelectedValues.Contains(power.ToString())) + if ((request.Power ?? Array.Empty()).Contains(power.ToString())) { var oldValue = calcResult; calcResult = Math.Pow(calcResult, power); diff --git a/src/Extensions/Senparc.Xncf.AIAgentsHub/OHS/Local/PL/MyFunctionRequest.cs b/src/Extensions/Senparc.Xncf.AIAgentsHub/OHS/Local/PL/MyFunctionRequest.cs index 19a2b0e7b..635d25fa2 100644 --- a/src/Extensions/Senparc.Xncf.AIAgentsHub/OHS/Local/PL/MyFunctionRequest.cs +++ b/src/Extensions/Senparc.Xncf.AIAgentsHub/OHS/Local/PL/MyFunctionRequest.cs @@ -1,7 +1,9 @@ using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; +using Senparc.Ncf.XncfBase; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Senparc.Xncf.AIAgentsHub.OHS.Local.PL { @@ -22,7 +24,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase public int Number2 { get; set; } [Description("运算符||")]//下拉列表 - public SelectionList Operator { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } + + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("+","加法","数字1 + 数字2",false), new SelectionItem("-","减法","数字1 - 数字2",true), new SelectionItem("×","乘法","数字1 × 数字2",false), @@ -30,7 +36,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase }); [Description("计算平方||")]//多选框 - public SelectionList Power { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } + + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); diff --git a/src/Extensions/Senparc.Xncf.AIKernel/OHS/Local/PL/AIModelStudioRequest.cs b/src/Extensions/Senparc.Xncf.AIKernel/OHS/Local/PL/AIModelStudioRequest.cs index 6581b5a7a..396d4eab4 100644 --- a/src/Extensions/Senparc.Xncf.AIKernel/OHS/Local/PL/AIModelStudioRequest.cs +++ b/src/Extensions/Senparc.Xncf.AIKernel/OHS/Local/PL/AIModelStudioRequest.cs @@ -7,10 +7,12 @@ using Senparc.AI.Interfaces; using Senparc.AI.Kernel; using Senparc.CO2NET.Trace; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; using Senparc.Xncf.AIKernel.Domain.Models.DatabaseModel.Dto; using Senparc.Xncf.AIKernel.Domain.Services; +using System.Text.Json.Serialization; namespace Senparc.Xncf.AIKernel.OHS.Local.PL { @@ -18,7 +20,11 @@ public class AIModelStudioRequest_RunModelAsync : FunctionAppRequestBase { [Required] [Description("选择模型||")]//下拉列表 - public SelectionList Model { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List()); + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(ModelOptions))] + public string[] Model { get; set; } + + [JsonIgnore] + public SelectionList ModelOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List()); //TODO: 更多 AI 参数 @@ -60,7 +66,7 @@ public override async Task LoadData(IServiceProvider serviceProvider) { var value = z.ModelAlias; var text = z.DisplayText; - Model.Items.Add(new SelectionItem(value, text, $"来自:{z.From}", false) { BindData = z.SenparcAiSetting }); + ModelOptions.Items.Add(new SelectionItem(value, text, $"来自:{z.From}", false) { BindData = z.SenparcAiSetting }); }); await base.LoadData(serviceProvider); diff --git a/src/Extensions/Senparc.Xncf.AIKernel/OHS/Local/PL/MyFunctionRequest.cs b/src/Extensions/Senparc.Xncf.AIKernel/OHS/Local/PL/MyFunctionRequest.cs index 1737c90d1..3e3acabe7 100644 --- a/src/Extensions/Senparc.Xncf.AIKernel/OHS/Local/PL/MyFunctionRequest.cs +++ b/src/Extensions/Senparc.Xncf.AIKernel/OHS/Local/PL/MyFunctionRequest.cs @@ -1,7 +1,9 @@ using Senparc.Ncf.XncfBase.FunctionRenders; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.Functions; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Senparc.Xncf.AIKernel.OHS.Local.PL { @@ -22,7 +24,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase public int Number2 { get; set; } [Description("运算符||")]//下拉列表 - public SelectionList Operator { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } + + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("+","加法","数字1 + 数字2",false), new SelectionItem("-","减法","数字1 - 数字2",true), new SelectionItem("×","乘法","数字1 × 数字2",false), @@ -30,7 +36,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase }); [Description("计算平方||")]//多选框 - public SelectionList Power { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } + + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs index 461a5be88..22ce9f94c 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/AppService/ChatGroupAppService.cs @@ -69,7 +69,7 @@ public async Task ManageChatGroupManage(ChatGroup_ManageChatG ChatGroup chatGroup = null; var chatGroupDto = new ChatGroupDto(request.Name, true, ChatGroupState.Unstart, request.Description, adminId, enterAgentId); var isNew = false; - if (request.ChatGroup.IsSelected("New")) + if (string.Equals(request.ChatGroup, "New", StringComparison.OrdinalIgnoreCase)) { //新建 chatGroup = new ChatGroup(chatGroupDto); @@ -77,7 +77,7 @@ public async Task ManageChatGroupManage(ChatGroup_ManageChatG } else { - int.TryParse(request.ChatGroup.SelectedValues.FirstOrDefault(), out int chatGroupId); + int.TryParse(request.ChatGroup, out int chatGroupId); chatGroup = await _chatGroupService.GetObjectAsync(z => z.Id == chatGroupId); if (chatGroup == null) { @@ -94,7 +94,7 @@ public async Task ManageChatGroupManage(ChatGroup_ManageChatG //添加成员 var memberList = new List(); var rawMemberInputs = new List(); - rawMemberInputs.AddRange(request.Members.SelectedValues.Where(z => !string.IsNullOrWhiteSpace(z))); + rawMemberInputs.AddRange((request.Members ?? Array.Empty()).Where(z => !string.IsNullOrWhiteSpace(z))); rawMemberInputs.AddRange(SplitInputs(request.MemberNamesOrIds)); var memberIdList = new List(); @@ -318,12 +318,12 @@ public async Task RunChatGroup(ChatGroup_RunChatGroupRequest return await this.GetStringResponseAsync(async (response, logger) => { //群主 - if (request.ChatGroups.SelectedValues.Count() == 0) + if ((request.ChatGroups?.Length ?? 0) == 0) { return "至少选择一个组!"; } - var aiModelSelected = request.AIModel.SelectedValues.FirstOrDefault(); + var aiModelSelected = request.AIModel; var aiModelId = 0; if (!string.IsNullOrWhiteSpace(aiModelSelected) @@ -334,7 +334,7 @@ public async Task RunChatGroup(ChatGroup_RunChatGroupRequest aiModelId = selectedModelId; } - foreach (var chatGroupId in request.ChatGroups.SelectedValues.Select(z => int.Parse(z))) + foreach (var chatGroupId in request.ChatGroups.Select(z => int.Parse(z))) { var runRequest = new ChatGroup_RunGroupRequest { @@ -343,7 +343,7 @@ public async Task RunChatGroup(ChatGroup_RunChatGroupRequest AiModelId = aiModelId, PromptCommand = request.Command, Description = "由 FunctionRender 启动", - Personality = request.Individuation.IsSelected("1"), + Personality = request.Individuation, HookPlatform = HookPlatform.None, HookParameter = string.Empty, ChatMaxRound = ChatGroupService.ChatMaxRound @@ -722,7 +722,7 @@ public async Task DeleteChatGroup(ChatGroup_DeleteChatGroupRe { return await this.GetStringResponseAsync(async (response, logger) => { - if (request.ChatGroups.SelectedValues.Count() == 0) + if ((request.ChatGroups?.Length ?? 0) == 0) { return "请选择要删除的对话!"; } @@ -733,7 +733,7 @@ public async Task DeleteChatGroup(ChatGroup_DeleteChatGroupRe return "请勾选\"确认删除\"复选框来确认删除操作!"; } - var chatGroupIdList = request.ChatGroups.SelectedValues + var chatGroupIdList = request.ChatGroups .Where(z => int.TryParse(z, out _)) .Select(z => int.Parse(z)) .ToList(); diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs b/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs index b3b4b8e01..b0d584eae 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Application/DTOs/ChatGroupRequest.cs @@ -10,18 +10,24 @@ using Senparc.Xncf.AgentsManager.Domain.Services; using Microsoft.Extensions.DependencyInjection; using Senparc.Ncf.Service; +using Senparc.Ncf.XncfBase; using Senparc.Xncf.AgentsManager.Models.DatabaseModel.Models; using Senparc.Xncf.XncfBuilder.OHS.PL; using System.Web.Mvc; using Senparc.Xncf.AgentsManager.Domain.Models.DatabaseModel; +using System.Text.Json.Serialization; namespace Senparc.Xncf.AgentsManager.OHS.Local.PL { public class ChatGroup_ManageChatGroupRequest : FunctionAppRequestBase { [Description("选择组||选择需要操作的组,或新增")] - public SelectionList ChatGroup { get; set; } = new SelectionList(SelectionType.DropDownList, new List { - new SelectionItem("New","新建组","新建",true) + [FunctionParameterUi(ParameterType.DropDownList, nameof(ChatGroupOptions))] + public string ChatGroup { get; set; } + + [JsonIgnore] + public SelectionList ChatGroupOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List { + new SelectionItem("New","新建组","新建",true) }); [Required] @@ -30,19 +36,31 @@ public class ChatGroup_ManageChatGroupRequest : FunctionAppRequestBase public string Name { get; set; } [Description("群成员||群成员(可不选,改用“群成员(手动输入)”)")] - public SelectionList Members { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List()); + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(MembersOptions))] + public string[] Members { get; set; } + + [JsonIgnore] + public SelectionList MembersOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List()); [Description("群成员(手动输入)||可选。支持名称、ID、PromptCode,多个值可用逗号、分号、换行分隔")] public string MemberNamesOrIds { get; set; } [Description("群主||优先自动使用评分最高的“主持人”Agent;手动输入仅作兼容")] - public SelectionList Admin { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); + [FunctionParameterUi(ParameterType.DropDownList, nameof(AdminOptions))] + public string Admin { get; set; } + + [JsonIgnore] + public SelectionList AdminOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); [Description("群主(手动输入)||可选。支持名称、ID 或 PromptCode")] public string AdminNameOrId { get; set; } [Description("对接人||优先自动使用评分最高的“主持人”Agent;手动输入仅作兼容")] - public SelectionList EnterAgent { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); + [FunctionParameterUi(ParameterType.DropDownList, nameof(EnterAgentOptions))] + public string EnterAgent { get; set; } + + [JsonIgnore] + public SelectionList EnterAgentOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); [Description("对接人(手动输入)||可选。支持名称、ID 或 PromptCode")] public string EnterAgentNameOrId { get; set; } @@ -59,17 +77,17 @@ public override async Task LoadData(IServiceProvider serviceProvider) var chatGroups = await chatGroupService.GetFullListAsync(z => true, z => z.Id, Ncf.Core.Enums.OrderingType.Ascending); chatGroups.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)) - .ToList().ForEach(z => ChatGroup.Items.Add(z)); + .ToList().ForEach(z => ChatGroupOptions.Items.Add(z)); //Agent var agentTemplateService = serviceProvider.GetService(); var agentsTemplates = await agentTemplateService.GetFullListAsync(z => z.Enable, z => z.Name, Ncf.Core.Enums.OrderingType.Ascending); - Members.Items = agentsTemplates.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList(); - Admin.Items = agentsTemplates.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList(); - EnterAgent.Items = agentsTemplates.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList(); + MembersOptions.Items = agentsTemplates.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList(); + AdminOptions.Items = agentsTemplates.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList(); + EnterAgentOptions.Items = agentsTemplates.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList(); - var admin = Admin.Items.FirstOrDefault(z => z.Text == "群主"); + var admin = AdminOptions.Items.FirstOrDefault(z => z.Text == "群主"); if (admin != null) { admin.DefaultSelected = true; @@ -82,16 +100,28 @@ public override async Task LoadData(IServiceProvider serviceProvider) public class ChatGroup_RunChatGroupRequest : FunctionAppRequestBase { [Description("选择组||选择需要运行的组")] - public SelectionList ChatGroups { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List()); + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(ChatGroupsOptions))] + public string[] ChatGroups { get; set; } + + [JsonIgnore] + public SelectionList ChatGroupsOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List()); [Description("AI 模型||请选择运行此程序的外围 AI 模型")] - public SelectionList AIModel { get; set; } = new SelectionList(SelectionType.DropDownList, new List + [FunctionParameterUi(ParameterType.DropDownList, nameof(AIModelOptions))] + public string AIModel { get; set; } + + [JsonIgnore] + public SelectionList AIModelOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List { //new SelectionItem("Default","系统默认","通过系统默认配置的固定 AI 模型信息",true) }); [Description("个性化智能体||")] - public SelectionList Individuation { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(IndividuationOptions))] + public bool Individuation { get; set; } = true; + + [JsonIgnore] + public SelectionList IndividuationOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List { new SelectionItem("1","是","采用个性化 AI 参数运行 Agent",true) }); @@ -107,10 +137,10 @@ public override async Task LoadData(IServiceProvider serviceProvider) var chatGroupService = serviceProvider.GetService>(); var chatGroups = await chatGroupService.GetFullListAsync(z => true, z => z.Id, Ncf.Core.Enums.OrderingType.Ascending); - ChatGroups.Items = chatGroups.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList(); + ChatGroupsOptions.Items = chatGroups.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList(); //载入 AI 模型 - await BuildXncfRequestHelper.LoadAiModelData(serviceProvider, AIModel); + await BuildXncfRequestHelper.LoadAiModelData(serviceProvider, AIModelOptions); await base.LoadData(serviceProvider); } @@ -174,7 +204,11 @@ public class ChatGroup_RunGroupRequest public class ChatGroup_DeleteChatGroupRequest : FunctionAppRequestBase { [Description("选择要删除的对话||选择需要删除的对话(包括所有消息和任务)")] - public SelectionList ChatGroups { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List()); + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(ChatGroupsOptions))] + public string[] ChatGroups { get; set; } + + [JsonIgnore] + public SelectionList ChatGroupsOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List()); [Description("确认删除||勾选此项以确认删除此对话及其所有数据")] public bool ConfirmDelete { get; set; } @@ -185,7 +219,7 @@ public override async Task LoadData(IServiceProvider serviceProvider) var chatGroupService = serviceProvider.GetService(); var chatGroups = await chatGroupService.GetFullListAsync(z => true, z => z.Id, Ncf.Core.Enums.OrderingType.Descending); - ChatGroups.Items = chatGroups.Select(z => new SelectionItem( + ChatGroupsOptions.Items = chatGroups.Select(z => new SelectionItem( z.Id.ToString(), z.Name, $"{z.Description} (创建时间: {z.AddTime:yyyy-MM-dd HH:mm})" diff --git a/src/Extensions/Senparc.Xncf.ChangeNamespace/OHS/Local/NameSpaceAppService.cs b/src/Extensions/Senparc.Xncf.ChangeNamespace/OHS/Local/NameSpaceAppService.cs index cb88a616d..6e08a7e91 100644 --- a/src/Extensions/Senparc.Xncf.ChangeNamespace/OHS/Local/NameSpaceAppService.cs +++ b/src/Extensions/Senparc.Xncf.ChangeNamespace/OHS/Local/NameSpaceAppService.cs @@ -185,7 +185,7 @@ public async Task DownloadSourceCode(NameSpace_DownloadSource { return await this.GetResponseAsync(async (response, logger) => { - if (Enum.TryParse(request.Site.SelectedValues.FirstOrDefault(), out var siteType)) + if (Enum.TryParse(request.Site, out var siteType)) { switch (siteType) { diff --git a/src/Extensions/Senparc.Xncf.ChangeNamespace/OHS/PL/NameSpaceRequest.cs b/src/Extensions/Senparc.Xncf.ChangeNamespace/OHS/PL/NameSpaceRequest.cs index 3267ab862..5c7467650 100644 --- a/src/Extensions/Senparc.Xncf.ChangeNamespace/OHS/PL/NameSpaceRequest.cs +++ b/src/Extensions/Senparc.Xncf.ChangeNamespace/OHS/PL/NameSpaceRequest.cs @@ -5,6 +5,8 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text; +using Senparc.Ncf.XncfBase.Functions.Parameters; +using System.Text.Json.Serialization; namespace Senparc.Xncf.ChangeNamespace.OHS.PL { @@ -30,7 +32,11 @@ public class NameSpace_DownloadSourceCodeRequest : FunctionAppRequestBase ///
z [Required] [Description("源码来源||目前更新最快的是 GitHub,Gitee(码云)在国内下载速度更快,但是不能确定是最新代码,下载前请注意核对最新 GitHub 上的版本。")] - public SelectionList Site { get; set; } = new SelectionList(SelectionType.DropDownList, new[] + [FunctionParameterUi(ParameterType.DropDownList, nameof(SiteOptions))] + public string Site { get; set; } + + [JsonIgnore] + public SelectionList SiteOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem(Parameters_Site.GitHub.ToString(),Parameters_Site.GitHub.ToString()), new SelectionItem(Parameters_Site.Gitee.ToString(),Parameters_Site.Gitee.ToString()) diff --git a/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/OHS/Local/AppService/DatabaseBackupAppService.cs b/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/OHS/Local/AppService/DatabaseBackupAppService.cs index d276c70d7..5ff70060c 100644 --- a/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/OHS/Local/AppService/DatabaseBackupAppService.cs +++ b/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/OHS/Local/AppService/DatabaseBackupAppService.cs @@ -41,11 +41,12 @@ public async Task Backup(DatabaseBackup_BackupRequest request try { var path = request.Path; + var options = request.Options ?? Array.Empty(); if (File.Exists(path)) { var copyPath = path + ".last.bak"; - if (request.Options.SelectedValues.Contains($"{(int)BackupDatabaseOptions.如果文件存在则不覆盖}")) + if (options.Contains($"{(int)BackupDatabaseOptions.如果文件存在则不覆盖}")) { logger.Append("检测到同名文件,停止覆盖。地址:" + copyPath); return response.Data = logger.Append("检测到同名文件,停止覆盖!"); @@ -73,7 +74,7 @@ public async Task Backup(DatabaseBackup_BackupRequest request logger.Append("执行完毕,备份结束。affectRows:" + affectRows); } - if (request.Options.SelectedValues.Contains($"{(int)BackupDatabaseOptions.校验备份成功}")) + if (options.Contains($"{(int)BackupDatabaseOptions.校验备份成功}")) { logger.Append("检查备份文件:" + path); if (File.Exists(path)) diff --git a/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/OHS/Local/PL/DatabaseBackupRequest.cs b/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/OHS/Local/PL/DatabaseBackupRequest.cs index 02e8dc101..c78b2e2b0 100644 --- a/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/OHS/Local/PL/DatabaseBackupRequest.cs +++ b/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/OHS/Local/PL/DatabaseBackupRequest.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Senparc.Ncf.Service; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; using System; @@ -8,6 +9,7 @@ using System.ComponentModel.DataAnnotations; using System.Text; using System.Threading.Tasks; +using System.Text.Json.Serialization; namespace Senparc.Xncf.DatabaseToolkit.OHS.Local.PL { @@ -25,7 +27,11 @@ public class DatabaseBackup_BackupRequest : FunctionAppRequestBase public string Path { get; set; } [Description("选项||备份数据库选项")] - public SelectionList Options { get; set; } = new SelectionList(SelectionType.CheckBoxList, + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(OptionsList))] + public string[] Options { get; set; } + + [JsonIgnore] + public SelectionList OptionsList { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem($"{(int)BackupDatabaseOptions.如果文件存在则不覆盖}","如果文件存在,则不覆盖","文件已存在的情况下,不会执行备份操作"), new SelectionItem($"{(int)BackupDatabaseOptions.校验备份成功}","备份完成后校验.bak文件是否更新成功","此操作只能检查文件是否被更新,无法检测文件内部内容",true), diff --git a/src/Extensions/Senparc.Xncf.DynamicData/OHS/Local/AppService/MyFuctionAppService.cs b/src/Extensions/Senparc.Xncf.DynamicData/OHS/Local/AppService/MyFuctionAppService.cs index 4d09a8856..13263a826 100644 --- a/src/Extensions/Senparc.Xncf.DynamicData/OHS/Local/AppService/MyFuctionAppService.cs +++ b/src/Extensions/Senparc.Xncf.DynamicData/OHS/Local/AppService/MyFuctionAppService.cs @@ -37,7 +37,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques */ double calcResult = request.Number1; - var theOperator = request.Operator.SelectedValues.FirstOrDefault(); + var theOperator = request.Operator; switch (theOperator) { case "+": @@ -68,7 +68,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques Action raisePower = power => { - if (request.Power.SelectedValues.Contains(power.ToString())) + if ((request.Power ?? Array.Empty()).Contains(power.ToString())) { var oldValue = calcResult; calcResult = Math.Pow(calcResult, power); diff --git a/src/Extensions/Senparc.Xncf.DynamicData/OHS/Local/PL/MyFunctionRequest.cs b/src/Extensions/Senparc.Xncf.DynamicData/OHS/Local/PL/MyFunctionRequest.cs index a8202ccc3..a562a667d 100644 --- a/src/Extensions/Senparc.Xncf.DynamicData/OHS/Local/PL/MyFunctionRequest.cs +++ b/src/Extensions/Senparc.Xncf.DynamicData/OHS/Local/PL/MyFunctionRequest.cs @@ -1,7 +1,9 @@ using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; +using Senparc.Ncf.XncfBase; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Senparc.Xncf.DynamicData.OHS.Local.PL { @@ -22,7 +24,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase public int Number2 { get; set; } [Description("运算符||")]//下拉列表 - public SelectionList Operator { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } + + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("+","加法","数字1 + 数字2",false), new SelectionItem("-","减法","数字1 - 数字2",true), new SelectionItem("×","乘法","数字1 × 数字2",false), @@ -30,7 +36,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase }); [Description("计算平方||")]//多选框 - public SelectionList Power { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } + + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); diff --git a/src/Extensions/Senparc.Xncf.FileManager/OHS/Local/AppService/MyFuctionAppService.cs b/src/Extensions/Senparc.Xncf.FileManager/OHS/Local/AppService/MyFuctionAppService.cs index 63d0033c9..a5e85fe61 100644 --- a/src/Extensions/Senparc.Xncf.FileManager/OHS/Local/AppService/MyFuctionAppService.cs +++ b/src/Extensions/Senparc.Xncf.FileManager/OHS/Local/AppService/MyFuctionAppService.cs @@ -37,7 +37,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques */ double calcResult = request.Number1; - var theOperator = request.Operator.SelectedValues.FirstOrDefault(); + var theOperator = request.Operator; switch (theOperator) { case "+": @@ -68,7 +68,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques Action raisePower = power => { - if (request.Power.SelectedValues.Contains(power.ToString())) + if ((request.Power ?? Array.Empty()).Contains(power.ToString())) { var oldValue = calcResult; calcResult = Math.Pow(calcResult, power); diff --git a/src/Extensions/Senparc.Xncf.FileManager/OHS/Local/PL/MyFunctionRequest.cs b/src/Extensions/Senparc.Xncf.FileManager/OHS/Local/PL/MyFunctionRequest.cs index 6268957a8..49d675c4b 100644 --- a/src/Extensions/Senparc.Xncf.FileManager/OHS/Local/PL/MyFunctionRequest.cs +++ b/src/Extensions/Senparc.Xncf.FileManager/OHS/Local/PL/MyFunctionRequest.cs @@ -1,7 +1,9 @@ using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; +using Senparc.Ncf.XncfBase; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Senparc.Xncf.FileManager.OHS.Local.PL { @@ -22,7 +24,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase public int Number2 { get; set; } [Description("运算符||")]//下拉列表 - public SelectionList Operator { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } + + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("+","加法","数字1 + 数字2",false), new SelectionItem("-","减法","数字1 - 数字2",true), new SelectionItem("×","乘法","数字1 × 数字2",false), @@ -30,7 +36,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase }); [Description("计算平方||")]//多选框 - public SelectionList Power { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } + + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); diff --git a/src/Extensions/Senparc.Xncf.KnowledgeBase/OHS/Local/AppService/MyFuctionAppService.cs b/src/Extensions/Senparc.Xncf.KnowledgeBase/OHS/Local/AppService/MyFuctionAppService.cs index db26a8889..26f3b4334 100644 --- a/src/Extensions/Senparc.Xncf.KnowledgeBase/OHS/Local/AppService/MyFuctionAppService.cs +++ b/src/Extensions/Senparc.Xncf.KnowledgeBase/OHS/Local/AppService/MyFuctionAppService.cs @@ -37,7 +37,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques */ double calcResult = request.Number1; - var theOperator = request.Operator.SelectedValues.FirstOrDefault(); + var theOperator = request.Operator; switch (theOperator) { case "+": @@ -68,7 +68,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques Action raisePower = power => { - if (request.Power.SelectedValues.Contains(power.ToString())) + if ((request.Power ?? Array.Empty()).Contains(power.ToString())) { var oldValue = calcResult; calcResult = Math.Pow(calcResult, power); diff --git a/src/Extensions/Senparc.Xncf.KnowledgeBase/OHS/Local/PL/MyFunctionRequest.cs b/src/Extensions/Senparc.Xncf.KnowledgeBase/OHS/Local/PL/MyFunctionRequest.cs index 798ee41f4..b0ca17ae7 100644 --- a/src/Extensions/Senparc.Xncf.KnowledgeBase/OHS/Local/PL/MyFunctionRequest.cs +++ b/src/Extensions/Senparc.Xncf.KnowledgeBase/OHS/Local/PL/MyFunctionRequest.cs @@ -1,7 +1,9 @@ using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; +using Senparc.Ncf.XncfBase; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Senparc.Xncf.KnowledgeBase.OHS.Local.PL { @@ -22,7 +24,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase public int Number2 { get; set; } [Description("运算符||")]//下拉列表 - public SelectionList Operator { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } + + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("+","加法","数字1 + 数字2",false), new SelectionItem("-","减法","数字1 - 数字2",true), new SelectionItem("×","乘法","数字1 × 数字2",false), @@ -30,7 +36,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase }); [Description("计算平方||")]//多选框 - public SelectionList Power { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } + + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); diff --git a/src/Extensions/Senparc.Xncf.MCP/OHS/Local/AppService/MyFuctionAppService.cs b/src/Extensions/Senparc.Xncf.MCP/OHS/Local/AppService/MyFuctionAppService.cs index d512d3766..3746db47e 100644 --- a/src/Extensions/Senparc.Xncf.MCP/OHS/Local/AppService/MyFuctionAppService.cs +++ b/src/Extensions/Senparc.Xncf.MCP/OHS/Local/AppService/MyFuctionAppService.cs @@ -105,7 +105,7 @@ public async Task GetMcpResult(MyFunction_MCPCallRequest requ // 根据 MCP 服务器选择来确定端点 string endpoint; - var selectedMcpServer = request.McpServerSelection.SelectedValues.FirstOrDefault(); + var selectedMcpServer = request.McpServerSelection; if (!string.IsNullOrEmpty(selectedMcpServer) && selectedMcpServer != "Manual") { @@ -278,7 +278,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques */ double calcResult = request.Number1; - var theOperator = request.Operator.SelectedValues.FirstOrDefault(); + var theOperator = request.Operator; switch (theOperator) { case "+": @@ -309,7 +309,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques Action raisePower = power => { - if (request.Power.SelectedValues.Contains(power.ToString())) + if ((request.Power ?? Array.Empty()).Contains(power.ToString())) { var oldValue = calcResult; calcResult = Math.Pow(calcResult, power); diff --git a/src/Extensions/Senparc.Xncf.MCP/OHS/Local/PL/MyFunctionRequest.cs b/src/Extensions/Senparc.Xncf.MCP/OHS/Local/PL/MyFunctionRequest.cs index 1c46dbeac..95bb405da 100644 --- a/src/Extensions/Senparc.Xncf.MCP/OHS/Local/PL/MyFunctionRequest.cs +++ b/src/Extensions/Senparc.Xncf.MCP/OHS/Local/PL/MyFunctionRequest.cs @@ -1,4 +1,5 @@ using Senparc.Ncf.XncfBase.FunctionRenders; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.Functions; using System.ComponentModel; using System.ComponentModel.DataAnnotations; @@ -7,14 +8,18 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using System.Linq; -using Senparc.Ncf.XncfBase; +using System.Text.Json.Serialization; namespace Senparc.Xncf.MCP.OHS.Local.PL { public class MyFunction_MCPCallRequest : FunctionAppRequestBase { [Description("MCP 服务器选择||选择已注册的 MCP 服务器,或选择 手动输入 自定义服务器地址")] - public SelectionList McpServerSelection { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); + [FunctionParameterUi(ParameterType.DropDownList, nameof(McpServerSelectionOptions))] + public string McpServerSelection { get; set; } + + [JsonIgnore] + public SelectionList McpServerSelectionOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); [Description("MCP 服务器地址||MCP 服务器地址,当选择 手动输入 时需要填写,默认为 http://localhost:5000/mcp-senparc-xncf-mcp/sse")] public string Endpoint { get; set; } @@ -26,7 +31,7 @@ public class MyFunction_MCPCallRequest : FunctionAppRequestBase public override async Task LoadData(IServiceProvider serviceProvider) { // 添加手动输入选项 - McpServerSelection.Items.Add(new SelectionItem("Manual", "手动输入", "手动输入 MCP 服务器地址", true)); + McpServerSelectionOptions.Items.Add(new SelectionItem("Manual", "手动输入", "手动输入 MCP 服务器地址", true)); // 从 XncfRegisterManager 获取已注册的 MCP 服务器 var mcpServers = XncfRegisterManager.McpServerInfoCollection.Values.ToList(); @@ -38,7 +43,7 @@ public override async Task LoadData(IServiceProvider serviceProvider) // 使用服务器的唯一标识作为 Value,而不是路由 var serverKey = $"{mcpServer.XncfName}|{mcpServer.McpRoute}"; - McpServerSelection.Items.Add(new SelectionItem(serverKey, displayText, description)); + McpServerSelectionOptions.Items.Add(new SelectionItem(serverKey, displayText, description)); } await base.LoadData(serviceProvider); @@ -61,7 +66,11 @@ public class MyFunction_CaculateRequest : FunctionAppRequestBase public int Number2 { get; set; } [Description("运算符||")]//下拉列表 - public SelectionList Operator { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } + + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("+","加法","数字1 + 数字2",false), new SelectionItem("-","减法","数字1 - 数字2",true), new SelectionItem("×","乘法","数字1 × 数字2",false), @@ -69,7 +78,11 @@ public class MyFunction_CaculateRequest : FunctionAppRequestBase }); [Description("计算平方||")]//多选框 - public SelectionList Power { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } + + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); diff --git a/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/AppService/MyFuctionAppService.cs b/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/AppService/MyFuctionAppService.cs index acc5b53b0..ac4f81475 100644 --- a/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/AppService/MyFuctionAppService.cs +++ b/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/AppService/MyFuctionAppService.cs @@ -83,7 +83,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques */ double calcResult = request.Number1; - var theOperator = request.Operator.SelectedValues.FirstOrDefault(); + var theOperator = request.Operator; switch (theOperator) { case "+": @@ -114,7 +114,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques Action raisePower = power => { - if (request.Power.SelectedValues.Contains(power.ToString())) + if ((request.Power ?? Array.Empty()).Contains(power.ToString())) { var oldValue = calcResult; calcResult = Math.Pow(calcResult, power); diff --git a/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/PL/MyFunctionRequest.cs b/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/PL/MyFunctionRequest.cs index 66f32edb3..3906e54ce 100644 --- a/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/PL/MyFunctionRequest.cs +++ b/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/PL/MyFunctionRequest.cs @@ -1,7 +1,9 @@ using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; +using Senparc.Ncf.XncfBase; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Senparc.Xncf.SenMapic.OHS.Local.PL { @@ -37,7 +39,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase public int Number2 { get; set; } [Description("运算符||")]//下拉列表 - public SelectionList Operator { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } + + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("+","加法","数字1 + 数字2",false), new SelectionItem("-","减法","数字1 - 数字2",true), new SelectionItem("×","乘法","数字1 × 数字2",false), @@ -45,7 +51,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase }); [Description("计算平方||")]//多选框 - public SelectionList Power { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } + + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/OHS/Local/AppService/MyFuctionAppService.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/OHS/Local/AppService/MyFuctionAppService.cs index 4391cfabd..1571508c3 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/OHS/Local/AppService/MyFuctionAppService.cs +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/OHS/Local/AppService/MyFuctionAppService.cs @@ -37,7 +37,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques */ double calcResult = request.Number1; - var theOperator = request.Operator.SelectedValues.FirstOrDefault(); + var theOperator = request.Operator; switch (theOperator) { case "+": @@ -68,7 +68,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques Action raisePower = power => { - if (request.Power.SelectedValues.Contains(power.ToString())) + if ((request.Power ?? Array.Empty()).Contains(power.ToString())) { var oldValue = calcResult; calcResult = Math.Pow(calcResult, power); diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/OHS/Local/PL/MyFunctionRequest.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/OHS/Local/PL/MyFunctionRequest.cs index 020e7db31..61feb5107 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/OHS/Local/PL/MyFunctionRequest.cs +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/OHS/Local/PL/MyFunctionRequest.cs @@ -1,7 +1,9 @@ using Senparc.Ncf.XncfBase.FunctionRenders; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.Functions; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Template_OrgName.Xncf.Template_XncfName.OHS.Local.PL { @@ -22,7 +24,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase public int Number2 { get; set; } [Description("运算符||")]//下拉列表 - public SelectionList Operator { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } + + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("+","加法","数字1 + 数字2",false), new SelectionItem("-","减法","数字1 - 数字2",true), new SelectionItem("×","乘法","数字1 × 数字2",false), @@ -30,7 +36,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase }); [Description("计算平方||")]//多选框 - public SelectionList Power { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } + + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.AI.MCP.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.AI.MCP.cs index 91fdd4904..6b02a0ca3 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.AI.MCP.cs +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.AI.MCP.cs @@ -69,35 +69,20 @@ public async Task Build( MenuName = menuName, Icon = icon, Description = description, - UseSammple = new Ncf.XncfBase.Functions.SelectionList(Ncf.XncfBase.Functions.SelectionType.CheckBoxList, new[] { - new Ncf.XncfBase.Functions.SelectionItem("1","使用示例","使用示例",true), - }), - UseModule = new Ncf.XncfBase.Functions.SelectionList(Ncf.XncfBase.Functions.SelectionType.CheckBoxList, new[] { - new Ncf.XncfBase.Functions.SelectionItem("database","数据库","使用数据库",true), - }), + UseSammple = true, + UseModule = new[] { "database" }, // UseWeb = new Ncf.XncfBase.Functions.SelectionList( Ncf.XncfBase.Functions.SelectionType.CheckBoxList, new[] { // new Ncf.XncfBase.Functions.SelectionItem("1","使用Web","使用Web",true), // }), // UseWebApi = new Ncf.XncfBase.Functions.SelectionList( Ncf.XncfBase.Functions.SelectionType.CheckBoxList, new[] { // new Ncf.XncfBase.Functions.SelectionItem("1","使用WebApi","使用WebApi",true), // }), - NewSlnFile = new Ncf.XncfBase.Functions.SelectionList(Ncf.XncfBase.Functions.SelectionType.CheckBoxList, new[] { - new Ncf.XncfBase.Functions.SelectionItem("backup","备份 .sln 文件(推荐)","如果使用覆盖现有 .sln 文件,对当前文件进行备份",true), - }), - TemplatePackage = new Ncf.XncfBase.Functions.SelectionList(Ncf.XncfBase.Functions.SelectionType.DropDownList, new[] { - new Ncf.XncfBase.Functions.SelectionItem("no","已安装,不需要安装新版本","请确保已经在本地安装过版本(无论新旧),否则将自动从在线获取",true), - }), - FrameworkVersion = new Ncf.XncfBase.Functions.SelectionList(Ncf.XncfBase.Functions.SelectionType.DropDownList, new[] { - new Ncf.XncfBase.Functions.SelectionItem("net8.0","net8.0","使用 .NET 8.0",false), - }) + NewSlnFile = new[] { "backup" }, + TemplatePackage = "no", + FrameworkVersion = "net8.0" }; request.SlnFilePath = request.GetSlnFilePath(); - request.UseSammple.SelectedValues = new[] { "1" }; - request.UseModule.SelectedValues = new[] { "database" }; - request.NewSlnFile.SelectedValues = new[] { "backup" }; - request.TemplatePackage.SelectedValues = new[] { "no" }; - request.FrameworkVersion.SelectedValues = new[] { "net8.0" }; Console.WriteLine("XNCF Builder parameters:" + request.ToJson(true)); diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.AI.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.AI.cs index 3c49e282e..b2a103347 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.AI.cs +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.AI.cs @@ -37,11 +37,11 @@ public async Task CreateDatabaseEntity(BuildXncf_CreateDataba return await this.GetStringResponseAsync(async (response, logger) => { var promptBuilderService = base.ServiceProvider.GetRequiredService(); - var aiModelSelected = request.AIModel.SelectedValues.FirstOrDefault(); + var aiModelSelected = request.AIModel; ISenparcAiSetting aiSetting = null; #region PromptRange 是否已经初始化 - if (request.UseDatabasePrompt.IsSelected("1")) + if (request.UseDatabasePrompt) { var promptRangeRegister = new Xncf.PromptRange.Register(); var promptRangeModule = this._xncfModuleService.GetObject(z => z.Uid == promptRangeRegister.Uid); @@ -96,7 +96,7 @@ public async Task CreateDatabaseEntity(BuildXncf_CreateDataba var input = request.Requirement; - var projectPath = request.InjectDomain.SelectedValues.FirstOrDefault(); + var projectPath = request.InjectDomain; if (projectPath.IsNullOrEmpty() || projectPath == "N/A") { @@ -122,7 +122,7 @@ public async Task CreateDatabaseEntity(BuildXncf_CreateDataba #region 生成实体 DTO - if (request.MoreActions.IsSelected("BuildDto")) + if ((request.MoreActions ?? Array.Empty()).Contains("BuildDto")) { var entityDtoResult = await promptBuilderService.RunPromptAsync(aiSetting, Domain.PromptBuildType.EntityDtoClass, fileContent, className, null, projectPath, @namespace); logger.Append("生成实体 DTO:"); @@ -140,7 +140,7 @@ public async Task CreateDatabaseEntity(BuildXncf_CreateDataba #endregion #region 进行 Migration - if (request.MoreActions.IsSelected("BuildMigration")) + if ((request.MoreActions ?? Array.Empty()).Contains("BuildMigration")) { await Console.Out.WriteLineAsync("进入 Migration,可能耗时较长,请等待"); logger.Append(); @@ -199,11 +199,11 @@ public async Task CreateDatabaseEntity(BuildXncf_CreateDataba await requestObj.LoadData(this.ServiceProvider); //指定项目路径 - requestObj.ProjectPath.SelectedValues = new[] { projectPath }; + requestObj.ProjectPath = projectPath; //选中所有数据库 - requestObj.DatabaseTypes.SelectedValues = requestObj.DatabaseTypes.Items.Select(z => z.Value).ToArray(); + requestObj.DatabaseTypes = requestObj.DatabaseTypeOptions.Items.Select(z => z.Value).ToArray(); //选中输出详情 - requestObj.OutputVerbose.SelectedValues = new[] { requestObj.OutputVerbose.Items.First().Value }; + requestObj.OutputVerbose = true; var databaseMigrationsAppService = base.ServiceProvider.GetRequiredService(); var migrationResult = await databaseMigrationsAppService.AddMigration(requestObj); @@ -241,8 +241,8 @@ public async Task InitPrompt(BuildXncf_InitPromptRequest requ { return await this.GetStringResponseAsync(async (response, logger) => { - var needOverride = request.Override.SelectedValues.Contains("1"); - var aiModel = request.AIModel.SelectedValues.FirstOrDefault(); + var needOverride = request.Override; + var aiModel = request.AIModel; var promptBuilderService = base.ServiceProvider.GetRequiredService(); var log = await promptBuilderService.InitPromptAsync("XncfBuilderPlugin", needOverride, aiModel); diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.cs index 303a3856c..1adb63f22 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.cs +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/BuildXncfAppService.cs @@ -129,7 +129,7 @@ async Task ReadOutputAsync(Process process) logger.Append(templateOutput); var unInstallTemplatePackage = !templateOutput.Contains("Custom XNCF Module Template"); var installPackageCmd = string.Empty; - switch (request.TemplatePackage.SelectedValues.First()) + switch (request.TemplatePackage) { case "online": logger.Append("配置在线安装 XNCF 模板"); @@ -180,20 +180,21 @@ async Task ReadOutputAsync(Process process) } var frameworkVersion = string.IsNullOrEmpty(request.OtherFrameworkVersion) - ? request.FrameworkVersion.SelectedValues.First() + ? request.FrameworkVersion : request.OtherFrameworkVersion; string xncfBaseVersion = getLibVersionParam("Senparc.Ncf.XncfBase.dll", "XncfBaseVersion"); string ncfAreaBaseVersion = getLibVersionParam("Senparc.Ncf.AreaBase.dll", "NcfAreaBaseVersion"); - var isUseSample = request.UseSammple.SelectedValues.Contains("1"); - var isUseDatabase = isUseSample || request.UseModule.SelectedValues.Contains("database"); + var useModules = request.UseModule ?? Array.Empty(); + var isUseSample = request.UseSammple; + var isUseDatabase = isUseSample || useModules.Contains("database"); //var useSample = getBoolParam(isUseSample, "Sample"); - var useFunction = request.UseModule.SelectedValues.Contains("function"); - var isUseWeb = isUseSample || request.UseModule.SelectedValues.Contains("web"); + var useFunction = useModules.Contains("function"); + var isUseWeb = isUseSample || useModules.Contains("web"); //var useWeb = getBoolParam(isUseWeb, "Web"); //var useDatabase = getBoolParam(isUseDatabase, "Database"); - var useWebApi = request.UseModule.SelectedValues.Contains("webapi"); + var useWebApi = useModules.Contains("webapi"); //采用一个独立的进程 var args = new List @@ -349,7 +350,8 @@ public async Task Build(BuildXncf_BuildRequest request) else if (File.Exists(request.SlnFilePath)) { //是否创建新的 .sln 文件 - var useNewSlnFile = request.NewSlnFile.SelectedValues.Contains("new"); + var newSlnFileOptions = request.NewSlnFile ?? Array.Empty(); + var useNewSlnFile = newSlnFileOptions.Contains("new"); var slnFileName = Path.GetFileName(request.SlnFilePath); string newSlnFileName = slnFileName; @@ -363,7 +365,7 @@ public async Task Build(BuildXncf_BuildRequest request) } else { - var backupSln = request.NewSlnFile.SelectedValues.Contains("backup"); + var backupSln = newSlnFileOptions.Contains("backup"); var backupFileName = $"{slnFileName}-backup-{SystemTime.Now.DateTime.ToString("yyyyMMdd_HHmmss")}.sln"; var backupFilePath = Path.Combine(Path.GetDirectoryName(request.SlnFilePath), backupFileName); File.Copy(request.SlnFilePath, backupFilePath); diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/DatabaseMigrationsAppService.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/DatabaseMigrationsAppService.cs index 36ea26d25..99a468023 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/DatabaseMigrationsAppService.cs +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/Local/DatabaseMigrationsAppService.cs @@ -43,7 +43,8 @@ public async Task AddMigration(DatabaseMigrations_MigrationRe { return await this.GetStringResponseAsync(async (response, logger) => { - if (request.DatabaseTypes.SelectedValues.Count() == 0) + var databaseTypes = request.DatabaseTypes ?? Array.Empty(); + if (databaseTypes.Length == 0) { response.Success = false; response.Data = "至少选择 1 个数据库!"; @@ -63,10 +64,10 @@ public async Task AddMigration(DatabaseMigrations_MigrationRe commandTexts.Add(@$"cd ""{projectPath}"""); //执行迁移 - foreach (var dbType in request.DatabaseTypes.SelectedValues) + foreach (var dbType in databaseTypes) { string migrationDir = GetMigrationDir(request, dbType); - var outputVerbose = request.OutputVerbose.SelectedValues.Contains("1") ? " -v" : ""; + var outputVerbose = request.OutputVerbose ? " -v" : ""; //数据库上下文实体名称 var dbContextName = request.DbContextName; @@ -166,7 +167,7 @@ public async Task AddMigration(DatabaseMigrations_MigrationRe logger.Append(""); logger.Append("==== 版本号更新 ===="); - var updateVesionType = request.UpdateVersion.SelectedValues.FirstOrDefault(); + var updateVesionType = request.UpdateVersion; if (updateVesionType != "0") { var registerFile = Path.Combine(projectPath, "Register.cs"); diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/BuildXncfRequest.AI.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/BuildXncfRequest.AI.cs index 24b5a9f9e..21c87cdc7 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/BuildXncfRequest.AI.cs +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/BuildXncfRequest.AI.cs @@ -6,11 +6,13 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; using Senparc.Xncf.AIKernel.OHS.Local.AppService; using Senparc.AI.Exceptions; using Senparc.Xncf.AIKernel.Models; +using System.Text.Json.Serialization; namespace Senparc.Xncf.XncfBuilder.OHS.PL { @@ -52,10 +54,18 @@ public class BuildXncf_CreateDatabaseEntityRequest : FunctionAppRequestBase public string Requirement { get; set; } [Description("领域||指定需要生成到的领域")] - public SelectionList InjectDomain { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); + [FunctionParameterUi(ParameterType.DropDownList, nameof(InjectDomainOptions))] + public string InjectDomain { get; set; } + + [JsonIgnore] + public SelectionList InjectDomainOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); [Description("后续操作||指定生成数据库实体后的后续操作")] - public SelectionList MoreActions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(MoreActionsOptions))] + public string[] MoreActions { get; set; } + + [JsonIgnore] + public SelectionList MoreActionsOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("BuildDto","创建 DTO","创建 DTO 对象(已强制生成)",true), new SelectionItem("BuildMigration","直接生成数据库迁移信息","使用 EF Core Migration 生成迁移信息(建议查看后进行)",true), new SelectionItem("CreateRepository","创建 Repository","创建和实体匹配的 Repository",false), @@ -64,12 +74,20 @@ public class BuildXncf_CreateDatabaseEntityRequest : FunctionAppRequestBase }); [Description("使用 PromptRange ||指定 Prompt 来源。如果选中,系统将自动安装 PromptRange 模块并初始化 Prompt(此时需要提前配置好系统默认 AI 模型),全程无需任何人为干预;如不选中此选项,请在运行项目下 Domain/PromptPlugins/ 文件夹下存放 XncfBuilderPlugin 文件夹及所有文件内容。")] - public SelectionList UseDatabasePrompt { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(UseDatabasePromptOptions))] + public bool UseDatabasePrompt { get; set; } = true; + + [JsonIgnore] + public SelectionList UseDatabasePromptOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("1","是","",true) }); [Description("AI 模型||当不使用 PromptRange 时,需要选择生成代码所使用的 AI 模型")] - public SelectionList AIModel { get; set; } = new SelectionList(SelectionType.DropDownList, new List + [FunctionParameterUi(ParameterType.DropDownList, nameof(AIModelOptions))] + public string AIModel { get; set; } + + [JsonIgnore] + public SelectionList AIModelOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List { //new SelectionItem("Default","系统默认","通过系统默认配置的固定 AI 模型信息",true) }); @@ -78,10 +96,10 @@ public override async Task LoadData(IServiceProvider serviceProvider) { //扫描当前解决方案包含的所有领域项目 var newItems = FunctionHelper.LoadXncfProjects(true, null,"Senparc.Areas.Admin"); - newItems.ForEach(z => InjectDomain.Items.Add(z)); + newItems.ForEach(z => InjectDomainOptions.Items.Add(z)); //载入 AI 模型 - await BuildXncfRequestHelper.LoadAiModelData(serviceProvider, AIModel); + await BuildXncfRequestHelper.LoadAiModelData(serviceProvider, AIModelOptions); await base.LoadData(serviceProvider); } @@ -90,12 +108,20 @@ public override async Task LoadData(IServiceProvider serviceProvider) public class BuildXncf_InitPromptRequest : FunctionAppRequestBase { [Description("覆盖||如果记录已存在,则删除 XncfBuilderPlugin 靶场,使用官方版本重建")] - public SelectionList Override { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(OverrideOptions))] + public bool Override { get; set; } + + [JsonIgnore] + public SelectionList OverrideOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("1","是","",false) }); [Description("AI 模型||请选择新建的靶场(Range)中所有靶道(Tactics)使用的 AI 模型")] - public SelectionList AIModel { get; set; } = new SelectionList(SelectionType.DropDownList, new List + [FunctionParameterUi(ParameterType.DropDownList, nameof(AIModelOptions))] + public string AIModel { get; set; } + + [JsonIgnore] + public SelectionList AIModelOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List { //new SelectionItem("Default","系统默认","通过系统默认配置的固定 AI 模型信息",true) }); @@ -103,7 +129,7 @@ public class BuildXncf_InitPromptRequest : FunctionAppRequestBase public override async Task LoadData(IServiceProvider serviceProvider) { //载入 AI 模型 - await BuildXncfRequestHelper.LoadAiModelData(serviceProvider, AIModel); + await BuildXncfRequestHelper.LoadAiModelData(serviceProvider, AIModelOptions); await base.LoadData(serviceProvider); } diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/BuildXncfRequest.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/BuildXncfRequest.cs index 8a248a181..821812cc4 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/BuildXncfRequest.cs +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/BuildXncfRequest.cs @@ -2,6 +2,7 @@ using Senparc.CO2NET.Extensions; using Senparc.CO2NET.Trace; using Senparc.Ncf.Service; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; using Senparc.Xncf.AIKernel.Domain.Services; @@ -14,6 +15,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Text.Json.Serialization; namespace Senparc.Xncf.XncfBuilder.OHS.PL { @@ -25,21 +27,33 @@ public class BuildXncf_BuildRequest : FunctionAppRequestBase public string SlnFilePath { get; set; } [Description("配置解决方案文件(.sln)||")] - public SelectionList NewSlnFile { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(NewSlnFileOptions))] + public string[] NewSlnFile { get; set; } + + [JsonIgnore] + public SelectionList NewSlnFileOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("backup","备份 .sln 文件(推荐)","如果使用覆盖现有 .sln 文件,对当前文件进行备份",true), new SelectionItem("new","生成新的 .sln 文件","如果不选择,将覆盖现有 .sln 文件(不会影响已有功能,但如果 sln 解决方案正在运行,可能会触发自动重启服务),并推荐使用备份功能",false), }); [MaxLength(250)] [Description("安装新模板||安装 XNCF 的模板,如果重新安装可能需要 30-40s,如果已安装过模板,可选择【已安装】,以节省模板获取时间。")] - public SelectionList TemplatePackage { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(TemplatePackageOptions))] + public string TemplatePackage { get; set; } + + [JsonIgnore] + public SelectionList TemplatePackageOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("online","在线获取(从 Nuget.org 等在线环境获取最新版本,时间会略长)","从 Nuget.org 等在线环境获取最新版本,时间会略长",false), new SelectionItem("local","本地安装(从 .sln 同级目录下安装 Senparc.Xncf.XncfBuilder.Template.*.nupkg 包)","从 .sln 同级目录下安装 Senparc.Xncf.XncfBuilder.Template.*.nupkg 包",false), new SelectionItem("no","已安装,不需要安装新版本","请确保已经在本地安装过版本(无论新旧),否则将自动从在线获取",true), }); [Description("目标框架版本||指定项目的 TFM(Target Framework Moniker)")] - public SelectionList FrameworkVersion { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(FrameworkVersionOptions))] + public string FrameworkVersion { get; set; } + + [JsonIgnore] + public SelectionList FrameworkVersionOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { //new SelectionItem("netstandard2.1","netstandard2.1","使用 .NET Standard 2.1(兼容 .NET Core 3.1 和 .NET 5.0-8.0)",true), //new SelectionItem("netcoreapp3.1","netcoreapp3.1","使用 .NET Core 3.1",false), //new SelectionItem("net6.0","net6.0","使用 .NET 6.0",false), @@ -86,7 +100,11 @@ public class BuildXncf_BuildRequest : FunctionAppRequestBase public string Description { get; set; } [Description("功能配置||")] - public SelectionList UseModule { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(UseModuleOptions))] + public string[] UseModule { get; set; } + + [JsonIgnore] + public SelectionList UseModuleOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("function","配置“函数”功能","是否需要使用函数模块(Function)",false), new SelectionItem("database","配置“数据库”功能","是否需要使用数据库模块(Database),将配置空数据库",false), new SelectionItem("webapi","配置“WebApi”功能","是否需要使用WebApi模块(WebApi)",false), @@ -94,7 +112,11 @@ public class BuildXncf_BuildRequest : FunctionAppRequestBase }); [Description("安装 Sample||")] - public SelectionList UseSammple { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(UseSammpleOptions))] + public bool UseSammple { get; set; } + + [JsonIgnore] + public SelectionList UseSammpleOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("1","是","是否安装数据库示例,由于展示需要,将自动安装上述“数据库”、“Web(Area) 页面”功能",false), }); diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/DatabaseMigrationRequest.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/DatabaseMigrationRequest.cs index 73b9f1bce..e862c43fb 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/DatabaseMigrationRequest.cs +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/OHS/PL/DatabaseMigrationRequest.cs @@ -3,6 +3,7 @@ using Senparc.Ncf.Core.Exceptions; using Senparc.Ncf.Core.Models; using Senparc.Ncf.Service; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; using Senparc.Xncf.XncfBuilder.Domain.Models.Services; @@ -14,6 +15,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Text.Json.Serialization; namespace Senparc.Xncf.XncfBuilder.OHS.PL { @@ -25,14 +27,22 @@ public class DatabaseMigrations_MigrationRequest : FunctionAppRequestBase public string DatabasePlantPath { get; set; } [Description("XNCF 项目路径||选择 XNCF 项目根目录的完整物理路径")] - public SelectionList ProjectPath { get; set; } = new SelectionList(SelectionType.DropDownList); + [FunctionParameterUi(ParameterType.DropDownList, nameof(ProjectPathOptions))] + public string ProjectPath { get; set; } + + [JsonIgnore] + public SelectionList ProjectPathOptions { get; set; } = new SelectionList(SelectionType.DropDownList); [MaxLength(250)] [Description("自定义 XNCF 项目路径||仅当“XNCF 项目路径”选择“自定义路径”时有效。输入 XNCF 项目根目录的完整物理路径,如:E:\\Senparc项目\\NeuCharFramework\\NCF\\src\\MyDemo.Xncf.NewApp\\")] public string CustomProjectPath { get; set; } [Description("生成数据库类型||更多类型陆续添加中")] - public SelectionList DatabaseTypes { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(DatabaseTypeOptions))] + public string[] DatabaseTypes { get; set; } + + [JsonIgnore] + public SelectionList DatabaseTypeOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem(MultipleDatabaseType.Sqlite.ToString(),MultipleDatabaseType.Sqlite.ToString(),"",true), new SelectionItem(MultipleDatabaseType.SqlServer.ToString(),MultipleDatabaseType.SqlServer.ToString(),"",true), new SelectionItem(MultipleDatabaseType.MySql.ToString(),MultipleDatabaseType.MySql.ToString(),"",true), @@ -53,7 +63,11 @@ public class DatabaseMigrations_MigrationRequest : FunctionAppRequestBase [Description("自动更新版本号||自动更新 Register.cs 中的版本号")] - public SelectionList UpdateVersion { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(UpdateVersionOptions))] + public string UpdateVersion { get; set; } + + [JsonIgnore] + public SelectionList UpdateVersionOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("0","不更新","",true), new SelectionItem("1","主版本号(Major) + 1","",false), new SelectionItem("2","次版本号(Minor) + 1","",false), @@ -62,7 +76,11 @@ public class DatabaseMigrations_MigrationRequest : FunctionAppRequestBase [Description("输出详细日志||使用 add-migration 的 -v 参数")] - public SelectionList OutputVerbose { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(OutputVerboseOptions))] + public bool OutputVerbose { get; set; } + + [JsonIgnore] + public SelectionList OutputVerboseOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("1","使用","",false) }); @@ -77,7 +95,7 @@ public override async Task LoadData(IServiceProvider serviceProvider) { //TODO:单独生成一个表来记录 - this.ProjectPath.Items.Add(new SelectionItem("N/A", "自定义路径", "", true)); + this.ProjectPathOptions.Items.Add(new SelectionItem("N/A", "自定义路径", "", true)); //添加“停机坪”路径 var configService = serviceProvider.GetService(); @@ -91,13 +109,13 @@ public override async Task LoadData(IServiceProvider serviceProvider) //添加当前解决方案的项目选项 var projectList = FunctionHelper.LoadXncfProjects(false, null, "Senparc.Areas.Admin"); - projectList.OrderBy(z=>z.Value).ToList().ForEach(z => ProjectPath.Items.Add(z)); + projectList.OrderBy(z=>z.Value).ToList().ForEach(z => ProjectPathOptions.Items.Add(z)); //添加 NcfPackageSource 项目的解决方案的项目选项 var sourceRootDir = Path.Combine(System.IO.Directory.GetCurrentDirectory(), "..", "..", "..", "src"); Console.WriteLine("查找 Source 项目源文件根目录:" + sourceRootDir); var sourceProjectList = FunctionHelper.LoadXncfProjects(false, sourceRootDir, "Senparc.Areas.Admin"); - sourceProjectList.OrderBy(z => z.Value).ToList().ForEach(z => ProjectPath.Items.Add(z)); + sourceProjectList.OrderBy(z => z.Value).ToList().ForEach(z => ProjectPathOptions.Items.Add(z)); } } catch @@ -114,7 +132,7 @@ public override async Task LoadData(IServiceProvider serviceProvider) /// public string GetProjectPath(DatabaseMigrations_MigrationRequest request) { - var projectPath = request.ProjectPath.SelectedValues.FirstOrDefault(); + var projectPath = request.ProjectPath; if (projectPath == "N/A") { projectPath = request.CustomProjectPath; diff --git a/src/Extensions/System/Senparc.Xncf.XncfModuleManager/OHS/Local/AppService/XncfStateAppService.cs b/src/Extensions/System/Senparc.Xncf.XncfModuleManager/OHS/Local/AppService/XncfStateAppService.cs index 236c588d7..db81b53ea 100644 --- a/src/Extensions/System/Senparc.Xncf.XncfModuleManager/OHS/Local/AppService/XncfStateAppService.cs +++ b/src/Extensions/System/Senparc.Xncf.XncfModuleManager/OHS/Local/AppService/XncfStateAppService.cs @@ -34,7 +34,7 @@ public async Task ShowFunctions(XncfState_ShowFunctionsReques { return await this.GetStringResponseAsync(async (response, logger) => { - var selectedRegisterUid = request.XncfModule.SelectedValues.FirstOrDefault(); + var selectedRegisterUid = request.XncfModule; var register = XncfRegisterManager.RegisterList.FirstOrDefault(z => z.Uid == selectedRegisterUid); if (register == null) { @@ -89,7 +89,7 @@ public async Task InstallAndOpenModule(XncfState_InstallAndOp { return await this.GetStringResponseAsync(async (response, logger) => { - var selectedRegisterUid = request.XncfModule.SelectedValues.FirstOrDefault(); + var selectedRegisterUid = request.XncfModule; if (string.IsNullOrWhiteSpace(selectedRegisterUid)) { response.Data = logger.Append("请先选择需要安装的 XNCF 模块。"); diff --git a/src/Extensions/System/Senparc.Xncf.XncfModuleManager/OHS/Local/PL/XncfStateRequest.cs b/src/Extensions/System/Senparc.Xncf.XncfModuleManager/OHS/Local/PL/XncfStateRequest.cs index 34f4a7194..0f7fad231 100644 --- a/src/Extensions/System/Senparc.Xncf.XncfModuleManager/OHS/Local/PL/XncfStateRequest.cs +++ b/src/Extensions/System/Senparc.Xncf.XncfModuleManager/OHS/Local/PL/XncfStateRequest.cs @@ -7,14 +7,20 @@ using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.FunctionRenders; using Senparc.Ncf.XncfBase.Functions; +using Senparc.Ncf.XncfBase.Functions.Parameters; using Senparc.Xncf.XncfModuleManager.Domain.Services; +using System.Text.Json.Serialization; namespace Senparc.Xncf.XncfModuleManager.OHS.Local.PL { public class XncfState_ShowFunctionsRequest : FunctionAppRequestBase { [Description("XNCF 模块||查看具体 XNCF 模块的 Function 情况")] - public SelectionList XncfModule { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); + [FunctionParameterUi(ParameterType.DropDownList, nameof(XncfModuleOptions))] + public string XncfModule { get; set; } + + [JsonIgnore] + public SelectionList XncfModuleOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); public override Task LoadData(IServiceProvider serviceProvider) { @@ -23,7 +29,7 @@ public override Task LoadData(IServiceProvider serviceProvider) .ToList(); foreach (var item in registers) { - XncfModule.Items.Add(new SelectionItem(item.Uid, item.Name)); + XncfModuleOptions.Items.Add(new SelectionItem(item.Uid, item.Name)); } return base.LoadData(serviceProvider); @@ -33,7 +39,11 @@ public override Task LoadData(IServiceProvider serviceProvider) public class XncfState_InstallAndOpenModuleRequest : FunctionAppRequestBase { [Description("XNCF 模块||选择需要安装并开放的模块")] - public SelectionList XncfModule { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); + [FunctionParameterUi(ParameterType.DropDownList, nameof(XncfModuleOptions))] + public string XncfModule { get; set; } + + [JsonIgnore] + public SelectionList XncfModuleOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new List()); public override async Task LoadData(IServiceProvider serviceProvider) { @@ -47,15 +57,15 @@ public override async Task LoadData(IServiceProvider serviceProvider) foreach (var register in canInstallRegisters) { - XncfModule.Items.Add(new SelectionItem(register.Uid, $"{register.MenuName} ({register.Name})")); + XncfModuleOptions.Items.Add(new SelectionItem(register.Uid, $"{register.MenuName} ({register.Name})")); } } - if (XncfModule.Items.Count == 0) + if (XncfModuleOptions.Items.Count == 0) { foreach (var register in XncfRegisterManager.RegisterList.Where(z => !z.IgnoreInstall).OrderBy(z => z.MenuName)) { - XncfModule.Items.Add(new SelectionItem(register.Uid, $"{register.MenuName} ({register.Name})")); + XncfModuleOptions.Items.Add(new SelectionItem(register.Uid, $"{register.MenuName} ({register.Name})")); } } diff --git a/tools/NcfSimulatedSite/Senparc.Xncf.Accounts/OHS/Local/PL/MyFunctionRequest.cs b/tools/NcfSimulatedSite/Senparc.Xncf.Accounts/OHS/Local/PL/MyFunctionRequest.cs index 68a0d900e..71d5dc2fb 100644 --- a/tools/NcfSimulatedSite/Senparc.Xncf.Accounts/OHS/Local/PL/MyFunctionRequest.cs +++ b/tools/NcfSimulatedSite/Senparc.Xncf.Accounts/OHS/Local/PL/MyFunctionRequest.cs @@ -1,7 +1,9 @@ using Senparc.Ncf.XncfBase.FunctionRenders; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.Functions; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Senparc.Xncf.Accounts.OHS.Local.PL { @@ -22,7 +24,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase public int Number2 { get; set; } [Description("运算符||")]//下拉列表 - public SelectionList Operator { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } + + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("+","加法","数字1 + 数字2",false), new SelectionItem("-","减法","数字1 - 数字2",true), new SelectionItem("×","乘法","数字1 × 数字2",false), @@ -30,7 +36,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase }); [Description("计算平方||")]//多选框 - public SelectionList Power { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } + + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); diff --git a/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/OHS/Local/AppService/MyFuctionAppService.cs b/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/OHS/Local/AppService/MyFuctionAppService.cs index 4391cfabd..1571508c3 100644 --- a/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/OHS/Local/AppService/MyFuctionAppService.cs +++ b/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/OHS/Local/AppService/MyFuctionAppService.cs @@ -37,7 +37,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques */ double calcResult = request.Number1; - var theOperator = request.Operator.SelectedValues.FirstOrDefault(); + var theOperator = request.Operator; switch (theOperator) { case "+": @@ -68,7 +68,7 @@ public async Task Calculate(MyFunction_CaculateRequest reques Action raisePower = power => { - if (request.Power.SelectedValues.Contains(power.ToString())) + if ((request.Power ?? Array.Empty()).Contains(power.ToString())) { var oldValue = calcResult; calcResult = Math.Pow(calcResult, power); diff --git a/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/OHS/Local/PL/MyFunctionRequest.cs b/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/OHS/Local/PL/MyFunctionRequest.cs index 020e7db31..61feb5107 100644 --- a/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/OHS/Local/PL/MyFunctionRequest.cs +++ b/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/OHS/Local/PL/MyFunctionRequest.cs @@ -1,7 +1,9 @@ using Senparc.Ncf.XncfBase.FunctionRenders; +using Senparc.Ncf.XncfBase; using Senparc.Ncf.XncfBase.Functions; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Template_OrgName.Xncf.Template_XncfName.OHS.Local.PL { @@ -22,7 +24,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase public int Number2 { get; set; } [Description("运算符||")]//下拉列表 - public SelectionList Operator { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } + + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { new SelectionItem("+","加法","数字1 + 数字2",false), new SelectionItem("-","减法","数字1 - 数字2",true), new SelectionItem("×","乘法","数字1 × 数字2",false), @@ -30,7 +36,11 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase }); [Description("计算平方||")]//多选框 - public SelectionList Power { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } + + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); From dacad3c20d9f12dce382e68c329bbdbe9a980927 Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Fri, 24 Apr 2026 21:00:50 +0800 Subject: [PATCH 15/19] Refactor code structure for improved readability and maintainability --- .../Senparc.Ncf.XncfBase.csproj | 3 +- .../Senparc.Xncf.AIAgentsHub.csproj | 3 +- .../Senparc.Xncf.AIKernel.csproj | 3 +- .../Senparc.Xncf.AgentsManager.csproj | 3 +- .../Senparc.Xncf.ChangeNamespace.csproj | 3 +- .../Senparc.Xncf.DatabaseToolkit.csproj | 3 +- .../Senparc.Xncf.DynamicData.csproj | 3 +- .../Senparc.Xncf.FileManager.csproj | 3 +- .../Senparc.Xncf.KnowledgeBase.csproj | 3 +- .../Senparc.Xncf.MCP/Senparc.Xncf.MCP.csproj | 3 +- .../Senparc.Xncf.SenMapic.csproj | 3 +- .../Senparc.Xncf.XncfBuilder.Template.csproj | 3 +- ...late_OrgName.Xncf.Template_XncfName.csproj | 3 +- .../BuildXncfAppService.Generated.cs | 1525 +++++++++++++++++ ...5950]Senparc.Xncf.XncfModuleManager.csproj | 3 +- .../Senparc.Web/appsettings.json | 4 +- .../Senparc.Xncf.Accounts.csproj | 3 +- ...late_OrgName.Xncf.Template_XncfName.csproj | 3 +- 18 files changed, 1559 insertions(+), 18 deletions(-) create mode 100644 src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs diff --git a/src/Basic/Senparc.Ncf.XncfBase/Senparc.Ncf.XncfBase.csproj b/src/Basic/Senparc.Ncf.XncfBase/Senparc.Ncf.XncfBase.csproj index ab484f0d9..d2ff37ef6 100644 --- a/src/Basic/Senparc.Ncf.XncfBase/Senparc.Ncf.XncfBase.csproj +++ b/src/Basic/Senparc.Ncf.XncfBase/Senparc.Ncf.XncfBase.csproj @@ -2,7 +2,7 @@ net8.0 - 0.22.17-preview.1 + 0.22.18-preview.1 10.0 Senparc.Ncf.XncfBase Senparc.Ncf.XncfBase @@ -55,6 +55,7 @@ [2025-06-27] v0.22.13-preview.1 Add dynamic XNCF Template dynamic generator [2025-11-01] update basic libraries, including Senparc.AI [2026-01-07] v0.22.16-preview.1 Add rootDir parameter to FunctionHelper.LoadXncfProjects() method + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.AIAgentsHub/Senparc.Xncf.AIAgentsHub.csproj b/src/Extensions/Senparc.Xncf.AIAgentsHub/Senparc.Xncf.AIAgentsHub.csproj index 8235fd508..338bfbadc 100644 --- a/src/Extensions/Senparc.Xncf.AIAgentsHub/Senparc.Xncf.AIAgentsHub.csproj +++ b/src/Extensions/Senparc.Xncf.AIAgentsHub/Senparc.Xncf.AIAgentsHub.csproj @@ -1,7 +1,7 @@ net8.0 - 0.11.12-preview.1 + 0.11.13-preview.1 Senparc.Xncf.AIAgentsHub Senparc.Xncf.AIAgentsHub true @@ -49,6 +49,7 @@ [2025-06-20] v0.11.9-preview1 Add MCP functions in XncfRegisterBase class [2025-11-01] update basic libraries, including Senparc.AI + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.AIKernel/Senparc.Xncf.AIKernel.csproj b/src/Extensions/Senparc.Xncf.AIKernel/Senparc.Xncf.AIKernel.csproj index c19b84da9..ee4e41332 100644 --- a/src/Extensions/Senparc.Xncf.AIKernel/Senparc.Xncf.AIKernel.csproj +++ b/src/Extensions/Senparc.Xncf.AIKernel/Senparc.Xncf.AIKernel.csproj @@ -1,7 +1,7 @@ net8.0 - 0.12.24-preview.1 + 0.12.25-preview.1 Senparc.Xncf.AIKernel Senparc.Xncf.AIKernel true @@ -54,6 +54,7 @@ [2025-05-18] v0.12.18-preview1 Update Installation [2025-06-20] v0.12.20-preview Add MCP functions in XncfRegisterBase class [2026-03-24] v0.12.24-preview.1 update Senparc.AI.Kernel to v0.28.0 + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility icon.jpg https://github.com/NeuCharFramework/NcfPackageSources diff --git a/src/Extensions/Senparc.Xncf.AgentsManager/Senparc.Xncf.AgentsManager.csproj b/src/Extensions/Senparc.Xncf.AgentsManager/Senparc.Xncf.AgentsManager.csproj index 047c783cc..314722724 100644 --- a/src/Extensions/Senparc.Xncf.AgentsManager/Senparc.Xncf.AgentsManager.csproj +++ b/src/Extensions/Senparc.Xncf.AgentsManager/Senparc.Xncf.AgentsManager.csproj @@ -1,7 +1,7 @@ net8.0 - 0.9.18-preview.1 + 0.9.19-preview.1 Senparc.Xncf.AgentsManager Senparc.Xncf.AgentsManager true @@ -63,6 +63,7 @@ [2026-03-24] v0.9.18-preview.1 update Senparc.AI.Agents to v0.7.1 [2025-11-01] update basic libraries, including Senparc.AI + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.ChangeNamespace/Senparc.Xncf.ChangeNamespace.csproj b/src/Extensions/Senparc.Xncf.ChangeNamespace/Senparc.Xncf.ChangeNamespace.csproj index 6d5f596a0..f8474bf29 100644 --- a/src/Extensions/Senparc.Xncf.ChangeNamespace/Senparc.Xncf.ChangeNamespace.csproj +++ b/src/Extensions/Senparc.Xncf.ChangeNamespace/Senparc.Xncf.ChangeNamespace.csproj @@ -1,7 +1,7 @@ netstandard2.1 - 0.20.12-preview.1 + 0.20.13-preview.1 Senparc.Xncf.ChangeNamespace Senparc.Xncf.ChangeNamespace true @@ -28,6 +28,7 @@ [2025-06-20] v0.20.9-preview1 Add MCP functions in XncfRegisterBase class [2025-11-01] update basic libraries, including Senparc.AI + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit.csproj b/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit.csproj index f586db401..13844ffa2 100644 --- a/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit.csproj +++ b/src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit.csproj @@ -4,7 +4,7 @@ - 0.24.13-preview.1 + 0.24.14-preview.1 Senparc.Xncf.DatabaseToolkit Senparc.Xncf.DatabaseToolkit true @@ -43,6 +43,7 @@ [2025-06-20] v0.24.10-preview1 Add MCP functions in XncfRegisterBase class [2025-11-01] update basic libraries, including Senparc.AI + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.DynamicData/Senparc.Xncf.DynamicData.csproj b/src/Extensions/Senparc.Xncf.DynamicData/Senparc.Xncf.DynamicData.csproj index fff95cf46..5dd6b58d6 100644 --- a/src/Extensions/Senparc.Xncf.DynamicData/Senparc.Xncf.DynamicData.csproj +++ b/src/Extensions/Senparc.Xncf.DynamicData/Senparc.Xncf.DynamicData.csproj @@ -1,7 +1,7 @@ net8.0 - 0.4.15-preview.1 + 0.4.16-preview.1 Senparc.Xncf.DynamicData Senparc.Xncf.DynamicData true @@ -38,6 +38,7 @@ [2025-06-20] v0.4.12-pre-alpha Add MCP functions in XncfRegisterBase class [2025-11-01] update basic libraries, including Senparc.AI + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.FileManager/Senparc.Xncf.FileManager.csproj b/src/Extensions/Senparc.Xncf.FileManager/Senparc.Xncf.FileManager.csproj index ccbda79b2..84a8c72d7 100644 --- a/src/Extensions/Senparc.Xncf.FileManager/Senparc.Xncf.FileManager.csproj +++ b/src/Extensions/Senparc.Xncf.FileManager/Senparc.Xncf.FileManager.csproj @@ -1,7 +1,7 @@ net8.0 - 0.1.10-preview.1 + 0.1.11-preview.1 Senparc.Xncf.FileManager Senparc.Xncf.FileManager true @@ -24,6 +24,7 @@ [2025-07-27] v0.1.6-preview.1 update Knowledge Base [2025-11-01] update basic libraries, including Senparc.AI [2026-01-07] v0.1.9-preview.1 Reset all database migrations + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.KnowledgeBase/Senparc.Xncf.KnowledgeBase.csproj b/src/Extensions/Senparc.Xncf.KnowledgeBase/Senparc.Xncf.KnowledgeBase.csproj index 57d737fcb..5edac4b2f 100644 --- a/src/Extensions/Senparc.Xncf.KnowledgeBase/Senparc.Xncf.KnowledgeBase.csproj +++ b/src/Extensions/Senparc.Xncf.KnowledgeBase/Senparc.Xncf.KnowledgeBase.csproj @@ -1,7 +1,7 @@ net8.0 - 0.1.9-preview.1 + 0.1.10-preview.1 Senparc.Xncf.KnowledgeBase Senparc.Xncf.KnowledgeBase true @@ -22,6 +22,7 @@ [2025-07-27] v0.1.5-preview1s update Knowledge Base [2025-11-01] update basic libraries, including Senparc.AI [2025-01-07] v0.1.8-preview.1 Reset all database migrations + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.MCP/Senparc.Xncf.MCP.csproj b/src/Extensions/Senparc.Xncf.MCP/Senparc.Xncf.MCP.csproj index 6025295ca..c3dae91dc 100644 --- a/src/Extensions/Senparc.Xncf.MCP/Senparc.Xncf.MCP.csproj +++ b/src/Extensions/Senparc.Xncf.MCP/Senparc.Xncf.MCP.csproj @@ -1,7 +1,7 @@ net8.0 - 0.2.6-preview.1 + 0.2.7-preview.1 Senparc.Xncf.MCP Senparc.Xncf.MCP true @@ -25,6 +25,7 @@ [2025-06-28] v0.2.3-preview.1 Add MCP server selection in MyFunction_MCPCallRequest [2025-11-01] update basic libraries, including Senparc.AI + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.SenMapic/Senparc.Xncf.SenMapic.csproj b/src/Extensions/Senparc.Xncf.SenMapic/Senparc.Xncf.SenMapic.csproj index 5fbfbff9d..1fbb9834f 100644 --- a/src/Extensions/Senparc.Xncf.SenMapic/Senparc.Xncf.SenMapic.csproj +++ b/src/Extensions/Senparc.Xncf.SenMapic/Senparc.Xncf.SenMapic.csproj @@ -1,7 +1,7 @@ net8.0 - 0.1.12-preview.1 + 0.1.13-preview.1 Senparc.Xncf.SenMapic Senparc.Xncf.SenMapic true @@ -28,6 +28,7 @@ [2025-06-20] v0.1.9-alpha Add MCP functions in XncfRegisterBase class [2025-11-01] update basic libraries, including Senparc.AI + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/Senparc.Xncf.XncfBuilder.Template.csproj b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/Senparc.Xncf.XncfBuilder.Template.csproj index 132fc3535..5f3f20aa0 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/Senparc.Xncf.XncfBuilder.Template.csproj +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/Senparc.Xncf.XncfBuilder.Template.csproj @@ -2,7 +2,7 @@ net8.0 Template - 0.12.10-preview.1 + 0.12.11-preview.1 Senparc.Xncf.XncfBuilder.Template XNCF Template JeffreySu @@ -54,6 +54,7 @@ v0.12.8-beta1 添加达梦数据库迁移(测试) v0.12.9-preview.7 更新 CO2NET.WebApi、DatabasePlant 等基础库 [2025-11-01] update basic libraries, including Senparc.AI + [2026-04-24] Update FunctionRender request templates for simplified values and SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Apache-2.0 diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/Template_OrgName.Xncf.Template_XncfName.csproj b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/Template_OrgName.Xncf.Template_XncfName.csproj index bd71434a1..e528d5448 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/Template_OrgName.Xncf.Template_XncfName.csproj +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.Template/templates/template1/Template_OrgName.Xncf.Template_XncfName.csproj @@ -1,7 +1,7 @@ net8.0 - 1.0.1-preview.1 + 1.0.2-preview.1 Template_OrgName.Xncf.Template_XncfName Template_OrgName.Xncf.Template_XncfName true @@ -19,6 +19,7 @@ icon.jpg v1.0.0 创世 + [2026-04-24] Update FunctionRender request templates for simplified values and SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs new file mode 100644 index 000000000..f09d62519 --- /dev/null +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Generated/Senparc.Xncf.XncfBuilder.DynamicContentGenerator/MultiFileCodeGenerator/BuildXncfAppService.Generated.cs @@ -0,0 +1,1525 @@ +// +// This file was generated by MultiFileCodeGenerator +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. +// + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Senparc.Xncf.XncfBuilder.OHS.Local +{ + /// + /// 自动生成的NCF模板常量类,包含所有模板文件的内容 + /// + public partial class BuildXncfAppService + { +public const string BackendTemplate = @$" +## Database EntityFramework DbContext class sample +File Name: Template_XncfNameSenparcEntities.cs +File Path: /Domain/Models/DatabaseModel +Code: +```csharp +{SenparcEntitiesTemplate} +``` + +## Database Entity class sample +File Name: Color.cs +File Path: /Domain/Models/DatabaseModel +Code: +```csharp +{ColorModelTemplate} +``` + +## Database Entity DTO class sample +File Name: ColorDto.cs +File Path: /Domain/Models/DatabaseModel/Dto +Code: +```csharp +{ColorDtoTemplate} +``` + +## Service class sample +File Name: Template_XncfNameService.cs +File Path: /Domain/Services +Code: +```csharp +{ColorServiceTemplate} +``` +"; + +public const string FrontendTemplate = @$" +## Page UI sample (front-end) +File Name: DatabaseSampleIndex.cshtml +File Path: < ModuleRootPath >/ Areas / Admin / Pages / Template_XncfName +Code: +```razorpage +{DatabaseSampleIndexViewTemplate} +``` + +## Page UI sample (back-end) +File Name: DatabaseSampleIndex.cshtml.cs +File Path: < ModuleRootPath >/ Areas / Admin / Pages / Template_XncfName +Code: +```csharp +{DatabaseSampleIndexCodeBehindTemplate} +``` + +## Page JavaScript file sample +File Name: databaseSampleIndex.js +File Path: < ModuleRootPath >/ wwwroot / js / Admin / Template_XncfName +Code: +```javascript +{DatabaseSampleIndexJsTemplate} +``` + +## Page CSS file sample +File Name: databaseSampleIndex.css +File Path: < ModuleRootPath >/ wwwroot / css / Admin / Template_XncfName +Code: +```css +{DatabaseSampleIndexCssTemplate} +``` +"; + + + #region CODE Templates + + /// + /// 请求类代码 + /// 类型: code + /// + public const string RequestCode = @"using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Senparc.Xncf.XncfBuilder +{ + public class Request + { + public string? Method { get; set; } + public string? Path { get; set; } + public string? Body { get; set; } + + // 新增字段测试动态更新 + public Dictionary? Headers { get; set; } + public DateTime Timestamp { get; set; } = DateTime.Now; + } + +} +"; + + #endregion + + #region BACKEND_TEMPLATE Templates + + /// + /// Senparc实体类模板 + /// 类型: backend_template + /// + public const string SenparcEntitiesTemplate = @"using Microsoft.EntityFrameworkCore; +using Senparc.Ncf.Database; +using Senparc.Ncf.Core.Models; +using Senparc.Ncf.XncfBase.Database; + +namespace Template_OrgName.Xncf.Template_XncfName.Models +{ + public class Template_XncfNameSenparcEntities : XncfDatabaseDbContext + { + public Template_XncfNameSenparcEntities(DbContextOptions dbContextOptions) : base(dbContextOptions) + { + } + + public DbSet Colors { get; set; } + + //DOT REMOVE OR MODIFY THIS LINE 请勿移除或修改本行 - Entities Point + //ex. public DbSet Colors { get; set; } + + //如无特殊需需要,OnModelCreating 方法可以不用写,已经在 Register 中要求注册 + //protected override void OnModelCreating(ModelBuilder modelBuilder) + //{ + //} + } +} +"; + + /// + /// 颜色模型模板 + /// 类型: backend_template + /// + public const string ColorModelTemplate = @"using Senparc.Ncf.Core.Models; +using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; + +namespace Template_OrgName.Xncf.Template_XncfName +{ + /// + /// Color 实体类 + /// + [Table(Register.DATABASE_PREFIX + nameof(Color))]//必须添加前缀,防止全系统中发生冲突 + [Serializable] + public class Color : EntityBase + { + /// + /// 颜色码,0-255 + /// + public int Red { get; private set; } + /// + /// 颜色码,0-255 + /// + public int Green { get; private set; } + + /// + /// 颜色码,0-255 + /// + public int Blue { get; private set; } + + /// + /// 附加列,测试多次数据库 Migrate + /// + public string AdditionNote { get; private set; } + + private Color() { } + + public Color(int red, int green, int blue) + { + if (red < 0 || green < 0 || blue < 0) + { + Random();//随机 + } + else + { + Red = red; + Green = green; + Blue = blue; + } + } + + public Color(int red, int green, int blue, string additionNote) : this(red, green, blue) + { + AdditionNote = additionNote; + } + + public Color(ColorDto colorDto) + { + Red = colorDto.Red; + Green = colorDto.Green; + Blue = colorDto.Blue; + } + + public void Random() + { + //随机产生颜色代码 + var radom = new Random(); + Func getRadomColorCode = () => radom.Next(0, 255); + Red = getRadomColorCode(); + Green = getRadomColorCode(); + Blue = getRadomColorCode(); + } + + public void Brighten() + { + Red = Math.Min(255, Red + 10); + Green = Math.Min(255, Green + 10); + Blue = Math.Min(255, Blue + 10); + } + + public void Darken() + { + Red = Math.Max(0, Red - 10); + Green = Math.Max(0, Green - 10); + Blue = Math.Max(0, Blue - 10); + } + + public void Update(UpdateColorRequestDto dto) + { + Red = dto.Red; + Green = dto.Green; + Blue = dto.Blue; + AdditionNote = dto.AdditionNote; + } + } +} +"; + + /// + /// 颜色DTO模板 + /// 类型: backend_template + /// + public const string ColorDtoTemplate = @"using Senparc.Ncf.Core.Models; + +namespace Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto +{ + public class ColorDto : DtoBase + { + /// + /// 颜色码,0-255 + /// + public int Red { get; set; } + /// + /// 颜色码,0-255 + /// + public int Green { get; set; } + /// + /// 颜色码,0-255 + /// + public int Blue { get; set; } + + /// + /// 附加列,测试多次数据库 Migrate + /// + public string AdditionNote { get; set; } + + public ColorDto() { } + } +} +"; + + /// + /// 颜色服务模板 + /// 类型: backend_template + /// + public const string ColorServiceTemplate = @"using Senparc.Ncf.Core.Enums; +using Senparc.Ncf.Repository; +using Senparc.Ncf.Service; +using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; +using System; +using System.Threading.Tasks; + +namespace Template_OrgName.Xncf.Template_XncfName.Domain.Services +{ + public class ColorService : ServiceBase + { + public ColorService(IRepositoryBase repo, IServiceProvider serviceProvider) + : base(repo, serviceProvider) + { + } + + public async Task CreateNewColor() + { + Color color = new Color(-1, -1, -1); + await base.SaveObjectAsync(color).ConfigureAwait(false); + ColorDto colorDto = base.Mapper.Map(color); + return colorDto; + } + + public async Task GetOrInitColor() + { + var color = await base.GetObjectAsync(z => true); + if (color == null)//如果是纯第一次安装,理论上不会有残留数据 + { + //创建默认颜色 + ColorDto colorDto = await this.CreateNewColor().ConfigureAwait(false); + return colorDto; + } + + return base.Mapper.Map(color); + } + + public async Task Brighten() + { + //TODO:异步方法需要添加排序功能 + var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); + obj.Brighten(); + await base.SaveObjectAsync(obj).ConfigureAwait(false); + return base.Mapper.Map(obj); + } + + public async Task Darken() + { + //TODO:异步方法需要添加排序功能 + var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); + obj.Darken(); + await base.SaveObjectAsync(obj).ConfigureAwait(false); + return base.Mapper.Map(obj); + } + + public async Task Random() + { + //TODO:异步方法需要添加排序功能 + var obj = await this.GetObjectAsync(z => true, z => z.Id, OrderingType.Descending); + obj.Random(); + await base.SaveObjectAsync(obj).ConfigureAwait(false); + return base.Mapper.Map(obj); + } + + //TODO: 更多业务方法可以写到这里 + } +} +"; + + #endregion + + #region FRONTEND_TEMPLATE Templates + + /// + /// 数据库示例索引页面视图模板 + /// 类型: frontend_template + /// + public const string DatabaseSampleIndexViewTemplate = @"@page +@model Template_OrgName.Xncf.Template_XncfName.Areas.Template_XncfName.Pages.DatabaseSampleIndex +@{ + ViewData[""Title""] = ""Color 数据库管理""; + Layout = ""_Layout_Vue""; +} + +@section Style { + +} + +@section breadcrumbs { + 扩展模块 + Template_MenuName + Color 数据库管理 +} + +
+
+ 添加颜色 + 刷新 + 调试信息 +
+ + +
+

调试信息:

+

tableData长度: {{ tableData ? tableData.length : 'null/undefined' }}

+

total: {{ total }}

+

tableLoading: {{ tableLoading }}

+

Vue实例是否正常: {{ $el ? '是' : '否' }}

+
0""> + 第一条数据: +
{{ JSON.stringify(tableData[0], null, 2) }}
+
+ + +
+
简化数据显示测试:
+
+ ID: {{item.id}} | + RGB: {{item.red}},{{item.green}},{{item.blue}} | + 时间: {{item.addTime}} +
+

+ 没有数据显示! +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + @* dialog for 添加颜色 *@ + + + + + + + + + + + + +
+ RGB({{addForm.red}}, {{addForm.green}}, {{addForm.blue}}) +
+
+ + + + + 随机颜色 + +
+ + 取 消 + 确 定 + +
+ + @* dialog for 编辑颜色 *@ + + + + + + + + + + + + +
+ RGB({{editForm.red}}, {{editForm.green}}, {{editForm.blue}}) +
+
+ + + + + 随机颜色 + +
+ + 取 消 + 确 定 + +
+
+ +@section scripts{ + +} "; + + /// + /// 数据库示例索引页面代码后置模板 + /// 类型: frontend_template + /// + public const string DatabaseSampleIndexCodeBehindTemplate = @"using Microsoft.AspNetCore.Mvc; +using Senparc.Ncf.Service; +using Senparc.Ncf.Utility; +using Template_OrgName.Xncf.Template_XncfName.Domain.Services; +using System; +using System.Linq; +using System.Threading.Tasks; +using Template_OrgName.Xncf.Template_XncfName.Domain.Models.DatabaseModel.Dto; + +namespace Template_OrgName.Xncf.Template_XncfName.Areas.Template_XncfName.Pages +{ + public class DatabaseSampleIndex : Senparc.Ncf.AreaBase.Admin.AdminXncfModulePageModelBase + { + private readonly ColorService _colorService; + + public DatabaseSampleIndex(Lazy xncfModuleService, ColorService colorService) : base(xncfModuleService) + { + _colorService = colorService; + } + + public void OnGet() + { + } + + /// + /// 获取颜色列表(分页) + /// + /// 关键词 + /// 排序字段 + /// 页码 + /// 页大小 + /// + public async Task OnGetColorListAsync(string keyword, string orderField, int pageIndex, int pageSize) + { + try + { + // 调试信息 + System.Diagnostics.Debug.WriteLine($""ColorList API Called - PageIndex: {pageIndex}, PageSize: {pageSize}, OrderField: {orderField}""); + + var seh = new SenparcExpressionHelper(); + // 可以根据需要添加搜索条件 + // seh.ValueCompare.AndAlso(!string.IsNullOrEmpty(keyword), _ => _.Remark.Contains(keyword)); + var where = seh.BuildWhereExpression(); + var response = await _colorService.GetObjectListAsync(pageIndex, pageSize, where, orderField ?? ""Id desc""); + + // 调试信息 + System.Diagnostics.Debug.WriteLine($""Database Query Result - TotalCount: {response.TotalCount}, ItemCount: {response.Count()}""); + + var result = new + { + success = true, + message = ""数据获取成功"", + data = new { + totalCount = response.TotalCount, + pageIndex = response.PageIndex, + list = response.Select(_ => new + { + id = _.Id, + red = _.Red, + green = _.Green, + blue = _.Blue, + additionNote = _.AdditionNote, + addTime = _.AddTime, + lastUpdateTime = _.LastUpdateTime, + remark = _.Remark + }).ToList() + } + }; + + // 调试信息 + System.Diagnostics.Debug.WriteLine($""API Response - ListCount: {result.data.list.Count}""); + + return Ok(result); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($""ColorList API Error: {ex.Message}""); + return Ok(new { + success = false, + message = ""获取数据失败: "" + ex.Message, + totalCount = 0, + pageIndex = pageIndex, + list = new object[0] + }); + } + } + + /// + /// 创建新颜色 + /// + /// 创建颜色请求 + /// + public async Task OnPostCreateColorAsync([FromBody] CreateColorRequestDto request) + { + try + { + // 调试信息 + System.Diagnostics.Debug.WriteLine($""CreateColor API Called - Red: {request.Red}, Green: {request.Green}, Blue: {request.Blue}, AdditionNote: {request.AdditionNote}""); + + if (request == null) + { + return Ok(new { success = false, message = ""请求参数不能为空"" }); + } + + var color = new Color(request.Red, request.Green, request.Blue, request.AdditionNote); + await _colorService.SaveObjectAsync(color); + + return Ok(new { success = true, message = ""颜色创建成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.AddTime, color.LastUpdateTime } }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($""CreateColor API Error: {ex.Message}""); + return Ok(new { success = false, message = ""创建失败:"" + ex.Message }); + } + } + + /// + /// 更新颜色 + /// + /// 更新颜色请求 + /// + public async Task OnPostUpdateColorAsync([FromBody] UpdateColorRequestDto request) + { + try + { + // 调试信息 + System.Diagnostics.Debug.WriteLine($""UpdateColor API Called - Id: {request.Id}, Red: {request.Red}, Green: {request.Green}, Blue: {request.Blue}, AdditionNote: {request.AdditionNote}""); + + if (request == null) + { + return Ok(new { success = false, message = ""请求参数不能为空"" }); + } + + var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); + if (color == null) + { + return Ok(new { success = false, message = ""颜色不存在"" }); + } + + // 更新 + color.Update(request); + + await _colorService.SaveObjectAsync(color); + + return Ok(new { success = true, message = ""颜色更新成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.LastUpdateTime } }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($""UpdateColor API Error: {ex.Message}""); + return Ok(new { success = false, message = ""更新失败:"" + ex.Message }); + } + } + + /// + /// 删除颜色 + /// + /// 删除颜色请求 + /// + public async Task OnPostDeleteColorAsync([FromBody] DeleteColorRequestDto request) + { + try + { + // 调试信息 + System.Diagnostics.Debug.WriteLine($""DeleteColor API Called - Id: {request.Id}""); + + if (request == null) + { + return Ok(new { success = false, message = ""请求参数不能为空"" }); + } + + var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); + if (color == null) + { + return Ok(new { success = false, message = ""颜色不存在"" }); + } + + await _colorService.DeleteObjectAsync(color); + return Ok(new { success = true, message = ""颜色删除成功"" }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($""DeleteColor API Error: {ex.Message}""); + return Ok(new { success = false, message = ""删除失败:"" + ex.Message }); + } + } + + /// + /// 随机化指定颜色 + /// + /// 随机化颜色请求 + /// + public async Task OnPostRandomizeColorAsync([FromBody] RandomizeColorRequestDto request) + { + try + { + // 调试信息 + System.Diagnostics.Debug.WriteLine($""RandomizeColor API Called - Id: {request.Id}""); + + if (request == null) + { + return Ok(new { success = false, message = ""请求参数不能为空"" }); + } + + var color = await _colorService.GetObjectAsync(c => c.Id == request.Id); + if (color == null) + { + return Ok(new { success = false, message = ""颜色不存在"" }); + } + + color.Random(); + await _colorService.SaveObjectAsync(color); + + return Ok(new { success = true, message = ""颜色随机化成功"", data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.LastUpdateTime } }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($""RandomizeColor API Error: {ex.Message}""); + return Ok(new { success = false, message = ""随机化失败:"" + ex.Message }); + } + } + + /// + /// 获取颜色详情 + /// + /// 颜色ID + /// + public async Task OnGetColorDetailAsync(int id) + { + try + { + var color = await _colorService.GetObjectAsync(c => c.Id == id); + if (color == null) + { + return Ok(new { success = false, message = ""颜色不存在"" }); + } + + return Ok(new { success = true, data = new { color.Id, color.Red, color.Green, color.Blue, color.AdditionNote, color.AddTime, color.LastUpdateTime, color.Remark } }); + } + catch (Exception ex) + { + return Ok(new { success = false, message = ""获取失败:"" + ex.Message }); + } + } + } +} "; + + #endregion + + #region FRONTEND_SCRIPT Templates + + /// + /// 数据库示例索引页面JavaScript模板 + /// 类型: frontend_script + /// + public const string DatabaseSampleIndexJsTemplate = @"var app = new Vue({ + el: ""#app"", + data() { + return { + page: { + page: 1, + size: 10 + }, + tableLoading: true, + tableData: [], + showDebug: false, + addFormDialogVisible: false, + addForm: { + red: 128, + green: 128, + blue: 128, + additionNote: '' + }, + editFormDialogVisible: false, + editForm: { + id: 0, + red: 128, + green: 128, + blue: 128, + additionNote: '' + }, + total: 0, + addRules: { + red: [ + { required: true, message: '请设置红色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '红色值范围为0-255', trigger: 'change' } + ], + green: [ + { required: true, message: '请设置绿色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '绿色值范围为0-255', trigger: 'change' } + ], + blue: [ + { required: true, message: '请设置蓝色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '蓝色值范围为0-255', trigger: 'change' } + ] + }, + editRules: { + red: [ + { required: true, message: '请设置红色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '红色值范围为0-255', trigger: 'change' } + ], + green: [ + { required: true, message: '请设置绿色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '绿色值范围为0-255', trigger: 'change' } + ], + blue: [ + { required: true, message: '请设置蓝色值', trigger: 'change' }, + { type: 'number', min: 0, max: 255, message: '蓝色值范围为0-255', trigger: 'change' } + ] + } + } + }, + mounted() { + //wait page load + setTimeout(async () => { + await this.init(); + }, 100) + }, + methods: { + async init() { + await this.getDataList(); + }, + async handleSizeChange(val) { + this.page.size = val; + await this.getDataList(); + }, + async handleCurrentChange(val) { + this.page.page = val; + await this.getDataList(); + }, + async getDataList() { + this.tableLoading = true + await service.get('/Admin/Template_XncfName/DatabaseSampleIndex?handler=ColorList', { + params: { + pageIndex: this.page.page, + pageSize: this.page.size, + orderField: ""Id desc"", + keyword: """" + } + }) + .then(res => { + console.log('=== API Response Debug ==='); + console.log('Complete Response:', res); + console.log('Response Data:', res.data); + console.log('Response Data Type:', typeof res.data); + console.log('Has res.data.data?:', res.data && res.data.data); + console.log('Has res.data.data.list?:', res.data && res.data.data && res.data.data.list); + console.log('res.data.data.list value:', res.data && res.data.data ? res.data.data.list : 'nested data not found'); + console.log('=================='); + + // 尝试多种可能的数据结构 + let dataList = null; + let totalCount = 0; + let dataSource = ''; + + if (res.data && res.data.data && res.data.data.data && res.data.data.data.list) { + // NCF框架标准格式 + 新的API格式: {data: {data: {success, message, data: {list, totalCount}}}} + dataList = res.data.data.data.list; + totalCount = res.data.data.data.totalCount || 0; + dataSource = 'NCF标准格式: res.data.data.data.list'; + console.log('✅ 使用NCF标准格式: res.data.data.data.list'); + console.log('✅ List数据:', dataList); + console.log('✅ TotalCount:', totalCount); + } else if (res.data && res.data.data && res.data.data.list) { + // 简单格式: {data: {list, totalCount}} + dataList = res.data.data.list; + totalCount = res.data.data.totalCount || 0; + dataSource = '简单格式: res.data.data.list'; + console.log('✅ 使用简单格式: res.data.data.list'); + } else if (res.data && Array.isArray(res.data)) { + // 如果data直接是数组 + dataList = res.data; + totalCount = res.data.length; + dataSource = '数组格式: res.data (array)'; + console.log('✅ 使用数组格式: res.data (array)'); + } else if (res && res.list) { + // 如果list在顶层 + dataList = res.list; + totalCount = res.totalCount || 0; + dataSource = '顶层格式: res.list'; + console.log('✅ 使用顶层格式: res.list'); + } else { + console.error('❌ 无法识别的数据格式:', res); + console.log('🔍 尝试的路径:'); + console.log('- res.data.data.list:', res.data && res.data.data ? res.data.data.list : 'not found'); + console.log('- res.data.list:', res.data ? res.data.list : 'not found'); + console.log('- res.data (array):', res.data && Array.isArray(res.data) ? 'is array' : 'not array'); + console.log('- res.list:', res.list ? res.list : 'not found'); + dataList = []; + totalCount = 0; + dataSource = '无法识别格式'; + } + + console.log('🎯 Final dataList:', dataList); + console.log('🎯 Final totalCount:', totalCount); + console.log('🎯 Data source:', dataSource); + + // 数据赋值前的状态 + console.log('📋 赋值前 tableData:', this.tableData); + console.log('📋 赋值前 total:', this.total); + + this.tableData = dataList || []; + this.total = totalCount; + + // 数据赋值后的状态 + console.log('📋 赋值后 tableData:', this.tableData); + console.log('📋 赋值后 tableData.length:', this.tableData.length); + console.log('📋 赋值后 total:', this.total); + + // 强制Vue更新 + this.$forceUpdate(); + console.log('🔄 Vue已强制更新'); + + // 延迟检查数据是否正确绑定 + setTimeout(() => { + console.log('⏰ 延迟检查 tableData:', this.tableData); + console.log('⏰ 延迟检查 tableData.length:', this.tableData ? this.tableData.length : 'null'); + }, 100); + + this.tableLoading = false + }) + .catch(error => { + console.error('获取数据失败:', error); + this.tableLoading = false; + this.$message.error('获取数据失败: ' + (error.message || error)); + }); + }, + addColor() { + this.addFormDialogVisible = true; + }, + refreshList() { + this.getDataList(); + }, + async addColorSubmit() { + this.$refs.addForm.validate(async (valid) => { + if (valid) { + console.log('📤 发送创建请求:', { + red: this.addForm.red, + green: this.addForm.green, + blue: this.addForm.blue, + additionNote: this.addForm.additionNote + }); + + await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=CreateColor', { + red: this.addForm.red, + green: this.addForm.green, + blue: this.addForm.blue, + additionNote: this.addForm.additionNote + }, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(res => { + console.log('📥 创建响应:', res); + // 兼容NCF框架的嵌套响应格式 + const responseData = res.data.data || res.data; + this.$message({ + type: responseData.success ? 'success' : 'error', + message: responseData.message || '操作完成' + }); + if (responseData.success) { + this.getDataList() + this.clearAddForm() + this.addFormDialogVisible = false; + } + }) + .catch(error => { + console.error('创建失败:', error); + this.$message.error('创建失败'); + }); + } else { + return false; + } + }); + }, + clearAddForm() { + this.addForm = { + red: 128, + green: 128, + blue: 128, + additionNote: '' + }; + if (this.$refs.addForm) { + this.$refs.addForm.resetFields(); + } + }, + clearEditForm() { + this.editForm = { + id: 0, + red: 128, + green: 128, + blue: 128, + additionNote: '' + }; + if (this.$refs.editForm) { + this.$refs.editForm.resetFields(); + } + }, + async editColorSubmit() { + this.$refs.editForm.validate(async (valid) => { + if (valid) { + console.log('📤 发送更新请求:', { + id: this.editForm.id, + red: this.editForm.red, + green: this.editForm.green, + blue: this.editForm.blue, + additionNote: this.editForm.additionNote + }); + + await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=UpdateColor', { + id: this.editForm.id, + red: this.editForm.red, + green: this.editForm.green, + blue: this.editForm.blue, + additionNote: this.editForm.additionNote + }, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(res => { + console.log('📥 更新响应:', res); + // 兼容NCF框架的嵌套响应格式 + const responseData = res.data.data || res.data; + this.$message({ + type: responseData.success ? 'success' : 'error', + message: responseData.message || '操作完成' + }); + if (responseData.success) { + this.getDataList() + this.clearEditForm() + this.editFormDialogVisible = false; + } + }) + .catch(error => { + console.error('更新失败:', error); + this.$message.error('更新失败'); + }); + } else { + return false; + } + }); + }, + dateformatter(date) { + if (!date) return ''; + + try { + // 使用原生JavaScript格式化日期 + const d = new Date(date); + + // 检查日期是否有效 + if (isNaN(d.getTime())) { + return date; // 如果无法解析,返回原始值 + } + + // 格式化为 YYYY-MM-DD HH:mm:ss + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + const hours = String(d.getHours()).padStart(2, '0'); + const minutes = String(d.getMinutes()).padStart(2, '0'); + const seconds = String(d.getSeconds()).padStart(2, '0'); + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + } catch (error) { + console.warn('日期格式化错误:', error, '原始值:', date); + return date; // 如果格式化失败,返回原始值 + } + }, + editColor(row) { + this.editForm = { + id: row.id, + red: row.red, + green: row.green, + blue: row.blue, + additionNote: row.additionNote || '' + }; + this.editFormDialogVisible = true; + }, + deleteColor(row) { + this.$confirm('此操作将永久删除该颜色, 是否继续?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(async () => { + console.log('📤 发送删除请求:', { id: row.id }); + + await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=DeleteColor', { + id: row.id + }, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(res => { + console.log('📥 删除响应:', res); + // 兼容NCF框架的嵌套响应格式 + const responseData = res.data.data || res.data; + this.$message({ + type: responseData.success ? 'success' : 'error', + message: responseData.message || '操作完成' + }); + if (responseData.success) { + this.getDataList(); + } + }) + .catch(error => { + console.error('删除失败:', error); + this.$message.error('删除失败'); + }); + }).catch(() => { + this.$message({ + type: 'info', + message: '已取消删除' + }); + }); + }, + async randomizeColor(row) { + console.log('📤 发送随机化请求:', { id: row.id }); + + await service.post('/Admin/Template_XncfName/DatabaseSampleIndex?handler=RandomizeColor', { + id: row.id + }, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(res => { + console.log('📥 随机化响应:', res); + // 兼容NCF框架的嵌套响应格式 + const responseData = res.data.data || res.data; + this.$message({ + type: responseData.success ? 'success' : 'error', + message: responseData.message || '操作完成' + }); + if (responseData.success) { + this.getDataList(); + } + }) + .catch(error => { + console.error('随机化失败:', error); + this.$message.error('随机化失败'); + }); + }, + randomizeForm() { + this.addForm.red = Math.floor(Math.random() * 256); + this.addForm.green = Math.floor(Math.random() * 256); + this.addForm.blue = Math.floor(Math.random() * 256); + }, + randomizeEditForm() { + this.editForm.red = Math.floor(Math.random() * 256); + this.editForm.green = Math.floor(Math.random() * 256); + this.editForm.blue = Math.floor(Math.random() * 256); + }, + debugInfo() { + this.showDebug = !this.showDebug; + console.log('=== Vue Component Debug Info ==='); + console.log('Current tableData:', this.tableData); + console.log('tableData length:', this.tableData ? this.tableData.length : 'null/undefined'); + console.log('Total:', this.total); + console.log('Page:', this.page); + console.log('Table Loading:', this.tableLoading); + console.log('Show Debug:', this.showDebug); + console.log('Vue instance $el:', this.$el); + console.log('================================'); + + // 测试Vue响应性 + if (this.tableData && this.tableData.length === 0) { + console.log('测试:添加假数据'); + this.tableData = [ + {id: 999, red: 255, green: 0, blue: 0, addTime: new Date().toISOString(), lastUpdateTime: new Date().toISOString(), remark: 'test'} + ]; + this.total = 1; + setTimeout(() => { + console.log('2秒后清除假数据'); + this.tableData = []; + this.total = 0; + }, 2000); + } + } + } +}); "; + + #endregion + + #region FRONTEND_STYLE Templates + + /// + /// 数据库示例索引页面CSS模板 + /// 类型: frontend_style + /// + public const string DatabaseSampleIndexCssTemplate = @"/* 通用样式 */ +.d-flex{ + display: flex; +} +.justify-content-between{ + justify-content: space-between; +} +.align-items-center{ + align-items: center; +} + +/* 过滤器容器样式 */ +.filter-container { + margin-bottom: 20px; + padding: 10px 0; +} + +.filter-container .el-button { + margin-right: 10px; +} + +/* 颜色预览样式 */ +.color-preview { + width: 100%; + height: 40px; + border-radius: 4px; + border: 1px solid #dcdfe6; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 12px; + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); +} + +.color-preview-large { + width: 100%; + height: 80px; + border-radius: 8px; + border: 2px solid #dcdfe6; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 16px; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.7); + margin: 10px 0; + transition: all 0.3s ease; +} + +.color-preview-large:hover { + transform: scale(1.02); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); +} + +/* 分页容器样式 */ +.pagination-container { + margin-top: 20px; + text-align: center; +} + +/* 表格样式增强 */ +.el-table { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1); +} + +.el-table th { + background-color: #fafafa; + color: #333; + font-weight: 600; +} + +/* 颜色标签样式 */ +.el-tag { + min-width: 50px; + text-align: center; + font-weight: bold; + border: none !important; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); +} + +/* 对话框样式 */ +.el-dialog { + border-radius: 12px; + overflow: hidden; +} + +.el-dialog__header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 20px 20px 0 20px; +} + +.el-dialog__title { + color: white; + font-weight: 600; +} + +.el-dialog__body { + padding: 30px 20px; +} + +/* 滑块样式 */ +.el-slider { + margin: 20px 0; +} + +.el-slider__runway { + height: 6px; + background-color: #e4e7ed; + border-radius: 3px; +} + +.el-slider__button { + width: 20px; + height: 20px; + border: 2px solid #409eff; +} + +/* 按钮样式增强 */ +.el-button--mini { + padding: 5px 10px; + font-size: 12px; + border-radius: 4px; +} + +.el-button--primary { + background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%); + border: none; +} + +.el-button--success { + background: linear-gradient(135deg, #67c23a 0%, #5daf34 100%); + border: none; +} + +.el-button--warning { + background: linear-gradient(135deg, #e6a23c 0%, #cf9236 100%); + border: none; +} + +.el-button--danger { + background: linear-gradient(135deg, #f56c6c 0%, #f25c5c 100%); + border: none; +} + +.el-button--info { + background: linear-gradient(135deg, #909399 0%, #82848a 100%); + border: none; +} + +/* 表单项样式 */ +.el-form-item { + margin-bottom: 22px; +} + +.el-form-item__label { + font-weight: 600; + color: #333; +} + +/* 加载动画样式 */ +.el-loading-mask { + background-color: rgba(255, 255, 255, 0.9); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .filter-container { + text-align: center; + } + + .filter-container .el-button { + margin: 5px; + width: auto; + } + + .color-preview { + height: 30px; + font-size: 10px; + } + + .color-preview-large { + height: 60px; + font-size: 14px; + } +} + +/* 动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.el-table tbody tr { + animation: fadeIn 0.3s ease-out; +} + +/* 鼠标悬停效果 */ +.el-table tbody tr:hover { + background-color: #f5f7fa !important; + transition: background-color 0.3s ease; +} + +.el-button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0,0,0,0.15); + transition: all 0.3s ease; +} "; + + #endregion + #region Template Types + + /// + /// 模板类型常量 + /// + public static class TemplateTypes + { + public const string CODE = "code"; + public const string BACKENDTEMPLATE = "backend_template"; + public const string FRONTENDTEMPLATE = "frontend_template"; + public const string FRONTENDSCRIPT = "frontend_script"; + public const string FRONTENDSTYLE = "frontend_style"; + } + + #endregion + + #region File Information + + /// + /// 所有模板文件信息 + /// + public static readonly TemplateFileInfo[] AllTemplateFiles = new TemplateFileInfo[] + { + new TemplateFileInfo("RequestCode", "请求类代码", "code", "Request.cs", RequestCode), + new TemplateFileInfo("SenparcEntitiesTemplate", "Senparc实体类模板", "backend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Domain/Models/DatabaseModel/Template_XncfNameSenparcEntities.cs", SenparcEntitiesTemplate), + new TemplateFileInfo("ColorModelTemplate", "颜色模型模板", "backend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Domain/Models/DatabaseModel/Color.cs", ColorModelTemplate), + new TemplateFileInfo("ColorDtoTemplate", "颜色DTO模板", "backend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Domain/Models/DatabaseModel/Dto/ColorDto.cs", ColorDtoTemplate), + new TemplateFileInfo("ColorServiceTemplate", "颜色服务模板", "backend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Domain/Services/ColorService.cs", ColorServiceTemplate), + new TemplateFileInfo("DatabaseSampleIndexViewTemplate", "数据库示例索引页面视图模板", "frontend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Areas/Admin/Pages/Template_XncfName/DatabaseSampleIndex.cshtml", DatabaseSampleIndexViewTemplate), + new TemplateFileInfo("DatabaseSampleIndexCodeBehindTemplate", "数据库示例索引页面代码后置模板", "frontend_template", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Areas/Admin/Pages/Template_XncfName/DatabaseSampleIndex.cshtml.cs", DatabaseSampleIndexCodeBehindTemplate), + new TemplateFileInfo("DatabaseSampleIndexJsTemplate", "数据库示例索引页面JavaScript模板", "frontend_script", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/wwwroot/js/Admin/Template_XncfName/databaseSampleIndex.js", DatabaseSampleIndexJsTemplate), + new TemplateFileInfo("DatabaseSampleIndexCssTemplate", "数据库示例索引页面CSS模板", "frontend_style", "../../../../tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/wwwroot/css/Admin/Template_XncfName/databaseSampleIndex.css", DatabaseSampleIndexCssTemplate), + }; + + /// + /// 模板文件信息结构 + /// + public record TemplateFileInfo(string Name, string Description, string Type, string Path, string Content); + + #endregion + + #region Helper Methods + + /// + /// 根据类型获取模板文件 + /// + public static TemplateFileInfo[] GetTemplatesByType(string templateType) + { + return AllTemplateFiles.Where(f => f.Type == templateType).ToArray(); + } + + /// + /// 根据名称获取模板内容 + /// + public static string? GetTemplateContent(string templateName) + { + return AllTemplateFiles.FirstOrDefault(f => f.Name == templateName)?.Content; + } + + /// + /// 获取所有模板类型 + /// + public static string[] GetAllTemplateTypes() + { + return AllTemplateFiles.Select(f => f.Type).Distinct().ToArray(); + } + + #endregion + } +} diff --git a/src/Extensions/System/Senparc.Xncf.XncfModuleManager/[5950]Senparc.Xncf.XncfModuleManager.csproj b/src/Extensions/System/Senparc.Xncf.XncfModuleManager/[5950]Senparc.Xncf.XncfModuleManager.csproj index 7d6c9e23d..006601c4c 100644 --- a/src/Extensions/System/Senparc.Xncf.XncfModuleManager/[5950]Senparc.Xncf.XncfModuleManager.csproj +++ b/src/Extensions/System/Senparc.Xncf.XncfModuleManager/[5950]Senparc.Xncf.XncfModuleManager.csproj @@ -1,7 +1,7 @@ net8.0 - 0.13.16-preview.1 + 0.13.17-preview.1 Senparc.Xncf.XncfModuleManager Senparc.Xncf.XncfModuleManager true @@ -41,6 +41,7 @@ [2025-06-20] v0.13.13-preview1 Add MCP functions in XncfRegisterBase class [2025-11-01] update basic libraries, including Senparc.AI + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease diff --git a/tools/NcfSimulatedSite/Senparc.Web/appsettings.json b/tools/NcfSimulatedSite/Senparc.Web/appsettings.json index 28ec5c0d4..80a40ebc3 100644 --- a/tools/NcfSimulatedSite/Senparc.Web/appsettings.json +++ b/tools/NcfSimulatedSite/Senparc.Web/appsettings.json @@ -53,14 +53,14 @@ //Senparc.AI 设置 "SenparcAiSetting": { "IsDebug": true, - "AiPlatform": "UnSet", //注意修改为自己平台对应的枚举值 + "AiPlatform": "NeuCharAI", //注意修改为自己平台对应的枚举值 "VectorDB": { "Type": "Memory", "ConnectionString": "" }, "NeuCharAIKeys": { "ApiKey": "", //在 https://www.neuchar.com/Developer/AiApp 申请 - "NeuCharEndpoint": "https://www.neuchar.com/", //查看 ApiKey 时可看到 DeveloperId + "NeuCharEndpoint": "https://www.neuchar.com/2", //查看 ApiKey 时可看到 DeveloperId "ModelName": { "Chat": "gpt-35-turbo", "Embedding": "text-embedding-ada-002", diff --git a/tools/NcfSimulatedSite/Senparc.Xncf.Accounts/Senparc.Xncf.Accounts.csproj b/tools/NcfSimulatedSite/Senparc.Xncf.Accounts/Senparc.Xncf.Accounts.csproj index d4585e748..13c144d96 100644 --- a/tools/NcfSimulatedSite/Senparc.Xncf.Accounts/Senparc.Xncf.Accounts.csproj +++ b/tools/NcfSimulatedSite/Senparc.Xncf.Accounts/Senparc.Xncf.Accounts.csproj @@ -1,7 +1,7 @@  net8.0 - 0.1 + 0.2 Senparc.Xncf.Accounts Senparc.Xncf.Accounts true @@ -18,6 +18,7 @@ https://sdk.weixin.senparc.com/Images/logo-square-ncf.jpg--> v0.1 创世 + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease;Debug-Dapr;NcfDebugForPromptRange;ModifyPublish diff --git a/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Template_OrgName.Xncf.Template_XncfName.csproj b/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Template_OrgName.Xncf.Template_XncfName.csproj index 005297ef3..96f218bd0 100644 --- a/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Template_OrgName.Xncf.Template_XncfName.csproj +++ b/tools/NcfSimulatedSite/Template_OrgName.Xncf.Template_XncfName/Template_OrgName.Xncf.Template_XncfName.csproj @@ -1,7 +1,7 @@ net8.0 - 1.0.0 + 1.0.1 Template_OrgName.Xncf.Template_XncfName Template_OrgName.Xncf.Template_XncfName true @@ -19,6 +19,7 @@ icon.jpg v1.0.0 创世 + [2026-04-24] Update FunctionRender request templates for simplified values and SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease From f54675880fb50c1b0e0e9527ce6cbb78a7a7512d Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Fri, 24 Apr 2026 21:01:40 +0800 Subject: [PATCH 16/19] feat: Update version to 0.35.8-preview.1 and add release note for FunctionRender parameter simplification --- .../Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.csproj b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.csproj index dff05ddb5..2fe0bde3f 100644 --- a/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.csproj +++ b/src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder.csproj @@ -1,7 +1,7 @@ net8.0 - 0.35.7-preview.1 + 0.35.8-preview.1 latest Senparc.Xncf.XncfBuilder Senparc.Xncf.XncfBuilder @@ -75,6 +75,7 @@ [2025-06-28] v0.35.3-preview.10 Add dynamic XNCF Template dynamic generator, support for dotnet CLI and Visual Studio together [2025-11-01] update basic libraries, including Senparc.AI [2026-01-07] v0.35.6-preview.1 Add NcfSourcePackage project path to DatabaseMigrationRequest loading method + [2026-04-24] Simplify FunctionRender request parameters and keep SelectionList UI metadata compatibility https://github.com/NeuCharFramework/NcfPackageSources Debug;Release;Test;TemplateRelease From d443dc1fe3202007f97184a28816ffc5ac3e414c Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Sat, 25 Apr 2026 16:28:34 +0800 Subject: [PATCH 17/19] feat: Add debug logging for loaded functions in AdminChatAiService and improve function parameter handling --- .../OHS/Local/PL/MyFunctionRequest.cs | 50 ++++----- .../Domain/Services/AdminChatAiService.cs | 102 ++++++++++++++++++ 2 files changed, 127 insertions(+), 25 deletions(-) diff --git a/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/PL/MyFunctionRequest.cs b/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/PL/MyFunctionRequest.cs index 3906e54ce..98ca315ab 100644 --- a/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/PL/MyFunctionRequest.cs +++ b/src/Extensions/Senparc.Xncf.SenMapic/OHS/Local/PL/MyFunctionRequest.cs @@ -7,22 +7,22 @@ namespace Senparc.Xncf.SenMapic.OHS.Local.PL { - public class MyFunction_SenMapicRequest: FunctionAppRequestBase -{ - [Required] - [Description("网址||请输入要爬取的网址")] -public string Url { get; set; } + public class MyFunction_SenMapicRequest : FunctionAppRequestBase + { + [Required] + [Description("网址||请输入要爬取的网址")] + public string Url { get; set; } -[Required] -[Description("深度||请输入最大要爬取的深度")] -public int Deepth { get; set; } + [Required] + [Description("深度||请输入最大要爬取的深度")] + public int Deepth { get; set; } -[Required] -[Description("网页数量||请输入要爬取的最大数量")] -public int PageNumber { get; set; } + [Required] + [Description("网页数量||请输入要爬取的最大数量")] + public int PageNumber { get; set; } -} - public class MyFunction_CaculateRequest: FunctionAppRequestBase + } + public class MyFunction_CaculateRequest : FunctionAppRequestBase { [Required] [MaxLength(50)] @@ -38,24 +38,24 @@ public class MyFunction_CaculateRequest: FunctionAppRequestBase [Description("数字||数字2")] public int Number2 { get; set; } - [Description("运算符||")]//下拉列表 - [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] - public string Operator { get; set; } + [Description("运算符||只能在以下选项中选择:+ - × ")]//下拉列表 + [FunctionParameterUi(ParameterType.DropDownList, nameof(OperatorOptions))] + public string Operator { get; set; } - [JsonIgnore] - public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { - new SelectionItem("+","加法","数字1 + 数字2",false), - new SelectionItem("-","减法","数字1 - 数字2",true), + [JsonIgnore] + public SelectionList OperatorOptions { get; set; } = new SelectionList(SelectionType.DropDownList, new[] { + new SelectionItem("+","加法","数字1 + 数字2",true), + new SelectionItem("-","减法","数字1 - 数字2",false), new SelectionItem("×","乘法","数字1 × 数字2",false), new SelectionItem("÷","除法","数字1 ÷ 数字2",false) }); - [Description("计算平方||")]//多选框 - [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] - public string[] Power { get; set; } + [Description("计算平方||如果传入,只能在以下选项中选择:2 3")]//多选框 + [FunctionParameterUi(ParameterType.CheckBoxList, nameof(PowerOptions))] + public string[] Power { get; set; } - [JsonIgnore] - public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { + [JsonIgnore] + public SelectionList PowerOptions { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] { new SelectionItem("2","平方","计算上述结果之后再计算平方",false), new SelectionItem("3","三次方","计算上述结果之后再计算三次方",false) }); diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs index 403ed3adf..e8d280d7d 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs @@ -10,6 +10,8 @@ using Senparc.Ncf.Core.Exceptions; using Senparc.Ncf.Core.AppServices; using Senparc.Ncf.XncfBase; +using Senparc.Ncf.XncfBase.Functions; +using Senparc.Ncf.XncfBase.FunctionRenders; using System; using System.Collections.Generic; using System.Globalization; @@ -58,6 +60,8 @@ public AdminChatAiService( /// 返回回复文本与模型标识。 public async Task<(string response, string modelIdentifier)> GenerateResponseAsync(int sessionId, int userId, string userMessage) { + var showLoadedFunctionsInConsole = true;//是否输出 function 的 schema 信息到控制台,便于调试和验证 Function Calling 功能是否正确加载了函数 + var setting = Senparc.AI.Config.SenparcAiSetting as SenparcAiSetting; if (setting == null) { @@ -106,6 +110,7 @@ public AdminChatAiService( var importedFunctionCount = 0; var importedFunctionSignatures = new List(); + var loadedFunctionDebugLines = new List(); foreach (var pluginGroup in functionPluginGroups) { @@ -126,6 +131,8 @@ public AdminChatAiService( { try { + var functionParameters = await FunctionHelper.GetFunctionParameterInfoAsync(_serviceProvider, functionBag); + var options = new KernelFunctionFromMethodOptions { FunctionName = functionBag.MethodInfo.Name, @@ -135,6 +142,7 @@ public AdminChatAiService( var kernelFunction = KernelFunctionFactory.CreateFromMethod(functionBag.MethodInfo, plugin, options); kernelFunctions.Add(kernelFunction); importedFunctionSignatures.Add($"{pluginName}.{functionBag.MethodInfo.Name}({functionBag.FunctionRenderAttribute?.Description ?? "N/A"})"); + loadedFunctionDebugLines.AddRange(BuildFunctionDebugLines(pluginName, functionBag, functionParameters)); } catch (Exception ex) { @@ -170,6 +178,13 @@ public AdminChatAiService( moduleUids.Count, string.Join(",", moduleUids)); } + else + { + if (showLoadedFunctionsInConsole) + { + WriteLoadedFunctionsToConsole(sessionId, userId, loadedFunctionDebugLines); + } + } _logger.LogInformation( "AdminChat FunctionCalling 插件加载完成:SessionId={SessionId}, UserId={UserId}, ModuleCount={ModuleCount}, Modules={Modules}, Plugins={Plugins}, Functions={Functions}, FunctionList={FunctionList}", @@ -225,6 +240,93 @@ private static string ComputeShortHash(string input, int length) return hex.Length > length ? hex.Substring(0, length) : hex; } + private static void WriteLoadedFunctionsToConsole(int sessionId, int userId, List loadedFunctionDebugLines) + { + if (loadedFunctionDebugLines == null || loadedFunctionDebugLines.Count == 0) + { + return; + } + + Console.WriteLine($"[AdminChat Functions] SessionId={sessionId}, UserId={userId}, LoadedFunctions={loadedFunctionDebugLines.Count(line => line.StartsWith("- Function:", StringComparison.Ordinal))}"); + foreach (var line in loadedFunctionDebugLines) + { + Console.WriteLine(line); + } + } + + private static List BuildFunctionDebugLines(string pluginName, FunctionRenderBag functionBag, List functionParameters) + { + var lines = new List + { + $"- Function: {pluginName}.{functionBag.MethodInfo.Name}", + $" Description: {functionBag.FunctionRenderAttribute?.Description ?? "(none)"}", + $" RequestType: {functionBag.FunctionParameterType?.FullName ?? "(none)"}" + }; + + if (functionParameters == null || functionParameters.Count == 0) + { + lines.Add(" Parameters: (none)"); + return lines; + } + + lines.Add($" Parameters: {functionParameters.Count}"); + foreach (var parameter in functionParameters) + { + lines.Add($" - {parameter.Name}: type={parameter.SystemType}, required={parameter.IsRequired}, ui={parameter.ParameterType}, title={FormatInlineValue(parameter.Title)}, description={FormatInlineValue(parameter.Description)}, default={FormatParameterValue(parameter.Value)}, maxLength={parameter.MaxLength}, filterable={parameter.Filterable}, allowCreate={parameter.AllowCreate}"); + + var selectionItems = parameter.SelectionList?.Items ?? new List(); + if (selectionItems.Count == 0) + { + continue; + } + + lines.Add($" Options({selectionItems.Count}): {string.Join(" | ", selectionItems.Select(item => FormatSelectionItem(parameter.SelectionList, item)))}"); + } + + return lines; + } + + private static string FormatSelectionItem(SelectionList selectionList, SelectionItem item) + { + if (item == null) + { + return "(null)"; + } + + var currentSelected = selectionList?.IsSelected(item.Value) ?? false; + return $"text={FormatInlineValue(item.Text)}, value={FormatInlineValue(item.Value)}, defaultSelected={item.DefaultSelected}, currentSelected={currentSelected}, note={FormatInlineValue(item.Note)}"; + } + + private static string FormatParameterValue(object value) + { + if (value == null) + { + return "(null)"; + } + + if (value is string stringValue) + { + return FormatInlineValue(stringValue); + } + + if (value is IEnumerable stringValues) + { + return $"[{string.Join(", ", stringValues.Select(FormatInlineValue))}]"; + } + + return FormatInlineValue(value.ToString()); + } + + private static string FormatInlineValue(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return "(empty)"; + } + + return value.Replace("\r", " ").Replace("\n", " ").Trim(); + } + private static string BuildSystemMessage(List modules) { var sb = new StringBuilder(); From de0e6167f142a1ceb584eb8324cd599e0a3fed00 Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Sat, 25 Apr 2026 16:59:36 +0800 Subject: [PATCH 18/19] refactor: Simplify function debug line generation and enhance parameter handling in AdminChatAiService --- .../Domain/Services/AdminChatAiService.cs | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs index e8d280d7d..62c65dca0 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs @@ -10,7 +10,6 @@ using Senparc.Ncf.Core.Exceptions; using Senparc.Ncf.Core.AppServices; using Senparc.Ncf.XncfBase; -using Senparc.Ncf.XncfBase.Functions; using Senparc.Ncf.XncfBase.FunctionRenders; using System; using System.Collections.Generic; @@ -131,8 +130,6 @@ public AdminChatAiService( { try { - var functionParameters = await FunctionHelper.GetFunctionParameterInfoAsync(_serviceProvider, functionBag); - var options = new KernelFunctionFromMethodOptions { FunctionName = functionBag.MethodInfo.Name, @@ -141,8 +138,6 @@ public AdminChatAiService( var kernelFunction = KernelFunctionFactory.CreateFromMethod(functionBag.MethodInfo, plugin, options); kernelFunctions.Add(kernelFunction); - importedFunctionSignatures.Add($"{pluginName}.{functionBag.MethodInfo.Name}({functionBag.FunctionRenderAttribute?.Description ?? "N/A"})"); - loadedFunctionDebugLines.AddRange(BuildFunctionDebugLines(pluginName, functionBag, functionParameters)); } catch (Exception ex) { @@ -159,9 +154,11 @@ public AdminChatAiService( continue; } - iWantToRun.Kernel.Plugins.AddFromFunctions(pluginName, kernelFunctions); + var addedPlugin = iWantToRun.Kernel.Plugins.AddFromFunctions(pluginName, kernelFunctions); + importedFunctionSignatures.AddRange(addedPlugin.Select(kernelFunction => $"{kernelFunction.Metadata.PluginName}.{kernelFunction.Metadata.Name}({kernelFunction.Metadata.Description ?? "N/A"})")); + loadedFunctionDebugLines.AddRange(BuildKernelPluginDebugLines(addedPlugin)); importedPluginNames.Add(pluginName); - importedFunctionCount += kernelFunctions.Count; + importedFunctionCount += addedPlugin.Count(); } catch (Exception ex) { @@ -254,13 +251,27 @@ private static void WriteLoadedFunctionsToConsole(int sessionId, int userId, Lis } } - private static List BuildFunctionDebugLines(string pluginName, FunctionRenderBag functionBag, List functionParameters) + private static List BuildKernelPluginDebugLines(KernelPlugin kernelPlugin) { + var lines = new List(); + foreach (var kernelFunction in kernelPlugin) + { + lines.AddRange(BuildKernelFunctionDebugLines(kernelFunction)); + } + + return lines; + } + + private static List BuildKernelFunctionDebugLines(KernelFunction kernelFunction) + { + var metadata = kernelFunction.Metadata; + var functionParameters = metadata.Parameters?.ToList() ?? new List(); var lines = new List { - $"- Function: {pluginName}.{functionBag.MethodInfo.Name}", - $" Description: {functionBag.FunctionRenderAttribute?.Description ?? "(none)"}", - $" RequestType: {functionBag.FunctionParameterType?.FullName ?? "(none)"}" + $"- Function: {metadata.PluginName}.{metadata.Name}", + $" Description: {metadata.Description ?? "(none)"}", + $" ReturnType: {metadata.ReturnParameter.ParameterType?.FullName ?? "(none)"}", + $" ReturnSchema: {FormatSchema(metadata.ReturnParameter.Schema)}" }; if (functionParameters == null || functionParameters.Count == 0) @@ -272,31 +283,12 @@ private static List BuildFunctionDebugLines(string pluginName, FunctionR lines.Add($" Parameters: {functionParameters.Count}"); foreach (var parameter in functionParameters) { - lines.Add($" - {parameter.Name}: type={parameter.SystemType}, required={parameter.IsRequired}, ui={parameter.ParameterType}, title={FormatInlineValue(parameter.Title)}, description={FormatInlineValue(parameter.Description)}, default={FormatParameterValue(parameter.Value)}, maxLength={parameter.MaxLength}, filterable={parameter.Filterable}, allowCreate={parameter.AllowCreate}"); - - var selectionItems = parameter.SelectionList?.Items ?? new List(); - if (selectionItems.Count == 0) - { - continue; - } - - lines.Add($" Options({selectionItems.Count}): {string.Join(" | ", selectionItems.Select(item => FormatSelectionItem(parameter.SelectionList, item)))}"); + lines.Add($" - {parameter.Name}: type={parameter.ParameterType?.FullName ?? "(none)"}, required={parameter.IsRequired}, description={FormatInlineValue(parameter.Description)}, default={FormatParameterValue(parameter.DefaultValue)}, schema={FormatSchema(parameter.Schema)}"); } return lines; } - private static string FormatSelectionItem(SelectionList selectionList, SelectionItem item) - { - if (item == null) - { - return "(null)"; - } - - var currentSelected = selectionList?.IsSelected(item.Value) ?? false; - return $"text={FormatInlineValue(item.Text)}, value={FormatInlineValue(item.Value)}, defaultSelected={item.DefaultSelected}, currentSelected={currentSelected}, note={FormatInlineValue(item.Note)}"; - } - private static string FormatParameterValue(object value) { if (value == null) @@ -317,6 +309,11 @@ private static string FormatParameterValue(object value) return FormatInlineValue(value.ToString()); } + private static string FormatSchema(KernelJsonSchema schema) + { + return schema == null ? "(none)" : FormatInlineValue(schema.ToString()); + } + private static string FormatInlineValue(string value) { if (string.IsNullOrWhiteSpace(value)) From b89a9a1c94cbb91871dfb063feae76f0f2a6762c Mon Sep 17 00:00:00 2001 From: Jeffrey Su Date: Sun, 26 Apr 2026 08:42:38 +0800 Subject: [PATCH 19/19] feat: Implement AI model selection and loading in AdminChat, enhancing user experience with dynamic options --- .../Areas/Admin/Pages/AdminChat/Chat.cshtml | 41 +++++++++ .../Areas/Admin/Pages/Index.cshtml.cs | 24 +++++ .../Admin/Pages/Shared/_MenuPartial.cshtml | 1 + .../DatabaseModel/Dto/AdminChatMessageDto.cs | 5 ++ .../DatabaseModel/Dto/AdminChatSessionDto.cs | 5 ++ .../Domain/Services/AdminChatAiService.cs | 84 +++++++++++++++--- .../Local/AppService/AdminChatAppService.cs | 87 ++++++++++++++++++- .../wwwroot/js/Admin/Pages/AdminChat/Chat.js | 36 ++++++++ .../Pages/AdminChat/ChatLauncherMixin.js | 57 ++++++++++++ 9 files changed, 325 insertions(+), 15 deletions(-) diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/AdminChat/Chat.cshtml b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/AdminChat/Chat.cshtml index 1d992f477..b246abf65 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/AdminChat/Chat.cshtml +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/AdminChat/Chat.cshtml @@ -70,6 +70,24 @@ {{ currentSessionTitle || 'AI 智能助手' }}
+ + +
+ {{ item.name }} + {{ item.description }} +
+
+
+
+
+ AI 算力 +
+ + +
+ {{ item.name }} + {{ item.description }} +
+
+
+
+ + z.ParentId == null && + (string.Equals(z.MenuName, "系统管理", StringComparison.OrdinalIgnoreCase) + || string.Equals(z.MenuName, "System Management", StringComparison.OrdinalIgnoreCase))) + ?? dest.FirstOrDefault(z => + z.ParentId == null && + dest.Any(child => child.ParentId == z.Id && string.Equals(child.MenuName, "管理员管理", StringComparison.OrdinalIgnoreCase))); + + var adminChatParentId = systemManagementMenu?.Id; + var hasAdminChatMenu = dest.Any(z => string.Equals(z.Url, "/Admin/AdminChat/Chat", StringComparison.OrdinalIgnoreCase)); + + if (!hasAdminChatMenu) + { + dest.Insert(0,new SysMenuDto() + { + MenuName = "AI 智能助手", + Url = "/Admin/AdminChat/Chat", + Icon = "fa fa-comments-o", + Id = (index++).ToString(), + ParentId = adminChatParentId + }); + } + GetSysMenuTreesRecursive(dest, sysMenuTrees, null); return sysMenuTrees; } diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/Shared/_MenuPartial.cshtml b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/Shared/_MenuPartial.cshtml index 1693ad088..881a6ef3a 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/Shared/_MenuPartial.cshtml +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Areas/Admin/Pages/Shared/_MenuPartial.cshtml @@ -23,6 +23,7 @@ { return "current-page-xncf"; } + return ""; } diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Models/DatabaseModel/Dto/AdminChatMessageDto.cs b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Models/DatabaseModel/Dto/AdminChatMessageDto.cs index 2ec5eeab4..4a07f466f 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Models/DatabaseModel/Dto/AdminChatMessageDto.cs +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Models/DatabaseModel/Dto/AdminChatMessageDto.cs @@ -77,6 +77,11 @@ public class ChatMessageInputDto [Required] public int SessionId { get; set; } + /// + /// 选中的 AIModelId,0 表示系统级 SenparcAiSetting + /// + public int AiModelId { get; set; } + /// /// 消息内容 /// diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Models/DatabaseModel/Dto/AdminChatSessionDto.cs b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Models/DatabaseModel/Dto/AdminChatSessionDto.cs index cdca4063a..17a01dc4f 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Models/DatabaseModel/Dto/AdminChatSessionDto.cs +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Models/DatabaseModel/Dto/AdminChatSessionDto.cs @@ -80,6 +80,11 @@ public class CreateChatSessionInputDto /// public string InitialMessage { get; set; } + /// + /// 选中的 AIModelId,0 表示系统级 SenparcAiSetting + /// + public int AiModelId { get; set; } + /// /// 关联的模块 UID 列表 /// diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs index 62c65dca0..5224aaf6f 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/Domain/Services/AdminChatAiService.cs @@ -10,6 +10,8 @@ using Senparc.Ncf.Core.Exceptions; using Senparc.Ncf.Core.AppServices; using Senparc.Ncf.XncfBase; +using Senparc.Xncf.AIKernel.Domain.Models.DatabaseModel.Dto; +using Senparc.Xncf.AIKernel.Domain.Services; using Senparc.Ncf.XncfBase.FunctionRenders; using System; using System.Collections.Generic; @@ -22,7 +24,8 @@ namespace Senparc.Areas.Admin.Domain.Services { /// - /// AdminChatAiService:管理后台聊天 AI 调用服务(直接使用 appsettings 的 SenparcAiSetting) + /// AdminChatAiService:管理后台聊天 AI 调用服务。 + /// 默认使用 appsettings 中的 SenparcAiSetting,也支持按请求切换到 AIKernel 中配置的 Chat 模型。 /// public class AdminChatAiService { @@ -56,21 +59,13 @@ public AdminChatAiService( /// 会话 Id。 /// 用户 Id。 /// 用户输入消息。 + /// 可选 AIModelId,0 表示系统级 SenparcAiSetting。 /// 返回回复文本与模型标识。 - public async Task<(string response, string modelIdentifier)> GenerateResponseAsync(int sessionId, int userId, string userMessage) + public async Task<(string response, string modelIdentifier)> GenerateResponseAsync(int sessionId, int userId, string userMessage, int aiModelId = 0) { var showLoadedFunctionsInConsole = true;//是否输出 function 的 schema 信息到控制台,便于调试和验证 Function Calling 功能是否正确加载了函数 - var setting = Senparc.AI.Config.SenparcAiSetting as SenparcAiSetting; - if (setting == null) - { - throw new NcfExceptionBase("未读取到 SenparcAiSetting,请检查 appsettings.json 配置。"); - } - - if (setting.AiPlatform == AiPlatform.UnSet) - { - throw new NcfExceptionBase("SenparcAiSetting.AiPlatform 仍为 UnSet,请先在 appsettings.json 中设置可用平台。"); - } + var (setting, modelIdentifier) = await ResolveChatSettingAsync(aiModelId); var (messages, _) = await _messageService.GetSessionMessagesAsync(sessionId); var modules = await _sessionModuleService.GetSessionModulesAsync(sessionId); @@ -209,7 +204,70 @@ public AdminChatAiService( result = "抱歉,我暂时没有生成有效回复,请稍后再试。"; } - return (result, ResolveModelIdentifier(setting)); + return (result, modelIdentifier); + } + + private async Task<(SenparcAiSetting setting, string modelIdentifier)> ResolveChatSettingAsync(int aiModelId) + { + var defaultSetting = Senparc.AI.Config.SenparcAiSetting as SenparcAiSetting; + if (defaultSetting == null) + { + throw new NcfExceptionBase("未读取到 SenparcAiSetting,请检查 appsettings.json 配置。"); + } + + if (defaultSetting.AiPlatform == AiPlatform.UnSet) + { + throw new NcfExceptionBase("SenparcAiSetting.AiPlatform 仍为 UnSet,请先在 appsettings.json 中设置可用平台。"); + } + + if (aiModelId <= 0) + { + return (defaultSetting, ResolveModelIdentifier(defaultSetting)); + } + + if (!await IsAiKernelAvailableAsync()) + { + throw new NcfExceptionBase("当前系统未安装或未启用 AIKernel 模块,无法切换到指定 AI 模型。"); + } + + var aiModelService = _serviceProvider.GetService(typeof(AIModelService)) as AIModelService; + if (aiModelService == null) + { + throw new NcfExceptionBase("未能解析 AIModelService,无法加载指定 AI 模型。"); + } + + var aiModel = await aiModelService.GetObjectAsync(z => z.Id == aiModelId); + if (aiModel == null) + { + throw new NcfExceptionBase($"当前选择的 AI 模型不存在:{aiModelId}"); + } + + if (aiModel.ConfigModelType != Senparc.Xncf.AIKernel.Domain.Models.ConfigModelType.Chat) + { + throw new NcfExceptionBase($"当前选择的 AI 模型不是 Chat 类型:{aiModelId}"); + } + + var aiModelDto = aiModelService.Mapper.Map(aiModel); + var selectedSetting = aiModelService.BuildSenparcAiSetting(aiModelDto); + var selectedModelIdentifier = !string.IsNullOrWhiteSpace(aiModelDto.Alias) + ? $"{aiModelDto.Alias} [{ResolveModelIdentifier(selectedSetting)}]" + : ResolveModelIdentifier(selectedSetting); + + return (selectedSetting, selectedModelIdentifier); + } + + private async Task IsAiKernelAvailableAsync() + { + var aiKernelRegister = XncfRegisterManager.RegisterList.FirstOrDefault(z => + string.Equals(z.Name, "Senparc.Xncf.AIKernel", StringComparison.OrdinalIgnoreCase)); + + if (aiKernelRegister == null) + { + return false; + } + + var registerManager = new XncfRegisterManager(_serviceProvider); + return await registerManager.CheckXncfAvailable(aiKernelRegister); } private static string BuildFunctionPluginName(Type pluginType) diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/OHS/Local/AppService/AdminChatAppService.cs b/tools/NcfSimulatedSite/Senparc.Areas.Admin/OHS/Local/AppService/AdminChatAppService.cs index 6a8c54f87..2fe3049b2 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/OHS/Local/AppService/AdminChatAppService.cs +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/OHS/Local/AppService/AdminChatAppService.cs @@ -10,6 +10,7 @@ using Senparc.Ncf.Core.Models; using Senparc.Ncf.XncfBase; using Microsoft.Extensions.Localization; +using Senparc.Xncf.AIKernel.Domain.Services; using System; using System.Collections.Generic; using System.Linq; @@ -94,7 +95,7 @@ await _messageService.AddMessageAsync( ChatMessageRoleType.User, request.InitialMessage); - var (aiResponse, modelIdentifier) = await _chatAiService.GenerateResponseAsync(session.Id, userId, request.InitialMessage); + var (aiResponse, modelIdentifier) = await _chatAiService.GenerateResponseAsync(session.Id, userId, request.InitialMessage, request.AiModelId); await _messageService.AddMessageAsync( session.Id, ChatMessageRoleType.Assistant, @@ -226,7 +227,7 @@ public async Task> SendMessageAsync([FromBo await _sessionService.UpdateLastMessageTimeAsync(request.SessionId); - var (aiResponse, modelIdentifier) = await _chatAiService.GenerateResponseAsync(request.SessionId, userId, request.Content); + var (aiResponse, modelIdentifier) = await _chatAiService.GenerateResponseAsync(request.SessionId, userId, request.Content, request.AiModelId); var assistantMessage = await _messageService.AddMessageAsync( request.SessionId, @@ -392,6 +393,68 @@ public async Task> GetSessionModulesA }); } + /// + /// 获取 Chat 可选 AI 模型列表。 + /// + [ApiBind(ApiRequestMethod = ApiRequestMethod.Get)] + public async Task> GetAiModelOptionsAsync() + { + return await this.GetResponseAsync, GetAiModelOptionsResponse>(async (response, logger) => + { + var optionList = new List + { + new AiModelOptionDto + { + Id = 0, + Name = "系统级 SenparcAiSetting", + Description = "使用 appsettings.json 中当前生效的默认 Chat 配置", + IsDefault = true + } + }; + + var aiKernelRegister = XncfRegisterManager.RegisterList.FirstOrDefault(z => + string.Equals(z.Name, "Senparc.Xncf.AIKernel", StringComparison.OrdinalIgnoreCase)); + + var aiKernelAvailable = false; + if (aiKernelRegister != null) + { + var registerManager = new XncfRegisterManager(ServiceProvider); + aiKernelAvailable = await registerManager.CheckXncfAvailable(aiKernelRegister); + } + + if (aiKernelAvailable) + { + var aiModelService = ServiceProvider.GetService(typeof(AIModelService)) as AIModelService; + if (aiModelService != null) + { + var aiModels = await aiModelService.GetFullListAsync(z => + z.Show && z.ConfigModelType == Senparc.Xncf.AIKernel.Domain.Models.ConfigModelType.Chat); + + optionList.AddRange(aiModels + .OrderByDescending(z => z.Show) + .ThenBy(z => z.Alias) + .Select(z => new AiModelOptionDto + { + Id = z.Id, + Name = !string.IsNullOrWhiteSpace(z.Alias) + ? $"{z.Alias} ({z.ModelId})" + : $"{z.DeploymentName} ({z.ModelId})", + Description = string.IsNullOrWhiteSpace(z.Note) + ? $"{z.AiPlatform} | {z.Endpoint}" + : z.Note, + IsDefault = false + })); + } + } + + return new GetAiModelOptionsResponse + { + AiKernelAvailable = aiKernelAvailable, + Models = optionList + }; + }); + } + private static AdminChatSessionModuleDto MapModuleDtoWithRegisterInfo(AdminChatSessionModuleDto dto) { if (dto == null) @@ -497,5 +560,25 @@ public class GetSessionModulesResponse public List Modules { get; set; } } + /// + /// 获取 AI 模型选项响应 + /// + public class GetAiModelOptionsResponse + { + public bool AiKernelAvailable { get; set; } + public List Models { get; set; } + } + + /// + /// AI 模型选项 + /// + public class AiModelOptionDto + { + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool IsDefault { get; set; } + } + #endregion } diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/AdminChat/Chat.js b/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/AdminChat/Chat.js index 226f55e20..f3f208440 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/AdminChat/Chat.js +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/AdminChat/Chat.js @@ -6,6 +6,10 @@ var chatApp = new Vue({ currentSessionId: 0, currentSessionTitle: '', currentSessionModules: [], + currentSessionAiModelId: 0, + aiModelOptions: [], + aiKernelAvailable: false, + loadingAiModelOptions: false, sessionList: [], messageList: [], inputMessage: '', @@ -34,6 +38,10 @@ var chatApp = new Vue({ } } + this.currentSessionAiModelId = this.getSessionAiModelId(this.currentSessionId); + this.launcherAiModelId = this.currentSessionAiModelId; + + this.loadAiModelOptions(); this.loadSessionList(); if (this.currentSessionId > 0) { @@ -41,6 +49,30 @@ var chatApp = new Vue({ } }, methods: { + async loadAiModelOptions() { + this.loadingAiModelOptions = true; + try { + const response = await service.get('/api/Senparc.Areas.Admin/AdminChatAppService/Areas.Admin_AdminChatAppService.GetAiModelOptionsAsync'); + + if (response.data && response.data.success && response.data.data) { + this.aiKernelAvailable = !!response.data.data.aiKernelAvailable; + this.aiModelOptions = response.data.data.models || []; + } else { + this.aiModelOptions = [{ id: 0, name: '系统级 SenparcAiSetting', description: '使用 appsettings.json 中当前生效的默认 Chat 配置', isDefault: true }]; + } + } catch (error) { + console.error('加载 AI 模型失败:', error); + this.aiModelOptions = [{ id: 0, name: '系统级 SenparcAiSetting', description: '使用 appsettings.json 中当前生效的默认 Chat 配置', isDefault: true }]; + } finally { + this.loadingAiModelOptions = false; + } + }, + + handleCurrentSessionAiModelChange(value) { + this.currentSessionAiModelId = this.normalizeAiModelId(value); + this.setSessionAiModelId(this.currentSessionId, this.currentSessionAiModelId); + }, + handleChatInputKeydown(event) { // 保持与首页一致:Ctrl+Enter (Windows/Linux) 或 Cmd+Enter (Mac) 发送。 if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) { @@ -80,6 +112,7 @@ var chatApp = new Vue({ this.currentSessionTitle = session.title; this.messageList = session.messages || []; this.currentSessionModules = session.modules || []; + this.currentSessionAiModelId = this.getSessionAiModelId(this.currentSessionId); this.clearMessageSelection(); this.isManageMode = false; @@ -131,6 +164,7 @@ var chatApp = new Vue({ try { const requestData = { sessionId: this.currentSessionId, + aiModelId: this.normalizeAiModelId(this.currentSessionAiModelId), content: messageContent }; @@ -194,6 +228,7 @@ var chatApp = new Vue({ if (this.currentSessionId === sessionId) return; this.currentSessionId = sessionId; + this.currentSessionAiModelId = this.getSessionAiModelId(sessionId); this.messageList = []; this.currentSessionModules = []; this.clearMessageSelection(); @@ -202,6 +237,7 @@ var chatApp = new Vue({ }, async createNewSession() { + this.launcherAiModelId = this.currentSessionAiModelId || this.launcherAiModelId || 0; this.currentSessionId = 0; this.currentSessionTitle = ''; this.currentSessionModules = []; diff --git a/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/AdminChat/ChatLauncherMixin.js b/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/AdminChat/ChatLauncherMixin.js index f696a78de..a805e7e60 100644 --- a/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/AdminChat/ChatLauncherMixin.js +++ b/tools/NcfSimulatedSite/Senparc.Areas.Admin/wwwroot/js/Admin/Pages/AdminChat/ChatLauncherMixin.js @@ -2,7 +2,10 @@ window.ChatLauncherMixin = { data() { return { moduleStorageKey: 'ncf.admin.chat.selectedModuleUids', + aiModelStorageKey: 'ncf.admin.chat.sessionAiModelMap', chatInputText: '', + launcherAiModelId: 0, + sessionAiModelMap: {}, selectedModules: [], moduleSelectorVisible: false, moduleSearchKeyword: '', @@ -65,6 +68,7 @@ window.ChatLauncherMixin = { }, mounted() { this.restoreSelectedModuleUids(); + this.restoreSessionAiModelMap(); this.ensureModuleOptionsLoaded(false); }, watch: { @@ -109,6 +113,57 @@ window.ChatLauncherMixin = { } }, + normalizeAiModelId(value) { + const parsedValue = Number.parseInt(value, 10); + return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : 0; + }, + + persistSessionAiModelMap() { + try { + localStorage.setItem(this.aiModelStorageKey, JSON.stringify(this.sessionAiModelMap || {})); + } catch (error) { + console.warn('保存会话 AI 模型失败:', error); + } + }, + + restoreSessionAiModelMap() { + try { + const raw = localStorage.getItem(this.aiModelStorageKey); + if (!raw) { + return; + } + + const parsed = JSON.parse(raw); + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { + return; + } + + this.sessionAiModelMap = Object.keys(parsed).reduce((result, key) => { + result[key] = this.normalizeAiModelId(parsed[key]); + return result; + }, {}); + } catch (error) { + console.warn('读取会话 AI 模型失败:', error); + } + }, + + setSessionAiModelId(sessionId, aiModelId) { + if (!sessionId) { + return; + } + + this.$set(this.sessionAiModelMap, String(sessionId), this.normalizeAiModelId(aiModelId)); + this.persistSessionAiModelMap(); + }, + + getSessionAiModelId(sessionId) { + if (!sessionId) { + return 0; + } + + return this.normalizeAiModelId(this.sessionAiModelMap[String(sessionId)]); + }, + syncSelectedModulesFromUids() { const uidSet = new Set(this.selectedModuleUids); this.selectedModules = this.availableModules.filter((item) => uidSet.has(item.uid)); @@ -223,12 +278,14 @@ window.ChatLauncherMixin = { try { const requestData = { initialMessage: this.chatInputText.trim(), + aiModelId: this.normalizeAiModelId(this.launcherAiModelId), moduleUids: this.selectedModules.map((item) => item.uid) }; const response = await service.post('/api/Senparc.Areas.Admin/AdminChatAppService/Areas.Admin_AdminChatAppService.CreateSessionAsync', requestData); if (response.data && response.data.success && response.data.data) { const sessionId = response.data.data.sessionId; + this.setSessionAiModelId(sessionId, this.launcherAiModelId); window.location.href = '/Admin/AdminChat/Chat?sessionId=' + sessionId; return; }