mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-22 06:28:11 +02:00
Images du fil : miniatures des deux côtés, loupe au clic
Une pièce jointe n'affichait que son nom de fichier, et une image posée par l'IA sortait à sa taille NATIVE — .chatimg portait la classe mais aucune règle CSS, donc une capture remplissait la colonne et poussait le texte hors de vue. - pièce jointe image : vignette dans le composeur (objectURL local, rien n'est encore parti au serveur) et tuile 200x160 dans le message envoyé, hydratée comme les captures (fetch authentifié + blob:) ; - images de l'IA plafonnées à 280px de haut ; - clic sur n'importe quelle image = loupe plein écran (Échap ou clic sur le fond pour fermer). La source est relue AU CLIC, l'hydratation remplaçant le src par un blob: après coup. align-items:flex-start sur .msg-files au passage : une pastille texte voisine d'une vignette s'étirait à sa hauteur et, arrondie à 999px, devenait un gros ovale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
781520afd8
commit
88e1b38582
5 files changed
+310
-10
No files matched your search
@@ -26,12 +26,28 @@ function fmtSize(n){
|
||||
if(n >= 1024) return Math.round(n/1024)+' Ko';
|
||||
return n+' o';
|
||||
}
|
||||
// Un nom de fichier ne dit pas ce qu'on a joint : pour une image, on montre
|
||||
// l'image. Reconnaissance à l'extension — le serveur, lui, renifle le CONTENU
|
||||
// (handleChatImage) et répond 415 si ça n'en est pas une, donc au pire on affiche
|
||||
// une vignette cassée qui retombe sur son texte de repli.
|
||||
const IMG_EXT = /\.(png|jpe?g|gif|webp|bmp|svg|avif|ico|heic|heif|tiff?)$/i;
|
||||
function isImageName(name){ return IMG_EXT.test(String(name||'')); }
|
||||
// Pastille de fichier, partagée par le composeur et les bulles du fil : même
|
||||
// objet visuel des deux côtés, seul le contexte (CSS) change.
|
||||
//
|
||||
// opts.thumb = URL d'aperçu : la pastille porte alors une vignette à gauche du
|
||||
// nom. Le composeur s'en sert avec un objectURL local (rien n'est encore parti
|
||||
// vers le serveur à ce stade).
|
||||
function fileChip(name, size, opts){
|
||||
opts=opts||{};
|
||||
const chip=document.createElement('div');
|
||||
chip.className='chip-file'+(opts.cls?' '+opts.cls:'');
|
||||
if(opts.thumb){
|
||||
const t=document.createElement('img');
|
||||
t.className='cf-thumb'; t.src=opts.thumb; t.alt='';
|
||||
chip.appendChild(t);
|
||||
chip.classList.add('has-thumb');
|
||||
}
|
||||
const n=document.createElement('span');
|
||||
n.className='cf-name'; n.textContent=name; n.title=opts.title||name;
|
||||
const s=document.createElement('span');
|
||||
@@ -49,12 +65,31 @@ function fileChip(name, size, opts){
|
||||
// Fichiers joints à un message DÉJÀ envoyé : posés AU-DESSUS de la bulle, dans
|
||||
// leur propre rangée. Les mettre dedans étirait la bulle vers le haut et donnait
|
||||
// un bloc bâtard, moitié texte moitié pastilles.
|
||||
// Vignette d'une image DÉJÀ déposée : on la redemande au serveur par son chemin
|
||||
// de travail. hydrateImages fait le fetch authentifié et pose le blob: — une
|
||||
// balise <img> ne peut pas porter la clé de pilotage.
|
||||
function msgImgTile(f){
|
||||
const box=document.createElement('div');
|
||||
box.className='msg-img';
|
||||
const img=document.createElement('img');
|
||||
img.src='/api/chat/image?path='+encodeURIComponent(f.path);
|
||||
img.alt=f.name||''; img.title=f.name||'';
|
||||
box.appendChild(img);
|
||||
return box;
|
||||
}
|
||||
function addMsgFiles(el, files){
|
||||
if(!el || !files || !files.length) return;
|
||||
const box=document.createElement('div');
|
||||
box.className='msg-files';
|
||||
for(const f of files) box.appendChild(fileChip(f.name, f.size, {title:f.path||f.name}));
|
||||
for(const f of files){
|
||||
// Image : la vignette REMPLACE la pastille texte. Deux objets pour un seul
|
||||
// fichier (une vignette + son nom en pastille) doublerait la rangée sans rien
|
||||
// apprendre — le nom vit dans l'infobulle et dans la loupe.
|
||||
if(f.path && isImageName(f.name)) box.appendChild(msgImgTile(f));
|
||||
else box.appendChild(fileChip(f.name, f.size, {title:f.path||f.name}));
|
||||
}
|
||||
el.parentNode.insertBefore(box, el);
|
||||
hydrateImages(box);
|
||||
// Envoi sans un mot : la bulle serait un rectangle vide sous les pastilles.
|
||||
markEmptyMsg(el);
|
||||
return box;
|
||||
@@ -202,11 +237,18 @@ function renderAttach(){
|
||||
sizeText: a.state==='err' ? 'échec'
|
||||
: (a.state==='up' && a.size>ATTACH_CHUNK) ? Math.round((a.sent||0)*100/a.size)+' %'
|
||||
: fmtSize(a.size),
|
||||
onRemove: ()=>{ ATTACH=ATTACH.filter(o=>o.id!==a.id); renderAttach(); }
|
||||
thumb: a.thumb,
|
||||
onRemove: ()=>{ ATTACH=ATTACH.filter(o=>o.id!==a.id); dropThumb(a); renderAttach(); }
|
||||
}));
|
||||
}
|
||||
}
|
||||
function clearAttach(){ ATTACH=[]; renderAttach(); }
|
||||
// L'aperçu du composeur vit sur un objectURL local : il meurt avec sa pastille,
|
||||
// sinon chaque fichier retiré laisse son image en mémoire jusqu'au rechargement.
|
||||
// (La bulle envoyée, elle, repart du serveur — elle ne dépend pas de cet URL.)
|
||||
function dropThumb(a){
|
||||
if(a && a.thumb){ URL.revokeObjectURL(a.thumb); a.thumb=null; }
|
||||
}
|
||||
function clearAttach(){ for(const a of ATTACH) dropThumb(a); ATTACH=[]; renderAttach(); }
|
||||
// Ce qui part avec le message, pour l'afficher dans sa bulle.
|
||||
function attachSent(){ return ATTACH.filter(a=>a.state==='ok').map(a=>({name:a.name,size:a.size,path:a.path})); }
|
||||
|
||||
@@ -277,7 +319,11 @@ function addFiles(files){
|
||||
for(const f of files||[]){
|
||||
if(f.size>ATTACH_MAX){ toast('« '+f.name+' » fait '+fmtSize(f.size)+' — maximum '+fmtSize(ATTACH_MAX)); continue; }
|
||||
if(!f.size){ toast('« '+f.name+' » est vide'); continue; }
|
||||
ATTACH.push({id:++ATTACH_SEQ, name:f.name||'fichier', size:f.size, file:f, path:null, state:'queued'});
|
||||
// Aperçu tout de suite, depuis le fichier local : rien n'est encore parti
|
||||
// vers le serveur (le dépôt n'a lieu qu'à l'envoi), donc c'est la seule
|
||||
// source d'image disponible à ce stade.
|
||||
const thumb = isImageName(f.name) ? URL.createObjectURL(f) : null;
|
||||
ATTACH.push({id:++ATTACH_SEQ, name:f.name||'fichier', size:f.size, file:f, path:null, state:'queued', thumb:thumb});
|
||||
}
|
||||
renderAttach();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user