Files
AX-Copilot/src/AxCopilot/ViewModels/ReplayTimelineViewModel.cs

154 lines
5.5 KiB
C#

using System.Collections.ObjectModel;
using System.ComponentModel;
using AxCopilot.Services.Agent;
namespace AxCopilot.ViewModels;
/// <summary>Phase 18-B: 에이전트 이벤트 타임라인 재생 ViewModel.</summary>
public class ReplayTimelineViewModel : INotifyPropertyChanged
{
private readonly AgentReplayService _replayService;
private CancellationTokenSource? _replayCts;
public ObservableCollection<ReplayEventItem> Events { get; } = new();
public ObservableCollection<ReplaySessionInfo> Sessions { get; } = new();
private ReplaySessionInfo? _selectedSession;
public ReplaySessionInfo? SelectedSession
{
get => _selectedSession;
set { _selectedSession = value; OnPropertyChanged(); OnPropertyChanged(nameof(CanReplay)); }
}
private bool _isReplaying;
public bool IsReplaying
{
get => _isReplaying;
set { _isReplaying = value; OnPropertyChanged(); OnPropertyChanged(nameof(CanReplay)); }
}
public bool CanReplay => SelectedSession != null && !IsReplaying;
private int _replaySpeedMs = 200;
public int ReplaySpeedMs
{
get => _replaySpeedMs;
set { _replaySpeedMs = Math.Max(0, value); OnPropertyChanged(); }
}
private int _progressValue;
public int ProgressValue
{
get => _progressValue;
set { _progressValue = value; OnPropertyChanged(); }
}
private int _progressMax = 100;
public int ProgressMax
{
get => _progressMax;
set { _progressMax = value; OnPropertyChanged(); }
}
public ReplayTimelineViewModel()
{
_replayService = new AgentReplayService();
}
public void LoadSessions()
{
Sessions.Clear();
foreach (var s in _replayService.GetSessions())
Sessions.Add(s);
if (Sessions.Count > 0)
SelectedSession = Sessions[0];
}
public async Task StartReplayAsync()
{
if (SelectedSession == null || IsReplaying) return;
Events.Clear();
IsReplaying = true;
ProgressValue = 0;
_replayCts = new CancellationTokenSource();
try
{
// 전체 이벤트 수 먼저 파악
var allEvents = await _replayService.LoadAllEventsAsync(
SelectedSession.SessionId, _replayCts.Token);
ProgressMax = Math.Max(1, allEvents.Count);
int i = 0;
await _replayService.ReplayAsync(
SelectedSession.SessionId,
async record =>
{
var item = new ReplayEventItem(record);
// Dispatcher needed — raise event for UI thread
ReplayEventReceived?.Invoke(item);
i++;
ProgressValue = i;
await Task.CompletedTask;
},
ReplaySpeedMs,
_replayCts.Token);
}
catch (OperationCanceledException) { }
finally
{
IsReplaying = false;
_replayCts?.Dispose();
_replayCts = null;
}
}
public void StopReplay()
{
_replayCts?.Cancel();
}
/// <summary>UI 스레드에서 이벤트 항목을 추가하기 위한 이벤트.</summary>
public event Action<ReplayEventItem>? ReplayEventReceived;
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
/// <summary>타임라인에 표시할 단일 이벤트 항목.</summary>
public class ReplayEventItem
{
public AgentEventRecord Record { get; }
public string TypeLabel { get; }
public string Icon { get; }
public string TimeLabel { get; }
public ReplayEventItem(AgentEventRecord record)
{
Record = record;
TimeLabel = record.Timestamp.ToString("HH:mm:ss");
(TypeLabel, Icon) = record.Type switch
{
AgentEventLogType.SessionStart => ("세션 시작", "\uE768"),
AgentEventLogType.SessionEnd => ("세션 종료", "\uE711"),
AgentEventLogType.UserMessage => ("사용자 메시지", "\uE8BD"),
AgentEventLogType.AssistantMessage => ("AI 응답", "\uE8D4"),
AgentEventLogType.ToolRequest => ("도구 요청", "\uE756"),
AgentEventLogType.ToolResult => ("도구 결과", "\uE73E"),
AgentEventLogType.HookFired => ("훅 실행", "\uE81C"),
AgentEventLogType.HookResult => ("훅 결과", "\uE73E"),
AgentEventLogType.SkillActivated => ("스킬 활성화", "\uE82D"),
AgentEventLogType.SkillCompleted => ("스킬 완료", "\uE930"),
AgentEventLogType.CompactionTriggered => ("컨텍스트 압축", "\uE8EC"),
AgentEventLogType.CompactionCompleted => ("압축 완료", "\uE930"),
AgentEventLogType.SubagentSpawned => ("서브에이전트 생성", "\uE718"),
AgentEventLogType.SubagentCompleted => ("서브에이전트 완료", "\uE930"),
AgentEventLogType.ReflexionSaved => ("반성 저장", "\uE90F"),
AgentEventLogType.Error => ("오류", "\uE783"),
_ => (record.Type.ToString(), "\uE946")
};
}
}