Initial commit to new repository
This commit is contained in:
153
src/AxCopilot/ViewModels/ReplayTimelineViewModel.cs
Normal file
153
src/AxCopilot/ViewModels/ReplayTimelineViewModel.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
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")
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user