Impostazioni: controllo e aggiornamento dei CLI con pulsante evidenziato
Sezione Aggiornamenti nel riquadro Impostazioni: per ogni CLI installato versione, esito del controllo (disponibile, aggiornato, non riuscito) e pulsante Aggiorna che esegue il comando ufficiale. Il pulsante e un puntino sul pulsante Impostazioni si evidenziano solo se c'è una versione nuova. Il controllo legge l'ultima versione dalle sorgenti ufficiali (downloads.claude.ai e release GitHub di Codex), parte all'avvio e all'apertura del riquadro con cache di 5 minuti e non fallisce mai in modo invasivo. L'aggiornamento ha tempo massimo di 10 minuti, blocca invio e cambio CLI, e in caso di errore suggerisce il comando manuale per npm, Homebrew, WinGet o installazione nativa. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6498e45a0d
commit
8b20f5224e
7 files changed
+328
-16
No files matched your search
+94
-8
@@ -22,12 +22,41 @@ class ChatItem(string id, string title, DateTime updated, bool active, string en
|
||||
public bool IsActive { get; } = active;
|
||||
}
|
||||
|
||||
/// <summary>Voce dell'interruttore CLI: un motore non installato è mostrato ma non selezionabile.</summary>
|
||||
class EngineChoice(Engine engine)
|
||||
/// <summary>Voce dell'interruttore CLI: un motore non installato è mostrato ma non selezionabile. Porta anche versione ed esito dell'ultimo aggiornamento.</summary>
|
||||
class EngineChoice(Engine engine) : ObservableObject
|
||||
{
|
||||
string version = "";
|
||||
string status = "";
|
||||
bool statusIsError;
|
||||
bool? available;
|
||||
string? latest;
|
||||
|
||||
public Engine Engine { get; } = engine;
|
||||
public bool Installed { get; } = engine.IsInstalled();
|
||||
public string Name => Engine.Name;
|
||||
public string Label => Installed ? Engine.Name : $"{Engine.Name} (non installato)";
|
||||
public string Version { get => version; set => Set(ref version, value); }
|
||||
public string Status { get => status; set { if (Set(ref status, value)) Raise(nameof(HasStatus)); } }
|
||||
public bool HasStatus => status != "";
|
||||
public bool StatusIsError { get => statusIsError; set => Set(ref statusIsError, value); }
|
||||
|
||||
/// <summary>Esito del controllo: true = c'è una versione nuova, false = aggiornato, null = non controllato o sconosciuto.</summary>
|
||||
public bool? Available { get => available; private set { if (Set(ref available, value)) { Raise(nameof(UpdateAvailable)); Raise(nameof(CheckText)); } } }
|
||||
public bool UpdateAvailable => available == true;
|
||||
public string CheckText => available switch
|
||||
{
|
||||
true => $"Aggiornamento disponibile: {latest}",
|
||||
false => "Aggiornato",
|
||||
null => checkedOnce ? "Controllo non riuscito" : "",
|
||||
};
|
||||
bool checkedOnce;
|
||||
|
||||
public void SetCheck(bool? isAvailable, string? latestVersion)
|
||||
{
|
||||
latest = latestVersion; checkedOnce = true;
|
||||
Raise(nameof(CheckText));
|
||||
Available = isAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
class MainViewModel : ObservableObject
|
||||
@@ -39,6 +68,10 @@ class MainViewModel : ObservableObject
|
||||
public ObservableCollection<MessageVm> Messages { get; } = [];
|
||||
public ObservableCollection<ChatItem> Chats { get; } = [];
|
||||
public EngineChoice[] EngineChoices { get; } = Engines.All.Select(e => new EngineChoice(e)).ToArray();
|
||||
/// <summary>Righe della sezione Aggiornamenti: solo i CLI installati.</summary>
|
||||
public EngineChoice[] InstalledEngines => EngineChoices.Where(c => c.Installed).ToArray();
|
||||
/// <summary>Puntino sul pulsante Impostazioni quando almeno un CLI ha una versione nuova.</summary>
|
||||
public bool AnyUpdateAvailable => EngineChoices.Any(c => c.UpdateAvailable);
|
||||
|
||||
public ICommand NewChatCommand => newChat;
|
||||
public ICommand CopyCommand { get; }
|
||||
@@ -46,6 +79,7 @@ class MainViewModel : ObservableObject
|
||||
public ICommand OpenCommand { get; }
|
||||
public ICommand DeleteCommand { get; }
|
||||
public ICommand ToggleSidebarCommand { get; }
|
||||
public ICommand UpdateCommand { get; }
|
||||
|
||||
/// <summary>Conferma di eliminazione, fornita dalla finestra.</summary>
|
||||
public Func<Task<bool>> ConfirmDelete { get; set; } = () => Task.FromResult(true);
|
||||
@@ -65,7 +99,7 @@ class MainViewModel : ObservableObject
|
||||
ModelOption[] modelChoices = [];
|
||||
string[] effortChoices = [];
|
||||
string input = "";
|
||||
bool busy, sidebarOpen = true;
|
||||
bool busy, updating, sidebarOpen = true;
|
||||
ModelOption selectedModel = Default;
|
||||
string customModel = "";
|
||||
string selectedEffort = DefaultEffort;
|
||||
@@ -77,14 +111,17 @@ class MainViewModel : ObservableObject
|
||||
ApplyEngine(selectedEngine);
|
||||
send = new(_ => _ = SendAsync(), () => CanSend);
|
||||
SendCommand = send;
|
||||
newChat = new(_ => NewChat(), () => !busy);
|
||||
newChat = new(_ => NewChat(), () => !busy && !updating);
|
||||
CopyCommand = new RelayCommand<string>(t => { if (t is not null) _ = CopyText(t); });
|
||||
OpenCommand = new RelayCommand<ChatItem>(c => { if (c is not null) OpenChat(c.Id); });
|
||||
DeleteCommand = new RelayCommand<ChatItem>(c => { if (c is not null) _ = DeleteAsync(c); });
|
||||
ToggleSidebarCommand = new RelayCommand<object>(_ => SidebarOpen = !SidebarOpen);
|
||||
UpdateCommand = new RelayCommand<EngineChoice>(c => { if (c is not null) _ = UpdateEngineAsync(c); });
|
||||
Messages.CollectionChanged += (_, _) => { Raise(nameof(HasMessages)); Raise(nameof(ShowEmpty)); };
|
||||
ResetSession();
|
||||
|
||||
_ = CheckUpdatesAsync();
|
||||
|
||||
// all'avvio riapre la chat più recente
|
||||
if (ChatStore.List().FirstOrDefault() is { } last) OpenChat(last.Id); else RefreshChats();
|
||||
}
|
||||
@@ -93,7 +130,9 @@ class MainViewModel : ObservableObject
|
||||
|
||||
public bool HasMessages => Messages.Count > 0;
|
||||
public bool ShowEmpty => Messages.Count == 0;
|
||||
public bool CanSend => !busy && !string.IsNullOrWhiteSpace(input);
|
||||
public bool CanSend => !busy && !updating && !string.IsNullOrWhiteSpace(input);
|
||||
/// <summary>I pulsanti Aggiorna sono attivi solo se non c'è una risposta né un altro aggiornamento in corso.</summary>
|
||||
public bool CanUpdate => !busy && !updating;
|
||||
public string Author => Engine.Author;
|
||||
public string InputPlaceholder => $"Scrivi un messaggio a {Engine.Author}…";
|
||||
public string BusyText => $"{Engine.Author} sta scrivendo…";
|
||||
@@ -107,12 +146,18 @@ class MainViewModel : ObservableObject
|
||||
public bool IsBusy
|
||||
{
|
||||
get => busy;
|
||||
private set { if (Set(ref busy, value)) { Raise(nameof(CanSend)); Raise(nameof(CanSwitchEngine)); send.Refresh(); newChat.Refresh(); } }
|
||||
private set { if (Set(ref busy, value)) { Raise(nameof(CanSend)); Raise(nameof(CanSwitchEngine)); Raise(nameof(CanUpdate)); send.Refresh(); newChat.Refresh(); } }
|
||||
}
|
||||
|
||||
public bool SidebarOpen { get => sidebarOpen; set => Set(ref sidebarOpen, value); }
|
||||
|
||||
public bool CanSwitchEngine => !busy;
|
||||
bool IsUpdating
|
||||
{
|
||||
get => updating;
|
||||
set { if (Set(ref updating, value)) { Raise(nameof(CanSend)); Raise(nameof(CanSwitchEngine)); Raise(nameof(CanUpdate)); send.Refresh(); newChat.Refresh(); } }
|
||||
}
|
||||
|
||||
public bool CanSwitchEngine => !busy && !updating;
|
||||
|
||||
/// <summary>Interruttore CLI: cambiare motore apre una nuova chat (il contesto non passa da un motore all'altro).</summary>
|
||||
public EngineChoice SelectedEngine
|
||||
@@ -120,7 +165,7 @@ class MainViewModel : ObservableObject
|
||||
get => selectedEngine;
|
||||
set
|
||||
{
|
||||
if (value is null || value == selectedEngine || busy || !value.Installed) return;
|
||||
if (value is null || value == selectedEngine || busy || updating || !value.Installed) return;
|
||||
ApplyEngine(value);
|
||||
NewChat();
|
||||
}
|
||||
@@ -225,6 +270,47 @@ class MainViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
static readonly TimeSpan CheckEvery = TimeSpan.FromMinutes(5);
|
||||
DateTime lastCheck = DateTime.MinValue;
|
||||
bool checking;
|
||||
|
||||
/// <summary>Controlla (sola lettura) se i CLI hanno una versione nuova. All'avvio, all'apertura del riquadro (al massimo ogni 5 minuti) e dopo un aggiornamento.</summary>
|
||||
public async Task CheckUpdatesAsync(bool force = false)
|
||||
{
|
||||
if (checking || (!force && DateTime.UtcNow - lastCheck < CheckEvery)) return;
|
||||
checking = true;
|
||||
try
|
||||
{
|
||||
foreach (var c in InstalledEngines)
|
||||
{
|
||||
var r = await EngineUpdater.CheckAsync(c.Engine);
|
||||
if (r.InstalledText is not null) c.Version = r.InstalledText;
|
||||
c.SetCheck(r.Available, r.Latest);
|
||||
Raise(nameof(AnyUpdateAvailable));
|
||||
}
|
||||
lastCheck = DateTime.UtcNow;
|
||||
}
|
||||
finally { checking = false; }
|
||||
}
|
||||
|
||||
// Aggiorna un solo CLI con il suo comando ufficiale e mostra l'esito sulla sua riga (ADR-0004).
|
||||
async Task UpdateEngineAsync(EngineChoice c)
|
||||
{
|
||||
if (!CanUpdate || !c.Installed) return;
|
||||
IsUpdating = true;
|
||||
c.StatusIsError = false; c.Status = "Aggiornamento in corso…";
|
||||
try
|
||||
{
|
||||
var r = await EngineUpdater.UpdateAsync(c.Engine);
|
||||
c.StatusIsError = !r.Ok;
|
||||
c.Status = r.Message;
|
||||
if (r.Ok) c.Version = r.After ?? r.Before ?? c.Version;
|
||||
}
|
||||
catch (Exception ex) { c.StatusIsError = true; c.Status = ex.Message; }
|
||||
finally { IsUpdating = false; }
|
||||
await CheckUpdatesAsync(force: true); // dopo l'aggiornamento: ripete il controllo, il pulsante non resta evidenziato
|
||||
}
|
||||
|
||||
void Add(string role, string text) { history.Add(new(role, text)); Messages.Add(new(role, text, Author)); }
|
||||
|
||||
void Persist()
|
||||
|
||||
Reference in new issue
Block a user