@@ -91,7 +91,7 @@ private async Task HandleSmartPasteHotkeyAsync()
9191 return ;
9292 }
9393
94- var request = new ActionExecutionRequest ( result . Action , result . Action . OpenInWindow , null ) ;
94+ var request = new ActionExecutionRequest ( result . Action , OutputMode . InPlace , null ) ; // Smart Paste is always InPlace
9595 await ProcessRequestAsync ( request ) ;
9696 }
9797
@@ -122,84 +122,21 @@ private async Task ProcessRequestAsync(ActionExecutionRequest request)
122122 conversationHistory . Add ( new ChatMessage ( "system" , systemInstruction ) ) ;
123123 conversationHistory . Add ( new ChatMessage ( "user" , $ "{ request . ActionToExecute . Prefix } { userInput } ") ) ;
124124
125- if ( request . ForceOpenInWindow || request . ActionToExecute . OpenInWindow )
126- {
127- // Windowed processing loop
128- while ( true )
129- {
130- var executionResult = await ExecuteRequestWithFallbackAsync ( conversationHistory , request . ProviderOverride , localSessionId ) ;
131-
132- if ( executionResult is null )
133- {
134- // Both primary and fallback failed, or no providers are configured.
135- AppEvents . RequestNotification ( "All available AI providers failed." , NotificationType . Error ) ;
136- break ;
137- }
138-
139- if ( executionResult . Value . Provider . Type == ProviderType . Local && localSessionId is null )
140- {
141- // If this is the first turn in a windowed local session. Create a new session.
142- localSessionId = _localSessionService . StartSession ( ) ;
143- if ( localSessionId is null )
144- {
145- AppEvents . RequestNotification ( "Failed to start a local model session." , NotificationType . Error ) ;
146- return ;
147- }
148- }
149-
150- var ( aiResponse , provider , latencyMs ) = executionResult . Value ;
151- conversationHistory . Add ( new ChatMessage ( "assistant" , aiResponse . Content ) ) ;
125+ var outputMode = request . Mode == OutputMode . Default
126+ ? ( request . ActionToExecute . OpenInWindow ? OutputMode . Windowed : OutputMode . InPlace )
127+ : request . Mode ;
152128
153- // Log to DB
154- await LogToHistoryAsync (
155- request . ActionToExecute . Name ,
156- provider . Name ,
157- aiResponse . ProviderName ,
158- conversationHistory . Last ( m => m . Role == "user" ) . Content ,
159- aiResponse . Content ,
160- aiResponse . PromptTokens ,
161- aiResponse . CompletionTokens ,
162- latencyMs ,
163- aiResponse . TokensPerSecond ) ;
164-
165- // Parse and show the result window
166- var ( mainOutput , explanation ) = ParseOutput ( aiResponse . Content , request . ActionToExecute . ExplainChanges ) ;
167-
168- // Show the window and wait for the user to either close it or request a refinement
169- var windowData = new ResultWindowData ( request . ActionToExecute . Name , mainOutput , explanation ) ;
170- var refinementRequest = await AppEvents . RequestResultWindowAsync ( windowData ) ;
171-
172- if ( refinementRequest is null )
173- break ; // User closed the window
174-
175- conversationHistory . Add ( new ChatMessage ( "user" , refinementRequest . NewInstruction ) ) ;
176- }
177- }
178- else
129+ switch ( outputMode )
179130 {
180- // In-Place execution
181- var executionResult = await ExecuteRequestWithFallbackAsync ( conversationHistory , request . ProviderOverride ) ;
182-
183- if ( executionResult is null )
184- {
185- AppEvents . RequestNotification ( "All available AI providers failed." , NotificationType . Error ) ;
186- return ;
187- }
188-
189- var ( aiResponse , provider , latencyMs ) = executionResult . Value ;
190-
191- await LogToHistoryAsync (
192- request . ActionToExecute . Name ,
193- provider . Name ,
194- aiResponse . ProviderName ,
195- conversationHistory . Last ( m => m . Role == "user" ) . Content ,
196- aiResponse . Content ,
197- aiResponse . PromptTokens ,
198- aiResponse . CompletionTokens ,
199- latencyMs ,
200- aiResponse . TokensPerSecond ) ;
201-
202- await _clipboardService . PasteTextAsync ( aiResponse . Content ) ;
131+ case OutputMode . Windowed :
132+ localSessionId = await HandleWindowedModeAsync ( request , conversationHistory , localSessionId ) ;
133+ break ;
134+ case OutputMode . InPlace :
135+ await HandleInPlaceModeAsync ( request , conversationHistory ) ;
136+ break ;
137+ case OutputMode . Diff :
138+ localSessionId = await HandleDiffModeAsync ( request , userInput , conversationHistory , localSessionId ) ;
139+ break ;
203140 }
204141
205142 overallStopwatch . Stop ( ) ;
@@ -221,6 +158,138 @@ await LogToHistoryAsync(
221158 }
222159 }
223160
161+ private async Task < Guid ? > HandleWindowedModeAsync ( ActionExecutionRequest request , List < ChatMessage > conversationHistory , Guid ? localSessionId )
162+ {
163+ while ( true )
164+ {
165+ var executionResult = await ExecuteRequestWithFallbackAsync ( conversationHistory , request . ProviderOverride , localSessionId ) ;
166+
167+ if ( executionResult is null )
168+ {
169+ AppEvents . RequestNotification ( "All available AI providers failed." , NotificationType . Error ) ;
170+ break ;
171+ }
172+
173+ if ( executionResult . Value . Provider . Type == ProviderType . Local && localSessionId is null )
174+ {
175+ localSessionId = _localSessionService . StartSession ( ) ;
176+ if ( localSessionId is null )
177+ {
178+ AppEvents . RequestNotification ( "Failed to start a local model session." , NotificationType . Error ) ;
179+ return localSessionId ;
180+ }
181+ }
182+
183+ var ( aiResponse , provider , latencyMs ) = executionResult . Value ;
184+ conversationHistory . Add ( new ChatMessage ( "assistant" , aiResponse . Content ) ) ;
185+
186+ await LogToHistoryAsync (
187+ request . ActionToExecute . Name ,
188+ provider . Name ,
189+ aiResponse . ProviderName ,
190+ conversationHistory . Last ( m => m . Role == "user" ) . Content ,
191+ aiResponse . Content ,
192+ aiResponse . PromptTokens ,
193+ aiResponse . CompletionTokens ,
194+ latencyMs ,
195+ aiResponse . TokensPerSecond ) ;
196+
197+ var ( mainOutput , explanation ) = ParseOutput ( aiResponse . Content , request . ActionToExecute . ExplainChanges ) ;
198+ var windowData = new ResultWindowData ( request . ActionToExecute . Name , mainOutput , explanation ) ;
199+ var refinementRequest = await AppEvents . RequestResultWindowAsync ( windowData ) ;
200+
201+ if ( refinementRequest is null ) break ;
202+
203+ conversationHistory . Add ( new ChatMessage ( "user" , refinementRequest . NewInstruction ) ) ;
204+ }
205+ return localSessionId ;
206+ }
207+
208+ private async Task HandleInPlaceModeAsync ( ActionExecutionRequest request , List < ChatMessage > conversationHistory )
209+ {
210+ var executionResult = await ExecuteRequestWithFallbackAsync ( conversationHistory , request . ProviderOverride ) ;
211+
212+ if ( executionResult is null )
213+ {
214+ AppEvents . RequestNotification ( "All available AI providers failed." , NotificationType . Error ) ;
215+ return ;
216+ }
217+
218+ var ( aiResponse , provider , latencyMs ) = executionResult . Value ;
219+
220+ await LogToHistoryAsync (
221+ request . ActionToExecute . Name ,
222+ provider . Name ,
223+ aiResponse . ProviderName ,
224+ conversationHistory . Last ( m => m . Role == "user" ) . Content ,
225+ aiResponse . Content ,
226+ aiResponse . PromptTokens ,
227+ aiResponse . CompletionTokens ,
228+ latencyMs ,
229+ aiResponse . TokensPerSecond ) ;
230+
231+ await _clipboardService . PasteTextAsync ( aiResponse . Content ) ;
232+ }
233+
234+ private async Task < Guid ? > HandleDiffModeAsync ( ActionExecutionRequest request , string originalInput , List < ChatMessage > conversationHistory , Guid ? localSessionId )
235+ {
236+ while ( true )
237+ {
238+ var executionResult = await ExecuteRequestWithFallbackAsync ( conversationHistory , request . ProviderOverride , localSessionId ) ;
239+
240+ if ( executionResult is null )
241+ {
242+ AppEvents . RequestNotification ( "All available AI providers failed." , NotificationType . Error ) ;
243+ return localSessionId ;
244+ }
245+
246+ var ( aiResponse , provider , latencyMs ) = executionResult . Value ;
247+
248+ if ( provider . Type == ProviderType . Local && localSessionId is null )
249+ {
250+ localSessionId = _localSessionService . StartSession ( ) ;
251+ if ( localSessionId is null )
252+ {
253+ AppEvents . RequestNotification ( "Failed to start a local model session." , NotificationType . Error ) ;
254+ return localSessionId ;
255+ }
256+ }
257+
258+ var diffData = new DiffViewData ( request . ActionToExecute . Name , originalInput , aiResponse . Content ) ;
259+ var userDecision = await AppEvents . RequestDiffViewAsync ( diffData ) ;
260+
261+ switch ( userDecision )
262+ {
263+ case Accepted accepted :
264+ await LogToHistoryAsync (
265+ request . ActionToExecute . Name ,
266+ provider . Name ,
267+ aiResponse . ProviderName ,
268+ conversationHistory . Last ( m => m . Role == "user" ) . Content ,
269+ accepted . NewText ,
270+ aiResponse . PromptTokens ,
271+ aiResponse . CompletionTokens ,
272+ latencyMs ,
273+ aiResponse . TokensPerSecond ) ;
274+ await _clipboardService . PasteTextAsync ( accepted . NewText ) ;
275+ return localSessionId ;
276+
277+ case Refined refined :
278+ // The last message in history is the assistant's previous response. Add it before the user's refinement.
279+ conversationHistory . Add ( new ChatMessage ( "assistant" , aiResponse . Content ) ) ;
280+ conversationHistory . Add ( new ChatMessage ( "user" , refined . RefinementInstruction ) ) ;
281+ continue ;
282+
283+ case Regenerated :
284+ continue ;
285+
286+ case Cancelled or null :
287+ return localSessionId ;
288+ }
289+ }
290+ }
291+
292+
224293 /// <summary>
225294 /// Executes an AI request, trying the primary provider first and then the fallback provider upon failure.
226295 /// </summary>
0 commit comments