11using System . Security . Claims ;
2+ using System . Text . RegularExpressions ;
23using CrestApps . OrchardCore . AI . Chat . Interactions . ViewModels ;
34using CrestApps . OrchardCore . AI . Core ;
45using CrestApps . OrchardCore . AI . Models ;
@@ -244,6 +245,63 @@ public async Task<ActionResult> Chat(string source, string itemId)
244245 return View ( model ) ;
245246 }
246247
248+ [ Admin ( "ai/chat/interaction/new-chat/{source}" , "NewInteractionsChat" ) ]
249+ public async Task < ActionResult > New ( string source )
250+ => RedirectToAction ( nameof ( Chat ) , new { source } ) ;
251+
252+ [ Admin ( "ai/chat/interaction/clone-chat/{itemId}" , "CloneInteractionsChat" ) ]
253+ public async Task < ActionResult > Clone ( string itemId )
254+ {
255+ var interaction = await _interactionManager . FindByIdAsync ( itemId ) ;
256+
257+ if ( interaction is null )
258+ {
259+ return NotFound ( ) ;
260+ }
261+
262+ if ( ! await _authorizationService . AuthorizeAsync ( User , AIPermissions . EditChatInteractions , interaction ) )
263+ {
264+ return Forbid ( ) ;
265+ }
266+
267+ var clonedInteraction = await _interactionManager . NewAsync ( interaction . Source , interaction . Properties ) ;
268+ clonedInteraction . Title = GetNextTitle ( interaction . Title ) ;
269+ clonedInteraction . DeploymentId = interaction . DeploymentId ;
270+ clonedInteraction . ConnectionName = interaction . ConnectionName ;
271+ clonedInteraction . SystemMessage = interaction . SystemMessage ;
272+ clonedInteraction . Temperature = interaction . Temperature ;
273+ clonedInteraction . TopP = interaction . TopP ;
274+ clonedInteraction . FrequencyPenalty = interaction . FrequencyPenalty ;
275+ clonedInteraction . PresencePenalty = interaction . PresencePenalty ;
276+ clonedInteraction . MaxTokens = interaction . MaxTokens ;
277+ clonedInteraction . PastMessagesCount = interaction . PastMessagesCount ;
278+ clonedInteraction . DocumentTopN = interaction . DocumentTopN ;
279+ clonedInteraction . ToolNames = interaction . ToolNames . ToList ( ) ;
280+ clonedInteraction . ToolInstanceIds = interaction . ToolInstanceIds . ToList ( ) ;
281+ clonedInteraction . McpConnectionIds = interaction . McpConnectionIds . ToList ( ) ;
282+ clonedInteraction . Documents = interaction . Documents . ToList ( ) ;
283+ clonedInteraction . Prompts = [ ] ; // Start with no prompts in the cloned interaction.
284+ clonedInteraction . DocumentIndex = interaction . Documents . Count ; // Set the document index based on the cloned documents.
285+
286+ if ( ! await _authorizationService . AuthorizeAsync ( User , AIPermissions . EditChatInteractions , clonedInteraction ) )
287+ {
288+ return Forbid ( ) ;
289+ }
290+
291+ // Save the interaction immediately so it can be used by the SignalR hub.
292+ await _interactionManager . CreateAsync ( clonedInteraction ) ;
293+
294+ var model = new EditChatInteractionEntryViewModel
295+ {
296+ ItemId = clonedInteraction . ItemId ,
297+ Source = clonedInteraction . Source ,
298+ DisplayName = clonedInteraction . Title ,
299+ Editor = await _interactionDisplayManager . BuildEditorAsync ( clonedInteraction , _updateModelAccessor . ModelUpdater , isNew : true ) ,
300+ } ;
301+
302+ return View ( nameof ( Chat ) , model ) ;
303+ }
304+
247305 [ HttpPost ]
248306 public async Task < IActionResult > Delete ( string itemId )
249307 {
@@ -270,4 +328,28 @@ public async Task<IActionResult> Delete(string itemId)
270328
271329 return RedirectToAction ( nameof ( Index ) ) ;
272330 }
331+
332+ private static readonly Regex PostfixRegex = new Regex ( @"\s*\((\d+)\)$" , RegexOptions . Compiled ) ;
333+
334+ private static string GetNextTitle ( string title )
335+ {
336+ if ( string . IsNullOrWhiteSpace ( title ) )
337+ {
338+ return "Untitled (1)" ;
339+ }
340+
341+ var match = PostfixRegex . Match ( title ) ;
342+
343+ if ( match . Success )
344+ {
345+ // Extract number and increment
346+ var number = int . Parse ( match . Groups [ 1 ] . Value ) ;
347+ var baseTitle = title . Substring ( 0 , match . Index ) . TrimEnd ( ) ;
348+
349+ return $ "{ baseTitle } ({ number + 1 } )";
350+ }
351+
352+ // No postfix found → start at (1)
353+ return $ "{ title } (1)";
354+ }
273355}
0 commit comments