Commit 2f8390cb authored by 段廷瑞's avatar 段廷瑞

feat: 对话上报功能,用于对话分析与监控

parent bf290b43
No preview for this file type
This diff is collapsed.
......@@ -190,7 +190,7 @@ namespace Tools.MyAgent.Host
private sealed class LaunchOptions
{
public string Product;
public string User = "访客";
public string User = ""; // 启动参数 --user;默认空(未传 = 空串,展示层兜底"访客",上报记录原样透传)
public string ProjectRoot = "";
public string KnowledgeBaseRoot = "";
public string KnowledgeBaseScope = "";
......
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Newtonsoft.Json.Linq;
using Tools.MyAgent;
using Tools.MyAgent.Config;
using Tools.MyAgent.Support;
// 探针 4(2026-10-19):Agent日志回答区空白 —— RichEdit 真实渲染裁决
// STA 线程实例化 RichTextBox,用库里最新一条 AnswerText 走 BuildChatRtf 渲染,读回 .Text 验证解析结果
internal static class Program
{
private static int Main()
{
Console.OutputEncoding = Encoding.UTF8;
try
{
string cfgPath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"..\..\..\AppHome\Agent\config\AgentConfig.json"));
string plain = SecretProtector.Unprotect(JObject.Parse(File.ReadAllText(cfgPath)).Value<string>("RunDbConnection") ?? "");
string answer = null;
using (var conn = new SqlConnection(plain))
{
conn.Open();
using (var cmd = new SqlCommand("SELECT TOP 1 ISNULL(AnswerText, N'') FROM dbo.Agent_RunLog ORDER BY Id DESC", conn))
answer = Convert.ToString(cmd.ExecuteScalar());
}
Console.WriteLine("[1] DB AnswerText: len=" + (answer == null ? -1 : answer.Length));
int textLen = -1; string shown = ""; string err = null;
var t = new Thread(() =>
{
try
{
var box = new RichTextBox { Width = 600, Height = 400 };
var handle = box.Handle; // 强制创建句柄
var msg = new ChatMessage { Role = "assistant", Content = answer };
string rtf = MarkdownRenderer.BuildChatRtf(new List<ChatMessage> { msg }, null, "助手", "用户");
Console.WriteLine("[2] RTF len=" + rtf.Length + " hasHeader=" + rtf.StartsWith("{\\rtf1"));
box.Rtf = rtf;
textLen = box.Text.Length;
shown = box.Text;
}
catch (Exception ex) { err = ex.GetType().Name + ": " + ex.Message; }
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();
if (err != null) { Console.WriteLine("[3] RENDER FAIL: " + err); return 2; }
Console.WriteLine("[3] RichEdit 解析后 box.Text len=" + textLen + " content=[" + shown + "]");
Console.WriteLine(textLen > 0 ? "PASS(渲染管线正常 ⇒ 窗口空白 = 运行的是旧 DLL 或别的断点)" : "FAIL(RichEdit 解析为空 ⇒ 渲染管线有真问题)");
return textLen > 0 ? 0 : 3;
}
catch (Exception ex) { Console.WriteLine("FAIL: " + ex.Message); return 1; }
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A1B2C3D4-E5F6-4A7B-8C9D-0E1F2A3B4C5D}</ProjectGuid>
<OutputType>Exe</OutputType>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<LangVersion>7.3</LangVersion>
<OutputPath>..\Tools.Agent\bin\Debug\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="Newtonsoft.Json">
<HintPath>..\Tools.Agent\bin\Debug\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Tools.Agent">
<HintPath>..\Tools.Agent\bin\Debug\Tools.Agent.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
......@@ -305,6 +305,7 @@ namespace Tools.MyAgent.Agent
CostEstimator.Configure(cfg.CustomPrices); // R6 费用估算:应用自定义价格表覆盖
AgentTelemetry.Configure(cfg.TelemetryEnabled, cfg.ObservabilityLevel); // 可观测性开关与隐私级别
AgentRunDbReporter.ConfigureFromConfig(cfg); // 对话上报(agent_analysis_plan.md v1.3):连接串/表名随配置生效
// P0 连接串脱敏 / P3 工具权限:预存连接与角色规则随配置注入
DbConnections = cfg.DbConnections ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
_toolRoleAllow = cfg.ToolRoleAllow ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
......@@ -360,6 +361,9 @@ namespace Tools.MyAgent.Agent
if (_runScope != null)
{
_runScope.RunId = _diagnosticSessionId;
_runScope.UserName = CurrentUser ?? ""; // 启动参数 --user 原样透传(空 = 空串,展示层兜底不回写)
if (!string.IsNullOrEmpty(_currentQuestion))
_runScope.QuestionText = _currentQuestion; // 仅内存([JsonIgnore]),DB 上报用
if (AgentTelemetry.IsDetailed && !string.IsNullOrEmpty(_currentQuestion)) // §9:仅 detailed 存提问前 120 字
_runScope.QuestionPreview = _currentQuestion.Substring(0, Math.Min(120, _currentQuestion.Length));
}
......@@ -450,7 +454,12 @@ namespace Tools.MyAgent.Agent
if (!IsDisposed)
{
if (_runScope != null) { _runScope.ToolRounds = toolRounds; _runScope.AnswerKind = _answerKind.ToString(); }
if (_runScope != null)
{
_runScope.ToolRounds = toolRounds;
_runScope.AnswerKind = _answerKind.ToString();
_runScope.AnswerText = finalAnswer.ToString(); // 仅内存([JsonIgnore]),DB 上报用
}
AgentTelemetry.EndRun(_runScope, "success", "", null);
OnComplete?.Invoke();
if (_toolTrace.Count > 0)
......
......@@ -15,8 +15,8 @@ namespace Tools.MyAgent
/// <summary>配置、历史、索引、日志和输出的根目录。空值使用应用目录下的 Agent。</summary>
public string DataRoot { get; set; }
/// <summary>当前用户或角色,用于聊天展示、工具权限和数据库审计。</summary>
public string CurrentUser { get; set; } = "访客";
/// <summary>当前用户或角色,用于聊天展示、工具权限、数据库审计与对话上报记录。默认空(宿主未传即空串,展示层自行兜底"访客")。</summary>
public string CurrentUser { get; set; } = "";
/// <summary>宿主注入的诊断根目录;为空时由产品上下文 ProjectRoot 自动生成 source 根。</summary>
public IReadOnlyList<DiagnosticRoot> DiagnosticRoots { get; set; }
......@@ -106,6 +106,34 @@ namespace Tools.MyAgent
return new Tools.MyAgent.Forms.AgentAdminForm(null, days, allowDetailed, autoRefresh);
}
/// <summary>
/// 创建 Agent 日志查看窗体(agent_analysis_plan.md v1.3 / Q5 决议):只读查询对话上报记录。
/// <para>由宿主"实施工具"菜单直接打开;连接串/表名由宿主从 AgentConfig 读出传入(窗口自身不读配置文件)。
/// 连接串为空时仍返回窗体(查询时报错提示未配置)。</para>
/// </summary>
public Form CreateRunLogForm(string connection, string table = "Agent_RunLog")
{
return new Tools.MyAgent.Forms.AgentRunLogForm(connection, table);
}
/// <summary>
/// 读取本机默认位置(&lt;程序目录&gt;/Agent/config/AgentConfig.json)的 Agent 配置,
/// RunDbConnection 已解密为明文。供无上下文的工具模块(Activator 无参创建)自取上报配置;失败返回 null。
/// </summary>
public static Config.AgentConfig TryLoadLocalConfig()
{
try
{
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent", "config", "AgentConfig.json");
if (!File.Exists(path)) return null;
var cfg = Newtonsoft.Json.JsonConvert.DeserializeObject<Config.AgentConfig>(
File.ReadAllText(path, System.Text.Encoding.UTF8));
if (cfg != null) cfg.RunDbConnection = Config.SecretProtector.Unprotect(cfg.RunDbConnection);
return cfg;
}
catch { return null; }
}
private void EnsureDefaultResources()
{
Directory.CreateDirectory(DataRoot);
......
......@@ -222,5 +222,19 @@
/// <summary>看板 30s 自动刷新(默认关闭,避免常驻定时器拖住宿主)。</summary>
public bool AdminAutoRefresh { get; set; } = false;
// ══════════════ 高级项:对话上报(每轮一条记录落 SQL Server,供统计分析;见 agent_analysis_plan.md v1.3) ══════════════
/// <summary>对话上报总开关;关闭则不订阅 OnRunCompleted、不连库。</summary>
public bool RunDbEnabled { get; set; } = false;
/// <summary>
/// SQL Server 连接串(库由连接串决定,缺表自动创建 Agent_RunLog)。
/// <para>落盘时经 DPAPI 加密(见 SecretProtector),内存中为明文;设置页「高级 → 对话上报」可配置。</para>
/// </summary>
public string RunDbConnection { get; set; } = "";
/// <summary>上报目标表名(仅允许字母/数字/下划线,防注入;建表与 INSERT 均使用该名)。</summary>
public string RunDbTable { get; set; } = "Agent_RunLog";
}
}
This diff is collapsed.
This diff is collapsed.
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
......@@ -122,6 +122,7 @@ namespace Tools.MyAgent.Forms
BuildToolPermissionUi(); // P3:工具级权限(按角色)输入区
BuildDashboardTab(); // 可观测性:统计看板入口(替代隐晦的 /admin)
BuildRemoteKbUi(); // v4.1:远程知识库条目接口(高级 → 语义检索页底部)
BuildRunDbTab(); // 对话上报(agent_analysis_plan.md v1.3):高级 → 二级 Tab「对话上报」
// 回答形态不再提供设置页(v1.2):全部内部默认(见 AgentConfig 注释),降低设置页认知负担
BindConfig(); // 配置值回填控件(控件创建见 Designer.cs)
ApplyProviderMode(); // 布局联动:BaseUrl 行显隐
......@@ -210,6 +211,100 @@ namespace Tools.MyAgent.Forms
tabMain.TabPages.Add(tabDb);
}
// ══ 对话上报(agent_analysis_plan.md v1.3):高级 → 二级 Tab「对话上报」(程序化创建,避免侵入设计器) ══
private CheckBox chkRunDbEnabled;
private TextBox txtRunDbConn;
private TextBox txtRunDbTable;
private Button btnTestRunDb;
/// <summary>
/// 高级配置新增二级 Tab「对话上报」:每轮对话完成后自动上报一条记录到 SQL Server
/// (库由连接串决定,缺表自动创建 Agent_RunLog;cancel 不上报;提问/回答原文恒落库)。
/// </summary>
private void BuildRunDbTab()
{
var tabRunDb = new TabPage { Text = "对话上报", Padding = new Padding(3) };
chkRunDbEnabled = new CheckBox
{
Text = "启用对话上报(每轮对话完成后写入一条运行记录,客户取消的轮次不记录)",
Location = new Point(12, 12),
AutoSize = true,
};
var lblConn = new Label { Text = "SQL Server 连接串(库由连接串决定;保存后加密存储)", Location = new Point(12, 40), AutoSize = true };
txtRunDbConn = new TextBox
{
Location = new Point(12, 62),
Size = new Size(556, 46),
Multiline = true,
Font = new Font("Consolas", 9F),
};
var lblTable = new Label { Text = "目标表名(缺表自动创建;仅字母/数字/下划线)", Location = new Point(12, 116), AutoSize = true };
txtRunDbTable = new TextBox
{
Location = new Point(12, 138),
Size = new Size(240, 23),
Text = "Agent_RunLog",
Font = new Font("Consolas", 9F),
};
btnTestRunDb = new Button
{
Text = "测试连接",
Location = new Point(268, 136),
Size = new Size(96, 26),
};
btnTestRunDb.Click += new EventHandler(OnTestRunDb);
var hint = new Label
{
Text = "提问与回答原文会存入该库,用于统计分析与体验优化。查看入口:实施工具 → Agent日志。\r\n示例:Server=主机;Database=库名;User Id=账号;Password=密码;Trusted_Connection=False",
Location = new Point(12, 172),
AutoSize = false,
Size = new Size(556, 40),
ForeColor = Color.Gray,
};
tabRunDb.Controls.Add(chkRunDbEnabled);
tabRunDb.Controls.Add(lblConn);
tabRunDb.Controls.Add(txtRunDbConn);
tabRunDb.Controls.Add(lblTable);
tabRunDb.Controls.Add(txtRunDbTable);
tabRunDb.Controls.Add(btnTestRunDb);
tabRunDb.Controls.Add(hint);
tabAdvSub.TabPages.Add(tabRunDb);
}
/// <summary>测试连接:Open + SELECT 1,成功再预演建表(无建表权限时提示具体错误,不落任何数据)。</summary>
private void OnTestRunDb(object sender, EventArgs e)
{
try
{
string conn = txtRunDbConn.Text.Trim();
if (conn.Length == 0)
{
AgentUtils.Toast("请先填写连接串");
return;
}
string table = string.IsNullOrWhiteSpace(txtRunDbTable.Text) ? "Agent_RunLog" : txtRunDbTable.Text.Trim();
// 建表预演:复用 Reporter 的幂等建表(表已存在 = 无副作用;返回错误消息而非抛异常)
string error = AgentRunDbReporter.EnsureTableForTest(conn, table);
if (error == null)
{
// 2026-10-19 真机踩坑:连接串/表名已保存但「启用对话上报」未勾选 ⇒ 表里永远无记录(测试连接与开关无关)
if (!chkRunDbEnabled.Checked)
AgentUtils.Toast("连接成功,表已就绪:" + table + "\n⚠ 但「启用对话上报」未勾选,上报不会生效!", 6000);
else
AgentUtils.Toast("连接成功,建表/表已就绪:" + table, 3000);
}
else
AgentUtils.Toast("连接失败:" + error, 5000);
}
catch (Exception ex)
{
AgentUtils.Toast("连接失败:" + ex.Message, 5000);
}
}
// ══ P3:工具级权限(按角色) ══
private TextBox txtToolRoles;
......@@ -493,6 +588,11 @@ namespace Tools.MyAgent.Forms
cmbEmbModel.Text = _config.EmbeddingModel;
txtEmbKey.Text = _config.EmbeddingApiKey;
// 高级配置:对话上报(agent_analysis_plan.md v1.3)
chkRunDbEnabled.Checked = _config.RunDbEnabled;
txtRunDbConn.Text = _config.RunDbConnection ?? ""; // LoadConfig 已解密为明文
txtRunDbTable.Text = string.IsNullOrWhiteSpace(_config.RunDbTable) ? "Agent_RunLog" : _config.RunDbTable;
// 远程知识库(v4.1):URL 模板逐行回填
txtRemoteKb.Text = string.Join(Environment.NewLine, _config.RemoteKbEntries ?? new List<string>());
......@@ -955,6 +1055,10 @@ namespace Tools.MyAgent.Forms
_config.EmbeddingBaseUrl = txtEmbUrl.Text.Trim();
_config.EmbeddingModel = string.IsNullOrEmpty(cmbEmbModel.Text.Trim()) ? "bge-m3" : cmbEmbModel.Text.Trim();
_config.EmbeddingApiKey = txtEmbKey.Text.Trim();
// 高级配置:对话上报(agent_analysis_plan.md v1.3;连接串加密由 wMyAgent.SaveAgentConfig 投影层处理)
_config.RunDbEnabled = chkRunDbEnabled.Checked;
_config.RunDbConnection = txtRunDbConn.Text.Trim();
_config.RunDbTable = string.IsNullOrWhiteSpace(txtRunDbTable.Text) ? "Agent_RunLog" : txtRunDbTable.Text.Trim();
// 远程知识库(v4.1):每行一条 URL 模板,空行忽略
_config.RemoteKbEntries = (txtRemoteKb != null ? txtRemoteKb.Lines : new string[0])
.Select(l => (l ?? "").Trim())
......
This diff is collapsed.
......@@ -39,6 +39,15 @@ namespace Tools.MyAgent
/// <summary>提问前 120 字(仅 detailed 级别填充;basic 恒空 —— §9 隐私分级)。</summary>
public string QuestionPreview = "";
/// <summary>提问原文(仅内存,[JsonIgnore] 不进 jsonl —— 供 DB 上报用,落库由 AgentRunDbReporter 决定)。</summary>
[JsonIgnore] public string QuestionText = "";
/// <summary>回答原文(仅内存,[JsonIgnore] 不进 jsonl;success 路径由引擎填充,cancel/error 留空)。</summary>
[JsonIgnore] public string AnswerText = "";
/// <summary>当前用户(启动参数 --user 原样透传;仅内存,[JsonIgnore] 不进 jsonl,落库列 UserName)。</summary>
[JsonIgnore] public string UserName = "";
/// <summary>配置模型。</summary>
public string Model = "";
......
......@@ -122,6 +122,7 @@
<Compile Include="Config\AgentConfig.cs" />
<Compile Include="Config\RoleTemplateStore.cs" />
<Compile Include="Config\SecretProtector.cs" />
<Compile Include="Support\AgentRunDbReporter.cs" />
<Compile Include="Forms\AgentAdminForm.cs">
<SubType>Form</SubType>
</Compile>
......@@ -140,6 +141,12 @@
<Compile Include="Forms\AgentSettingsForm.Designer.cs">
<DependentUpon>AgentSettingsForm.cs</DependentUpon>
</Compile>
<Compile Include="Forms\AgentRunLogForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\AgentRunLogForm.Designer.cs">
<DependentUpon>AgentRunLogForm.cs</DependentUpon>
</Compile>
<Compile Include="Forms\DocLineViewer.cs">
<SubType>Form</SubType>
</Compile>
......@@ -187,6 +194,9 @@
<EmbeddedResource Include="Forms\AgentHistoryForm.resx">
<DependentUpon>AgentHistoryForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\AgentRunLogForm.resx">
<DependentUpon>AgentRunLogForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\AgentSettingsForm.resx">
<DependentUpon>AgentSettingsForm.cs</DependentUpon>
</EmbeddedResource>
......
......@@ -307,6 +307,7 @@ namespace Tools.MyAgent
cfg.ApiKey = SecretProtector.Unprotect(cfg.ApiKey);
cfg.EmbeddingApiKey = SecretProtector.Unprotect(cfg.EmbeddingApiKey);
cfg.BackupApiKey = SecretProtector.Unprotect(cfg.BackupApiKey);
cfg.RunDbConnection = SecretProtector.Unprotect(cfg.RunDbConnection); // 对话上报连接串(DPAPI 落盘)
if (cfg.DbConnections != null)
{
var plain = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
......@@ -401,6 +402,10 @@ namespace Tools.MyAgent
AdminRoles = cfg.AdminRoles,
DashboardDays = cfg.DashboardDays,
AdminAutoRefresh = cfg.AdminAutoRefresh,
// 对话上报(agent_analysis_plan.md v1.3;漏登记 ⇒ 设置页保存一次即被静默清空)
RunDbEnabled = cfg.RunDbEnabled,
RunDbConnection = SecretProtector.Protect(cfg.RunDbConnection),
RunDbTable = cfg.RunDbTable,
};
File.WriteAllText(AgentConfigFilePath, Newtonsoft.Json.JsonConvert.SerializeObject(toSave), System.Text.Encoding.UTF8);
}
......
......@@ -49,6 +49,7 @@ namespace Tools
this.CreateModule(Tools_imp, typeof(UI.wEmptyTable), "清空表", "tr:输入主表、自动清空关联表的工具"),
this.CreateModule(Tools_imp, typeof(UI.wExcelExportSetting), "Excel导出设置", "tr:Excel导出配置图形化版本"),
this.CreateModule(Tools_imp, typeof(UI.wLogViewer), "日志查看器", "tr:可以方便的查看系统日志"),
this.CreateModule(Tools_imp, typeof(Tools.MyAgent.Forms.AgentRunLogForm), "Agent日志", "tr:查看AI对话上报记录(按用户提问统计分析)"),
this.CreateModule(Tools_imp, typeof(UI.wTextFormatter), "格式化工具", "tr:SQL和JSON文本格式化工具"),
this.CreateModule(Tools_imp, typeof(UI.wTextDiff), "文本对比工具", "tr:行级文本差异对比,支持左右/上下布局"),
this.CreateModule(Tools_imp, typeof(DotNetVersionChecker.FrmVersion), "系统检测", "lxj:检测操作系统运行环境"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment