]> git.giacomolli.uk - onechat/blob - image_model.go
Accept owner photos and image files in Telegram
[onechat] / image_model.go
1 package main
2
3 import (
4         "encoding/base64"
5         "errors"
6         "fmt"
7         "strings"
8 )
9
10 // imageUserMessage attaches pixels only to messages selected as new by a turn.
11 func imageUserMessage(store *ImageStore, text string) (any, error) {
12         names := imageFiles(text)
13         if len(names) == 0 {
14                 return modelUserMessage(text), nil
15         }
16
17         parts, err := imageParts(store, text, names)
18         if err != nil {
19                 return nil, fmt.Errorf("make image message: %w", err)
20         }
21
22         return modelUserParts(parts), nil
23 }
24
25 // imageParts is the shared wire format for image messages and zoom results.
26 // Tool results use this list directly as function_call_output.output.
27 func imageParts(store *ImageStore, text string, names []string) ([]ModelInputPart, error) {
28         parts := []ModelInputPart{{
29                 Type: "input_text",
30                 Text: text,
31         }}
32         for _, name := range names {
33                 if store == nil {
34                         return nil, errors.New("image store is not available")
35                 }
36
37                 data, err := store.Read(name)
38                 if err != nil {
39                         return nil, fmt.Errorf("read image part: %w", err)
40                 }
41
42                 mime := "image/" + strings.TrimPrefix(name[64:], ".")
43                 if strings.HasSuffix(name, ".jpg") {
44                         mime = "image/jpeg"
45                 }
46
47                 parts = append(parts, ModelInputPart{
48                         Type:     "input_image",
49                         ImageURL: "data:" + mime + ";base64," + base64.StdEncoding.EncodeToString(data),
50                 })
51         }
52
53         return parts, nil
54 }
55
56 // imageToolOutput leaves all results as text except zoom of one image message.
57 // Only the text goes to the log, never the data URLs.
58 func imageToolOutput(box *Toolbox, name, arguments, text string) (any, error) {
59         if name != "zoom" || strings.HasPrefix(text, "error:") {
60                 return text, nil
61         }
62
63         var args ToolZoomArguments
64
65         err := toolDecodeArguments(arguments, &args)
66         if err != nil {
67                 return nil, fmt.Errorf("decode image zoom: %w", err)
68         }
69
70         if args.Name != nil || args.N != 1 {
71                 return text, nil
72         }
73
74         message, err := box.chat.Message(args.ID)
75         if err != nil {
76                 return nil, fmt.Errorf("read image zoom: %w", err)
77         }
78
79         names := imageFiles(message.Text)
80         if len(names) == 0 {
81                 return text, nil
82         }
83
84         parts, err := imageParts(box.images, text, names)
85         if err != nil {
86                 return nil, fmt.Errorf("make image zoom: %w", err)
87         }
88
89         return parts, nil
90 }