Sostituisce WinForms, WebView2 e ui.html con una GUI Avalonia (XAML, ViewModel, tema chiaro/scuro) e un renderer markdown che trasforma l'albero di Markdig in controlli. Un solo progetto net10.0 per Windows e Linux, con publish single-file compresso. L'avvio di claude è in un solo punto (Core/Claude.cs) condiviso da CLI e GUI. Il nome dell'app viene dall'AssemblyName; ChatStore migra la cartella dati LlmRun alla nuova. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
78 lines
2.9 KiB
C#
78 lines
2.9 KiB
C#
using System.Diagnostics;
|
|
|
|
namespace AgentWikiUi.Core;
|
|
|
|
record ModelOption(string Label, string Id);
|
|
|
|
static class Catalog
|
|
{
|
|
// Da aggiornare quando escono nuovi modelli.
|
|
public static readonly ModelOption[] Models =
|
|
[
|
|
new("Fable 5.1", "claude-fable-5-1"),
|
|
new("Opus 5.5", "claude-opus-5-5"),
|
|
new("Sonnet 5.5", "claude-sonnet-5-5"),
|
|
new("Haiku 5.5", "claude-haiku-5-5"),
|
|
];
|
|
|
|
public static readonly string[] Efforts = ["low", "medium", "high", "xhigh", "max"];
|
|
}
|
|
|
|
record RunResult(int Code, string Out, string Err);
|
|
|
|
// Unico punto di avvio di `claude`, condiviso da CLI e GUI.
|
|
static class Claude
|
|
{
|
|
// sessionId null = nessun contesto (CLI). Model/effort vuoti = flag omessi.
|
|
public static ProcessStartInfo CreateStart(string prompt, bool redirectOutput, string? sessionId = null, bool resume = false, string model = "", string effort = "")
|
|
{
|
|
var psi = new ProcessStartInfo("claude")
|
|
{
|
|
WorkingDirectory = AppContext.BaseDirectory, // mai la cwd del chiamante né Assembly.Location (vuoto in single-file)
|
|
UseShellExecute = false,
|
|
RedirectStandardInput = true, // chiuso subito dal chiamante: evita l'attesa di stdin di claude
|
|
RedirectStandardOutput = redirectOutput,
|
|
RedirectStandardError = redirectOutput,
|
|
CreateNoWindow = true,
|
|
};
|
|
psi.ArgumentList.Add("-p");
|
|
psi.ArgumentList.Add(prompt);
|
|
if (sessionId is not null)
|
|
{
|
|
psi.ArgumentList.Add(resume ? "--resume" : "--session-id");
|
|
psi.ArgumentList.Add(sessionId);
|
|
}
|
|
foreach (var (flag, value) in new[] { ("--model", model), ("--effort", effort) })
|
|
if (value != "") { psi.ArgumentList.Add(flag); psi.ArgumentList.Add(value); }
|
|
return psi;
|
|
}
|
|
|
|
// CLI: stdout/stderr ereditati (streaming), exit code propagato, 127 se `claude` manca.
|
|
public static int RunInherited(string prompt)
|
|
{
|
|
try
|
|
{
|
|
using var p = Process.Start(CreateStart(prompt, redirectOutput: false))!;
|
|
p.StandardInput.Close();
|
|
p.WaitForExit();
|
|
return p.ExitCode;
|
|
}
|
|
catch (System.ComponentModel.Win32Exception)
|
|
{
|
|
Console.Error.WriteLine("'claude' non trovato nel PATH.");
|
|
return 127;
|
|
}
|
|
}
|
|
|
|
// GUI: output catturato. Win32Exception se `claude` non è nel PATH.
|
|
public static async Task<RunResult> RunCapturedAsync(string prompt, string sessionId, bool resume, string model, string effort)
|
|
{
|
|
using var p = Process.Start(CreateStart(prompt, redirectOutput: true, sessionId, resume, model, effort))!;
|
|
p.StandardInput.Close();
|
|
var o = p.StandardOutput.ReadToEndAsync();
|
|
var e = p.StandardError.ReadToEndAsync();
|
|
await p.WaitForExitAsync();
|
|
return new(p.ExitCode, (await o).Trim(), (await e).Trim());
|
|
}
|
|
}
|