Files
html/api/ambre-session-chat.php
2026-04-22 00:00:06 +02:00

120 lines
5.4 KiB
PHP

<?php
/**
* ambre-session-chat.php v2 · onboarding + empathy + identity memory
* First message of session → greeting with identity ask
* Subsequent messages → contextual reply with memory
* Auto-detects: identity declaration, emotion, questions, follow-ups
*/
require_once __DIR__ . "/ambre-session-memory.php";
header("Content-Type: application/json; charset=utf-8");
$raw = file_get_contents("php://input");
$in = json_decode($raw, true) ?: $_POST;
$msg = trim($in["message"] ?? "");
$sid = trim($in["session_id"] ?? "");
if (!$msg) { echo json_encode(["error"=>"message required"]); exit; }
if (!$sid) $sid = "anon-" . substr(md5(($_SERVER["REMOTE_ADDR"] ?? "x") . time()), 0, 10);
$history = AmbreSessionMemory::context_messages($sid, 12);
$turns_before = count($history);
// Extract identity if present (name + company)
$identity = null;
$history_full = AmbreSessionMemory::load($sid);
foreach ($history_full as $m) {
if ($m["role"] === "meta" && strpos($m["content"], "identity:") === 0) {
$identity = trim(substr($m["content"], 9));
break;
}
}
// Try to extract identity from current message
$extracted_name = null;
$extracted_org = null;
// Patterns: "je m'appelle X", "mon nom est X", "I'm X", "je suis X", "X de Y"
if (preg_match('/(?:je\s+m[\'\s]?appelle|mon\s+nom\s+est|je\s+suis|c[\'\s]?est\s+moi|i[\'\s]?m|my\s+name\s+is)\s+([A-ZÀ-Üa-zà-ü][A-ZÀ-Üa-zà-ü\-\s]{1,40}?)(?:\s+(?:de|from|at|chez|pour|travaille|,|\.|!|$))/iu', $msg, $m)) {
$extracted_name = trim($m[1]);
}
if (preg_match('/(?:de|from|chez|at|travaille\s+(?:chez|pour|à))\s+([A-ZÀ-Üa-zà-ü][A-ZÀ-Üa-zà-ü0-9\-\s]{2,40}?)(?:\s*[\.,!]|\s+et|\s*$)/iu', $msg, $m)) {
$extracted_org = trim($m[1]);
}
if (($extracted_name || $extracted_org) && !$identity) {
$id_parts = [];
if ($extracted_name) $id_parts[] = "nom=$extracted_name";
if ($extracted_org) $id_parts[] = "org=$extracted_org";
$identity = implode(" · ", $id_parts);
AmbreSessionMemory::append($sid, "meta", "identity: $identity");
}
// === FIRST TURN : onboarding ===
if ($turns_before === 0 && !$identity) {
// Check if first message IS an identity declaration
if ($extracted_name || $extracted_org) {
// They told us, move to friendly greeting
$greeting_sys = "L'utilisateur vient de se présenter. Salue-le chaleureusement en utilisant son nom si connu et son entreprise si connue. Demande-lui comment tu peux l'aider. 2-3 phrases max. Reste en français.";
$context = "Identité détectée: " . ($identity ?: "inconnue");
} else {
// First message was a direct question without intro
// Reply to the question but ASK identity in a friendly way
$greeting_sys = "Premier échange avec un nouvel utilisateur. Réponds brièvement à sa question, PUIS demande avec élégance son prénom et son entreprise ou domaine d'activité pour personnaliser l'aide. Style chaleureux, 3-4 phrases.";
$context = "Première interaction, identité inconnue.";
}
$messages = [["role"=>"system","content"=>"Tu es WEVIA, une IA professionnelle de WEVAL Consulting. $greeting_sys"]];
if ($context) $messages[] = ["role"=>"system","content"=>$context];
$messages[] = ["role"=>"user","content"=>$msg];
} else {
// === SUBSEQUENT TURNS : contextual with memory ===
$sys_parts = [
"Tu es WEVIA, l'IA de WEVAL Consulting.",
"Tu mémorises les échanges de cette conversation et tu t'adaptes au ton et au contexte.",
];
if ($identity) {
$sys_parts[] = "Identité de l'utilisateur : $identity. Utilise son nom naturellement quand c'est pertinent.";
} else {
$sys_parts[] = "Identité inconnue. Si pertinent, demande-lui son prénom de façon fluide.";
}
$sys_parts[] = "Réponds en français, concis, professionnel, empathique si émotion détectée.";
$sys_parts[] = "Si l'utilisateur revient sur un sujet antérieur, reconnais-le. Si changement de sujet, adapte-toi fluidement.";
$sys_parts[] = "Si demande d'amélioration d'un rendu antérieur, propose une V2 meilleure en te basant sur l'historique.";
$messages = [["role"=>"system","content"=>implode(" ", $sys_parts)]];
foreach ($history as $h) {
if ($h["role"] !== "meta") $messages[] = $h;
}
$messages[] = ["role"=>"user","content"=>$msg];
}
// Call LLM
$t0 = microtime(true);
$raw_llm = @file_get_contents("http://127.0.0.1:4000/v1/chat/completions", false, stream_context_create([
"http" => [
"method"=>"POST",
"header"=>"Content-Type: application/json\r\n",
"content"=>json_encode(["model"=>"fast","messages"=>$messages,"max_tokens"=>1200,"temperature"=>0.5]),
"timeout"=>30,
],
]));
$elapsed = round((microtime(true)-$t0)*1000);
$d = @json_decode($raw_llm, true);
$reply = $d["choices"][0]["message"]["content"] ?? "";
if (!$reply) $reply = "Désolé, je n'ai pas pu traiter la demande. Peux-tu reformuler ?";
AmbreSessionMemory::append($sid, "user", $msg);
AmbreSessionMemory::append($sid, "assistant", $reply);
$summary = AmbreSessionMemory::summary($sid);
echo json_encode([
"response" => $reply,
"provider" => "ambre-session-v2",
"intent" => $turns_before === 0 ? "onboarding" : "contextual_reply",
"session_id" => $sid,
"turns_in_memory" => $summary["turns"],
"history_used" => count($history),
"identity" => $identity,
"elapsed_ms" => $elapsed,
], JSON_UNESCAPED_UNICODE);