using ManagerService.Data; using ManagerService.DTOs; using Microsoft.Extensions.AI; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace ManagerService.Services { public class AssistantService { private readonly IChatClient _chatClient; private readonly MyInfoMateDbContext _context; private const int MaxHistoryMessages = 10; public AssistantService(IChatClient chatClient, MyInfoMateDbContext context) { _chatClient = chatClient; _context = context; } public async Task ChatAsync(AiChatRequest request) { var messages = new List(); ChatOptions options; if (request.ConfigurationId != null) { // Scope configuration : l'IA connaît uniquement les sections de cette configuration var sections = _context.Sections.Where(s => s.ConfigurationId == request.ConfigurationId).ToList(); var config = _context.Configurations.FirstOrDefault(c => c.Id == request.ConfigurationId); var sectionsSummary = string.Join("\n", sections.Select(s => { var title = s.Title?.FirstOrDefault(t => t.language == request.Language)?.value ?? s.Title?.FirstOrDefault()?.value ?? s.Label; return $"- id:{s.Id} | type:{s.Type} | titre:\"{title}\""; })); messages.Add(new ChatMessage(ChatRole.System, $""" Tu es l'assistant de visite de "{config?.Label ?? "cette application"}". Tu réponds en {request.Language}. Tu es chaleureux, concis et utile. Voici les sections disponibles dans cette application : {sectionsSummary} Pour obtenir le détail d'une section spécifique, utilise l'outil GetSectionDetail. Pour afficher une liste d'éléments (événements, activités...) de façon visuelle, appelle show_cards avec les titres et sous-titres. Pour proposer à l'utilisateur d'aller directement dans une section, appelle navigate_to_section. Accompagne toujours tes outils d'un texte explicatif. Ne parle jamais de sections qui ne sont pas dans cette liste. """)); foreach (var h in request.History.TakeLast(MaxHistoryMessages)) messages.Add(new ChatMessage(h.Role == "user" ? ChatRole.User : ChatRole.Assistant, h.Content)); messages.Add(new ChatMessage(ChatRole.User, request.Message)); NavigationActionDTO? navigation = null; List? cards = null; // Outil scopé : vérifie que la section appartient bien à cette configuration var scopedSections = sections; var tools = new List { AIFunctionFactory.Create( (string sectionId) => { var section = scopedSections.FirstOrDefault(s => s.Id == sectionId); if (section == null) return "Section non trouvée dans cette application."; var title = section.Title?.FirstOrDefault(t => t.language == request.Language)?.value ?? section.Title?.FirstOrDefault()?.value ?? section.Label; var desc = section.Description?.FirstOrDefault(t => t.language == request.Language)?.value ?? section.Description?.FirstOrDefault()?.value ?? ""; return $"Titre: {title}\nDescription: {desc}\nType: {section.Type}"; }, "GetSectionDetail", "Récupère le titre et la description complète d'une section à partir de son id" ), AIFunctionFactory.Create( (string sectionId, string sectionTitle, string sectionType) => { navigation = new NavigationActionDTO { SectionId = sectionId, SectionTitle = sectionTitle, SectionType = sectionType }; return Task.FromResult("Navigation proposée à l'utilisateur."); }, "navigate_to_section", "Propose navigation to a specific section when the user wants to go there or see it." ), AIFunctionFactory.Create( (string[] titles, string[] subtitles, string[]? icons) => { cards = titles.Select((t, i) => new AiCardDTO { Title = t, Subtitle = i < subtitles.Length ? subtitles[i] : "", Icon = icons != null && i < icons.Length ? icons[i] : null }).ToList(); return Task.FromResult("Cartes affichées."); }, "show_cards", "Display structured info cards in the chat. Use for lists of items (events, activities...) that benefit from visual presentation." ) }; options = new ChatOptions { Tools = tools }; var response = await _chatClient.GetResponseAsync(messages, options); return new AiChatResponse { Reply = response.Text ?? "", Cards = cards, Navigation = navigation }; } else { // Scope instance : l'IA connaît les configurations disponibles var configurations = _context.Configurations.Where(c => c.InstanceId == request.InstanceId).ToList(); var configsSummary = string.Join("\n", configurations.Select(c => { var title = c.Title?.FirstOrDefault(t => t.language == request.Language)?.value ?? c.Title?.FirstOrDefault()?.value ?? c.Label; return $"- id:{c.Id} | titre:\"{title}\""; })); messages.Add(new ChatMessage(ChatRole.System, $""" Tu es l'assistant de visite. Tu réponds en {request.Language}. Tu es chaleureux, concis et utile. Voici les expériences de visite disponibles : {configsSummary} Aide le visiteur à trouver ce qui l'intéresse parmi ces expériences. Pour orienter le visiteur vers une visite spécifique, appelle navigate_to_configuration. Accompagne toujours tes outils d'un texte explicatif. """)); foreach (var h in request.History.TakeLast(MaxHistoryMessages)) messages.Add(new ChatMessage(h.Role == "user" ? ChatRole.User : ChatRole.Assistant, h.Content)); messages.Add(new ChatMessage(ChatRole.User, request.Message)); NavigationActionDTO? navigation = null; var tools = new List { AIFunctionFactory.Create( (string configurationId, string configurationTitle) => { navigation = new NavigationActionDTO { SectionId = configurationId, SectionTitle = configurationTitle, SectionType = "Configuration" }; return Task.FromResult("Navigation vers la visite proposée."); }, "navigate_to_configuration", "Propose navigation to a specific visit/configuration when the user wants to start or explore it." ) }; options = new ChatOptions { Tools = tools }; var response = await _chatClient.GetResponseAsync(messages, options); return new AiChatResponse { Reply = response.Text ?? "", Navigation = navigation }; } } } }