Commit 41a89411 authored by 段廷瑞's avatar 段廷瑞

feat: 支持分发模式(服务端统一配置apikey和知识库,其他用户自动拉取)

parent 0020a375
...@@ -78,3 +78,5 @@ bin ...@@ -78,3 +78,5 @@ bin
/AppHome/_backup /AppHome/_backup
/Tools.Agent.ProbeKbPref /Tools.Agent.ProbeKbPref
/working /working
/Tools.Agent/Defaults/Agent/config
/Tools.Agent.ProbeCenter
No preview for this file type
This diff is collapsed.
...@@ -9,6 +9,7 @@ using System.Threading; ...@@ -9,6 +9,7 @@ using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using Tools.MyAgent; using Tools.MyAgent;
using Tools.MyAgent.Diagnostics; using Tools.MyAgent.Diagnostics;
using Tools.MyAgent.Support;
namespace Tools.MyAgent.Host namespace Tools.MyAgent.Host
{ {
...@@ -47,6 +48,8 @@ namespace Tools.MyAgent.Host ...@@ -47,6 +48,8 @@ namespace Tools.MyAgent.Host
{ {
DataRoot = options.DataRoot, DataRoot = options.DataRoot,
CurrentUser = options.User, CurrentUser = options.User,
Mode = options.Mode,
IsAdmin = options.IsAdmin,
ProductContext = product, ProductContext = product,
}; };
...@@ -197,6 +200,8 @@ namespace Tools.MyAgent.Host ...@@ -197,6 +200,8 @@ namespace Tools.MyAgent.Host
public string LogRoot = ""; public string LogRoot = "";
public string ConfigRoot = ""; public string ConfigRoot = "";
public string DeploymentRoot = ""; public string DeploymentRoot = "";
public AgentRunMode? Mode;
public bool? IsAdmin;
public string DataRoot; public string DataRoot;
public int ParentPid; public int ParentPid;
public bool ValidateOnly; public bool ValidateOnly;
...@@ -206,7 +211,7 @@ namespace Tools.MyAgent.Host ...@@ -206,7 +211,7 @@ namespace Tools.MyAgent.Host
"Tools.Agent.Host.exe --protocol 1 --product <pdm|erp|mes> --user <user> " "Tools.Agent.Host.exe --protocol 1 --product <pdm|erp|mes> --user <user> "
+ "--data-root <path> [--project-root <path>] [--kb-root <path>] " + "--data-root <path> [--project-root <path>] [--kb-root <path>] "
+ "[--kb-scope <path>] [--log-root <path>] [--config-root <path>] [--deployment-root <path>] " + "[--kb-scope <path>] [--log-root <path>] [--config-root <path>] [--deployment-root <path>] "
+ "[--parent-pid <pid>]"; + "[--mode <custom|distribution>] [--is-admin <true|false>] [--parent-pid <pid>]";
public static bool TryParse(string[] args, out LaunchOptions options, out string error) public static bool TryParse(string[] args, out LaunchOptions options, out string error)
{ {
...@@ -272,6 +277,30 @@ namespace Tools.MyAgent.Host ...@@ -272,6 +277,30 @@ namespace Tools.MyAgent.Host
values.TryGetValue("log-root", out options.LogRoot); values.TryGetValue("log-root", out options.LogRoot);
values.TryGetValue("config-root", out options.ConfigRoot); values.TryGetValue("config-root", out options.ConfigRoot);
values.TryGetValue("deployment-root", out options.DeploymentRoot); values.TryGetValue("deployment-root", out options.DeploymentRoot);
if (values.TryGetValue("mode", out value) && !string.IsNullOrWhiteSpace(value))
{
if (value.Equals("custom", StringComparison.OrdinalIgnoreCase))
options.Mode = AgentRunMode.Custom;
else if (value.Equals("distribution", StringComparison.OrdinalIgnoreCase))
options.Mode = AgentRunMode.Distribution;
else
{
error = "mode 必须是 custom 或 distribution。";
return false;
}
}
if (values.TryGetValue("is-admin", out value))
{
bool isAdmin;
if (value == "1") isAdmin = true;
else if (value == "0") isAdmin = false;
else if (!bool.TryParse(value, out isAdmin))
{
error = "is-admin 必须是 true、false、1 或 0。";
return false;
}
options.IsAdmin = isAdmin;
}
if (values.TryGetValue("parent-pid", out value) && !int.TryParse(value, out options.ParentPid)) if (values.TryGetValue("parent-pid", out value) && !int.TryParse(value, out options.ParentPid))
{ {
error = "parent-pid 不是有效整数。"; error = "parent-pid 不是有效整数。";
......
...@@ -237,7 +237,7 @@ namespace Tools.MyAgent.Agent ...@@ -237,7 +237,7 @@ namespace Tools.MyAgent.Agent
_backupClient = new OpenAiCompatibleClient(); _backupClient = new OpenAiCompatibleClient();
_failover = new FailoverPolicy(_client, _backupClient); _failover = new FailoverPolicy(_client, _backupClient);
string root = string.IsNullOrEmpty(agentRoot) string root = string.IsNullOrEmpty(agentRoot)
? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent") ? global::Tools.MyAgent.Support.AgentCenterClient.AgentRootPath(AppDomain.CurrentDomain.BaseDirectory)
: Path.GetFullPath(agentRoot); : Path.GetFullPath(agentRoot);
_dialect = new DialectService(root); _dialect = new DialectService(root);
_recall = new KbRecall(_kb); // P3:本地融合召回器(构造无副作用,仅持有 KbService 引用) _recall = new KbRecall(_kb); // P3:本地融合召回器(构造无副作用,仅持有 KbService 引用)
......
...@@ -150,7 +150,7 @@ namespace Tools.MyAgent.Agent.Tools ...@@ -150,7 +150,7 @@ namespace Tools.MyAgent.Agent.Tools
{ {
try try
{ {
string root = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent"); string root = global::Tools.MyAgent.Support.AgentCenterClient.AgentRootPath(AppDomain.CurrentDomain.BaseDirectory);
string cfgPath = Path.Combine(root, "config", "AgentConfig.json"); string cfgPath = Path.Combine(root, "config", "AgentConfig.json");
if (File.Exists(cfgPath)) if (File.Exists(cfgPath))
{ {
......
...@@ -23,6 +23,18 @@ namespace Tools.MyAgent ...@@ -23,6 +23,18 @@ namespace Tools.MyAgent
/// <summary>宿主固定的产品上下文;空值表示未绑定产品线。</summary> /// <summary>宿主固定的产品上下文;空值表示未绑定产品线。</summary>
public AgentProductContext ProductContext { get; set; } public AgentProductContext ProductContext { get; set; }
/// <summary>
/// 运行模式(config_center_plan.md v2.2):Custom=自定义(现状零改动);Distribution=分发(配置中心)。
/// 缺省 null = 按 &lt;程序目录&gt;/Agent/config/center.json 是否存在自动判定;显式传入时以入参为准。
/// </summary>
public Support.AgentRunMode? Mode { get; set; }
/// <summary>
/// 是否管理员(config_center_plan.md v2.2):宿主登录体系已知时直接传入,优先于中心 AdminRoles 名单;
/// 缺省 null = 分发模式下按中心名单比对 CurrentUser(自定义模式恒按可设置处理)。
/// </summary>
public bool? IsAdmin { get; set; }
} }
/// <summary>其他 .NET Framework 项目引用 Tools.Agent 后使用的稳定入口。</summary> /// <summary>其他 .NET Framework 项目引用 Tools.Agent 后使用的稳定入口。</summary>
...@@ -31,6 +43,7 @@ namespace Tools.MyAgent ...@@ -31,6 +43,7 @@ namespace Tools.MyAgent
private readonly AgentRuntimeOptions _options; private readonly AgentRuntimeOptions _options;
private readonly AgentProductContext _productContext; private readonly AgentProductContext _productContext;
private readonly IReadOnlyList<DiagnosticRoot> _diagnosticRoots; private readonly IReadOnlyList<DiagnosticRoot> _diagnosticRoots;
private readonly Support.CenterResolution _center;
/// <summary>当前运行时诊断根目录快照;返回独立副本,避免外部修改。</summary> /// <summary>当前运行时诊断根目录快照;返回独立副本,避免外部修改。</summary>
public IReadOnlyList<DiagnosticRoot> DiagnosticRoots public IReadOnlyList<DiagnosticRoot> DiagnosticRoots
...@@ -62,18 +75,35 @@ namespace Tools.MyAgent ...@@ -62,18 +75,35 @@ namespace Tools.MyAgent
context.DiagnosticRoots = new[] { new DiagnosticRoot { Id = "source", Path = context.ProjectRoot, Kind = "source" } }; context.DiagnosticRoots = new[] { new DiagnosticRoot { Id = "source", Path = context.ProjectRoot, Kind = "source" } };
} }
_productContext = ProductContextValidator.CreateSnapshot(context); _productContext = ProductContextValidator.CreateSnapshot(context);
// 配置中心(config_center_plan.md P0):模式判定 + 分发模式拉取(内部三级降级,任何异常不上抛)
_center = Support.AgentCenterClient.Fetch(
AppDomain.CurrentDomain.BaseDirectory,
_options.Mode,
_options.IsAdmin,
_options.CurrentUser,
ProductLineId);
} }
/// <summary>配置中心解析结果(v2.2);自定义模式下仅承载 Mode/IsAdmin,其余字段为空。</summary>
public Support.CenterResolution Center => _center;
/// <summary>生效模式(入参优先 / center.json 自动判定)。</summary>
public Support.AgentRunMode CenterMode => _center != null ? _center.Mode : Support.AgentRunMode.Custom;
/// <summary>当前用户是否管理员(宿主入参 &gt; 中心名单;自定义模式恒 true = 现状人人可设置)。</summary>
public bool CenterIsAdmin => _center != null ? _center.IsAdmin : true;
/// <summary>实际数据根目录。</summary> /// <summary>实际数据根目录。</summary>
public string DataRoot => string.IsNullOrEmpty(_options.DataRoot) public string DataRoot => string.IsNullOrEmpty(_options.DataRoot)
? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent") ? Support.AgentCenterClient.AgentRootPath(AppDomain.CurrentDomain.BaseDirectory)
: Path.GetFullPath(_options.DataRoot); : Path.GetFullPath(_options.DataRoot);
/// <summary>创建完整聊天窗体,可作为顶级窗体显示或嵌入宿主容器。</summary> /// <summary>创建完整聊天窗体,可作为顶级窗体显示或嵌入宿主容器。</summary>
public wMyAgent CreateChatForm() public wMyAgent CreateChatForm()
{ {
EnsureDefaultResources(); EnsureDefaultResources();
return new wMyAgent(DataRoot, _options.CurrentUser, ProductContext, _diagnosticRoots); return new wMyAgent(DataRoot, _options.CurrentUser, ProductContext, _diagnosticRoots, _center);
} }
/// <summary>创建已配置输出目录的知识库服务,供无 UI 宿主自行设置知识库根目录。</summary> /// <summary>创建已配置输出目录的知识库服务,供无 UI 宿主自行设置知识库根目录。</summary>
...@@ -124,7 +154,8 @@ namespace Tools.MyAgent ...@@ -124,7 +154,8 @@ namespace Tools.MyAgent
{ {
try try
{ {
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent", "config", "AgentConfig.json"); Support.AgentCenterClient.MigrateLegacyLayout(AppDomain.CurrentDomain.BaseDirectory);
string path = Path.Combine(Support.AgentCenterClient.AgentRootPath(AppDomain.CurrentDomain.BaseDirectory), "config", "AgentConfig.json");
if (!File.Exists(path)) return null; if (!File.Exists(path)) return null;
var cfg = Newtonsoft.Json.JsonConvert.DeserializeObject<Config.AgentConfig>( var cfg = Newtonsoft.Json.JsonConvert.DeserializeObject<Config.AgentConfig>(
File.ReadAllText(path, System.Text.Encoding.UTF8)); File.ReadAllText(path, System.Text.Encoding.UTF8));
...@@ -143,7 +174,8 @@ namespace Tools.MyAgent ...@@ -143,7 +174,8 @@ namespace Tools.MyAgent
{ {
try try
{ {
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent", "config", "AgentConfig.json"); Support.AgentCenterClient.MigrateLegacyLayout(AppDomain.CurrentDomain.BaseDirectory);
string path = Path.Combine(Support.AgentCenterClient.AgentRootPath(AppDomain.CurrentDomain.BaseDirectory), "config", "AgentConfig.json");
var root = File.Exists(path) var root = File.Exists(path)
? Newtonsoft.Json.Linq.JObject.Parse(File.ReadAllText(path, System.Text.Encoding.UTF8)) ? Newtonsoft.Json.Linq.JObject.Parse(File.ReadAllText(path, System.Text.Encoding.UTF8))
: new Newtonsoft.Json.Linq.JObject(); : new Newtonsoft.Json.Linq.JObject();
...@@ -160,7 +192,7 @@ namespace Tools.MyAgent ...@@ -160,7 +192,7 @@ namespace Tools.MyAgent
private void EnsureDefaultResources() private void EnsureDefaultResources()
{ {
Directory.CreateDirectory(DataRoot); Directory.CreateDirectory(DataRoot);
string defaults = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent"); string defaults = Support.AgentCenterClient.AgentRootPath(AppDomain.CurrentDomain.BaseDirectory);
if (string.Equals(Path.GetFullPath(defaults).TrimEnd('\\'), DataRoot.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase) if (string.Equals(Path.GetFullPath(defaults).TrimEnd('\\'), DataRoot.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase)
|| !Directory.Exists(defaults)) return; || !Directory.Exists(defaults)) return;
CopyMissing(Path.Combine(defaults, "prompts"), Path.Combine(DataRoot, "prompts")); CopyMissing(Path.Combine(defaults, "prompts"), Path.Combine(DataRoot, "prompts"));
......
...@@ -521,7 +521,7 @@ namespace Tools.MyAgent.Forms ...@@ -521,7 +521,7 @@ namespace Tools.MyAgent.Forms
{ {
var dlg = new SaveFileDialog { Filter = "ZIP|*.zip", FileName = "agent_diag.zip" }; var dlg = new SaveFileDialog { Filter = "ZIP|*.zip", FileName = "agent_diag.zip" };
if (dlg.ShowDialog(this) != DialogResult.OK) return; if (dlg.ShowDialog(this) != DialogResult.OK) return;
string agentRoot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent"); string agentRoot = global::Tools.MyAgent.Support.AgentCenterClient.AgentRootPath(AppDomain.CurrentDomain.BaseDirectory);
string path = BackupHelper.BackupAgent(agentRoot, null, dlg.FileName); string path = BackupHelper.BackupAgent(agentRoot, null, dlg.FileName);
MessageBox.Show(this, string.IsNullOrEmpty(path) ? "导出失败" : "已导出:" + path, MessageBox.Show(this, string.IsNullOrEmpty(path) ? "导出失败" : "已导出:" + path,
"诊断包", MessageBoxButtons.OK, MessageBoxIcon.Information); "诊断包", MessageBoxButtons.OK, MessageBoxIcon.Information);
......
...@@ -39,7 +39,6 @@ ...@@ -39,7 +39,6 @@
this.cmbOutcome = new System.Windows.Forms.ComboBox(); this.cmbOutcome = new System.Windows.Forms.ComboBox();
this.btnRefresh = new System.Windows.Forms.Button(); this.btnRefresh = new System.Windows.Forms.Button();
this.btnExport = new System.Windows.Forms.Button(); this.btnExport = new System.Windows.Forms.Button();
this.btnConn = new System.Windows.Forms.Button();
this.splitMain = new System.Windows.Forms.SplitContainer(); this.splitMain = new System.Windows.Forms.SplitContainer();
this.dgv = new System.Windows.Forms.DataGridView(); this.dgv = new System.Windows.Forms.DataGridView();
this.tabDetails = new System.Windows.Forms.TabControl(); this.tabDetails = new System.Windows.Forms.TabControl();
...@@ -75,7 +74,6 @@ ...@@ -75,7 +74,6 @@
this.panelTop.Controls.Add(this.cmbOutcome); this.panelTop.Controls.Add(this.cmbOutcome);
this.panelTop.Controls.Add(this.btnRefresh); this.panelTop.Controls.Add(this.btnRefresh);
this.panelTop.Controls.Add(this.btnExport); this.panelTop.Controls.Add(this.btnExport);
this.panelTop.Controls.Add(this.btnConn);
this.panelTop.Dock = System.Windows.Forms.DockStyle.Top; this.panelTop.Dock = System.Windows.Forms.DockStyle.Top;
this.panelTop.Location = new System.Drawing.Point(0, 0); this.panelTop.Location = new System.Drawing.Point(0, 0);
this.panelTop.Name = "panelTop"; this.panelTop.Name = "panelTop";
...@@ -167,16 +165,6 @@ ...@@ -167,16 +165,6 @@
this.btnExport.UseVisualStyleBackColor = true; this.btnExport.UseVisualStyleBackColor = true;
this.btnExport.Click += new System.EventHandler(this.OnExportCsvClick); this.btnExport.Click += new System.EventHandler(this.OnExportCsvClick);
// //
// btnConn(窗口内数据库连接配置:IP/账号/密码/库下拉)
//
this.btnConn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnConn.Margin = new System.Windows.Forms.Padding(6, 4, 3, 0);
this.btnConn.Name = "btnConn";
this.btnConn.Size = new System.Drawing.Size(88, 28);
this.btnConn.Text = "连接配置";
this.btnConn.UseVisualStyleBackColor = true;
this.btnConn.Click += new System.EventHandler(this.OnConnConfigClick);
//
// splitMain // splitMain
// //
this.splitMain.Dock = System.Windows.Forms.DockStyle.Fill; this.splitMain.Dock = System.Windows.Forms.DockStyle.Fill;
...@@ -333,7 +321,6 @@ ...@@ -333,7 +321,6 @@
private System.Windows.Forms.ComboBox cmbOutcome; private System.Windows.Forms.ComboBox cmbOutcome;
private System.Windows.Forms.Button btnRefresh; private System.Windows.Forms.Button btnRefresh;
private System.Windows.Forms.Button btnExport; private System.Windows.Forms.Button btnExport;
private System.Windows.Forms.Button btnConn;
private System.Windows.Forms.SplitContainer splitMain; private System.Windows.Forms.SplitContainer splitMain;
private System.Windows.Forms.DataGridView dgv; private System.Windows.Forms.DataGridView dgv;
private System.Windows.Forms.TabControl tabDetails; private System.Windows.Forms.TabControl tabDetails;
......
...@@ -13,15 +13,15 @@ namespace Tools.MyAgent.Forms ...@@ -13,15 +13,15 @@ namespace Tools.MyAgent.Forms
/// <summary> /// <summary>
/// Agent 日志查看窗口(agent_analysis_plan.md v1.3 / Q5 决议):只读查询对话上报记录(Agent_RunLog), /// Agent 日志查看窗口(agent_analysis_plan.md v1.3 / Q5 决议):只读查询对话上报记录(Agent_RunLog),
/// 左列表 + 右详情(两个 Tab:「问答」= 提问+回答聊天式同页渲染;「指标」= 运行明细),支持日期 / 用户 / 结果过滤与 CSV 导出。 /// 左列表 + 右详情(两个 Tab:「问答」= 提问+回答聊天式同页渲染;「指标」= 运行明细),支持日期 / 用户 / 结果过滤与 CSV 导出。
/// <para>**独立窗口**:由宿主"实施工具"菜单直接打开(<see cref="AgentRuntime.CreateRunLogForm"/>), /// <para>**入口(2026-10-22)**:设置页「高级 → 对话上报 → 查看上报日志」按钮打开(连接取页面当前填写值);
/// 无任何聊天命令唤出;窗口侧永不写库。</para> /// 原实施工具菜单入口与窗口内"连接配置"按钮已移除——连接统一在设置页管理。</para>
/// <para>控件布局由 AgentRunLogForm.Designer.cs(设计器)实现;本文件仅含逻辑。</para> /// <para>控件布局由 AgentRunLogForm.Designer.cs(设计器)实现;本文件仅含逻辑。</para>
/// </summary> /// </summary>
public partial class AgentRunLogForm : Form public partial class AgentRunLogForm : Form
{ {
private const int MaxRows = 500; private const int MaxRows = 500;
/// <summary>当前上报连接(明文,窗口内可经"连接配置"切换)。</summary> /// <summary>当前上报连接(明文,由设置页「对话上报」传入,窗口内只读)。</summary>
private string _connection; private string _connection;
/// <summary>当前日志表名。</summary> /// <summary>当前日志表名。</summary>
private string _table; private string _table;
...@@ -30,7 +30,7 @@ namespace Tools.MyAgent.Forms ...@@ -30,7 +30,7 @@ namespace Tools.MyAgent.Forms
private string _currentRunId; private string _currentRunId;
/// <summary> /// <summary>
/// 模块系统入口(Activator 无参创建,实施工具菜单直接打开):自读本机默认 Agent 配置取上报连接; /// 模块系统入口(Activator 无参创建兜底):自读本机默认 Agent 配置取上报连接;
/// 取不到 = 未配置(查询时提示先在设置页配置)。VS 设计器实例化时同样走此路径(读不到配置也不抛异常)。 /// 取不到 = 未配置(查询时提示先在设置页配置)。VS 设计器实例化时同样走此路径(读不到配置也不抛异常)。
/// </summary> /// </summary>
public AgentRunLogForm() public AgentRunLogForm()
...@@ -297,36 +297,6 @@ namespace Tools.MyAgent.Forms ...@@ -297,36 +297,6 @@ namespace Tools.MyAgent.Forms
txtMetrics.Text = metrics; txtMetrics.Text = metrics;
} }
// ══════════════ 连接配置(窗口内直配,2026-10-19) ══════════════
/// <summary>
/// "连接配置":IP → 账号 → 密码 → 库(下拉拉取 sys.databases);
/// 确定后写回 AgentConfig.json(DPAPI 加密)并刷新本窗口的连接与列表。
/// </summary>
private void OnConnConfigClick(object sender, EventArgs e)
{
using (var dlg = new AgentDbConfigForm(_connection, _table))
{
if (dlg.ShowDialog(this) != DialogResult.OK) return;
string plain = dlg.BuildConnectionString();
string table = dlg.Table;
if (!AgentRuntime.SaveLocalRunDbConfig(plain, table, dlg.EnableReporting))
{
AgentUtils.Toast("保存到配置文件失败(AgentConfig.json 不可写)", 4000);
return;
}
_connection = plain; // 本窗口立即生效,无需重开
_table = table;
_currentRunId = null;
SetDetailText("(已切换连接,请选择记录)", "(已切换连接)");
LoadUsers();
RefreshList();
AgentUtils.Toast(dlg.EnableReporting
? "已保存并启用对话上报;引擎侧在下次应用配置(重启客户端或设置页确定)后生效"
: "已保存连接;对话上报保持关闭(仅本窗口查询用)", 4000);
}
}
// ══════════════ 导出 ══════════════ // ══════════════ 导出 ══════════════
private void OnExportCsvClick(object sender, EventArgs e) private void OnExportCsvClick(object sender, EventArgs e)
......
...@@ -110,11 +110,16 @@ namespace Tools.MyAgent.Forms ...@@ -110,11 +110,16 @@ namespace Tools.MyAgent.Forms
{ {
} }
public AgentSettingsForm(AgentConfig config, string agentRoot) /// <summary>
/// 带参构造。
/// <para><paramref name="focusRunDbTab"/>(2026-10-22):分发模式未配置数据库连接时,
/// 管理员打开设置自动跳到「高级 → 对话上报」页,引导先完成配置。</para>
/// </summary>
public AgentSettingsForm(AgentConfig config, string agentRoot, bool focusRunDbTab = false)
{ {
_config = config ?? new AgentConfig(); _config = config ?? new AgentConfig();
_agentRoot = string.IsNullOrEmpty(agentRoot) _agentRoot = string.IsNullOrEmpty(agentRoot)
? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent") ? global::Tools.MyAgent.Support.AgentCenterClient.AgentRootPath(AppDomain.CurrentDomain.BaseDirectory)
: Path.GetFullPath(agentRoot); : Path.GetFullPath(agentRoot);
InitializeComponent(); InitializeComponent();
...@@ -127,6 +132,11 @@ namespace Tools.MyAgent.Forms ...@@ -127,6 +132,11 @@ namespace Tools.MyAgent.Forms
BindConfig(); // 配置值回填控件(控件创建见 Designer.cs) BindConfig(); // 配置值回填控件(控件创建见 Designer.cs)
ApplyProviderMode(); // 布局联动:BaseUrl 行显隐 ApplyProviderMode(); // 布局联动:BaseUrl 行显隐
ApplyKbMode(); // 布局联动:共享 账号/密码/测试连接 行显隐 ApplyKbMode(); // 布局联动:共享 账号/密码/测试连接 行显隐
if (focusRunDbTab && _tabRunDb != null)
{
tabMain.SelectedTab = tabAdv; // 外层先定位「高级」页
tabAdvSub.SelectedTab = _tabRunDb; // 内层定位「对话上报」
}
} }
// ══ v4.1:远程知识库条目接口(程序化创建,避免侵入设计器) ══ // ══ v4.1:远程知识库条目接口(程序化创建,避免侵入设计器) ══
...@@ -217,6 +227,8 @@ namespace Tools.MyAgent.Forms ...@@ -217,6 +227,8 @@ namespace Tools.MyAgent.Forms
private TextBox txtRunDbConn; private TextBox txtRunDbConn;
private TextBox txtRunDbTable; private TextBox txtRunDbTable;
private Button btnTestRunDb; private Button btnTestRunDb;
private Button btnViewRunLog;
private TabPage _tabRunDb; // 分发模式未配置连接时自动跳转目标(2026-10-22)
/// <summary> /// <summary>
/// 高级配置新增二级 Tab「对话上报」:每轮对话完成后自动上报一条记录到 SQL Server /// 高级配置新增二级 Tab「对话上报」:每轮对话完成后自动上报一条记录到 SQL Server
...@@ -255,9 +267,16 @@ namespace Tools.MyAgent.Forms ...@@ -255,9 +267,16 @@ namespace Tools.MyAgent.Forms
Size = new Size(96, 26), Size = new Size(96, 26),
}; };
btnTestRunDb.Click += new EventHandler(OnTestRunDb); btnTestRunDb.Click += new EventHandler(OnTestRunDb);
btnViewRunLog = new Button
{
Text = "查看上报日志",
Location = new Point(372, 136),
Size = new Size(110, 26),
};
btnViewRunLog.Click += new EventHandler(OnViewRunLog);
var hint = new Label var hint = new Label
{ {
Text = "提问与回答原文会存入该库,用于统计分析与体验优化。查看入口:实施工具 → Agent日志。\r\n示例:Server=主机;Database=库名;User Id=账号;Password=密码;Trusted_Connection=False", Text = "提问与回答原文会存入该库,用于统计分析与体验优化。点击「查看上报日志」可查询已上报记录。\r\n示例:Server=主机;Database=库名;User Id=账号;Password=密码;Trusted_Connection=False",
Location = new Point(12, 172), Location = new Point(12, 172),
AutoSize = false, AutoSize = false,
Size = new Size(556, 40), Size = new Size(556, 40),
...@@ -270,7 +289,9 @@ namespace Tools.MyAgent.Forms ...@@ -270,7 +289,9 @@ namespace Tools.MyAgent.Forms
tabRunDb.Controls.Add(lblTable); tabRunDb.Controls.Add(lblTable);
tabRunDb.Controls.Add(txtRunDbTable); tabRunDb.Controls.Add(txtRunDbTable);
tabRunDb.Controls.Add(btnTestRunDb); tabRunDb.Controls.Add(btnTestRunDb);
tabRunDb.Controls.Add(btnViewRunLog);
tabRunDb.Controls.Add(hint); tabRunDb.Controls.Add(hint);
_tabRunDb = tabRunDb;
tabAdvSub.TabPages.Add(tabRunDb); tabAdvSub.TabPages.Add(tabRunDb);
} }
...@@ -305,6 +326,25 @@ namespace Tools.MyAgent.Forms ...@@ -305,6 +326,25 @@ namespace Tools.MyAgent.Forms
} }
} }
/// <summary>
/// 查看上报日志(2026-10-22):用当前页面填写的连接串/表名直开 <see cref="AgentRunLogForm"/>
/// (未保存的修改也能即时预览);空连接串时提示先填写。窗口内只读,不再提供连接配置。
/// </summary>
private void OnViewRunLog(object sender, EventArgs e)
{
string conn = txtRunDbConn.Text.Trim();
if (conn.Length == 0)
{
AgentUtils.Toast("请先填写连接串");
return;
}
string table = string.IsNullOrWhiteSpace(txtRunDbTable.Text) ? "Agent_RunLog" : txtRunDbTable.Text.Trim();
using (var dlg = new AgentRunLogForm(conn, table))
{
dlg.ShowDialog(this);
}
}
// ══ P3:工具级权限(按角色) ══ // ══ P3:工具级权限(按角色) ══
private TextBox txtToolRoles; private TextBox txtToolRoles;
......
...@@ -64,7 +64,7 @@ namespace Tools.MyAgent.Kb.Remote ...@@ -64,7 +64,7 @@ namespace Tools.MyAgent.Kb.Remote
private static string CacheDir private static string CacheDir
{ {
get { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent", "cache", "remote_kb"); } get { return Path.Combine(global::Tools.MyAgent.Support.AgentCenterClient.AgentRootPath(AppDomain.CurrentDomain.BaseDirectory), "cache", "remote_kb"); }
} }
/// <summary>一个远程源(解析自 URL 模板;无效模板在解析期静默丢弃)</summary> /// <summary>一个远程源(解析自 URL 模板;无效模板在解析期静默丢弃)</summary>
......
This diff is collapsed.
...@@ -39,8 +39,10 @@ namespace Tools.MyAgent ...@@ -39,8 +39,10 @@ namespace Tools.MyAgent
/// <summary> /// <summary>
/// 执行检查(不含模型服务连通性——它是异步的,由调用方 <see cref="ModelService"/> 单独追加)。 /// 执行检查(不含模型服务连通性——它是异步的,由调用方 <see cref="ModelService"/> 单独追加)。
/// <para>v2.2 新增第 10 项 <c>config_center</c>:传入配置中心解析结果即输出该项(null = 自定义模式)。</para>
/// </summary> /// </summary>
public static HealthItem[] Check(AgentConfig cfg, KbService kb, IReadOnlyList<DiagnosticRoot> roots) public static HealthItem[] Check(AgentConfig cfg, KbService kb, IReadOnlyList<DiagnosticRoot> roots,
Support.CenterResolution center = null)
{ {
var list = new List<HealthItem>(); var list = new List<HealthItem>();
try try
...@@ -53,11 +55,43 @@ namespace Tools.MyAgent ...@@ -53,11 +55,43 @@ namespace Tools.MyAgent
list.Add(CheckSemantic(cfg)); list.Add(CheckSemantic(cfg));
list.Add(CheckDiagnosticRoots(roots)); list.Add(CheckDiagnosticRoots(roots));
list.Add(CheckTelemetrySelf()); list.Add(CheckTelemetrySelf());
list.Add(CheckConfigCenter(center));
} }
catch { } catch { }
return list.ToArray(); return list.ToArray();
} }
/// <summary>配置中心项(P2):模式 / 来源 / 角色 / KB 探测结论 / 缺项清单。</summary>
private static HealthItem CheckConfigCenter(Support.CenterResolution center)
{
try
{
if (center == null || center.Mode != Support.AgentRunMode.Distribution)
return Item("config_center", "配置中心", "ok", "自定义模式(本地配置)");
var parts = new List<string>
{
"来源=" + (center.Source == "snapshot" ? "本地快照" : center.Source == "db" ? "中心库" : "缺省"),
"角色=" + (center.IsAdmin ? "管理员" : "普通用户"),
};
if (center.KbProbe != null)
{
string kb = "KB=" + center.KbProbe.Conclusion + "(" + center.KbProbe.Resolved
+ (center.KbProbe.IsEmpty ? ",空库" : "") + ")";
parts.Add(kb);
}
string detail = string.Join(";", parts.ToArray());
if (center.MissingItems != null && center.MissingItems.Count > 0)
return Item("config_center", "配置中心", "fail",
detail + ";缺项:" + string.Join("、", center.MissingItems.ToArray()) + "(请联系管理员)");
if (center.Source == "none")
return Item("config_center", "配置中心", "fail", detail + "(中心库不可达且无本地快照)");
if (center.Source == "snapshot")
return Item("config_center", "配置中心", "warn", detail + "(中心库不可达,使用本地快照)");
return Item("config_center", "配置中心", "ok", detail);
}
catch (Exception ex) { return Item("config_center", "配置中心", "warn", ex.Message); }
}
/// <summary>模型服务连通性项(由调用方用 <c>TestConnectionAsync</c> 结果构造)。</summary> /// <summary>模型服务连通性项(由调用方用 <c>TestConnectionAsync</c> 结果构造)。</summary>
public static HealthItem ModelService(bool ok, string message) public static HealthItem ModelService(bool ok, string message)
{ {
......
using System;
using System.IO;
namespace Tools.MyAgent.Support
{
/// <summary>知识库根目录探测结论(config_center_plan.md v2.3)。</summary>
public sealed class KbProbeResult
{
/// <summary>原始模板(空 = 中心未下发 KB 配置)。</summary>
public string Template { get; set; }
/// <summary>最终采用的绝对路径({CLIENT_ROOT} 已展开;UNC 共享路径原样)。</summary>
public string Resolved { get; set; }
/// <summary>目录是否存在。</summary>
public bool Exists { get; set; }
/// <summary>存在但为空目录(允许,首次分发场景;/diag 呈现)。</summary>
public bool IsEmpty { get; set; }
/// <summary>结论:default_ok(未配置→宿主程序根目录) / ok / fallback_ok(已回退 BaseDirectory\Agent) / unavailable。</summary>
public string Conclusion { get; set; }
/// <summary>是否可用(Exists 且非 unavailable 类结论)。</summary>
public bool Usable { get { return Exists; } }
}
/// <summary>
/// 默认知识库探测器(config_center_plan.md v2.3/v2.4)。
/// <para>规则:模板空(中心未下发)→ 默认 <c>{CLIENT_ROOT}</c>(宿主程序根目录,静默兜底,不提示);
/// 已配置但解析后不存在 → 回退 <c>BaseDirectory\Agent</c>;仍不存在 → unavailable(提示"目录不可用")。
/// v2.4:占位符仅本地文件夹生效——UNC 共享模板不含占位符、原样解析。</para>
/// <para>红线:本类所有 public 方法体首句必须是 try —— 任何异常不上抛、不阻塞启动。</para>
/// </summary>
public static class AgentKbProbe
{
/// <summary>未配置模板时 KB 解析(v2.3):默认 {CLIENT_ROOT} = 宿主程序根目录。</summary>
public static KbProbeResult Probe(string template, string baseDirectory)
{
try
{
string root = AgentCenterClient.NormalizeRoot(baseDirectory);
var result = new KbProbeResult { Template = template ?? "" };
string requested = string.IsNullOrWhiteSpace(template)
? root
: AgentCenterClient.ExpandClientRoot(template, root);
result.Resolved = requested;
if (Directory.Exists(requested))
{
result.Exists = true;
result.IsEmpty = new DirectoryInfo(requested).GetFileSystemInfos().Length == 0;
result.Conclusion = string.IsNullOrWhiteSpace(template) ? "default_ok" : "ok";
return result;
}
// 已配置但不存在 → 回退 BaseDirectory\Agent(方案 §1.3)
string fallback = Path.Combine(root, "Agent");
if (Directory.Exists(fallback))
{
result.Exists = true;
result.IsEmpty = new DirectoryInfo(fallback).GetFileSystemInfos().Length == 0;
result.Resolved = fallback;
result.Conclusion = "fallback_ok";
return result;
}
result.Conclusion = "unavailable";
return result;
}
catch
{
return new KbProbeResult
{
Template = template ?? "",
Resolved = AgentCenterClient.NormalizeRoot(baseDirectory),
Conclusion = "unavailable",
};
}
}
}
}
...@@ -174,7 +174,9 @@ ...@@ -174,7 +174,9 @@
<Compile Include="ShareHelper.cs" /> <Compile Include="ShareHelper.cs" />
<Compile Include="Support\AgentLogger.cs" /> <Compile Include="Support\AgentLogger.cs" />
<Compile Include="Support\AgentTelemetry.cs" /> <Compile Include="Support\AgentTelemetry.cs" />
<Compile Include="Support\AgentCenterClient.cs" />
<Compile Include="Support\AgentHealth.cs" /> <Compile Include="Support\AgentHealth.cs" />
<Compile Include="Support\AgentKbProbe.cs" />
<Compile Include="Support\TelemetryStore.cs" /> <Compile Include="Support\TelemetryStore.cs" />
<Compile Include="Support\AgentUtils.cs" /> <Compile Include="Support\AgentUtils.cs" />
<Compile Include="Support\BackupHelper.cs" /> <Compile Include="Support\BackupHelper.cs" />
...@@ -230,6 +232,7 @@ ...@@ -230,6 +232,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="build\Ftt.Tools.Agent.targets" /> <None Include="build\Ftt.Tools.Agent.targets" />
<None Include="Defaults\Agent\config\center.sample.json" />
<None Include="Ftt.Tools.Agent.nuspec" /> <None Include="Ftt.Tools.Agent.nuspec" />
<None Include="README.md" /> <None Include="README.md" />
<None Include="THIRD-PARTY-NOTICES.txt" /> <None Include="THIRD-PARTY-NOTICES.txt" />
......
This diff is collapsed.
...@@ -49,7 +49,6 @@ namespace Tools ...@@ -49,7 +49,6 @@ namespace Tools
this.CreateModule(Tools_imp, typeof(UI.wEmptyTable), "清空表", "tr:输入主表、自动清空关联表的工具"), this.CreateModule(Tools_imp, typeof(UI.wEmptyTable), "清空表", "tr:输入主表、自动清空关联表的工具"),
this.CreateModule(Tools_imp, typeof(UI.wExcelExportSetting), "Excel导出设置", "tr:Excel导出配置图形化版本"), this.CreateModule(Tools_imp, typeof(UI.wExcelExportSetting), "Excel导出设置", "tr:Excel导出配置图形化版本"),
this.CreateModule(Tools_imp, typeof(UI.wLogViewer), "日志查看器", "tr:可以方便的查看系统日志"), 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.wTextFormatter), "格式化工具", "tr:SQL和JSON文本格式化工具"),
this.CreateModule(Tools_imp, typeof(UI.wTextDiff), "文本对比工具", "tr:行级文本差异对比,支持左右/上下布局"), this.CreateModule(Tools_imp, typeof(UI.wTextDiff), "文本对比工具", "tr:行级文本差异对比,支持左右/上下布局"),
this.CreateModule(Tools_imp, typeof(DotNetVersionChecker.FrmVersion), "系统检测", "lxj:检测操作系统运行环境"), 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