mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-20 21:48:47 +02:00
Captures de pages web (Playwright) affichées dans le fil
Outil web_screenshot : Chromium piloté par Playwright photographie une page RENDUE (JavaScript exécuté) dans le dossier de travail, et rend au modèle la ligne markdown exacte à recopier — lui laisser composer l'URL reviendrait à lui faire inventer un chemin, donc une image cassée. INDÉPENDANT DE LA VISION, souvent confondu : visionEnabled() (clé MMPROJ) ne décide que d'une chose, l'envoi d'images AU MODÈLE. Capturer et afficher ne passent pas par le modèle — sans projecteur, l'agent photographie sans regarder, ce qui suffit à illustrer une conversation. - route /api/chat/image : sert UNIQUEMENT des images du dossier de travail, en ligne. handleChatFile force le téléchargement de tout pour qu'un .html du modèle ne s'exécute pas dans l'origine de l'UI ; ici la même règle est tenue autrement — type déduit du CONTENU (pas de l'extension), nosniff, et CSP default-src 'none'. Un faux .png contenant du HTML est refusé en 415. - UI : une balise <img> ne peut pas porter d'en-tête Authorization, or /api/* exige la clé dès qu'elle est définie. Les images sont donc récupérées par fetch authentifié puis posées en blob:. - l'outil n'est déclaré au modèle QUE si Playwright est réellement présent : annoncer un outil absent envoie le modèle en boucle de réessai. - Node 22 (NodeSource) au lieu du Node 18 d'Ubuntu, exigé par Playwright et par la plupart des serveurs MCP. PLAYWRIGHT=0 bâtit une image sans navigateur. - CONFIG ACTIVE affichait « llama.cpp personnalisé » pour le moteur de l'image : il annonce maintenant « llama.cpp de l'image ». Description de l'outil tenue au plus court : TestSystemPromptStaysLean a attrapé le dépassement du budget de préambule (7888 car pour 7500). Vérifié dans l'image : capture réelle d'une page, servie en image/png avec CSP ; faux PNG rejeté (415) ; évasion du workspace bloquée (403). Playwright ajoute 816 Mo à l'image.
This commit is contained in:
10 files changed
+315
-5
No files matched your search
@@ -162,7 +162,10 @@ async function loadCfg(){
|
||||
// Moteur : précompilé / compilé / personnalisé (avec le chemin). Le title garde
|
||||
// toujours le chemin complet, quel que soit le libellé.
|
||||
let v;
|
||||
if(lc && lc.prebuilt && lc.prebuilt.in_use) v='llama.cpp précompilé';
|
||||
// En conteneur, le moteur vient de l'image : le dire, plutôt que de le
|
||||
// ranger dans « personnalisé » qui laisse croire à un bricolage.
|
||||
if(lc && lc.provided && sameBinPath(c.BIN, lc.provided_bin)) v='llama.cpp de l\'image';
|
||||
else if(lc && lc.prebuilt && lc.prebuilt.in_use) v='llama.cpp précompilé';
|
||||
else if(lc && lc.in_use) v='llama.cpp compilé';
|
||||
else v='llama.cpp personnalisé : '+c.BIN;
|
||||
rows.push(row('MOTEUR', v, c.BIN));
|
||||
|
||||
@@ -140,7 +140,34 @@ function setStats(el, text){
|
||||
}
|
||||
function bodyOf(el){ return el.querySelector('.body'); }
|
||||
// Render markdown into a message body in place; safe because md() escapes HTML.
|
||||
function renderBody(el, text){ const b=bodyOf(el); b.innerHTML = md(encodeMdLinkSpaces(text)); markNotices(b); addCopyButtons(b); markFileLinks(b); scrollMaybe(); }
|
||||
function renderBody(el, text){ const b=bodyOf(el); b.innerHTML = md(encodeMdLinkSpaces(text)); markNotices(b); addCopyButtons(b); markFileLinks(b); hydrateImages(b); scrollMaybe(); }
|
||||
// Images servies par Loki (/api/chat/image) : une balise <img> ne peut pas
|
||||
// porter d'en-tête Authorization, or /api/* exige la clé de pilotage dès qu'elle
|
||||
// est définie. On récupère donc l'image par fetch authentifié et on la pose en
|
||||
// blob:. Sans ça, toute instance protégée par une clé n'affichait que des images
|
||||
// cassées. Les URLs externes (http…) ne sont pas touchées.
|
||||
function hydrateImages(root){
|
||||
root.querySelectorAll('img[src*="/api/chat/image"]').forEach(async img => {
|
||||
if(img.dataset.hydrated) return;
|
||||
img.dataset.hydrated = '1';
|
||||
const src = img.getAttribute('src');
|
||||
try{
|
||||
const r = await jfetch(src.startsWith('/') ? src : '/' + src);
|
||||
if(!r.ok) throw new Error(r.status);
|
||||
const url = URL.createObjectURL(await r.blob());
|
||||
img.src = url;
|
||||
img.classList.add('chatimg');
|
||||
// La révocation attend le chargement : révoquer tout de suite laisserait
|
||||
// une image vide sur les navigateurs qui décodent en différé.
|
||||
img.addEventListener('load', () => URL.revokeObjectURL(url), {once:true});
|
||||
}catch(e){
|
||||
const note = document.createElement('span');
|
||||
note.className = 'muted';
|
||||
note.textContent = '[image indisponible : ' + src.replace(/^.*path=/, '') + ']';
|
||||
img.replaceWith(note);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Render a tool call as its own conversation message: the command the model
|
||||
// wrote, then the response it got back. textContent keeps it injection-safe.
|
||||
function renderToolMsg(el, tu){
|
||||
|
||||
Reference in new issue
Block a user