using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using YamlDotNet.Serialization; namespace AI.Models.SpecialMessages { /// /// 参数集单条字段的控件类型(用于可编辑参数卡片) /// public enum ParameterSetFieldType { /// 单行文本 Text, /// 数值 Number, /// 下拉选择(如成图算法) Choice, /// 文件路径,带“选择文件”按钮 FilePath, } /// /// 单条参数项:名称、当前值(可编辑)、描述、控件类型及选项。 /// public class ParameterSetItem : INotifyPropertyChanged { private string _name = string.Empty; private string _valueText = string.Empty; private string _description = string.Empty; private ParameterSetFieldType _fieldType = ParameterSetFieldType.Text; public string Name { get => _name; set => SetProperty(ref _name, value ?? string.Empty); } /// 当前值的展示/编辑文本 public string ValueText { get => _valueText; set { if (SetProperty(ref _valueText, value ?? string.Empty)) { OnPropertyChanged(nameof(NumericValue)); } } } /// /// 数字值(仅 Number 类型控件绑定使用)。 /// 写回时仅在 FieldType == Number 时才更新 ValueText, /// 防止不可见 NumericUpDown 的双向绑定覆盖 FilePath / Text 等字段的值。 /// public double NumericValue { get => double.TryParse( _valueText?.Trim(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var n) ? n : 0d; set { if (_fieldType == ParameterSetFieldType.Number) { var s = value.ToString(System.Globalization.CultureInfo.InvariantCulture); if (SetProperty(ref _valueText, s, nameof(ValueText))) { OnPropertyChanged(nameof(NumericValue)); } } } } public string Description { get => _description; set => SetProperty(ref _description, value ?? string.Empty); } /// 控件类型(Text / Number / Choice / FilePath) public ParameterSetFieldType FieldType { get => _fieldType; set => SetProperty(ref _fieldType, value); } /// 下拉选项(Choice 类型时使用) public List Options { get; set; } = new List(); public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); protected bool SetProperty(ref T field, T value, [CallerMemberName] string? propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } } /// /// 参数集特殊消息 - 用于在聊天流中展示一组「名称 / 值 / 描述」参数(如网格化参数等),由基本消息样式组合而成。 /// public class ParameterSetMessage : ISpecialMessage, INotifyPropertyChanged { private string _title = "参数"; public string Id { get; set; } = Guid.NewGuid().ToString(); public string TypeName => "ParameterSet"; public bool IsLive => false; public string Title { get => _title; set => SetProperty(ref _title, value ?? string.Empty); } /// 参数项列表(名称、值、描述) public ObservableCollection Items { get; } = new ObservableCollection(); public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); protected bool SetProperty(ref T field, T value, [CallerMemberName] string? propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } /// /// 从 YAML 文本填充参数集。格式示例: /// Input: /// Value: ... /// Description: 散点数据文件路径 /// Type: FilePath /// 支持 PascalCase / camelCase 键(Value/value, Type/type 等)。 /// public void LoadFromJson(string text) { Items.Clear(); if (string.IsNullOrWhiteSpace(text)) return; LoadFromYamlInternal(text.Trim()); } /// /// 从 YAML 解析参数集。根级为参数名(如 Input, Faultage),每项含 Value/Description/Type/Options。 /// 支持根级单键包装(如 parameters: 下再挂 Input/Faultage/...),会自动解包一层。 /// private void LoadFromYamlInternal(string yaml) { try { var deserializer = new DeserializerBuilder() .IgnoreUnmatchedProperties() .Build(); var root = deserializer.Deserialize>(yaml); if (root == null || root.Count == 0) return; // 若根级只有一键且其值为映射,认为是 parameters: { input: ..., faultage: ... },用内层作为参数表 IEnumerable> paramsIter = root; if (root.Count == 1) { var singleValue = root.Values.First(); if (singleValue is Dictionary inner) { var unwrapped = new Dictionary(); foreach (var e in inner) unwrapped[e.Key?.ToString() ?? ""] = e.Value; paramsIter = unwrapped; } else if (singleValue is IDictionary innerDict) { var unwrapped = new Dictionary(); foreach (var e in innerDict) unwrapped[e.Key?.ToString() ?? ""] = e.Value; paramsIter = unwrapped; } else if (singleValue is Dictionary innerStr) { paramsIter = innerStr; } } foreach (var kvp in paramsIter) { var name = kvp.Key ?? ""; if (string.IsNullOrWhiteSpace(name)) continue; var entry = ParseYamlEntry(kvp.Value, name); string valueText = entry.valueText; string description = entry.description; var fieldType = entry.fieldType; var options = entry.options; if (fieldType == ParameterSetFieldType.Choice && options.Count == 0) { options.AddRange(GetDefaultOptionsForChoice(name)); } if (string.Equals(name, "Input", StringComparison.OrdinalIgnoreCase) && fieldType == ParameterSetFieldType.Text) { fieldType = ParameterSetFieldType.FilePath; } Items.Add(new ParameterSetItem { Name = name, ValueText = valueText, Description = description, FieldType = fieldType, Options = options, }); } } catch { // 解析失败时保持空列表 } } private static (string valueText, string description, ParameterSetFieldType fieldType, List options) ParseYamlEntry(object? raw, string name) { string valueText = string.Empty; string description = string.Empty; var fieldType = ParameterSetFieldType.Text; var options = new List(); Dictionary? dictObj = raw as Dictionary; Dictionary? dictStr = raw as Dictionary; if (dictObj == null && dictStr == null) { return (valueText, description, fieldType, options); } // 支持 PascalCase(Value/Type/Options)与 camelCase(value/type/options) if (TryGetValueIgnoreCase(raw, "value", out var valObj)) { valueText = valObj == null ? "" : (valObj is bool b ? (b ? "true" : "false") : valObj.ToString() ?? ""); } if (TryGetValueIgnoreCase(raw, "description", out var descObj) && descObj != null) { description = descObj.ToString() ?? ""; } if (TryGetValueIgnoreCase(raw, "type", out var typeObj) && typeObj != null) { fieldType = ParseTypeString(typeObj.ToString() ?? ""); } else { fieldType = InferFieldType(name, valueText); } if (TryGetValueIgnoreCase(raw, "options", out var optObj)) { if (optObj is IEnumerable optList) { foreach (var o in optList) { if (o?.ToString() is string s) { options.Add(s); } } } else if (optObj is System.Collections.IEnumerable optEnum) { foreach (var item in optEnum) { if (item?.ToString() is string optStr) { options.Add(optStr); } } } } if (fieldType == ParameterSetFieldType.Text && (description.IndexOf("文件路径", StringComparison.OrdinalIgnoreCase) >= 0 || (description.IndexOf("文件", StringComparison.OrdinalIgnoreCase) >= 0 && description.IndexOf("路径", StringComparison.OrdinalIgnoreCase) >= 0))) { fieldType = ParameterSetFieldType.FilePath; } return (valueText, description, fieldType, options); } private static bool TryGetValueIgnoreCase(object? dict, string key, out object? value) { value = null; if (dict == null) { return false; } var keyEq = StringComparer.OrdinalIgnoreCase; if (dict is Dictionary objDict) { foreach (var kv in objDict) { if (keyEq.Equals(kv.Key?.ToString(), key)) { value = kv.Value; return true; } } return false; } if (dict is Dictionary strDict) { foreach (var kv in strDict) { if (keyEq.Equals(kv.Key, key)) { value = kv.Value; return true; } } return false; } return false; } private static ParameterSetFieldType ParseTypeString(string typeStr) { switch (typeStr.Trim().ToLowerInvariant()) { case "choice": return ParameterSetFieldType.Choice; case "number": return ParameterSetFieldType.Number; case "filepath": case "file": return ParameterSetFieldType.FilePath; case "boolean": case "bool": return ParameterSetFieldType.Text; default: return ParameterSetFieldType.Text; } } /// 根据键名和当前值推断控件类型 private static ParameterSetFieldType InferFieldType(string name, string valueText) { if (name.IndexOf("成图算法", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("算法", StringComparison.OrdinalIgnoreCase) >= 0) return ParameterSetFieldType.Choice; if (name.IndexOf("文件", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("路径", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("path", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("file", StringComparison.OrdinalIgnoreCase) >= 0) return ParameterSetFieldType.FilePath; if (name.IndexOf("步长", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("范围", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("min", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("max", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("列", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("行", StringComparison.OrdinalIgnoreCase) >= 0) return ParameterSetFieldType.Number; if (double.TryParse(valueText?.Trim(), System.Globalization.NumberStyles.Any, null, out _)) return ParameterSetFieldType.Number; return ParameterSetFieldType.Text; } /// 成图算法等键的默认下拉选项 private static IEnumerable GetDefaultOptionsForChoice(string name) { if (name.IndexOf("成图算法", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("算法", StringComparison.OrdinalIgnoreCase) >= 0) return new[] { "IDW", "Kriging", "自然邻点", "反距离加权" }; return Array.Empty(); } } }