Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
264b17b
Merge pull request #444 from NeuCharFramework/Developer-Desktop
JeffreySu Jul 27, 2025
6315f0c
Merge pull request #447 from NeuCharFramework/Developer-MCP
JeffreySu Jul 27, 2025
7e870a7
Merge pull request #449 from NeuCharFramework/Developer-MCP
JeffreySu Aug 11, 2025
fa1a93a
Merge pull request #452 from NeuCharFramework/Developer-MCP
JeffreySu Aug 18, 2025
25a5541
Merge pull request #453 from NeuCharFramework/Developer-MCP
JeffreySu Aug 18, 2025
e0c18d0
Merge pull request #454 from NeuCharFramework/Developer-MCP
JeffreySu Aug 19, 2025
1b1c1e0
Merge pull request #455 from NeuCharFramework/Developer-MCP
JeffreySu Aug 20, 2025
0f1fa0e
Merge pull request #456 from NeuCharFramework/Developer-MCP
JeffreySu Aug 20, 2025
3cc69ec
Merge pull request #458 from NeuCharFramework/Developer-MCP
JeffreySu Sep 10, 2025
007c91a
Merge pull request #459 from NeuCharFramework/Developer-MCP
JeffreySu Sep 10, 2025
4702277
Merge pull request #460 from NeuCharFramework/Developer-MCP
JeffreySu Sep 27, 2025
c3ea218
Merge pull request #461 from NeuCharFramework/Developer-MCP
JeffreySu Nov 1, 2025
e13cdfc
Merge pull request #462 from NeuCharFramework/Developer-MCP
JeffreySu Nov 1, 2025
6a6fabe
Merge pull request #463 from NeuCharFramework/Developer-MCP
JeffreySu Nov 1, 2025
6983dc3
Merge pull request #464 from NeuCharFramework/Developer-MCP
JeffreySu Nov 1, 2025
0ff7cda
Merge pull request #466 from NeuCharFramework/Developer-MCP
JeffreySu Nov 4, 2025
7e6efa1
Merge pull request #467 from NeuCharFramework/Developer-MCP
JeffreySu Nov 5, 2025
d0faa73
Merge pull request #471 from NeuCharFramework/Developer-MCP
JeffreySu Nov 5, 2025
c7a6f00
Merge pull request #472 from NeuCharFramework/Developer-MCP
JeffreySu Nov 5, 2025
c012dd4
Merge pull request #473 from NeuCharFramework/Developer-MCP
JeffreySu Nov 6, 2025
480f40f
Merge pull request #474 from NeuCharFramework/Developer-MCP
JeffreySu Nov 14, 2025
96e87f5
Merge pull request #475 from NeuCharFramework/Developer-MCP
JeffreySu Nov 16, 2025
bde9cf6
Merge pull request #476 from NeuCharFramework/Developer-MCP
JeffreySu Dec 10, 2025
caa31be
Merge pull request #477 from NeuCharFramework/Developer-MCP
JeffreySu Dec 13, 2025
e1a2b6e
Merge pull request #478 from NeuCharFramework/Developer-MCP
JeffreySu Dec 13, 2025
6028ab0
Merge pull request #479 from NeuCharFramework/refactor/prompt-js-modu…
JeffreySu Dec 15, 2025
f79220f
Merge pull request #480 from NeuCharFramework/refactor/prompt-js-modu…
JeffreySu Dec 16, 2025
1d0894e
Merge pull request #481 from NeuCharFramework/Developer-KnowledgeBase
JeffreySu Dec 28, 2025
7621b90
Merge pull request #482 from NeuCharFramework/Developer-KnowledgeBase
JeffreySu Dec 28, 2025
ef9aeee
Merge pull request #483 from NeuCharFramework/Developer-KnowledgeBase
JeffreySu Dec 28, 2025
aab2b33
Merge pull request #484 from NeuCharFramework/Developer-KnowledgeBase
JeffreySu Dec 28, 2025
e49d466
Merge pull request #485 from NeuCharFramework/Developer-KnowledgeBase
JeffreySu Dec 31, 2025
ceeb2a7
Merge pull request #486 from NeuCharFramework/Developer-KnowledgeBase
JeffreySu Feb 13, 2026
0b04dfe
Merge pull request #487 from NeuCharFramework/Developer
JeffreySu Feb 13, 2026
c906da0
Initial plan
Copilot Apr 4, 2026
3f1bb8b
Implement three features: 3D map improvements, XNCF auto-attach, crea…
Copilot Apr 4, 2026
cf095fb
Address code review feedback: fix error message, simplify description…
Copilot Apr 4, 2026
ab89ea3
Fix: 3D map keyboard/toolbar not rendering due to missing map3dNeedsA…
Copilot Apr 4, 2026
ddf5310
Merge branch 'origin/Developer-3DAgents': resolve conflicts in AgentT…
Copilot Apr 4, 2026
a08f8df
Enhance textSprite function: improve line handling and adjust sprite …
JeffreySu Apr 8, 2026
91f5226
Fix Q/E depth pan to sync orbit target
cursoragent Apr 8, 2026
0a6e566
fix(prompt-range): apply FilterRangeName in prompt code list
cursoragent Apr 8, 2026
14e091b
fix: add missing using directive for Senparc.CO2NET.Extensions
JeffreySu Apr 8, 2026
8e12f63
Fix Map3D keyboard shortcuts under IME/layout
cursoragent Apr 8, 2026
77ecc85
Merge branch 'copilot/optimize-3d-agent-functions' of https://github.…
JeffreySu Apr 8, 2026
822d995
fix: normalize prompt code to avoid storing alias in SystemMessage
JeffreySu Apr 8, 2026
c6d8a26
feat: add FindAgentTemplate function and enhance ChatGroupRequest for…
JeffreySu Apr 9, 2026
1ea003f
feat: enhance ChatGroup functionality with automatic host agent selec…
JeffreySu Apr 13, 2026
4295588
feat: Enhance parameter handling in XncfModule
JeffreySu Apr 24, 2026
9725c00
Refactor function request and service classes to use simplified data …
JeffreySu Apr 24, 2026
dacad3c
Refactor code structure for improved readability and maintainability
JeffreySu Apr 24, 2026
f546758
feat: Update version to 0.35.8-preview.1 and add release note for Fun…
JeffreySu Apr 24, 2026
d443dc1
feat: Add debug logging for loaded functions in AdminChatAiService an…
JeffreySu Apr 25, 2026
de0e616
refactor: Simplify function debug line generation and enhance paramet…
JeffreySu Apr 25, 2026
b89a9a1
feat: Implement AI model selection and loading in AdminChat, enhancin…
JeffreySu Apr 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -36,14 +38,41 @@ 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<SelectionItem>());

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<StringAppResponse> SetConfig(SetConfigFunctionAppRequest request)
{
return await this.GetStringResponseAsync(async (response, logger) =>
{
return request.BackupPath;
});
}

[FunctionRender("设置智能体", "测试简单值 + 下拉元数据", typeof(TestModuleRegister))]
public async Task<StringAppResponse> SetSelectionConfig(SetSelectionConfigFunctionAppRequest request)
{
return await this.GetStringResponseAsync(async (response, logger) =>
{
return request.AgentName;
});
}
}


Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Normalizes request payloads so legacy SelectionList JSON and simple string JSON
/// can both bind to FunctionRender request types.
/// </summary>
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<FunctionParameterUiAttribute>();
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<string> ExtractValues(JsonNode currentNode, bool forMultiple)
{
if (currentNode == null)
{
return new List<string>();
}

if (currentNode is JsonObject currentObject)
{
if (TryGetJsonProperty(currentObject, "SelectedValues", out _, out var selectedValuesNode))
{
return ExtractValues(selectedValuesNode, true);
}

return new List<string>();
}

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<string>();
}

private static List<string> SplitInput(string input, bool forMultiple)
{
if (string.IsNullOrWhiteSpace(input))
{
return new List<string>();
}

if (!forMultiple)
{
return new List<string> { 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<string>();
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;
}
}
}
41 changes: 38 additions & 3 deletions src/Basic/Senparc.Ncf.XncfBase/Functions/FunctionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -69,10 +70,33 @@ public static async Task<List<FunctionParameterInfo>> GetFunctionParameterInfoAs
List<FunctionParameterInfo> result = new List<FunctionParameterInfo>();
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<FunctionParameterUiAttribute>();
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)
Expand Down Expand Up @@ -130,12 +154,23 @@ public static async Task<List<FunctionParameterInfo>> 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<JsonIgnoreAttribute>() != null)
{
return true;
}

return propertyInfo.CustomAttributes.Any(z => z.AttributeType.FullName == "Newtonsoft.Json.JsonIgnoreAttribute");
}

/// <summary>
/// 给 SelectionList 对象添加当前解决方案中的 XNCF 项目
/// (扫描当前解决方案包含的所有领域项目)
Expand Down
Loading
Loading