using System.Diagnostics; using System.Net; using System.Windows.Forms; using Markdig; using Microsoft.Web.WebView2.WinForms; class Gui : Form { record Model(string Label, string Id) { public override string ToString() => Label; } static readonly Model[] Models = [ new("Fable 5.1 (claude-fable-5-1)", "claude-fable-5-1"), new("Opus 5.5 (claude-opus-5-5)", "claude-opus-5-5"), new("Sonnet 5.5 (claude-sonnet-5-5)", "claude-sonnet-5-5"), new("Haiku 5.5 (claude-haiku-5-5)", "claude-haiku-5-5"), ]; const string Default = "predefinito"; static readonly MarkdownPipeline Md = new MarkdownPipelineBuilder().UseAdvancedExtensions().DisableHtml().Build(); const string Css = """ :root { color-scheme: light dark; } body { font: 15px/1.6 'Segoe UI', sans-serif; margin: 0; padding: 16px 0 32px; background: Canvas; color: CanvasText; } .msg { max-width: 800px; margin: 0 auto 18px; padding: 0 16px; } .user { text-align: right; } .user div { display: inline-block; text-align: left; white-space: pre-wrap; background: color-mix(in srgb, CanvasText 10%, Canvas); padding: 10px 14px; border-radius: 18px; max-width: 85%; } .err div { color: #c0392b; white-space: pre-wrap; } .wait { opacity: .6; font-style: italic; } pre { background: color-mix(in srgb, CanvasText 8%, Canvas); padding: 12px; border-radius: 8px; overflow-x: auto; } code { font-family: Consolas, monospace; font-size: 13.5px; } table { border-collapse: collapse; } th, td { border: 1px solid color-mix(in srgb, CanvasText 25%, Canvas); padding: 4px 10px; } blockquote { margin-left: 0; padding-left: 12px; border-left: 3px solid color-mix(in srgb, CanvasText 30%, Canvas); } """; // (ruolo, testo): "user" | "ai" | "err" | "wait" readonly List<(string Role, string Text)> chat = []; readonly WebView2 view = new() { Dock = DockStyle.Fill }; readonly TextBox input = new() { Dock = DockStyle.Fill, Multiline = true, AcceptsReturn = true, ScrollBars = ScrollBars.Vertical, Font = new Font("Segoe UI", 11) }; readonly Button send = new() { Text = "Invia", Dock = DockStyle.Fill }; readonly Button reset = new() { Text = "Nuova chat", AutoSize = true }; readonly ComboBox model = new() { DropDownStyle = ComboBoxStyle.DropDown, Width = 300 }; // editabile: ID completo readonly ComboBox effort = new() { DropDownStyle = ComboBoxStyle.DropDownList, Width = 100 }; string sessionId = Guid.NewGuid().ToString(); bool started; // true dopo il primo messaggio riuscito: da lì in poi --resume public Gui() { Text = "LlmRun"; Width = 1000; Height = 700; model.Items.Add(Default); model.Items.AddRange(Models); model.SelectedIndex = 0; effort.Items.AddRange([Default, "low", "medium", "high", "xhigh", "max"]); effort.SelectedIndex = 0; var settings = new FlowLayoutPanel { Dock = DockStyle.Fill, WrapContents = false }; settings.Controls.AddRange([ new Label { Text = "Modello", AutoSize = true, Margin = new Padding(0, 8, 0, 0) }, model, new Label { Text = "Effort", AutoSize = true, Margin = new Padding(8, 8, 0, 0) }, effort, reset]); var bottom = new TableLayoutPanel { Dock = DockStyle.Bottom, Height = 140, Padding = new Padding(8), ColumnCount = 2, RowCount = 2 }; bottom.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); bottom.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 90)); bottom.RowStyles.Add(new RowStyle(SizeType.Absolute, 34)); bottom.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); bottom.Controls.Add(settings, 0, 0); bottom.SetColumnSpan(settings, 2); bottom.Controls.Add(input, 0, 1); bottom.Controls.Add(send, 1, 1); Controls.Add(view); Controls.Add(bottom); // Fill per primo: l'ordine inverso dei Dock send.Click += async (_, _) => await Send(); reset.Click += (_, _) => { chat.Clear(); sessionId = Guid.NewGuid().ToString(); started = false; Render(); }; input.KeyDown += async (_, e) => { if (e.KeyCode == Keys.Enter && !e.Shift) { e.SuppressKeyPress = true; await Send(); } }; Load += async (_, _) => { var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LlmRun"); await view.EnsureCoreWebView2Async(await Microsoft.Web.WebView2.Core.CoreWebView2Environment.CreateAsync(null, dir)); view.CoreWebView2.NavigationStarting += (_, e) => { if (e.Uri.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { e.Cancel = true; Process.Start(new ProcessStartInfo(e.Uri) { UseShellExecute = true }); } }; view.CoreWebView2.Settings.AreDefaultContextMenusEnabled = true; Render(); }; } void Render() { if (view.CoreWebView2 == null) return; var body = string.Concat(chat.Select(m => m.Role switch { "user" => $"