VoiceSessions : nombre de sessions ayant utilisé le vocal au moins une fois, lu dans le JSON de Metadata. Volontairement hors d'AppTypeDistribution — le vocal n'est pas une plateforme mais un mode d'interaction : un visiteur passe des lunettes à l'écran dans une même session, or la distribution compte une entrée par session tranchée sur l'événement le plus ancien. Le chiffre aurait dépendu de l'ordre dans lequel le visiteur a touché ses appareils. Colonne Metadata déjà existante : aucune migration, le gel du lot B tient. Le test InMemory ne prouve rien ici — le provider évalue le filtre côté client, donc une requête intraduisible passerait au vert. Un test Postgres l'accompagne. IsAutoTriggered devient IsVisitorQuestion (défaut true). Le nom d'hier ne couvrait qu'un des deux cas : le vrai sens n'est pas « déclenché automatiquement » mais « ce tour n'est pas une question de visiteur », ce qui vaut aussi pour l'aperçu de conversation, où c'est le gestionnaire qui teste sa personnalité. Renommé pendant qu'un seul commit en dépendait. Le défaut à true est délibéré : une app visiteur publiée qui n'envoie pas le champ continue de journaliser, et un test le fixe. dotnet test : 204 passés, 14 sautés, 0 échec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
111 lines
4.5 KiB
C#
111 lines
4.5 KiB
C#
using ManagerService.Data;
|
|
using System.Collections.Generic;
|
|
|
|
namespace ManagerService.DTOs
|
|
{
|
|
public class AiChatRequest
|
|
{
|
|
public string Message { get; set; }
|
|
public string InstanceId { get; set; }
|
|
public AppType AppType { get; set; }
|
|
public string ConfigurationId { get; set; } // null = scope instance, fourni = scope configuration
|
|
public string Language { get; set; }
|
|
/// <summary>
|
|
/// GUID généré par le client, stable sur une session de conversation.
|
|
/// History étant reconstruit côté client à chaque appel, c'est le seul moyen
|
|
/// de relier deux questions d'un même visiteur côté serveur.
|
|
/// Optionnel : un appel sans ConversationId reste valide, la ligne journalisée est isolée.
|
|
/// </summary>
|
|
public string ConversationId { get; set; }
|
|
public List<AiChatMessage> History { get; set; } = new();
|
|
/// <summary>
|
|
/// true = interaction vocale (lunettes, assistant vocal).
|
|
/// Le backend adapte le prompt : pas de markdown, pas de navigation,
|
|
/// dates en toutes lettres, réponses courtes audio-friendly.
|
|
/// </summary>
|
|
public bool IsVoice { get; set; } = false;
|
|
/// <summary>
|
|
/// false = ce tour n'est pas une question posée par un visiteur, et ne doit donc
|
|
/// pas être journalisé dans <c>VisitorQuestion</c>. Deux cas aujourd'hui :
|
|
/// <list type="bullet">
|
|
/// <item>le <b>mode proactif</b>, dont la « question » est une consigne que le
|
|
/// système s'écrit à lui-même (« Tu es un guide audio de musée. Le visiteur vient
|
|
/// d'entrer dans la zone X… ») ;</item>
|
|
/// <item>l'<b>aperçu de conversation</b> du Guide IA, où c'est le gestionnaire qui
|
|
/// teste sa personnalité — sans quoi il pollue son propre rapport.</item>
|
|
/// </list>
|
|
///
|
|
/// ⚠️ Les jetons restent comptés dans les deux cas : ils sont réellement consommés.
|
|
///
|
|
/// ⚠️ <b>Défaut à true, et c'est délibéré</b> : un client qui n'envoie pas le champ
|
|
/// journalise, ce qui est le bon comportement pour les apps visiteur déjà publiées.
|
|
/// </summary>
|
|
public bool IsVisitorQuestion { get; set; } = true;
|
|
}
|
|
|
|
public class AiChatMessage
|
|
{
|
|
public string Role { get; set; } // "user" | "assistant"
|
|
public string Content { get; set; }
|
|
}
|
|
|
|
public class AiCardDTO
|
|
{
|
|
public string Title { get; set; }
|
|
public string Subtitle { get; set; }
|
|
public string? Icon { get; set; }
|
|
}
|
|
|
|
public class NavigationActionDTO
|
|
{
|
|
public string SectionId { get; set; }
|
|
public string SectionTitle { get; set; }
|
|
public string SectionType { get; set; }
|
|
public string? ImageUrl { get; set; }
|
|
}
|
|
|
|
public class AiChatResponse
|
|
{
|
|
public string Reply { get; set; }
|
|
public List<AiCardDTO>? Cards { get; set; }
|
|
public NavigationActionDTO? Navigation { get; set; }
|
|
/// <summary>
|
|
/// false = le LLM ne s'attend pas à une réponse du visiteur (info pure, hors-sujet, politesse, beacon).
|
|
/// Le front peut désactiver l'écoute automatique après le TTS.
|
|
/// </summary>
|
|
public bool ExpectsReply { get; set; } = true;
|
|
public long TokensUsed { get; set; }
|
|
|
|
/// <summary>
|
|
/// Contenus sur lesquels la réponse s'appuie, relevés côté serveur à chaque appel de
|
|
/// SearchKnowledge. Ils ne passent pas par le modèle : la règle « ne mentionne jamais
|
|
/// les identifiants techniques » lui interdit précisément de les recopier.
|
|
/// Destinés au client, pas au visiteur — c'est ce qui rend une réponse vérifiable
|
|
/// quand un client conteste ce que dit son guide.
|
|
/// </summary>
|
|
public List<AiSourceDTO>? Sources { get; set; }
|
|
}
|
|
|
|
public class AiSourceDTO
|
|
{
|
|
public string ContentId { get; set; }
|
|
public string ContentType { get; set; }
|
|
|
|
/// <summary>Null hors document paginé.</summary>
|
|
public int? PageNumber { get; set; }
|
|
}
|
|
|
|
public class AiTranslateRequest
|
|
{
|
|
public string Text { get; set; }
|
|
public string SourceLang { get; set; }
|
|
public List<string> TargetLangs { get; set; } = new();
|
|
}
|
|
|
|
public class AiTranslateResponse
|
|
{
|
|
public Dictionary<string, string> Translations { get; set; } = new();
|
|
public long TokensUsed { get; set; }
|
|
}
|
|
}
|