fix: исправить навигацию клавиатуры в TUI через GlobalKeyboardService
- Добавить GlobalKeyboardService — выделенный поток с блокирующим Console.ReadKey, единственный источник клавишных событий для TUI - Убрать FocusManager из App.razor: перехватывал Tab до компонентов - Удалить @onkeydown с <Select>: RazorConsole не пробрасывает Tab/стрелки через этот механизм - Использовать FocusedValue вместо Value в Select для корректной подсветки - Обновить CLAUDE.md и AGENTS.md: архитектура TUI, RazorConsole gotchas - Добавить docs/tui_log.md: разбор проблемы и справочник по RazorConsole Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,40 +2,87 @@ using LazyBear.MCP.Services.Confluence;
|
||||
using LazyBear.MCP.Services.Jira;
|
||||
using LazyBear.MCP.Services.Kubernetes;
|
||||
using LazyBear.MCP.Services.Logging;
|
||||
using LazyBear.MCP.Services.Mcp;
|
||||
using LazyBear.MCP.Services.ToolRegistry;
|
||||
using LazyBear.MCP.TUI;
|
||||
using LazyBear.MCP.TUI.Components;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RazorConsole.Core;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// ── InMemoryLogSink: регистрируем Singleton и кастомный логгер ───────────────
|
||||
// ── Общий логгер и один DI-контейнер для TUI + MCP ──────────────────────────
|
||||
var logSink = new InMemoryLogSink();
|
||||
builder.Services.AddSingleton(logSink);
|
||||
builder.Logging.AddProvider(new InMemoryLoggerProvider(logSink));
|
||||
|
||||
// ── MCP-провайдеры ───────────────────────────────────────────────────────────
|
||||
builder.Services.AddSingleton<K8sClientProvider>();
|
||||
builder.Services.AddSingleton<JiraClientProvider>();
|
||||
builder.Services.AddSingleton<ConfluenceClientProvider>();
|
||||
var host = Host.CreateDefaultBuilder(args)
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton(logSink);
|
||||
services.AddSingleton<ToolRegistryService>();
|
||||
|
||||
// ── ToolRegistry ─────────────────────────────────────────────────────────────
|
||||
builder.Services.AddSingleton<ToolRegistryService>();
|
||||
// MCP-провайдеры
|
||||
services.AddSingleton<K8sClientProvider>();
|
||||
services.AddSingleton<JiraClientProvider>();
|
||||
services.AddSingleton<ConfluenceClientProvider>();
|
||||
|
||||
// ── Модули инструментов (generic: добавь новый IToolModule — он появится в TUI)
|
||||
builder.Services.AddSingleton<IToolModule, JiraToolModule>();
|
||||
builder.Services.AddSingleton<IToolModule, KubernetesToolModule>();
|
||||
builder.Services.AddSingleton<IToolModule, ConfluenceToolModule>();
|
||||
// Модули инструментов (добавь новый IToolModule — он появится в TUI)
|
||||
services.AddSingleton<IToolModule, JiraToolModule>();
|
||||
services.AddSingleton<IToolModule, KubernetesToolModule>();
|
||||
services.AddSingleton<IToolModule, ConfluenceToolModule>();
|
||||
|
||||
// ── MCP-сервер ───────────────────────────────────────────────────────────────
|
||||
builder.Services.AddMcpServer()
|
||||
.WithHttpTransport()
|
||||
.WithToolsFromAssembly();
|
||||
// HTTP MCP endpoint запускаем в фоне, чтобы TUI оставался владельцем консоли
|
||||
services.AddHostedService<McpWebHostedService>();
|
||||
|
||||
// ── TUI как фоновый сервис ───────────────────────────────────────────────────
|
||||
builder.Services.AddHostedService<TuiHostedService>();
|
||||
// Глобальный читатель клавиш — единственный источник клавишных событий для TUI
|
||||
services.AddSingleton<GlobalKeyboardService>();
|
||||
services.AddHostedService(sp => sp.GetRequiredService<GlobalKeyboardService>());
|
||||
})
|
||||
.ConfigureLogging(logging =>
|
||||
{
|
||||
logging.ClearProviders();
|
||||
logging.AddProvider(new InMemoryLoggerProvider(logSink));
|
||||
})
|
||||
.UseRazorConsole<App>(hostBuilder =>
|
||||
{
|
||||
hostBuilder.ConfigureServices(services =>
|
||||
{
|
||||
services.Configure<ConsoleAppOptions>(options =>
|
||||
{
|
||||
options.AutoClearConsole = true;
|
||||
options.EnableTerminalResizing = true;
|
||||
options.AfterRenderAsync = (_, _, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.CursorVisible = false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore terminals that do not support CursorVisible.
|
||||
}
|
||||
|
||||
var app = builder.Build();
|
||||
try
|
||||
{
|
||||
Console.Write("\u001b[?25l");
|
||||
Console.Out.Flush();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore terminals that do not support ANSI cursor control.
|
||||
}
|
||||
|
||||
app.MapMcp();
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
});
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
var urls = Environment.GetEnvironmentVariable("ASPNETCORE_URLS") ?? "http://localhost:5000";
|
||||
app.Run(urls);
|
||||
// ── Регистрируем модули один раз до старта TUI и web host ───────────────────
|
||||
var registry = host.Services.GetRequiredService<ToolRegistryService>();
|
||||
foreach (var module in host.Services.GetServices<IToolModule>())
|
||||
{
|
||||
registry.RegisterModule(module);
|
||||
}
|
||||
|
||||
await host.RunAsync();
|
||||
|
||||
Reference in New Issue
Block a user