mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Captures d'écran : montrées dans la bulle, sans dépendre du modèle
La capture n'apparaissait dans le fil QUE si le modèle recopiait la ligne markdown rendue par l'outil. Un petit modèle l'oublie, et l'utilisateur ne voyait jamais l'image qu'il avait demandée — la capture existait pourtant bien sur le disque. L'événement d'outil porte désormais le chemin de l'image et l'interface la rend directement dans la bulle web_screenshot (cliquable pour la loupe, comme les autres images du fil). Ce que le modèle en écrit ensuite reste possible, mais n'est plus la condition de l'affichage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0430aa2281
commit
4e5bc44607
6 files changed
+59
-1
No files matched your search
@@ -442,6 +442,9 @@ func (c *Conversation) generate(ctx context.Context, caps Caps, temperature floa
|
||||
if len(ev.ToolUsed.Diff) > 0 {
|
||||
tu["diff"] = ev.ToolUsed.Diff
|
||||
}
|
||||
if ev.ToolUsed.Image != "" {
|
||||
tu["image"] = ev.ToolUsed.Image
|
||||
}
|
||||
c.appendDelta(epoch, map[string]any{"tool_used": tu})
|
||||
case ev.NewHistory != nil:
|
||||
// Compaction faite en cours de tour : elle remplace la base au lieu de
|
||||
|
||||
@@ -155,6 +155,9 @@ func (c *Conversation) forwardStream(ev StreamEvent, epoch int) {
|
||||
if len(ev.ToolUsed.Diff) > 0 {
|
||||
tu["diff"] = ev.ToolUsed.Diff
|
||||
}
|
||||
if ev.ToolUsed.Image != "" {
|
||||
tu["image"] = ev.ToolUsed.Image
|
||||
}
|
||||
c.appendDelta(epoch, map[string]any{"tool_used": tu})
|
||||
case ev.Ask != nil:
|
||||
c.appendDelta(epoch, map[string]any{"ask": ev.Ask})
|
||||
|
||||
@@ -370,6 +370,12 @@ type ToolUsedEvent struct {
|
||||
// Diff : lignes ajoutées/retirées quand l'outil a MODIFIÉ quelque chose
|
||||
// (edit, mem_add, mem_edit). L'UI les affiche en vert (+) et rouge (-).
|
||||
Diff []DiffLine
|
||||
// Image : chemin (relatif au dossier de travail) d'une image PRODUITE par
|
||||
// l'outil — aujourd'hui la capture de web_screenshot. L'UI l'affiche dans la
|
||||
// bulle de l'outil. Sans ça, la capture n'apparaissait QUE si le modèle
|
||||
// pensait à recopier la ligne markdown rendue par l'outil : un petit modèle
|
||||
// l'oublie, et l'utilisateur ne voyait jamais l'image qu'il avait demandée.
|
||||
Image string
|
||||
}
|
||||
|
||||
// shownDisplayMax borne ce qu'un résultat d'outil occupe dans le FLUX vers l'UI.
|
||||
@@ -1210,7 +1216,13 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
|
||||
if !strings.HasPrefix(result, "[erreur]") {
|
||||
doneCalls[callKey] = result
|
||||
}
|
||||
cb(StreamEvent{ToolUsed: &ToolUsedEvent{Name: tc.Function.Name, Label: label, Result: shownResult(result), Done: true, Diff: diff}})
|
||||
// Capture d'écran : l'image part avec l'événement pour être
|
||||
// affichée dans la bulle, quoi que le modèle en fasse ensuite.
|
||||
shot := ""
|
||||
if tc.Function.Name == "web_screenshot" {
|
||||
shot = capturedRelPath(result)
|
||||
}
|
||||
cb(StreamEvent{ToolUsed: &ToolUsedEvent{Name: tc.Function.Name, Label: label, Result: shownResult(result), Done: true, Diff: diff, Image: shot}})
|
||||
toolMsg := Message{Role: "tool", ToolCallID: tc.ID, Content: result}
|
||||
messages = append(messages, toolMsg)
|
||||
extra = append(extra, toolMsg)
|
||||
|
||||
@@ -2026,6 +2026,11 @@ html[data-files="1"] #files-btn{color:var(--accent)}
|
||||
.askopts button{border:1px solid var(--accent);background:transparent;color:var(--accent);border-radius:3px;padding:4px 10px;font-size:13px;cursor:pointer;font-family:inherit}
|
||||
.askopts button:hover:not(:disabled){background:var(--accent-bg)}
|
||||
.askopts button:disabled{opacity:.5;cursor:default}
|
||||
|
||||
/* Capture d'écran rendue dans la bulle de l'outil (web_screenshot) : l'image
|
||||
ne dépend plus du modèle qui pense — ou non — à la recopier dans sa réponse. */
|
||||
.tool-shot{margin-top:8px}
|
||||
.tool-shot img{max-width:100%;max-height:420px;border:1px solid var(--border);border-radius:4px;display:block;cursor:zoom-in}
|
||||
</style></head><body>
|
||||
<button id="menubtn" onclick="toggleSide()" title="menu" aria-label="ouvrir le menu"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h16"/></svg></button>
|
||||
<!-- Installation d'un moteur en cours : le détail vit dans le panneau, qui est
|
||||
@@ -5458,6 +5463,21 @@ function renderToolMsg(el, tu){
|
||||
// la ligne en cours (max-height côté CSS l'empêche de pousser le fil).
|
||||
pre.scrollTop = pre.scrollHeight;
|
||||
}
|
||||
// Capture d'écran : l'image est rendue ICI, dans la bulle de l'outil. Elle ne
|
||||
// dépend donc plus de la bonne volonté du modèle à recopier la ligne markdown
|
||||
// — il l'oubliait, et l'utilisateur ne voyait jamais ce qu'il avait demandé.
|
||||
if(tu.image){
|
||||
const wrap=document.createElement('div'); wrap.className='tool-shot';
|
||||
const img=document.createElement('img');
|
||||
// src (et non data-src) : hydrateImages sélectionne sur src, puis le
|
||||
// remplace par un blob obtenu avec la clé de pilotage.
|
||||
img.setAttribute('src', '/api/chat/image?path='+encodeURIComponent(tu.image));
|
||||
img.alt = tu.image.split('/').pop();
|
||||
img.loading = 'lazy';
|
||||
wrap.appendChild(img);
|
||||
body.appendChild(wrap);
|
||||
hydrateImages(wrap);
|
||||
}
|
||||
// Diff d'une écriture (fichier ou page de mémoire) : lignes ajoutées en vert,
|
||||
// retirées en rouge, contexte en gris — comme un diff de terminal.
|
||||
if(tu.diff && tu.diff.length){
|
||||
|
||||
@@ -326,6 +326,21 @@ function renderToolMsg(el, tu){
|
||||
// la ligne en cours (max-height côté CSS l'empêche de pousser le fil).
|
||||
pre.scrollTop = pre.scrollHeight;
|
||||
}
|
||||
// Capture d'écran : l'image est rendue ICI, dans la bulle de l'outil. Elle ne
|
||||
// dépend donc plus de la bonne volonté du modèle à recopier la ligne markdown
|
||||
// — il l'oubliait, et l'utilisateur ne voyait jamais ce qu'il avait demandé.
|
||||
if(tu.image){
|
||||
const wrap=document.createElement('div'); wrap.className='tool-shot';
|
||||
const img=document.createElement('img');
|
||||
// src (et non data-src) : hydrateImages sélectionne sur src, puis le
|
||||
// remplace par un blob obtenu avec la clé de pilotage.
|
||||
img.setAttribute('src', '/api/chat/image?path='+encodeURIComponent(tu.image));
|
||||
img.alt = tu.image.split('/').pop();
|
||||
img.loading = 'lazy';
|
||||
wrap.appendChild(img);
|
||||
body.appendChild(wrap);
|
||||
hydrateImages(wrap);
|
||||
}
|
||||
// Diff d'une écriture (fichier ou page de mémoire) : lignes ajoutées en vert,
|
||||
// retirées en rouge, contexte en gris — comme un diff de terminal.
|
||||
if(tu.diff && tu.diff.length){
|
||||
|
||||
@@ -1995,3 +1995,8 @@ html[data-files="1"] #files-btn{color:var(--accent)}
|
||||
.askopts button{border:1px solid var(--accent);background:transparent;color:var(--accent);border-radius:3px;padding:4px 10px;font-size:13px;cursor:pointer;font-family:inherit}
|
||||
.askopts button:hover:not(:disabled){background:var(--accent-bg)}
|
||||
.askopts button:disabled{opacity:.5;cursor:default}
|
||||
|
||||
/* Capture d'écran rendue dans la bulle de l'outil (web_screenshot) : l'image
|
||||
ne dépend plus du modèle qui pense — ou non — à la recopier dans sa réponse. */
|
||||
.tool-shot{margin-top:8px}
|
||||
.tool-shot img{max-width:100%;max-height:420px;border:1px solid var(--border);border-radius:4px;display:block;cursor:zoom-in}
|
||||
Reference in new issue
Block a user