106 lines
4.0 KiB
C#
106 lines
4.0 KiB
C#
using System.ComponentModel;
|
|||
|
|
using System.Text.Json;
|
||
|
|
using NucleicApp.Services;
|
||
|
|
|
||
|
|
namespace NucleicApp.ViewModels;
|
||
|
|
|
||
|
|
/// Presentation state for the first WinUI Home screen. It derives every value from
|
||
|
|
/// ``RendererStore`` projections; it never mutates host-owned state.
|
||
|
|
public sealed class HomeViewModel : ViewModelBase, IDisposable
|
||
|
|
{
|
||
|
|
private readonly RendererStore store;
|
||
|
|
private readonly Func<DateTimeOffset> now;
|
||
|
|
|
||
|
|
public HomeViewModel(RendererStore store, Func<DateTimeOffset>? now = null)
|
||
|
|
{
|
||
|
|
this.store = store;
|
||
|
|
this.now = now ?? (() => DateTimeOffset.Now);
|
||
|
|
store.PropertyChanged += StoreChanged;
|
||
|
|
}
|
||
|
|
|
||
|
|
public string Greeting => now().Hour switch
|
||
|
|
{
|
||
|
|
>= 5 and < 12 => "Good morning",
|
||
|
|
>= 12 and < 17 => "Good afternoon",
|
||
|
|
>= 17 and < 22 => "Good evening",
|
||
|
|
_ => "Hello",
|
||
|
|
};
|
||
|
|
|
||
|
|
public DashboardCounts Counts
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (store.Dashboard is not { } dashboard ||
|
||
|
|
!dashboard.TryGetProperty("counts", out var counts)) return new();
|
||
|
|
return new DashboardCounts(
|
||
|
|
Int(counts, "projects"), Int(counts, "activeChats"),
|
||
|
|
Int(counts, "chats"), Int(counts, "messages"), Long(counts, "tokens"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public string Subtitle => Counts.Chats == 0
|
||
|
|
? "Start a chat below to get going."
|
||
|
|
: $"{Counts.ActiveChats} active of {Counts.Chats} chats · {Counts.Projects} "
|
||
|
|
+ (Counts.Projects == 1 ? "project" : "projects");
|
||
|
|
|
||
|
|
public IReadOnlyList<SessionSummary> LiveSessions => store.Sessions
|
||
|
|
.Where(session => !Bool(session.Raw, "archived"))
|
||
|
|
.OrderByDescending(UpdatedAt)
|
||
|
|
.ToArray();
|
||
|
|
|
||
|
|
public IReadOnlyList<SessionSummary> NeedsAttention => LiveSessions
|
||
|
|
.Where(session => session.PendingApprovalCount > 0 || session.Status == "awaitingApproval" ||
|
||
|
|
(session.Status == "awaitingInput" && String(session.Raw, "disposition") == "awaitingInput"))
|
||
|
|
.ToArray();
|
||
|
|
|
||
|
|
public IReadOnlyList<SessionSummary> InProgress => LiveSessions
|
||
|
|
.Where(session => session.Status is "provisioning" or "running")
|
||
|
|
.ToArray();
|
||
|
|
|
||
|
|
public int OpenTodoCount
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (store.Dashboard is not { } dashboard ||
|
||
|
|
!dashboard.TryGetProperty("todos", out var todos) ||
|
||
|
|
todos.ValueKind != JsonValueKind.Array) return 0;
|
||
|
|
return todos.EnumerateArray().Count(todo => String(todo, "status") != "done");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void StoreChanged(object? sender, PropertyChangedEventArgs args)
|
||
|
|
{
|
||
|
|
if (args.PropertyName == nameof(RendererStore.Sessions))
|
||
|
|
{
|
||
|
|
Raise(nameof(LiveSessions));
|
||
|
|
Raise(nameof(NeedsAttention));
|
||
|
|
Raise(nameof(InProgress));
|
||
|
|
}
|
||
|
|
else if (args.PropertyName == nameof(RendererStore.Dashboard))
|
||
|
|
{
|
||
|
|
Raise(nameof(Counts));
|
||
|
|
Raise(nameof(Subtitle));
|
||
|
|
Raise(nameof(OpenTodoCount));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static bool Bool(JsonElement root, string name) =>
|
||
|
|
root.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.True;
|
||
|
|
private static string? String(JsonElement root, string name) =>
|
||
|
|
root.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.String
|
||
|
|
? value.GetString() : null;
|
||
|
|
private static int Int(JsonElement root, string name) =>
|
||
|
|
root.TryGetProperty(name, out var value) && value.TryGetInt32(out var result) ? result : 0;
|
||
|
|
private static long Long(JsonElement root, string name) =>
|
||
|
|
root.TryGetProperty(name, out var value) && value.TryGetInt64(out var result) ? result : 0;
|
||
|
|
private static double UpdatedAt(SessionSummary session) =>
|
||
|
|
session.Raw.TryGetProperty("updatedAt", out var value) && value.TryGetDouble(out var result)
|
||
|
|
? result : 0;
|
||
|
|
|
||
|
|
public void Dispose() => store.PropertyChanged -= StoreChanged;
|
||
|
|
}
|
||
|
|
|
||
|
|
public sealed record DashboardCounts(
|
||
|
|
int Projects = 0, int ActiveChats = 0, int Chats = 0,
|
||
|
|
int Messages = 0, long Tokens = 0);
|