using System.Collections.ObjectModel; using System.Windows.Input; namespace Zcbot.WindowsNode; internal sealed class LocalJobItemViewModel(JobDisplaySnapshot snapshot) { internal JobDisplaySnapshot Snapshot { get; } = snapshot; public Guid JobId => Snapshot.JobId; public string StageText => FormatStage(Snapshot.Stage); public string Title => Snapshot.Title; public string InputFilename => Snapshot.InputFilename; public string ProgressText => Snapshot.Stage == "software_running" ? $"已执行 {FormatElapsed(DateTimeOffset.UtcNow - Snapshot.UpdatedAt)}" : $"{Snapshot.Progress}%"; public string AcceptedAtText => Snapshot.AcceptedAt.LocalDateTime.ToString("MM-dd HH:mm"); public string ShortId => Snapshot.JobId.ToString("N")[..8]; public string Tone => Snapshot.Stage switch { "failed" or "cloud_terminal" => "error", "succeeded" => "success", "cancelled" => "neutral", _ => "active", }; public string DetailText => string.Join("\n", [ $"{StageText} · {Snapshot.Detail}", $"能力:{Snapshot.Capability}", $"Job ID:{Snapshot.JobId}", $"更新时间:{Snapshot.UpdatedAt.LocalDateTime:yyyy-MM-dd HH:mm:ss}", ]); private static string FormatElapsed(TimeSpan elapsed) { if (elapsed < TimeSpan.Zero) elapsed = TimeSpan.Zero; return elapsed.TotalHours >= 1 ? $"{(int)elapsed.TotalHours}:{elapsed.Minutes:00}:{elapsed.Seconds:00}" : $"{elapsed.Minutes:00}:{elapsed.Seconds:00}"; } private static string FormatStage(string stage) => stage switch { "accepted" => "等待处理", "downloading_inputs" => "下载输入", "ready_to_run" => "准备执行", "software_running" => "软件执行中", "uploading_outputs" => "上传结果", "succeeded" => "成功", "failed" => "失败", "cloud_terminal" => "云端已终止", "cancelled" => "已取消", _ => stage, }; } internal sealed class JobPageViewModel : ObservableObject { private readonly IJobMonitorService service; private LocalJobItemViewModel? selectedJob; private string summary = "正在读取本机任务…"; internal JobPageViewModel(IJobMonitorService service) { this.service = service; RefreshCommand = new RelayCommand(Refresh); } public ObservableCollection Jobs { get; } = []; public LocalJobItemViewModel? SelectedJob { get => selectedJob; set { if (SetProperty(ref selectedJob, value)) { OnPropertyChanged(nameof(SelectedDetail)); } } } public string Summary { get => summary; private set => SetProperty(ref summary, value); } public string SelectedDetail => SelectedJob?.DetailText ?? "选择任务后可查看执行阶段、更新时间和完整 Job ID。"; public ICommand RefreshCommand { get; } internal void Refresh() { var selectedId = SelectedJob?.JobId; IReadOnlyList snapshots; try { snapshots = service.ReadSnapshots(); } catch (Exception exception) when ( exception is IOException or UnauthorizedAccessException) { Summary = $"无法读取本机任务:{exception.Message}"; return; } Jobs.Clear(); foreach (var snapshot in snapshots) { Jobs.Add(new LocalJobItemViewModel(snapshot)); } var active = snapshots.Count(item => item.IsActive); Summary = snapshots.Count == 0 ? "暂无本机任务" : $"活动任务 {active} 个 · 最近记录 {snapshots.Count} 条"; SelectedJob = Jobs.FirstOrDefault(item => item.JobId == selectedId) ?? Jobs.FirstOrDefault(); } }