Forge
csharpdeeb25a2
1#nullable enable
2
3using CascadeIDE.Features.Chat.Application;
4using CascadeIDE.Features.Chat.DataAcquisition;
5using CascadeIDE.Models.AgentChat;
6using CascadeIDE.Models.Intercom;
7using CascadeIDE.Services;
8using CascadeIDE.Services.Intercom;
9using CascadeIDE.ViewModels;
10
11namespace CascadeIDE.Features.Chat;
12
13public partial class ChatPanelViewModel
14{
15 private IntercomOutboundSendHost CreateIntercomOutboundSendHost() =>
16 new()
17 {
18 GetTrimmedInput = () => ChatInput.Trim(),
19 GetWorkspaceRoot = ResolveAttachWorkspaceRoot,
20 GetPendingAttachCount = () => _pendingAttachDrafts.Count,
21 TryHandleSlashLineAsync = tryHandleIntercomSlashLineAsync,
22 TryBuildOutboundAsync = buildOutboundOnBackgroundAsync,
23 BeginPrepareOutboundAsync = beginPrepareOutboundAsync,
24 EndPrepareOutboundAsync = endPrepareOutboundAsync,
25 ApplyProductSpine = ApplyAgentContextPrefixes,
26 FormatAgentInput = (display, outbound) => IntercomAttachmentPromptFormatter.AppendToUserMessage(
27 display,
28 outbound.Attachments,
29 outbound.SenderWorkspaceContext),
30 CommitUserMessageAsync = commitIntercomUserMessageAsync,
31 ConsumeDeliveryMode = ConsumePendingDeliveryMode,
32 ShouldDeferProviderDispatch = ShouldDeferProviderDispatch,
33 CancelActiveTurnIfSteer = CancelActiveAgentTurnIfSteer,
34 EnqueueFollowUpAgentInputAsync = text =>
35 {
36 EnqueueFollowUpAgentInput(text);
37 return Task.CompletedTask;
38 },
39 ProcessFollowUpQueueAsync = TryDispatchFollowUpQueueAsync,
40 GetChatMcpOnly = _getChatMcpOnly,
41 GetActiveAiProvider = _getActiveAiProvider,
42 SendCursorAcpAsync = SendChatWithCursorAcpAsync,
43 SendStreamingAsync = SendChatWithStreamingProviderAsync,
44 SetClarificationStatusAsync = text => UiScheduler.Default.InvokeAsync(() => ClarificationStatusText = text),
45 EndProviderTurnAsync = endIntercomProviderTurnAsync,
46 };
47
48 private async Task<(bool Ok, IntercomAttachmentMessageBuilder.Outbound Outbound, string Error)> buildOutboundOnBackgroundAsync(
49 string rawInput,
50 CancellationToken cancellationToken)
51 {
52 var pending = new Dictionary<string, AttachmentAnchor>(_pendingAttachDrafts, StringComparer.OrdinalIgnoreCase);
53 var prepared = await IntercomOutboundMessagePreparer.PrepareAsync(
54 rawInput,
55 pending,
56 BuildAttachEditorSnapshot(),
57 ResolveAttachWorkspaceRoot(),
58 ResolveAttachSolutionPath(),
59 cancellationToken,
60 ResolveAttachIndexDirectoryRelative()).ConfigureAwait(false);
61
62 if (prepared.IsCommittable)
63 {
64 await UiScheduler.Default.InvokeAsync(() =>
65 {
66 _pendingAttachDrafts.Clear();
67 ComposerAttachHint = "";
68 var hint = IntercomPreparedMessageCommit.FormatStatusHint(prepared);
69 if (!string.IsNullOrWhiteSpace(hint))
70 ClarificationStatusText = hint;
71 });
72
73 return (true, prepared.Outbound, "");
74 }
75
76 return (false, prepared.Outbound, prepared.Error ?? "Не удалось собрать сообщение.");
77 }
78
79 private Task beginPrepareOutboundAsync() =>
80 UiScheduler.Default.InvokeAsync(() => ChatLoadingStatusText = "Готовлю вложения…");
81
82 private Task endPrepareOutboundAsync() =>
83 UiScheduler.Default.InvokeAsync(() =>
84 {
85 if (!IsChatLoading)
86 ChatLoadingStatusText = "";
87 });
88
89 private async Task tryHandleIntercomSlashLineAsync_showSlashRunningAsync(string slashPath)
90 {
91 await UiScheduler.Default.InvokeAsync(() =>
92 ClarificationStatusText = $"Выполняю {slashPath}…").ConfigureAwait(false);
93 }
94
95 private async Task<bool> tryHandleIntercomSlashLineAsync(string rawInput)
96 {
97 if (!ChatSlashCommandParser.IsSlashLine(rawInput))
98 return false;
99
100 var slashPath = ChatSlashCommandPresentation.FormatDisplayPath(rawInput);
101 if (IsComposerAttachSlash(slashPath))
102 {
103 var attachOnly = await _slashCommandRunner.TryRunAsync(rawInput).ConfigureAwait(false);
104 await UiScheduler.Default.InvokeAsync(() =>
105 {
106 ChatInput = "";
107 if (!attachOnly.Success && !string.IsNullOrWhiteSpace(attachOnly.DetailText))
108 ClarificationStatusText = attachOnly.DetailText;
109 });
110 return true;
111 }
112
113 var slashResolved = ChatSlashCommandCatalog.TryResolveInput(rawInput, out var slashDescriptor, out var resolvedSlashTail);
114 var slashArgs = ChatSlashCommandPresentation.NormalizeArgsTail(resolvedSlashTail);
115 var slashAudience = slashResolved
116 ? slashDescriptor.MessageAudience
117 : IntercomMessageAudience.Channel;
118
119 if (slashResolved && slashDescriptor.ExecutionKind == ChatSlashCommandExecutionKind.LocalAgent)
120 await tryHandleIntercomSlashLineAsync_showSlashRunningAsync(slashPath).ConfigureAwait(false);
121
122 var slash = await _slashCommandRunner.TryRunAsync(rawInput).ConfigureAwait(false);
123 await UiScheduler.Default.InvokeAsync(() =>
124 {
125 var threadAtSlash = _activeThreadId;
126 var cmdMsg = ChatMessageViewModel.CreateSlashCommand(slashPath, slashArgs, threadAtSlash, slashAudience);
127 ChatInput = "";
128 ChatMessages.Add(cmdMsg);
129 if (_activeThreadId != threadAtSlash && _activeThreadId != Guid.Empty)
130 cmdMsg.AssignThread(_activeThreadId);
131 cmdMsg.ApplySlashCommandResult(slash);
132 var payload = ChatHistoryPayloadMapping.ToMessagePayload(cmdMsg);
133 _ = PersistEventAsync(ChatHistoryEventKind.MessageAdded, payload);
134 _ = PersistEventAsync(ChatHistoryEventKind.MessageCompleted, payload);
135 ClarificationStatusText = slash.Success ? "" : (slash.DetailText ?? "");
136 RefreshChatSurfaceSnapshot();
137 });
138 return true;
139 }
140
141 private async Task commitIntercomUserMessageAsync(
142 string displayInput,
143 IntercomAttachmentMessageBuilder.Outbound outbound,
144 bool startProviderLoading,
145 string deliveryMode)
146 {
147 await UiScheduler.Default.InvokeAsync(() =>
148 {
149 ChatInput = "";
150 var parent = _pendingParentForNextMessage;
151 _pendingParentForNextMessage = null;
152 var userMsg = new ChatMessageViewModel(
153 "user",
154 displayInput,
155 threadId: _activeThreadId,
156 parentMessageId: parent,
157 attachments: outbound.Attachments,
158 senderWorkspaceContext: outbound.SenderWorkspaceContext,
159 deliveryMode: deliveryMode);
160 ChatMessages.Add(userMsg);
161 SelectedChatThreadId = _activeThreadId;
162 _ = PersistEventAsync(ChatHistoryEventKind.MessageAdded, ChatHistoryPayloadMapping.ToMessagePayload(userMsg));
163 _ = MaybeMaterializeContextCardAsync(outbound.Attachments, "attach");
164 _ = TryAutoConnectIntercomTransportAsync();
165 RefreshChatSurfaceSnapshot();
166 _ = PersistSessionSolutionPathIfChangedAsync(CancellationToken.None);
167 _ = OnHarnessAfterUserMessageCommittedAsync();
168 if (startProviderLoading)
169 {
170 IsChatLoading = true;
171 ChatLoadingStatusText = "Модель отвечает…";
172 }
173 });
174 }
175
176 private Task endIntercomProviderTurnAsync() =>
177 UiScheduler.Default.InvokeAsync(async () =>
178 {
179 StopAcpWaitWatchdog();
180 IsChatLoading = false;
181 ChatLoadingStatusText = "";
182 OnPropertyChanged(nameof(IntercomComposerPlaceholder));
183 await TryDispatchFollowUpQueueAsync().ConfigureAwait(true);
184 });
185}
186
View only · write via MCP/CIDE