mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 15:08:16 +02:00
Repris d'AJEAN : recherche d'images, aperçu du navigateur, vignettes, liens d'images
- 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>
This commit is contained in:
1 parent
502f23e833
commit
5016396998
20 files changed
+759
-30
No files matched your search
@@ -0,0 +1,139 @@
|
||||
package loki
|
||||
|
||||
// web_images : la recherche d'images DuckDuckGo, pendant de web_search. Sans
|
||||
// elle, « montre-moi des photos de X » obligeait le modèle à ouvrir des pages
|
||||
// dans le navigateur et à en renvoyer des captures (lent, et des images de
|
||||
// pages au lieu de photos).
|
||||
//
|
||||
// DuckDuckGo sert les images par i.js, qui exige un jeton « vqd » lu dans la
|
||||
// page de recherche. Même client HTTP que le moteur intégré, quel que soit le
|
||||
// moteur web actif : Crawl4AI n'apporterait rien à un appel JSON.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func webImagesTool() Tool {
|
||||
return Tool{Type: "function", Function: ToolFunction{
|
||||
// Schéma minimal : le préambule a un budget (chat_prompt_size_test.go).
|
||||
// limit n'est pas annoncé ; toolWebImages l'accepte encore (6 par défaut).
|
||||
Name: "web_images",
|
||||
Description: "Find images.",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"query": map[string]any{"type": "string"},
|
||||
},
|
||||
"required": []string{"query"},
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
type imageResult struct {
|
||||
Image string `json:"image"`
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
var ddgVqdRe = regexp.MustCompile(`vqd=["']?([0-9-]+)`)
|
||||
|
||||
func ddgGet(ctx context.Context, target, referer string) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", goFetchUA)
|
||||
req.Header.Set("Accept-Language", "fr-FR,fr;q=0.9,en;q=0.8")
|
||||
if referer != "" {
|
||||
req.Header.Set("Referer", referer)
|
||||
req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")
|
||||
}
|
||||
resp, err := goHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("échec de la requête : %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := io.ReadAll(io.LimitReader(resp.Body, goFetchMaxBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func duckduckgoImages(query string, limit int) ([]imageResult, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), goFetchTimeout)
|
||||
defer cancel()
|
||||
page := "https://duckduckgo.com/?q=" + url.QueryEscape(query) + "&iax=images&ia=images"
|
||||
body, err := ddgGet(ctx, page, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := ddgVqdRe.FindSubmatch(body)
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("jeton DuckDuckGo introuvable (défi anti-bot ?)")
|
||||
}
|
||||
api := "https://duckduckgo.com/i.js?l=fr-fr&o=json&f=,,,,,&p=1&q=" + url.QueryEscape(query) + "&vqd=" + string(m[1])
|
||||
body, err = ddgGet(ctx, api, "https://duckduckgo.com/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var r struct {
|
||||
Results []imageResult `json:"results"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &r); err != nil {
|
||||
return nil, fmt.Errorf("réponse illisible : %v", err)
|
||||
}
|
||||
var out []imageResult
|
||||
seen := map[string]bool{}
|
||||
for _, it := range r.Results {
|
||||
if it.Image == "" || seen[it.Image] || !strings.HasPrefix(it.Image, "http") {
|
||||
continue
|
||||
}
|
||||
seen[it.Image] = true
|
||||
out = append(out, it)
|
||||
if len(out) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func toolWebImages(args map[string]any) string {
|
||||
query, _ := args["query"].(string)
|
||||
limit := 6
|
||||
if v, ok := args["limit"].(float64); ok {
|
||||
limit = int(v)
|
||||
}
|
||||
if limit < 1 {
|
||||
limit = 1
|
||||
}
|
||||
if limit > 20 {
|
||||
limit = 20
|
||||
}
|
||||
results, err := duckduckgoImages(query, limit)
|
||||
if err != nil {
|
||||
return "❌ Recherche d'images échouée : " + err.Error()
|
||||
}
|
||||
if len(results) == 0 {
|
||||
return fmt.Sprintf("Aucune image pour « %s »", query)
|
||||
}
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "# Images : %s\n%d résultat(s) DuckDuckGo\n\n", query, len(results))
|
||||
for i, r := range results {
|
||||
fmt.Fprintf(&b, "%d. %s (%dx%d)\n image : %s\n page : %s\n\n", i+1, r.Title, r.Width, r.Height, r.Image, r.URL)
|
||||
}
|
||||
b.WriteString("To show an image to the user, write  in your answer.")
|
||||
return b.String()
|
||||
}
|
||||
Reference in new issue
Block a user