130 lines
3.8 KiB
C#
130 lines
3.8 KiB
C#
namespace AxCopilot.Services;
|
|
|
|
/// <summary>
|
|
/// 실행 중/최근 task 상태를 관리하는 전용 저장소.
|
|
/// AppStateService와 UI가 같은 로직을 공유할 수 있게 분리합니다.
|
|
/// </summary>
|
|
public sealed class TaskRunStore
|
|
{
|
|
public sealed class TaskRun
|
|
{
|
|
public string Id { get; init; } = "";
|
|
public string Kind { get; set; } = "";
|
|
public string Title { get; set; } = "";
|
|
public string Status { get; set; } = "running";
|
|
public string Summary { get; set; } = "";
|
|
public DateTime StartedAt { get; set; } = DateTime.Now;
|
|
public DateTime UpdatedAt { get; set; } = DateTime.Now;
|
|
public string? FilePath { get; set; }
|
|
}
|
|
|
|
private readonly List<TaskRun> _active = new();
|
|
private readonly List<TaskRun> _recent = new();
|
|
|
|
public IReadOnlyList<TaskRun> ActiveTasks => _active;
|
|
public IReadOnlyList<TaskRun> RecentTasks => _recent;
|
|
|
|
public event Action? Changed;
|
|
|
|
public void Upsert(string id, string kind, string title, string summary, string status = "running", string? filePath = null)
|
|
{
|
|
var existing = _active.FirstOrDefault(t => string.Equals(t.Id, id, StringComparison.OrdinalIgnoreCase));
|
|
if (existing == null)
|
|
{
|
|
_active.Add(new TaskRun
|
|
{
|
|
Id = id,
|
|
Kind = kind,
|
|
Title = title,
|
|
Summary = summary,
|
|
Status = status,
|
|
FilePath = filePath,
|
|
StartedAt = DateTime.Now,
|
|
UpdatedAt = DateTime.Now,
|
|
});
|
|
Changed?.Invoke();
|
|
return;
|
|
}
|
|
|
|
existing.Kind = kind;
|
|
existing.Title = title;
|
|
existing.Summary = summary;
|
|
existing.Status = status;
|
|
existing.FilePath = filePath;
|
|
existing.UpdatedAt = DateTime.Now;
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void Complete(string id, string? summary = null, string status = "completed")
|
|
{
|
|
var existing = _active.FirstOrDefault(t => string.Equals(t.Id, id, StringComparison.OrdinalIgnoreCase));
|
|
if (existing == null)
|
|
return;
|
|
|
|
_active.Remove(existing);
|
|
existing.Status = status;
|
|
if (!string.IsNullOrWhiteSpace(summary))
|
|
existing.Summary = summary;
|
|
existing.UpdatedAt = DateTime.Now;
|
|
_recent.Insert(0, existing);
|
|
TrimRecent();
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void CompleteByPrefix(string prefix, string? summary = null, string status = "completed")
|
|
{
|
|
var matches = _active
|
|
.Where(t => t.Id.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
|
.ToList();
|
|
|
|
foreach (var match in matches)
|
|
{
|
|
_active.Remove(match);
|
|
match.Status = status;
|
|
if (!string.IsNullOrWhiteSpace(summary))
|
|
match.Summary = summary;
|
|
match.UpdatedAt = DateTime.Now;
|
|
_recent.Insert(0, match);
|
|
}
|
|
|
|
if (matches.Count == 0)
|
|
return;
|
|
|
|
TrimRecent();
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void RestoreRecent(IEnumerable<TaskRun>? recent)
|
|
{
|
|
RestoreState(null, recent);
|
|
}
|
|
|
|
public void RestoreState(IEnumerable<TaskRun>? active, IEnumerable<TaskRun>? recent)
|
|
{
|
|
_active.Clear();
|
|
_recent.Clear();
|
|
|
|
if (active != null)
|
|
{
|
|
_active.AddRange(active
|
|
.OrderByDescending(t => t.UpdatedAt)
|
|
.Take(25));
|
|
}
|
|
|
|
if (recent != null)
|
|
{
|
|
_recent.AddRange(recent
|
|
.OrderByDescending(t => t.UpdatedAt)
|
|
.Take(25));
|
|
}
|
|
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
private void TrimRecent()
|
|
{
|
|
if (_recent.Count > 25)
|
|
_recent.RemoveRange(25, _recent.Count - 25);
|
|
}
|
|
}
|