Forge
csharpdeeb25a2
1#nullable enable
2
3using Avalonia.Input;
4using CascadeIDE.Models;
5
6namespace CascadeIDE.Features.Chat;
7
8public partial class ChatPanelViewModel
9{
10 private string _pendingDeliveryMode = IntercomComposerDeliveryModes.Normal;
11 private readonly Queue<string> _followUpAgentInputs = new();
12 private CancellationTokenSource? _agentTurnCts;
13 private int _followUpQueueCount;
14
15 public int FollowUpQueueCount => _followUpQueueCount;
16
17 partial void OnIsChatLoadingChanged(bool value)
18 {
19 OnPropertyChanged(nameof(IntercomComposerPlaceholder));
20 SendChatCommand.NotifyCanExecuteChanged();
21 }
22
23 public string IntercomComposerPlaceholder =>
24 IsChatLoading
25 ? FollowUpQueueCount > 0
26 ? $"Агент отвечает… в очереди {FollowUpQueueCount}. Enter — перехват · Alt+Enter — очередь"
27 : "Агент отвечает… Enter — перехват · Alt+Enter — в очередь"
28 : "Сообщение, /команда или [M:Method]…";
29
30 /// <summary>Вызывается перед отправкой из composer (ADR 0116).</summary>
31 internal void PrepareDeliveryModeForComposerKey(IntercomComposerKeyKind kind, KeyEventArgs? keyEvent)
32 {
33 if (!IsChatLoading || kind != IntercomComposerKeyKind.Enter || keyEvent is null)
34 {
35 _pendingDeliveryMode = IntercomComposerDeliveryModes.Normal;
36 return;
37 }
38
39 var alt = keyEvent.KeyModifiers.HasFlag(KeyModifiers.Alt);
40 _pendingDeliveryMode = alt
41 ? IntercomComposerDeliveryModes.FollowUp
42 : IntercomComposerDeliveryModes.Steer;
43 }
44
45 internal string ConsumePendingDeliveryMode()
46 {
47 var mode = IntercomComposerDeliveryModes.Normalize(_pendingDeliveryMode);
48 _pendingDeliveryMode = IntercomComposerDeliveryModes.Normal;
49 return mode;
50 }
51
52 internal bool ShouldDeferProviderDispatch(string deliveryMode) =>
53 IsChatLoading && IntercomComposerDeliveryModes.IsFollowUp(deliveryMode);
54
55 internal void CancelActiveAgentTurnIfSteer(string deliveryMode)
56 {
57 if (!IsChatLoading || !IntercomComposerDeliveryModes.IsSteer(deliveryMode))
58 return;
59
60 try
61 {
62 _agentTurnCts?.Cancel();
63 }
64 catch (ObjectDisposedException)
65 {
66 }
67 }
68
69 internal void EnqueueFollowUpAgentInput(string agentInput)
70 {
71 if (string.IsNullOrWhiteSpace(agentInput))
72 return;
73
74 _followUpAgentInputs.Enqueue(agentInput.Trim());
75 _followUpQueueCount = _followUpAgentInputs.Count;
76 OnPropertyChanged(nameof(FollowUpQueueCount));
77 OnPropertyChanged(nameof(IntercomComposerPlaceholder));
78 ChatLoadingStatusText = _followUpQueueCount > 0
79 ? $"Агент отвечает… в очереди {_followUpQueueCount}"
80 : ChatLoadingStatusText;
81 }
82
83 internal CancellationToken BeginAgentTurnCancellation()
84 {
85 _agentTurnCts?.Cancel();
86 _agentTurnCts?.Dispose();
87 _agentTurnCts = new CancellationTokenSource();
88 return _agentTurnCts.Token;
89 }
90
91 private async Task TryDispatchFollowUpQueueAsync()
92 {
93 if (_followUpAgentInputs.Count == 0)
94 return;
95
96 var agentInput = _followUpAgentInputs.Dequeue();
97 _followUpQueueCount = _followUpAgentInputs.Count;
98 OnPropertyChanged(nameof(FollowUpQueueCount));
99 OnPropertyChanged(nameof(IntercomComposerPlaceholder));
100
101 IsChatLoading = true;
102 ChatLoadingStatusText = "Модель отвечает (очередь)…";
103 try
104 {
105 if (string.Equals(_getActiveAiProvider(), "CursorACP", StringComparison.Ordinal))
106 await SendChatWithCursorAcpAsync(agentInput).ConfigureAwait(false);
107 else
108 await SendChatWithStreamingProviderAsync(agentInput, agentInput).ConfigureAwait(false);
109 }
110 finally
111 {
112 await endIntercomProviderTurnAsync().ConfigureAwait(false);
113 }
114 }
115}
116
View only · write via MCP/CIDE