166 lines
8.3 KiB
C#

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<AiChatResponse> ChatAsync(AiChatRequest request)
{
var messages = new List<ChatMessage>();
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<AiCardDTO>? cards = null;
// Outil scopé : vérifie que la section appartient bien à cette configuration
var scopedSections = sections;
var tools = new List<AITool>
{
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<AITool>
{
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 };
}
}
}
}