using System.Threading.Channels;
using System.Windows;
using System.Windows.Threading;
using AxCopilot.Models;
using AxCopilot.Services;
using AxCopilot.Services.Agent;
namespace AxCopilot.Views;
///
/// 에이전트 이벤트의 무거운 작업(대화 변이·저장)을 백그라운드 스레드에서 처리합니다.
/// UI 스레드는 시각적 피드백만 담당하여 채팅창 멈춤을 방지합니다.
///
public partial class ChatWindow
{
/// 백그라운드 처리 대상 이벤트 큐 아이템.
private readonly record struct AgentEventWorkItem(
AgentEvent Event,
string EventTab,
string ActiveTab,
bool ShouldRender);
private readonly Channel _agentEventChannel =
Channel.CreateUnbounded(
new UnboundedChannelOptions { SingleReader = true, AllowSynchronousContinuations = false });
private Task? _agentEventProcessorTask;
/// 백그라운드 이벤트 프로세서를 시작합니다. 창 초기화 시 한 번 호출됩니다.
private void StartAgentEventProcessor()
{
_agentEventProcessorTask = Task.Run(ProcessAgentEventsAsync);
}
/// 백그라운드 이벤트 프로세서를 종료합니다. 창 닫기 시 호출됩니다.
private void StopAgentEventProcessor()
{
_agentEventChannel.Writer.TryComplete();
// 프로세서 완료를 동기 대기하지 않음 — 데드락 방지
// GC가 나머지를 정리합니다.
}
///
/// 에이전트 이벤트를 백그라운드 큐에 추가합니다.
/// ExecutionEvents는 UI 스레드에서 즉시 추가 (타임라인 렌더링 누락 방지).
/// 디스크 저장과 AgentRun 기록은 백그라운드에서 처리합니다.
///
private void EnqueueAgentEventWork(AgentEvent evt, string eventTab, bool shouldRender)
{
// ── 즉시 추가: ExecutionEvents에 동기적으로 반영 (RenderMessages 누락 방지) ──
try
{
lock (_convLock)
{
var session = _appState.ChatSession;
if (session != null)
{
var result = _chatEngine.AppendExecutionEvent(
session, null!, _currentConversation, _activeTab, eventTab, evt);
_currentConversation = result.CurrentConversation;
}
}
}
catch (Exception ex)
{
LogService.Debug($"UI 스레드 이벤트 즉시 추가 실패: {ex.Message}");
}
_agentEventChannel.Writer.TryWrite(new AgentEventWorkItem(evt, eventTab, _activeTab, shouldRender));
}
/// 백그라운드 전용: 채널에서 이벤트를 배치로 읽어 대화 변이 + 저장을 수행합니다.
private async Task ProcessAgentEventsAsync()
{
var reader = _agentEventChannel.Reader;
var persistStopwatch = System.Diagnostics.Stopwatch.StartNew();
ChatConversation? pendingPersist = null;
var batch = new List(16);
try
{
while (await reader.WaitToReadAsync().ConfigureAwait(false))
{
batch.Clear();
while (reader.TryRead(out var item))
batch.Add(item);
if (batch.Count == 0)
continue;
bool anyNeedsRender = false;
bool hasTerminalEvent = false;
foreach (var work in batch)
{
var evt = work.Event;
var eventTab = work.EventTab;
var activeTab = work.ActiveTab;
// ── 대화 변이: execution event 추가 ──
try
{
lock (_convLock)
{
var session = _appState.ChatSession;
if (session != null)
{
var result = _chatEngine.AppendExecutionEvent(
session, _storage, _currentConversation, activeTab, eventTab, evt);
_currentConversation = result.CurrentConversation;
pendingPersist = result.UpdatedConversation;
}
}
}
catch (Exception ex)
{
LogService.Debug($"백그라운드 이벤트 처리 오류 (execution): {ex.Message}");
}
// ── 대화 변이: agent run 추가 (Complete/Error) ──
if (evt.Type == AgentEventType.Complete)
{
try
{
lock (_convLock)
{
var session = _appState.ChatSession;
if (session != null)
{
var summary = string.IsNullOrWhiteSpace(evt.Summary) ? "작업 완료" : evt.Summary;
var result = _chatEngine.AppendAgentRun(
session, _storage, _currentConversation, activeTab, eventTab, evt, "completed", summary);
_currentConversation = result.CurrentConversation;
pendingPersist = result.UpdatedConversation;
}
}
}
catch (Exception ex)
{
LogService.Debug($"백그라운드 이벤트 처리 오류 (agent run complete): {ex.Message}");
}
hasTerminalEvent = true;
}
else if (evt.Type == AgentEventType.Error && string.IsNullOrWhiteSpace(evt.ToolName))
{
try
{
lock (_convLock)
{
var session = _appState.ChatSession;
if (session != null)
{
var summary = string.IsNullOrWhiteSpace(evt.Summary) ? "에이전트 실행 실패" : evt.Summary;
var result = _chatEngine.AppendAgentRun(
session, _storage, _currentConversation, activeTab, eventTab, evt, "failed", summary);
_currentConversation = result.CurrentConversation;
pendingPersist = result.UpdatedConversation;
}
}
}
catch (Exception ex)
{
LogService.Debug($"백그라운드 이벤트 처리 오류 (agent run error): {ex.Message}");
}
hasTerminalEvent = true;
}
if (work.ShouldRender)
anyNeedsRender = true;
}
// ── 디바운스 저장: 2초마다 또는 종료 이벤트 시 즉시 ──
if (pendingPersist != null && (hasTerminalEvent || persistStopwatch.ElapsedMilliseconds > 2000))
{
try
{
_storage.Save(pendingPersist);
var rememberTab = pendingPersist.Tab ?? "Cowork";
_appState.ChatSession?.RememberConversation(rememberTab, pendingPersist.Id);
}
catch (Exception ex)
{
LogService.Debug($"백그라운드 대화 저장 실패: {ex.Message}");
}
pendingPersist = null;
persistStopwatch.Restart();
}
// ── UI 새로고침 신호: 배치 전체에 대해 단 1회 ──
if (anyNeedsRender)
{
try
{
Application.Current?.Dispatcher?.BeginInvoke(
() => ScheduleExecutionHistoryRender(autoScroll: true),
DispatcherPriority.Background);
}
catch { /* 앱 종료 중 무시 */ }
}
}
}
catch (OperationCanceledException) { }
catch (ChannelClosedException) { }
catch (Exception ex)
{
LogService.Debug($"에이전트 이벤트 프로세서 종료: {ex.Message}");
}
// ── 종료 시 미저장 대화 플러시 ──
if (pendingPersist != null)
{
try { _storage.Save(pendingPersist); } catch { }
}
}
}