Files
Loki/internal/loki/ui/src/js/17-identity.js
T
MichaelandClaude Fable 5 b814862f40 Nom du modèle affiché à côté de « Loki » sur chaque réponse
Le nom du modèle (fichier .gguf, sans dossier ni extension) est journalisé
avec la borne de tour côté serveur : chaque réponse garde le modèle qui l'a
réellement produite, y compris après un rechargement de page (rejeu du
journal) et même si un autre modèle est chargé depuis.

Côté UI, une pastille discrète s'affiche dans l'étiquette de la bulle de
réponse. Elle vit dans dataset.model pour survivre au repeint des libellés
lors d'un changement d'identité (applyIdentity).

Vérifié en conditions réelles (moteur simulé, rechargement en pleine
génération) : badge, tok/s des bulles et chrono « en cours » sont bien
rejoués après un refresh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 15:50:51 +02:00

95 lines
3.5 KiB
JavaScript

// Identité — prénom et avatars affichés en tête des bulles du chat.
//
// Les valeurs vivent dans les préférences SERVEUR (comme le thème) : elles
// suivent donc d'un appareil à l'autre, au lieu de rester dans le localStorage
// d'un seul navigateur. La liste d'emojis est celle que sert le serveur — pas
// une seconde copie côté client, qui divergerait à la première modification.
let IDENT = {name:'', user:'🙂', ai:'🦊'};
let AVATARS = [];
// applyIdentity redessine les libellés des bulles DÉJÀ affichées : changer son
// prénom doit se voir tout de suite, sans recharger la page.
function applyIdentity(){
document.querySelectorAll('#chat .msg.user > .label, #chat .msg.assistant > .label')
.forEach(l => paintLabel(l, l.parentElement.classList.contains('user') ? 'user' : 'assistant'));
}
// paintLabel remplit un libellé : avatar + nom. Le prénom vient de
// l'utilisateur, donc textContent — jamais innerHTML.
function paintLabel(label, role){
if(!label) return;
label.textContent = '';
const av = document.createElement('i'); av.className = 'av';
av.textContent = role === 'user' ? (IDENT.user || '🙂') : (IDENT.ai || '🦊');
const who = document.createElement('span'); who.className = 'who';
who.textContent = role === 'user' ? (IDENT.name || 'toi') : 'Loki';
label.appendChild(av); label.appendChild(who);
// Badge du modèle : quelle IA a produit CETTE réponse. Le nom vit dans
// dataset.model (posé par tagModel, 09-stream.js) — c'est ce qui le fait
// survivre au repeint des libellés lors d'un changement d'identité.
if(role === 'assistant' && label.parentElement && label.parentElement.dataset.model){
label.appendChild(modelBadge(label.parentElement.dataset.model));
}
}
// modelBadge fabrique la pastille du nom de modèle (partagée paintLabel/tagModel).
function modelBadge(name){
const b = document.createElement('span');
b.className = 'modelbadge';
b.textContent = name;
b.title = 'modèle : ' + name;
return b;
}
async function loadIdentity(){
let r;
try{ r = await jget('/api/prefs'); }catch(_){ return; }
const p = r.prefs || {};
AVATARS = r.avatars || [];
IDENT = {
name: p.user_name || '',
user: p.avatar_user || '🙂',
ai: p.avatar_ai || '🦊',
};
const inp = document.getElementById('p-username');
if(inp && document.activeElement !== inp) inp.value = IDENT.name;
renderAvatarPicker('p-avatar-user', 'user');
renderAvatarPicker('p-avatar-ai', 'ai');
applyIdentity();
}
function renderAvatarPicker(elID, which){
const box = document.getElementById(elID);
if(!box) return;
box.innerHTML = '';
for(const e of AVATARS){
const b = document.createElement('button');
b.type = 'button'; b.textContent = e;
if(e === IDENT[which]) b.classList.add('sel');
b.onclick = () => pickAvatar(which, e);
box.appendChild(b);
}
}
async function pickAvatar(which, emoji){
IDENT[which] = emoji;
renderAvatarPicker(which === 'user' ? 'p-avatar-user' : 'p-avatar-ai', which);
applyIdentity();
try{ await jpost('/api/prefs', which === 'user' ? {avatar_user:emoji} : {avatar_ai:emoji}); }
catch(_){ toast('erreur réseau'); }
}
// Saisie du prénom : on n'écrit pas au serveur à chaque frappe.
let identTimer = null;
function saveIdentity(){
const inp = document.getElementById('p-username');
if(!inp) return;
IDENT.name = inp.value.trim();
applyIdentity();
clearTimeout(identTimer);
identTimer = setTimeout(async () => {
try{ await jpost('/api/prefs', {user_name: IDENT.name}); }catch(_){ toast('erreur réseau'); }
}, 500);
}