手动 develop
parent
4dd23d11fb
commit
17e6c0d8ae
@ -0,0 +1,238 @@
|
||||
[*.cs]
|
||||
|
||||
# 禁用命名规则,因为与 StyleCop 冲突
|
||||
dotnet_diagnostic.SA1300.severity = none
|
||||
# dotnet_diagnostic.SA1301.severity = none
|
||||
# dotnet_diagnostic.SA1302.severity = none
|
||||
# dotnet_diagnostic.SA1303.severity = none
|
||||
# dotnet_diagnostic.SA1304.severity = none
|
||||
# dotnet_diagnostic.SA1305.severity = none
|
||||
# dotnet_diagnostic.SA1306.severity = none
|
||||
# dotnet_diagnostic.SA1307.severity = none
|
||||
# dotnet_diagnostic.SA1308.severity = none
|
||||
# dotnet_diagnostic.SA1309.severity = none
|
||||
# dotnet_diagnostic.SA1310.severity = none
|
||||
# dotnet_diagnostic.SA1311.severity = none
|
||||
# dotnet_diagnostic.SA1312.severity = none
|
||||
# dotnet_diagnostic.SA1313.severity = none
|
||||
# dotnet_diagnostic.SA1314.severity = none
|
||||
|
||||
# SA1649: File name should match first type name
|
||||
dotnet_diagnostic.SA1649.severity = none
|
||||
|
||||
# SA1200: Using directives should be placed correctly
|
||||
dotnet_diagnostic.SA1200.severity = none
|
||||
|
||||
# SA1629: Documentation text should end with a period
|
||||
dotnet_diagnostic.SA1629.severity = none
|
||||
|
||||
# SA1512: Single-line comments should not be followed by blank line
|
||||
dotnet_diagnostic.SA1512.severity = none
|
||||
|
||||
# Element documentation header should be preceded by blank line
|
||||
dotnet_diagnostic.SA1514.severity = none
|
||||
|
||||
# SA1516: Elements should be separated by blank line
|
||||
dotnet_diagnostic.SA1516.severity = none
|
||||
|
||||
# SA1515: Single-line comment should be preceded by blank line
|
||||
dotnet_diagnostic.SA1515.severity = none
|
||||
|
||||
# SA1513: Closing brace should be followed by blank line
|
||||
dotnet_diagnostic.SA1513.severity = none
|
||||
|
||||
# SA1113: Comma should be on the same line as previous parameter
|
||||
dotnet_diagnostic.SA1113.severity = none
|
||||
|
||||
# SA1001: Commas should be spaced correctly
|
||||
dotnet_diagnostic.SA1001.severity = none
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
csharp_style_var_for_built_in_types = false:suggestion
|
||||
csharp_style_var_when_type_is_apparent = false:suggestion
|
||||
csharp_style_var_elsewhere = false:suggestion
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_style_namespace_declarations = block_scoped:silent
|
||||
|
||||
# SA1201: Elements should appear in the correct order
|
||||
# dotnet_diagnostic.SA1201.severity = none
|
||||
|
||||
[*.{cs,vb}]
|
||||
end_of_line = crlf
|
||||
dotnet_style_qualification_for_field = false:silent
|
||||
dotnet_style_qualification_for_property = false:silent
|
||||
dotnet_style_qualification_for_method = false:silent
|
||||
dotnet_style_qualification_for_event = false:silent
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
dotnet_code_quality_unused_parameters = all:suggestion
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
|
||||
dotnet_style_namespace_match_folder = true:suggestion
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||
dotnet_style_predefined_type_for_member_access = true:silent
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
[*.cs]
|
||||
#### 命名样式 ####
|
||||
|
||||
# 命名规则
|
||||
|
||||
dotnet_naming_rule.接口_should_be_以_i_开始.severity = warning
|
||||
dotnet_naming_rule.接口_should_be_以_i_开始.symbols = 接口
|
||||
dotnet_naming_rule.接口_should_be_以_i_开始.style = 以_i_开始
|
||||
|
||||
dotnet_naming_rule.私有方法_should_be_camel拼写法.severity = warning
|
||||
dotnet_naming_rule.私有方法_should_be_camel拼写法.symbols = 私有方法
|
||||
dotnet_naming_rule.私有方法_should_be_camel拼写法.style = camel拼写法
|
||||
|
||||
dotnet_naming_rule.方法_should_be_帕斯卡拼写法.severity = warning
|
||||
dotnet_naming_rule.方法_should_be_帕斯卡拼写法.symbols = 方法
|
||||
dotnet_naming_rule.方法_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
|
||||
|
||||
# 符号规范
|
||||
|
||||
dotnet_naming_symbols.接口.applicable_kinds = interface
|
||||
dotnet_naming_symbols.接口.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
dotnet_naming_symbols.接口.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.私有方法.applicable_kinds = method
|
||||
dotnet_naming_symbols.私有方法.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.私有方法.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.方法.applicable_kinds = method
|
||||
dotnet_naming_symbols.方法.applicable_accessibilities = public
|
||||
dotnet_naming_symbols.方法.required_modifiers =
|
||||
|
||||
# 命名样式
|
||||
|
||||
dotnet_naming_style.以_i_开始.required_prefix = I
|
||||
dotnet_naming_style.以_i_开始.required_suffix =
|
||||
dotnet_naming_style.以_i_开始.word_separator =
|
||||
dotnet_naming_style.以_i_开始.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.camel拼写法.required_prefix =
|
||||
dotnet_naming_style.camel拼写法.required_suffix =
|
||||
dotnet_naming_style.camel拼写法.word_separator =
|
||||
dotnet_naming_style.camel拼写法.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.帕斯卡拼写法.required_prefix =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_suffix =
|
||||
dotnet_naming_style.帕斯卡拼写法.word_separator =
|
||||
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_prefer_system_threading_lock = true:suggestion
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
csharp_prefer_static_anonymous_function = true:suggestion
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_style_prefer_readonly_struct = true:suggestion
|
||||
csharp_style_prefer_readonly_struct_member = true:suggestion
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
csharp_style_prefer_pattern_matching = true:silent
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_prefer_not_pattern = true:suggestion
|
||||
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||
csharp_style_prefer_implicitly_typed_lambda_expression = true:suggestion
|
||||
csharp_style_prefer_tuple_swap = true:suggestion
|
||||
csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_indent_labels = one_less_than_current
|
||||
|
||||
# IDE1006: 命名样式
|
||||
dotnet_diagnostic.IDE1006.severity = none
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
# 命名规则
|
||||
|
||||
dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始
|
||||
|
||||
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion
|
||||
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型
|
||||
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
|
||||
|
||||
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion
|
||||
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员
|
||||
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
|
||||
|
||||
# 符号规范
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||
dotnet_naming_symbols.类型.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||
dotnet_naming_symbols.非字段成员.required_modifiers =
|
||||
|
||||
# 命名样式
|
||||
|
||||
dotnet_naming_style.以_i_开始.required_prefix = I
|
||||
dotnet_naming_style.以_i_开始.required_suffix =
|
||||
dotnet_naming_style.以_i_开始.word_separator =
|
||||
dotnet_naming_style.以_i_开始.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.帕斯卡拼写法.required_prefix =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_suffix =
|
||||
dotnet_naming_style.帕斯卡拼写法.word_separator =
|
||||
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.帕斯卡拼写法.required_prefix =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_suffix =
|
||||
dotnet_naming_style.帕斯卡拼写法.word_separator =
|
||||
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
|
||||
@ -0,0 +1,24 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.2.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AI", "AI.csproj", "{F091FF6C-454D-F725-5E93-41983547B110}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F091FF6C-454D-F725-5E93-41983547B110}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F091FF6C-454D-F725-5E93-41983547B110}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F091FF6C-454D-F725-5E93-41983547B110}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F091FF6C-454D-F725-5E93-41983547B110}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FBDA3407-3A62-4B40-88D5-9D08353B1E4E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace AI.AgentIntegration
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||
public class ActionHandlerAttribute : Attribute
|
||||
{
|
||||
public AppActionType ActionType { get; }
|
||||
|
||||
public ActionHandlerAttribute(AppActionType actionType)
|
||||
{
|
||||
ActionType = actionType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace AI.AgentIntegration
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义代理的操作模式
|
||||
/// </summary>
|
||||
public enum AgentMode
|
||||
{
|
||||
/// <summary>
|
||||
/// 观察模式:仅观察用户操作,不主动干预
|
||||
/// </summary>
|
||||
Observe,
|
||||
|
||||
/// <summary>
|
||||
/// 协助模式:在用户请求时提供帮助
|
||||
/// </summary>
|
||||
Assist,
|
||||
|
||||
/// <summary>
|
||||
/// 控制模式:主动控制系统行为
|
||||
/// </summary>
|
||||
Control,
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
namespace AI.AgentIntegration
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示应用程序操作的结果
|
||||
/// </summary>
|
||||
public class AppActionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置操作是否成功
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置操作结果消息
|
||||
/// </summary>
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置操作返回的数据
|
||||
/// </summary>
|
||||
public object? Data { get; set; }
|
||||
|
||||
public static AppActionResult Sucess(string message, object? data = null)
|
||||
{
|
||||
return new AppActionResult()
|
||||
{
|
||||
Success = true,
|
||||
Message = message,
|
||||
Data = data,
|
||||
};
|
||||
}
|
||||
|
||||
public static AppActionResult Fail(string message)
|
||||
{
|
||||
return new AppActionResult()
|
||||
{
|
||||
Success = false,
|
||||
Message = message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,164 @@
|
||||
namespace AI.AgentIntegration
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义应用程序可执行的操作类型
|
||||
/// </summary>
|
||||
public enum AppActionType
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取打开的标签页列表
|
||||
/// </summary>
|
||||
GetOpenTabs,
|
||||
|
||||
/// <summary>
|
||||
/// 打开文件
|
||||
/// </summary>
|
||||
OpenFile,
|
||||
|
||||
/// <summary>
|
||||
/// 关闭当前文件
|
||||
/// </summary>
|
||||
CloseFile,
|
||||
|
||||
/// <summary>
|
||||
/// 关闭所有文件
|
||||
/// </summary>
|
||||
CloseAllFiles,
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前文件
|
||||
/// </summary>
|
||||
SaveFile,
|
||||
|
||||
/// <summary>
|
||||
/// 保存所有文件
|
||||
/// </summary>
|
||||
SaveAll,
|
||||
|
||||
/// <summary>
|
||||
/// 重命名文件
|
||||
/// </summary>
|
||||
RenameFile,
|
||||
|
||||
/// <summary>
|
||||
/// 重新加载文件
|
||||
/// </summary>
|
||||
ReloadFile,
|
||||
|
||||
/// <summary>
|
||||
/// 切换标签页
|
||||
/// </summary>
|
||||
SwitchTab,
|
||||
|
||||
/// <summary>
|
||||
/// 导航到指定视图
|
||||
/// </summary>
|
||||
Navigate,
|
||||
|
||||
/// <summary>
|
||||
/// 后退导航
|
||||
/// </summary>
|
||||
NavigateBack,
|
||||
|
||||
/// <summary>
|
||||
/// 前进导航
|
||||
/// </summary>
|
||||
NavigateForward,
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统忙状态
|
||||
/// </summary>
|
||||
SetBusy,
|
||||
|
||||
/// <summary>
|
||||
/// 刷新界面
|
||||
/// </summary>
|
||||
Refresh,
|
||||
|
||||
/// <summary>
|
||||
/// 退出应用程序
|
||||
/// </summary>
|
||||
Exit,
|
||||
|
||||
/// <summary>
|
||||
/// 撤销操作
|
||||
/// </summary>
|
||||
Undo,
|
||||
|
||||
/// <summary>
|
||||
/// 重做操作
|
||||
/// </summary>
|
||||
Redo,
|
||||
|
||||
/// <summary>
|
||||
/// 添加比例尺
|
||||
/// </summary>
|
||||
AddScaleBar,
|
||||
|
||||
/// <summary>
|
||||
/// 添加边框
|
||||
/// </summary>
|
||||
AddBorder,
|
||||
|
||||
/// <summary>
|
||||
/// 添加图例
|
||||
/// </summary>
|
||||
AddLegend,
|
||||
|
||||
/// <summary>
|
||||
/// 描述当前状态
|
||||
/// </summary>
|
||||
DescribeState,
|
||||
|
||||
/// <summary>
|
||||
/// 加载 xyz 文件
|
||||
/// </summary>
|
||||
GriddingModuleLoadXyz,
|
||||
|
||||
/// <summary>
|
||||
/// 获取列信息
|
||||
/// </summary>
|
||||
GriddingModuleGetColumns,
|
||||
|
||||
/// <summary>
|
||||
/// 列头匹配
|
||||
/// </summary>
|
||||
GriddingModuleMatchColumns,
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络化参数
|
||||
/// </summary>
|
||||
GriddingModuleGetParameters,
|
||||
|
||||
/// <summary>
|
||||
/// 设置网络化参数
|
||||
/// </summary>
|
||||
GriddingModuleSetParameters,
|
||||
|
||||
/// <summary>
|
||||
/// 导入数据
|
||||
/// </summary>
|
||||
GriddingModuleImport,
|
||||
|
||||
/// <summary>
|
||||
/// 数据预览
|
||||
/// </summary>
|
||||
GriddingModulePreviewData,
|
||||
|
||||
/// <summary>
|
||||
/// 成图
|
||||
/// </summary>
|
||||
GriddingModuleRun,
|
||||
|
||||
/// <summary>
|
||||
/// 从文件导入井点数据
|
||||
/// </summary>
|
||||
WellModuleImportWellPoints,
|
||||
|
||||
/// <summary>
|
||||
/// 从文件导入井曲线数据
|
||||
/// </summary>
|
||||
WellModuleImportCurves,
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AI.AgentIntegration
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义应用程序控制器的接口
|
||||
/// </summary>
|
||||
public interface IAppController
|
||||
{
|
||||
/// <summary>
|
||||
/// 当应用程序状态发生变化时触发的事件
|
||||
/// </summary>
|
||||
event EventHandler<AppState> StateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 获取应用程序的当前状态
|
||||
/// </summary>
|
||||
/// <returns>当前应用程序状态</returns>
|
||||
AppState GetCurrentState();
|
||||
|
||||
/// <summary>
|
||||
/// 执行指定的应用程序操作
|
||||
/// </summary>
|
||||
/// <param name="action">要执行的操作</param>
|
||||
/// <returns>操作执行结果</returns>
|
||||
Task<AppActionResult> ExecuteAsync(AppAction action);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Data.Core.Plugins;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace AI
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
public IServiceProvider? ServiceProvider { get; private set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
// Line below is needed to remove Avalonia data validation.
|
||||
// Without this line you will get duplicate validations from both Avalonia and CT
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
var collection = new ServiceCollection();
|
||||
collection.AddCommonServices();
|
||||
var services = collection.BuildServiceProvider();
|
||||
ServiceProvider = services;
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1766626731970" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5056" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M886.7 247.6L713.4 73.4c-6-6-14.2-9.4-22.7-9.4H192c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h640c35.3 0 64-28.7 64-64V270.2c0-8.5-3.3-16.6-9.3-22.6zM832 864c0 17.7-14.3 32-32 32H224c-17.7 0-32-14.3-32-32V160c0-17.7 14.3-32 32-32h384v160c0 35.3 28.7 64 64 64h160v512zM704 288c-17.7 0-32-14.3-32-32V128l160 160H704z" p-id="5057"></path><path d="M671 672H287c-17.7 0-32 14.3-32 32s14.3 32 32 32h384c17.7 0 32-14.3 32-32s-14.3-32-32-32zM287 480c-17.7 0-32 14.3-32 32s14.3 32 32 32h384c17.7 0 32-14.3 32-32s-14.3-32-32-32H287zM287 352h192c17.7 0 32-14.3 32-32s-14.3-32-32-32H287c-17.7 0-32 14.3-32 32s14.3 32 32 32z" p-id="5058"></path></svg>
|
||||
|
After Width: | Height: | Size: 973 B |
@ -0,0 +1,30 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using AI.Models;
|
||||
using System.Globalization;
|
||||
|
||||
namespace AI.Converters
|
||||
{
|
||||
public class AuthorToColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is AuthorType authorType)
|
||||
{
|
||||
return authorType switch
|
||||
{
|
||||
AuthorType.User => new SolidColorBrush(Color.Parse("#EAEEF6")),
|
||||
AuthorType.AI => new SolidColorBrush(Color.Parse("transparent")),
|
||||
AuthorType.Tool => new SolidColorBrush(Color.Parse("#FFF9E6")), // 淡黄色背景,用于工具调用记录
|
||||
_ => Brushes.Transparent
|
||||
};
|
||||
}
|
||||
return Brushes.Transparent;
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace AI.Converters
|
||||
{
|
||||
public class FileExtensionConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is string fileName)
|
||||
{
|
||||
string extension = Path.GetExtension(fileName);
|
||||
if (!string.IsNullOrEmpty(extension))
|
||||
{
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
using AI.Utils;
|
||||
using Avalonia.Data.Converters;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace AI.Converters
|
||||
{
|
||||
public class FileSizeConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is long fileSize)
|
||||
{
|
||||
return FileUnit.FormatFileSize(fileSize);
|
||||
}
|
||||
return "0 B";
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using AI.Models.Form;
|
||||
|
||||
namespace AI.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 表单字段类型与 ConverterParameter 相同时可见(用于按类型显示不同控件)
|
||||
/// </summary>
|
||||
public class FormFieldTypeVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is FormFieldType ft && parameter is string key)
|
||||
{
|
||||
if (string.Equals(key, "NotChoice", StringComparison.OrdinalIgnoreCase))
|
||||
return ft != FormFieldType.Choice;
|
||||
return ft.ToString() == key;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Markup.Xaml.Templates;
|
||||
using AI.Models;
|
||||
|
||||
namespace AI.Converters
|
||||
{
|
||||
public class MessageTemplateSelector : IDataTemplate
|
||||
{
|
||||
public required DataTemplate TextMessageTemplate { get; set; }
|
||||
public required DataTemplate FileMessageTemplate { get; set; }
|
||||
public required DataTemplate WorkflowStatusTemplate { get; set; }
|
||||
public required DataTemplate FormMessageTemplate { get; set; }
|
||||
public required DataTemplate TableMessageTemplate { get; set; }
|
||||
public required DataTemplate ColumnMatchMessageTemplate { get; set; }
|
||||
public required DataTemplate ParameterSetMessageTemplate { get; set; }
|
||||
public required DataTemplate XyzLoadCardTemplate { get; set; }
|
||||
public required DataTemplate GriddingParamCardTemplate { get; set; }
|
||||
|
||||
public Control? Build(object? param)
|
||||
{
|
||||
if (param is ChatMessageModel message)
|
||||
{
|
||||
return message.Type switch
|
||||
{
|
||||
MessageType.File => FileMessageTemplate.Build(param),
|
||||
MessageType.WorkflowStatus => WorkflowStatusTemplate.Build(param),
|
||||
MessageType.Form => FormMessageTemplate.Build(param),
|
||||
MessageType.Table => TableMessageTemplate.Build(param),
|
||||
MessageType.ColumnMatch => ColumnMatchMessageTemplate.Build(param),
|
||||
MessageType.ParameterSet => ParameterSetMessageTemplate.Build(param),
|
||||
MessageType.KnowledgeBase => TextMessageTemplate?.Build(param),
|
||||
MessageType.XyzLoadCard => XyzLoadCardTemplate.Build(param),
|
||||
MessageType.GriddingParamCard => GriddingParamCardTemplate.Build(param),
|
||||
_ => TextMessageTemplate?.Build(param),
|
||||
};
|
||||
}
|
||||
return TextMessageTemplate?.Build(param);
|
||||
}
|
||||
|
||||
public bool Match(object? data)
|
||||
{
|
||||
return data is ChatMessageModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using AI.Models.SpecialMessages;
|
||||
|
||||
namespace AI.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 参数集字段类型与 ConverterParameter 相同时可见(用于按类型显示不同控件)
|
||||
/// </summary>
|
||||
public class ParameterSetFieldTypeVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is ParameterSetFieldType ft && parameter is string key)
|
||||
{
|
||||
return ft.ToString() == key;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using AI.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace AI.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断工作流步骤状态是否为已完成(用于显示勾)
|
||||
/// </summary>
|
||||
public class StatusToCompletedConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is WorkflowStepStatus status)
|
||||
{
|
||||
return status == WorkflowStepStatus.Completed;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using AI.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace AI.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 将工作模式转换为按钮背景颜色
|
||||
/// </summary>
|
||||
public class WorkflowModeToColorConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is WorkflowMode currentMode && parameter is string targetMode)
|
||||
{
|
||||
var isActive = (currentMode == WorkflowMode.Ask && targetMode == "Ask") ||
|
||||
(currentMode == WorkflowMode.Workflow && targetMode == "Workflow");
|
||||
|
||||
return isActive
|
||||
? new SolidColorBrush(Color.Parse("#007AFF"))
|
||||
: new SolidColorBrush(Colors.Transparent);
|
||||
}
|
||||
|
||||
return new SolidColorBrush(Colors.Transparent);
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using AI.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace AI.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 将工作模式转换为按钮文字颜色
|
||||
/// </summary>
|
||||
public class WorkflowModeToTextColorConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is WorkflowMode currentMode && parameter is string targetMode)
|
||||
{
|
||||
var isActive = (currentMode == WorkflowMode.Ask && targetMode == "Ask") ||
|
||||
(currentMode == WorkflowMode.Workflow && targetMode == "Workflow");
|
||||
|
||||
return isActive
|
||||
? new SolidColorBrush(Colors.White)
|
||||
: new SolidColorBrush(Color.Parse("#666666"));
|
||||
}
|
||||
|
||||
return new SolidColorBrush(Color.Parse("#666666"));
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using AI.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace AI.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 将工作流步骤状态转换为颜色
|
||||
/// </summary>
|
||||
public class WorkflowStatusToColorConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is WorkflowStepStatus status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
WorkflowStepStatus.Completed => new SolidColorBrush(Color.Parse("#4CAF50")),
|
||||
WorkflowStepStatus.Running => new SolidColorBrush(Color.Parse("#2196F3")),
|
||||
WorkflowStepStatus.Failed => new SolidColorBrush(Color.Parse("#F44336")),
|
||||
WorkflowStepStatus.Skipped => new SolidColorBrush(Color.Parse("#9E9E9E")),
|
||||
WorkflowStepStatus.Pending => new SolidColorBrush(Color.Parse("#757575")),
|
||||
_ => new SolidColorBrush(Color.Parse("#757575"))
|
||||
};
|
||||
}
|
||||
|
||||
return new SolidColorBrush(Color.Parse("#757575"));
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
using AI.Models.Form;
|
||||
|
||||
namespace AI.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 表单注册表:根据 formId 获取表单定义。
|
||||
/// </summary>
|
||||
public interface IFormRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据表单 ID 获取表单定义,未注册则返回 null。
|
||||
/// </summary>
|
||||
FormDefinition? GetForm(string formId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已注册的表单定义(用于生成 Schema YAML、供 LLM 上下文等)。
|
||||
/// </summary>
|
||||
IEnumerable<FormDefinition> GetAll();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AI.KnowledgeBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 知识库接口
|
||||
/// </summary>
|
||||
public interface IKnowledgeBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询知识库中的信息
|
||||
/// </summary>
|
||||
/// <param name="query">查询语句</param>
|
||||
/// <returns>返回匹配的知识条目</returns>
|
||||
Task<string> SearchKnowledgeAsync(string query);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
namespace AI.Models.Form
|
||||
{
|
||||
/// <summary>
|
||||
/// 表单字段控件/值类型(通用 schema 类型)
|
||||
/// </summary>
|
||||
public enum FormFieldType
|
||||
{
|
||||
/// <summary>单行文本,常用属性: maxLength, placeholder, default</summary>
|
||||
Text,
|
||||
MultiLine,
|
||||
/// <summary>数值,常用属性: min, max, step, default</summary>
|
||||
Number,
|
||||
/// <summary>勾选/开关,常用属性: default</summary>
|
||||
Boolean,
|
||||
FilePath,
|
||||
/// <summary>单选下拉,常用属性: options[], default</summary>
|
||||
Choice,
|
||||
/// <summary>多选,常用属性: options[], default[]</summary>
|
||||
MultiSelect,
|
||||
Json,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AI.Models.Form
|
||||
{
|
||||
/// <summary>
|
||||
/// 多选项条目,用于绑定 CheckBox 与 FormFieldEntry.SelectedValues 同步
|
||||
/// </summary>
|
||||
public class MultiSelectOptionItem : INotifyPropertyChanged
|
||||
{
|
||||
private readonly FormFieldEntry _entry;
|
||||
private bool _isSelected;
|
||||
|
||||
public string Option { get; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set
|
||||
{
|
||||
if (_isSelected == value) return;
|
||||
_isSelected = value;
|
||||
if (value)
|
||||
{
|
||||
if (!_entry.SelectedValues.Contains(Option))
|
||||
_entry.SelectedValues.Add(Option);
|
||||
}
|
||||
else
|
||||
{
|
||||
_entry.SelectedValues.Remove(Option);
|
||||
}
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public MultiSelectOptionItem(FormFieldEntry entry, string option, bool isSelected = false)
|
||||
{
|
||||
_entry = entry;
|
||||
Option = option;
|
||||
_isSelected = isSelected;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AI.Models.SpecialMessages
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示一条「必需列 -> 预览列」的匹配项,用于绑定显示。
|
||||
/// </summary>
|
||||
public class ColumnMappingItem : INotifyPropertyChanged
|
||||
{
|
||||
private string _requiredColumn = string.Empty;
|
||||
private string _matchedColumn = string.Empty;
|
||||
|
||||
/// <summary>必需列名称</summary>
|
||||
public string RequiredColumn
|
||||
{
|
||||
get => _requiredColumn;
|
||||
set => SetProperty(ref _requiredColumn, value ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>匹配到的预览列名称(未匹配时可为空)</summary>
|
||||
public string MatchedColumn
|
||||
{
|
||||
get => _matchedColumn;
|
||||
set => SetProperty(ref _matchedColumn, value ?? string.Empty);
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
||||
field = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 列头匹配特殊消息 - 用于在聊天流中展示「必需列」与「预览列」及其匹配关系。
|
||||
/// </summary>
|
||||
public class ColumnMatchMessage : ISpecialMessage, INotifyPropertyChanged
|
||||
{
|
||||
private string _title = "列头匹配";
|
||||
|
||||
/// <summary>消息唯一标识符</summary>
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>类型名称</summary>
|
||||
public string TypeName => "ColumnMatch";
|
||||
|
||||
/// <summary>不需要实时更新</summary>
|
||||
public bool IsLive => false;
|
||||
|
||||
/// <summary>标题(如「列头匹配」)</summary>
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>必需列名称列表</summary>
|
||||
public ObservableCollection<string> RequiredColumns { get; } = new ObservableCollection<string>();
|
||||
|
||||
/// <summary>预览列(可用列)名称列表</summary>
|
||||
public ObservableCollection<string> PreviewColumns { get; } = new ObservableCollection<string>();
|
||||
|
||||
/// <summary>匹配关系:必需列 -> 匹配的预览列</summary>
|
||||
public ObservableCollection<ColumnMappingItem> Mappings { get; } = new ObservableCollection<ColumnMappingItem>();
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
||||
field = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>设置必需列与预览列,并清空匹配关系</summary>
|
||||
public void SetColumns(IEnumerable<string> requiredColumns, IEnumerable<string> previewColumns)
|
||||
{
|
||||
RequiredColumns.Clear();
|
||||
foreach (var c in requiredColumns ?? Array.Empty<string>())
|
||||
RequiredColumns.Add(c ?? string.Empty);
|
||||
PreviewColumns.Clear();
|
||||
foreach (var c in previewColumns ?? Array.Empty<string>())
|
||||
PreviewColumns.Add(c ?? string.Empty);
|
||||
Mappings.Clear();
|
||||
}
|
||||
|
||||
/// <summary>设置匹配关系(必需列 -> 预览列),会清空并替换当前 Mappings</summary>
|
||||
public void SetMappings(IEnumerable<KeyValuePair<string, string>> mappings)
|
||||
{
|
||||
Mappings.Clear();
|
||||
if (mappings == null) return;
|
||||
foreach (var kv in mappings)
|
||||
{
|
||||
Mappings.Add(new ColumnMappingItem
|
||||
{
|
||||
RequiredColumn = kv.Key ?? string.Empty,
|
||||
MatchedColumn = kv.Value ?? string.Empty,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
namespace AI.Models.SpecialMessages
|
||||
{
|
||||
/// <summary>
|
||||
/// 特殊消息接口 - 所有特殊消息类型的基接口
|
||||
/// </summary>
|
||||
public interface ISpecialMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 特殊消息的唯一标识符
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 特殊消息类型名称
|
||||
/// </summary>
|
||||
string TypeName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要实时更新
|
||||
/// </summary>
|
||||
bool IsLive { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AI.Models.SpecialMessages
|
||||
{
|
||||
/// <summary>
|
||||
/// 表格数据预览行 - 一行单元格
|
||||
/// </summary>
|
||||
public class TableDataRow : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>该行单元格(与列顺序一致)</summary>
|
||||
public ObservableCollection<string> Cells { get; } = new ObservableCollection<string>();
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
protected void OnPropertyChanged(string propertyName) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表格数据展示特殊消息 - 用于导入数据等在聊天流中做数据预览
|
||||
/// </summary>
|
||||
public class TableDataMessage : ISpecialMessage, INotifyPropertyChanged
|
||||
{
|
||||
private string _title = "数据预览";
|
||||
private int _totalRowCount;
|
||||
private int _maxPreviewRows = 50;
|
||||
|
||||
/// <summary>消息唯一标识符</summary>
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>类型名称</summary>
|
||||
public string TypeName => "Table";
|
||||
|
||||
/// <summary>不需要实时更新</summary>
|
||||
public bool IsLive => false;
|
||||
|
||||
/// <summary>标题(如“数据预览”)</summary>
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
/// <summary>列名(表头)</summary>
|
||||
public ObservableCollection<string> ColumnNames { get; } = new ObservableCollection<string>();
|
||||
|
||||
/// <summary>预览行数据</summary>
|
||||
public ObservableCollection<TableDataRow> Rows { get; } = new ObservableCollection<TableDataRow>();
|
||||
|
||||
/// <summary>数据总行数(用于显示“共 N 行,仅显示前 M 行”)</summary>
|
||||
public int TotalRowCount
|
||||
{
|
||||
get => _totalRowCount;
|
||||
set => SetProperty(ref _totalRowCount, value);
|
||||
}
|
||||
|
||||
/// <summary>最多预览行数</summary>
|
||||
public int MaxPreviewRows
|
||||
{
|
||||
get => _maxPreviewRows;
|
||||
set => SetProperty(ref _maxPreviewRows, value);
|
||||
}
|
||||
|
||||
/// <summary>是否有多余行未显示</summary>
|
||||
public bool HasMoreRows => TotalRowCount > Rows.Count;
|
||||
|
||||
/// <summary>摘要文案(如“共 100 行,仅显示前 50 行”)</summary>
|
||||
public string SummaryText => $"共 {TotalRowCount} 行,仅显示前 {Rows.Count} 行";
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(field, value))
|
||||
return false;
|
||||
field = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
if (propertyName == nameof(TotalRowCount) || propertyName == nameof(MaxPreviewRows))
|
||||
{
|
||||
OnPropertyChanged(nameof(HasMoreRows));
|
||||
OnPropertyChanged(nameof(SummaryText));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>从列名与行数据批量填充(每行为字符串数组,与列顺序一致)</summary>
|
||||
public void SetData(IEnumerable<string> columnNames, IEnumerable<IEnumerable<string>> rows, int? totalRowCount = null)
|
||||
{
|
||||
ColumnNames.Clear();
|
||||
foreach (var c in columnNames)
|
||||
ColumnNames.Add(c ?? string.Empty);
|
||||
|
||||
Rows.Clear();
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var r = new TableDataRow();
|
||||
foreach (var cell in row)
|
||||
r.Cells.Add(cell?.ToString() ?? string.Empty);
|
||||
Rows.Add(r);
|
||||
}
|
||||
|
||||
TotalRowCount = totalRowCount ?? Rows.Count;
|
||||
OnPropertyChanged(nameof(HasMoreRows));
|
||||
OnPropertyChanged(nameof(SummaryText));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AI.Models.SpecialMessages
|
||||
{
|
||||
/// <summary>
|
||||
/// 工作流状态特殊消息 - 在聊天流中显示工作流进度
|
||||
/// </summary>
|
||||
public class WorkflowStatusMessage : ISpecialMessage, INotifyPropertyChanged
|
||||
{
|
||||
private string _title = "工作流进度";
|
||||
private ObservableCollection<WorkflowStepModel> _steps = new();
|
||||
|
||||
/// <summary>
|
||||
/// 消息唯一标识符
|
||||
/// </summary>
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>
|
||||
/// 类型名称
|
||||
/// </summary>
|
||||
public string TypeName => "WorkflowStatus";
|
||||
|
||||
/// <summary>
|
||||
/// 需要实时更新
|
||||
/// </summary>
|
||||
public bool IsLive => true;
|
||||
|
||||
/// <summary>
|
||||
/// 工作流标题
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工作流步骤列表
|
||||
/// </summary>
|
||||
public ObservableCollection<WorkflowStepModel> Steps
|
||||
{
|
||||
get => _steps;
|
||||
set => SetProperty(ref _steps, value);
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(field, value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
field = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace AI.Models.Store
|
||||
{
|
||||
public static class ConversationEntryMapper
|
||||
{
|
||||
public static List<ConversationEntryDto> ToDtoList(IEnumerable<ConversationEntry>? entries)
|
||||
{
|
||||
if (entries == null)
|
||||
{
|
||||
return new List<ConversationEntryDto>();
|
||||
}
|
||||
|
||||
return entries.Select(e =>
|
||||
{
|
||||
if (e is TextConversationEntry t)
|
||||
{
|
||||
return new ConversationEntryDto
|
||||
{
|
||||
Kind = t.Kind,
|
||||
Role = t.Role.ToString(),
|
||||
Content = t.Content
|
||||
};
|
||||
}
|
||||
|
||||
if (e is SpecialConversationEntry s)
|
||||
{
|
||||
return new ConversationEntryDto
|
||||
{
|
||||
Kind = s.Kind,
|
||||
Type = s.Type,
|
||||
Payload = s.Payload
|
||||
};
|
||||
}
|
||||
|
||||
return new ConversationEntryDto { Kind = e?.Kind ?? string.Empty };
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public static List<ConversationEntry> ToEntryList(IEnumerable<ConversationEntryDto>? entries)
|
||||
{
|
||||
var list = new List<ConversationEntry>();
|
||||
if (entries == null)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
foreach (var e in entries)
|
||||
{
|
||||
if (e == null) continue;
|
||||
|
||||
if (string.Equals(e.Kind, "Text", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var role = AuthorRole.User;
|
||||
if (!string.IsNullOrEmpty(e.Role) && Enum.TryParse<AuthorRole>(e.Role, true, out var r))
|
||||
{
|
||||
role = r;
|
||||
}
|
||||
list.Add(new TextConversationEntry(role, e.Content ?? string.Empty));
|
||||
}
|
||||
else if (string.Equals(e.Kind, "Special", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
list.Add(new SpecialConversationEntry(e.Type ?? string.Empty, e.Payload ?? string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
namespace AI.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 工作流步骤状态
|
||||
/// </summary>
|
||||
public enum WorkflowStepStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 待执行
|
||||
/// </summary>
|
||||
Pending,
|
||||
|
||||
/// <summary>
|
||||
/// 执行中
|
||||
/// </summary>
|
||||
Running,
|
||||
|
||||
/// <summary>
|
||||
/// 已完成
|
||||
/// </summary>
|
||||
Completed,
|
||||
|
||||
/// <summary>
|
||||
/// 失败
|
||||
/// </summary>
|
||||
Failed,
|
||||
|
||||
/// <summary>
|
||||
/// 已跳过(被回溯清除)
|
||||
/// </summary>
|
||||
Skipped
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SemanticKernel;
|
||||
using AI.AgentIntegration;
|
||||
|
||||
namespace AI.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 导入相关功能插件(井点 / 曲线等从文件导入)
|
||||
/// </summary>
|
||||
/// <param name="controller">应用程序控制器实例</param>
|
||||
public class ImportPlugin(IAppController controller)
|
||||
{
|
||||
private readonly IAppController controller = controller;
|
||||
|
||||
/// <summary>
|
||||
/// 从指定路径导入井点数据
|
||||
/// </summary>
|
||||
/// <param name="path">井点数据文件路径</param>
|
||||
/// <returns>成功或失败消息</returns>
|
||||
[KernelFunction]
|
||||
[Description("从指定路径导入井点数据")]
|
||||
public async Task<string> ImportWellPointsAsync(
|
||||
[Description("井点数据文件路径")] string path)
|
||||
{
|
||||
var result = await controller.ExecuteAsync(AppAction.CreateImportWellPoints(path));
|
||||
return result.Success ? "井点导入成功" : result.Message ?? "井点导入失败";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定路径导入井曲线数据
|
||||
/// </summary>
|
||||
/// <param name="path">井曲线数据文件路径</param>
|
||||
/// <returns>成功或失败消息</returns>
|
||||
[KernelFunction]
|
||||
[Description("从指定路径导入井曲线数据")]
|
||||
public async Task<string> ImportWellCurvesAsync(
|
||||
[Description("井曲线数据文件路径")] string path)
|
||||
{
|
||||
var result = await controller.ExecuteAsync(AppAction.CreateImportWellCurves(path));
|
||||
return result.Success ? "曲线导入成功" : result.Message ?? "曲线导入失败";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
using Microsoft.SemanticKernel;
|
||||
using AI.KnowledgeBase;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AI.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 知识库插件:仅负责根据查询返回知识库内容,不依赖会话或 Store。
|
||||
/// 将结果写入当前会话 Store 由 <see cref="Filter.KnowledgeBaseStoreFilter"/> 在调用完成后统一处理。
|
||||
/// </summary>
|
||||
/// <param name="knowledgeBase">知识库实例</param>
|
||||
public class KnowledgeBasePlugin(IKnowledgeBase knowledgeBase)
|
||||
{
|
||||
private readonly IKnowledgeBase knowledgeBase = knowledgeBase;
|
||||
|
||||
/// <summary>
|
||||
/// 查询知识库中的信息
|
||||
/// </summary>
|
||||
/// <param name="query">查询语句</param>
|
||||
/// <returns>返回匹配的知识条目</returns>
|
||||
[KernelFunction]
|
||||
[Description("当需要回答有关特定主题的知识时,搜索知识库。")]
|
||||
[return: Description("从知识库中检索到的相关信息,用于回答用户的问题。")]
|
||||
public async Task<string> QueryKnowledgeBaseAsync(string query)
|
||||
{
|
||||
return await knowledgeBase.SearchKnowledgeAsync(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,209 @@
|
||||
using System.Linq;
|
||||
using AI.Models;
|
||||
|
||||
namespace AI.Service
|
||||
{
|
||||
/// <summary>
|
||||
/// 管理多个聊天会话;会话存储以 YAML 文件持久化到 Sessions 目录。
|
||||
/// </summary>
|
||||
public class ChatSessionManager
|
||||
{
|
||||
private readonly Dictionary<string, ChatSession> _sessions = new();
|
||||
private readonly object _lock = new object();
|
||||
private ChatSession? _currentSession;
|
||||
private readonly SessionStorage _storage;
|
||||
|
||||
public ChatSessionManager(SessionStorage? sessionStorage = null)
|
||||
{
|
||||
_storage = sessionStorage ?? new SessionStorage();
|
||||
LoadSessionsFromDisk();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 会话列表变化事件
|
||||
/// </summary>
|
||||
public event EventHandler? SessionsChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 当前会话变化事件
|
||||
/// </summary>
|
||||
public event EventHandler<ChatSession?>? CurrentSessionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有会话(按更新时间倒序)
|
||||
/// </summary>
|
||||
public IEnumerable<ChatSession> GetAllSessions()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _sessions.Values.OrderByDescending(s => s.UpdatedAt).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取会话
|
||||
/// </summary>
|
||||
public ChatSession? GetSession(string sessionId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _sessions.TryGetValue(sessionId, out var session) ? session : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置当前活跃会话
|
||||
/// </summary>
|
||||
public ChatSession? CurrentSession
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _currentSession;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_currentSession != value)
|
||||
{
|
||||
_currentSession = value;
|
||||
CurrentSessionChanged?.Invoke(this, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建新会话并设置为当前会话;会订阅存储变更并持久化到 YAML 文件。
|
||||
/// </summary>
|
||||
public ChatSession CreateSession(string? title = null)
|
||||
{
|
||||
var session = new ChatSession(title: title);
|
||||
session.Store.StoreChanged += (_, _) => SaveSession(session);
|
||||
lock (_lock)
|
||||
{
|
||||
_sessions[session.Id] = session;
|
||||
}
|
||||
SaveSession(session);
|
||||
CurrentSession = session;
|
||||
SessionsChanged?.Invoke(this, EventArgs.Empty);
|
||||
return session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将会话保存到 YAML 文件
|
||||
/// </summary>
|
||||
public void SaveSession(ChatSession session)
|
||||
{
|
||||
if (session == null) return;
|
||||
try
|
||||
{
|
||||
_storage.Save(session);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 持久化失败不抛,避免影响主流程
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除会话(同时删除对应 YAML 文件)
|
||||
/// </summary>
|
||||
public bool DeleteSession(string sessionId)
|
||||
{
|
||||
ChatSession? deletedSession = null;
|
||||
lock (_lock)
|
||||
{
|
||||
if (_sessions.TryGetValue(sessionId, out deletedSession))
|
||||
{
|
||||
_sessions.Remove(sessionId);
|
||||
if (_currentSession?.Id == sessionId)
|
||||
{
|
||||
_currentSession = _sessions.Values.OrderByDescending(s => s.UpdatedAt).FirstOrDefault();
|
||||
CurrentSessionChanged?.Invoke(this, _currentSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deletedSession != null)
|
||||
{
|
||||
try { _storage.Delete(sessionId); } catch { }
|
||||
SessionsChanged?.Invoke(this, EventArgs.Empty);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换当前会话
|
||||
/// </summary>
|
||||
public bool SwitchToSession(string sessionId)
|
||||
{
|
||||
var session = GetSession(sessionId);
|
||||
if (session != null)
|
||||
{
|
||||
CurrentSession = session;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前活跃会话数量
|
||||
/// </summary>
|
||||
public int SessionCount
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _sessions.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查会话是否存在
|
||||
/// </summary>
|
||||
public bool SessionExists(string sessionId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _sessions.ContainsKey(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSessionsFromDisk()
|
||||
{
|
||||
try
|
||||
{
|
||||
var loaded = _storage.LoadAll().ToList();
|
||||
if (loaded.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var session in loaded)
|
||||
{
|
||||
session.Store.StoreChanged += (_, _) => SaveSession(session);
|
||||
_sessions[session.Id] = session;
|
||||
}
|
||||
_currentSession = loaded.FirstOrDefault();
|
||||
if (_currentSession != null)
|
||||
{
|
||||
CurrentSessionChanged?.Invoke(this, _currentSession);
|
||||
}
|
||||
}
|
||||
SessionsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 加载失败则保持空列表,后续可新建会话
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using AI.Interface;
|
||||
using AI.Models.Form;
|
||||
|
||||
namespace AI.Service
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认表单注册表,内置「加载散点文件」等表单定义。
|
||||
/// </summary>
|
||||
public class FormRegistry : IFormRegistry
|
||||
{
|
||||
private readonly Dictionary<string, FormDefinition> _forms = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public FormRegistry()
|
||||
{
|
||||
RegisterLoadXyzForm();
|
||||
}
|
||||
|
||||
private void RegisterLoadXyzForm()
|
||||
{
|
||||
_forms["gridding-load-xyz"] = new FormDefinition
|
||||
{
|
||||
Id = "gridding-load-xyz",
|
||||
Title = "加载散点文件",
|
||||
SubmitTarget = "GriddingModuleLoadXyz",
|
||||
SubmitLabel = "加载",
|
||||
Fields = new List<FormField>
|
||||
{
|
||||
new FormField
|
||||
{
|
||||
Id = "path",
|
||||
Label = "文件路径",
|
||||
Type = FormFieldType.FilePath,
|
||||
Required = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public FormDefinition? GetForm(string formId)
|
||||
{
|
||||
return _forms.TryGetValue(formId ?? string.Empty, out var def) ? def : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<FormDefinition> GetAll()
|
||||
{
|
||||
return _forms.Values;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AI.Interface;
|
||||
using AI.Models;
|
||||
using AI.Models.Form;
|
||||
using AI.ViewModels;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace AI.Service
|
||||
{
|
||||
/// <summary>
|
||||
/// 表单请求通知实现:收到请求后在 UI 线程向当前会话插入表单消息。
|
||||
/// </summary>
|
||||
public class FormRequestNotifier : IFormRequestNotifier
|
||||
{
|
||||
private readonly MainWindowViewModel _mainViewModel;
|
||||
private readonly IFormRegistry _registry;
|
||||
private readonly Dictionary<string, Action<ChatSession?>> _handlers;
|
||||
|
||||
public const string FormIdLoadXyz = "gridding-load-xyz";
|
||||
public const string FormIdGriddingParams = "gridding-parameters";
|
||||
|
||||
public FormRequestNotifier(MainWindowViewModel mainViewModel, IFormRegistry registry)
|
||||
{
|
||||
_mainViewModel = mainViewModel ?? throw new ArgumentNullException(nameof(mainViewModel));
|
||||
_registry = registry ?? throw new ArgumentNullException(nameof(registry));
|
||||
|
||||
_handlers = new Dictionary<string, Action<ChatSession?>>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[FormIdLoadXyz] = ShowXyzLoadCard,
|
||||
[FormIdGriddingParams] = ShowGriddingParamCard,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RequestForm(string formId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(formId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var targetSession = CurrentSessionContext.Current;
|
||||
|
||||
if (_handlers.TryGetValue(formId, out var handler))
|
||||
{
|
||||
handler(targetSession);
|
||||
return;
|
||||
}
|
||||
|
||||
var definition = _registry.GetForm(formId);
|
||||
if (definition == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() => _mainViewModel.AddFormMessage(definition, targetSession));
|
||||
}
|
||||
|
||||
private void ShowXyzLoadCard(ChatSession? session)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => _mainViewModel.AddXyzLoadCardMessage(session));
|
||||
}
|
||||
|
||||
private void ShowGriddingParamCard(ChatSession? session)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => _mainViewModel.AddGriddingParamCardMessage(session));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
using AI.AgentIntegration;
|
||||
using AI.Interface;
|
||||
using AI.Service;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using AI.ViewModels;
|
||||
|
||||
namespace AI
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static void AddCommonServices(this IServiceCollection collection)
|
||||
{
|
||||
// 注册会话管理器
|
||||
collection.AddSingleton<ChatSessionManager>();
|
||||
|
||||
// 注册聊天后端
|
||||
collection.AddSingleton<IChatBackend, KernelService>();
|
||||
|
||||
// 注册主窗口 ViewModel
|
||||
collection.AddSingleton<MainWindowViewModel>();
|
||||
|
||||
// 注册表单注册表与表单通知器
|
||||
collection.AddSingleton<IAppController>(_ =>
|
||||
AppControllerHolder.Instance ?? new NoOpAppController());
|
||||
collection.AddSingleton<IFormRegistry, FormRegistry>();
|
||||
collection.AddSingleton<IFormRequestNotifier, FormRequestNotifier>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using AI.ViewModels;
|
||||
using Control = Avalonia.Controls.Control;
|
||||
|
||||
namespace AI
|
||||
{
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
|
||||
public Control? Build(object? data)
|
||||
{
|
||||
if (data is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
|
||||
var type = Type.GetType(name);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
var control = (Control)Activator.CreateInstance(type)!;
|
||||
control.DataContext = data;
|
||||
return control;
|
||||
}
|
||||
|
||||
return new TextBlock { Text = "Not Found: " + name };
|
||||
}
|
||||
|
||||
public bool Match(object? data)
|
||||
{
|
||||
return data is ViewModelBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
using AI.Models;
|
||||
|
||||
namespace AI.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 会话列表项的 ViewModel
|
||||
/// </summary>
|
||||
public class ChatSessionItemViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ChatSession _session;
|
||||
private bool _isSelected;
|
||||
|
||||
public ChatSessionItemViewModel(ChatSession session)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 会话ID
|
||||
/// </summary>
|
||||
public string Id => _session.Id;
|
||||
|
||||
/// <summary>
|
||||
/// 会话标题
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get => _session.Title;
|
||||
set
|
||||
{
|
||||
if (_session.Title != value)
|
||||
{
|
||||
_session.Title = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt => _session.CreatedAt;
|
||||
|
||||
/// <summary>
|
||||
/// 最后更新时间
|
||||
/// </summary>
|
||||
public DateTime UpdatedAt => _session.UpdatedAt;
|
||||
|
||||
/// <summary>
|
||||
/// 消息数量
|
||||
/// </summary>
|
||||
public int MessageCount => _session.MessageCount;
|
||||
|
||||
/// <summary>
|
||||
/// 是否被选中
|
||||
/// </summary>
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取底层会话对象
|
||||
/// </summary>
|
||||
public ChatSession Session => _session;
|
||||
|
||||
/// <summary>
|
||||
/// 更新显示信息(当会话变化时调用)
|
||||
/// </summary>
|
||||
public void Refresh()
|
||||
{
|
||||
OnPropertyChanged(nameof(Title));
|
||||
OnPropertyChanged(nameof(UpdatedAt));
|
||||
OnPropertyChanged(nameof(MessageCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,93 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using AI.ViewModels;
|
||||
|
||||
namespace AI.Views
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private bool _isUserAtBottom = true; // 跟踪用户是否在底部
|
||||
private const double ScrollThreshold = 50.0; // 距离底部的阈值(像素)
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += MainWindow_DataContextChanged;
|
||||
}
|
||||
|
||||
private void MainWindow_DataContextChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (DataContext is MainWindowViewModel vm)
|
||||
{
|
||||
vm.RequestScrollToBottom += ViewModel_RequestScrollToBottom;
|
||||
|
||||
// 订阅文件选择请求事件
|
||||
vm.RequestFileSelection += async () =>
|
||||
{
|
||||
var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "选择文件",
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = new[] { FilePickerFileTypes.All }
|
||||
});
|
||||
return files;
|
||||
};
|
||||
}
|
||||
|
||||
// 初始化滚动视图的滚动事件监听
|
||||
if (ChatMessagesScrollViewer != null)
|
||||
{
|
||||
ChatMessagesScrollViewer.ScrollChanged += ChatMessagesScrollViewer_ScrollChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void ChatMessagesScrollViewer_ScrollChanged(object? sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (sender is ScrollViewer scrollViewer)
|
||||
{
|
||||
// 检查用户是否在底部(考虑一个小的阈值,避免浮点数精度问题)
|
||||
var offset = scrollViewer.Offset.Y;
|
||||
var extent = scrollViewer.Extent.Height;
|
||||
var viewport = scrollViewer.Viewport.Height;
|
||||
|
||||
// 处理边界情况:如果内容高度小于视口高度,认为在底部
|
||||
if (extent <= viewport)
|
||||
{
|
||||
_isUserAtBottom = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var distanceFromBottom = extent - (offset + viewport);
|
||||
|
||||
// 如果距离底部小于阈值,认为用户在底部
|
||||
_isUserAtBottom = distanceFromBottom <= ScrollThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewModel_RequestScrollToBottom()
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
// 只在用户已经在底部时才自动滚动
|
||||
// 这样如果用户向上滚动查看历史消息,就不会被强制拉回底部
|
||||
if (_isUserAtBottom)
|
||||
{
|
||||
ChatMessagesScrollViewer.ScrollToEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDeleteSessionClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button && button.Tag is string sessionId)
|
||||
{
|
||||
if (DataContext is MainWindowViewModel vm)
|
||||
{
|
||||
vm.DeleteSessionCommand.Execute(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AI.Workflow
|
||||
{
|
||||
/// <summary>
|
||||
/// 规划结果
|
||||
/// </summary>
|
||||
public class Plan
|
||||
{
|
||||
/// <summary>
|
||||
/// 规划唯一标识符
|
||||
/// </summary>
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>
|
||||
/// 规划目标
|
||||
/// </summary>
|
||||
public string Goal { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 规划步骤列表
|
||||
/// </summary>
|
||||
public List<PlanStep> Steps { get; } = new List<PlanStep>();
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规划步骤
|
||||
/// </summary>
|
||||
public class PlanStep
|
||||
{
|
||||
/// <summary>
|
||||
/// 步骤唯一标识符
|
||||
/// </summary>
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>
|
||||
/// 步骤序号
|
||||
/// </summary>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 步骤描述
|
||||
/// </summary>
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 步骤状态
|
||||
/// </summary>
|
||||
public PlanStepStatus Status { get; set; } = PlanStepStatus.Pending;
|
||||
|
||||
/// <summary>
|
||||
/// 执行结果
|
||||
/// </summary>
|
||||
public string? Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始执行时间
|
||||
/// </summary>
|
||||
public DateTime? StartedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 完成时间
|
||||
/// </summary>
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规划步骤状态
|
||||
/// </summary>
|
||||
public enum PlanStepStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 待执行
|
||||
/// </summary>
|
||||
Pending,
|
||||
|
||||
/// <summary>
|
||||
/// 执行中
|
||||
/// </summary>
|
||||
Running,
|
||||
|
||||
/// <summary>
|
||||
/// 已完成
|
||||
/// </summary>
|
||||
Completed,
|
||||
|
||||
/// <summary>
|
||||
/// 执行失败
|
||||
/// </summary>
|
||||
Failed
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
namespace AI.Workflow
|
||||
{
|
||||
/// <summary>
|
||||
/// 工作流状态
|
||||
/// </summary>
|
||||
public enum WorkflowStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 未开始
|
||||
/// </summary>
|
||||
NotStarted,
|
||||
|
||||
/// <summary>
|
||||
/// 运行中
|
||||
/// </summary>
|
||||
Running,
|
||||
|
||||
/// <summary>
|
||||
/// 已完成
|
||||
/// </summary>
|
||||
Completed,
|
||||
|
||||
/// <summary>
|
||||
/// 执行失败
|
||||
/// </summary>
|
||||
Failed,
|
||||
|
||||
/// <summary>
|
||||
/// 已暂停
|
||||
/// </summary>
|
||||
Paused,
|
||||
|
||||
/// <summary>
|
||||
/// 已取消
|
||||
/// </summary>
|
||||
Cancelled
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ModelId": "deepseek-v3.2",
|
||||
"ApiKey": "your-api-key-here",
|
||||
"Endpoint": "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue