mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
- web_images (amont v0.17.7) : recherche d'images DuckDuckGo, pendant de web_search ; les images du web s'affichent dans le fil avec la loupe, sans Referer, et disparaissent si le site les refuse. Schéma minimal pour tenir le budget du préambule. - Aperçu en direct du navigateur piloté (cu_autoshot.go) : une capture après chaque action browser_* qui change la page, gardée en RAM, jamais envoyée au modèle, retirée du journal en fin de tour ; une carte dans le fil, en fondu. - Vignettes calculées par le serveur (?thumb=, amont v0.17.9) : ~560 px au lieu de l'original (7 Ko au lieu de 550 Ko sur une capture), chargées deux par deux avec un nouvel essai ; la loupe ouvre l'original. - Un lien vers une image ([graphique](graph.png)) l'affiche au lieu d'un bouton de téléchargement. - Chrome du computer use tué avec Loki sous Linux (Pdeathsig), même en cas d'arrêt brutal : plus de navigateur orphelin dans le conteneur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
2.5 KiB
Go
97 lines
2.5 KiB
Go
package loki
|
|
|
|
// Vignettes des images du fil (voir handleChatImage, paramètre thumb ; repris d'AJEAN). Une
|
|
// vignette est calculée une fois par fichier et par taille, puis gardée en
|
|
// mémoire tant que le fichier ne change pas (date et taille).
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"image/jpeg"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
_ "image/gif" // décodeurs enregistrés pour image.Decode
|
|
_ "image/png"
|
|
)
|
|
|
|
const (
|
|
thumbMaxSourceBytes = 40 << 20 // au-delà, pas de vignette : le client prendra l'original
|
|
thumbCacheMax = 300
|
|
)
|
|
|
|
type thumbEntry struct {
|
|
mod time.Time
|
|
size int64
|
|
data []byte
|
|
}
|
|
|
|
var (
|
|
thumbMu sync.Mutex
|
|
thumbCache = map[string]thumbEntry{}
|
|
)
|
|
|
|
// chatThumb renvoie une vignette JPEG dont le grand côté vaut px (borné
|
|
// 64..1600). Erreur si le fichier n'est pas une image décodable ici (webp…) :
|
|
// le client se rabat alors sur l'original.
|
|
func chatThumb(path string, st os.FileInfo, px int) ([]byte, string, error) {
|
|
if px < 64 {
|
|
px = 64
|
|
}
|
|
if px > 1600 {
|
|
px = 1600
|
|
}
|
|
if st.Size() > thumbMaxSourceBytes {
|
|
return nil, "", fmt.Errorf("image trop lourde pour une vignette")
|
|
}
|
|
key := path + "\x00" + strconv.Itoa(px)
|
|
thumbMu.Lock()
|
|
if e, ok := thumbCache[key]; ok && e.mod.Equal(st.ModTime()) && e.size == st.Size() {
|
|
thumbMu.Unlock()
|
|
return e.data, "image/jpeg", nil
|
|
}
|
|
thumbMu.Unlock()
|
|
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
src, _, err := image.Decode(bytes.NewReader(raw))
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("format non pris en charge pour une vignette")
|
|
}
|
|
if o := exifOrientation(raw); o >= 2 && o <= 8 {
|
|
src = applyOrientation(src, o)
|
|
}
|
|
if b := src.Bounds(); b.Dx() > px || b.Dy() > px {
|
|
src = scaleDownToMax(src, px)
|
|
}
|
|
// Fond blanc sous la transparence : en JPEG elle deviendrait noire.
|
|
flat := image.NewRGBA(src.Bounds())
|
|
draw.Draw(flat, flat.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src)
|
|
draw.Draw(flat, flat.Bounds(), src, src.Bounds().Min, draw.Over)
|
|
var buf bytes.Buffer
|
|
if err := jpeg.Encode(&buf, flat, &jpeg.Options{Quality: 78}); err != nil {
|
|
return nil, "", err
|
|
}
|
|
data := buf.Bytes()
|
|
|
|
thumbMu.Lock()
|
|
if len(thumbCache) >= thumbCacheMax {
|
|
for k := range thumbCache { // éviction grossière : la place compte plus que l'ordre
|
|
delete(thumbCache, k)
|
|
if len(thumbCache) < thumbCacheMax/2 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
thumbCache[key] = thumbEntry{mod: st.ModTime(), size: st.Size(), data: data}
|
|
thumbMu.Unlock()
|
|
return data, "image/jpeg", nil
|
|
}
|