Ctrl K
ComunidadGuías…

pluginapi/media.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package pluginapi

import (
	"encoding/base64"
	"fmt"
	"strings"
)

// DecodeMedia returns the inline payload of an image or audio part and its
// media type: an image carried as a data: URI, or audio carried as base64.
// ok is false for a part referenced by URL (nothing inline to decode), for
// other part kinds, and for a payload that does not decode. It is the
// reader half of [Prompt.SetMedia].
func (p Part) DecodeMedia() (data []byte, mediaType string, ok bool) {
	switch p.Kind {
	case PartImage:
		return decodeDataURI(p.URL)
	case PartAudio:
		payload := string(p.Data)
		if strings.HasPrefix(payload, "data:") {
			return decodeDataURI(payload)
		}
		decoded, err := base64.StdEncoding.DecodeString(payload)
		if err != nil || len(decoded) == 0 {
			return nil, "", false
		}
		return decoded, p.MediaType, true
	default:
		return nil, "", false
	}
}

// SetMedia replaces the payload of the image or audio part partIdx of
// message msgID with data of the given media type ("image/png",
// "audio/wav"). The part is re-encoded in the wire format the message
// arrived in: a data: URI for an image, base64 input audio for chat audio;
// other members of the wire part, such as an image's detail hint, are kept.
// Use [Prompt.SetToolResult] for media inside a tool result.
func (p *Prompt) SetMedia(msgID string, partIdx int, mediaType string, data []byte) error {
	m := p.Message(msgID)
	if m == nil {
		return unknownMessage(msgID)
	}
	if partIdx < 0 || partIdx >= len(m.Parts) {
		return fmt.Errorf("pluginapi: message %q has no part %d", msgID, partIdx)
	}
	part := &m.Parts[partIdx]
	if part.Kind != PartImage && part.Kind != PartAudio {
		return fmt.Errorf("pluginapi: part %d of message %q is %s, not image or audio", partIdx, msgID, part.Kind)
	}
	mediaType = strings.TrimSpace(mediaType)
	if !strings.Contains(mediaType, "/") {
		return fmt.Errorf("pluginapi: media type %q is not a MIME type", mediaType)
	}
	if prefix := string(part.Kind) + "/"; !strings.HasPrefix(mediaType, prefix) {
		return fmt.Errorf("pluginapi: media type %q does not fit the %s part %d of message %q", mediaType, part.Kind, partIdx, msgID)
	}
	if len(data) == 0 {
		return fmt.Errorf("pluginapi: media data for part %d of message %q is empty", partIdx, msgID)
	}
	encoded := base64.StdEncoding.EncodeToString(data)
	switch part.Kind {
	case PartImage:
		part.URL = "data:" + mediaType + ";base64," + encoded
		part.Data = nil
	case PartAudio:
		part.Data = []byte(encoded)
		part.URL = ""
	}
	part.MediaType = mediaType
	part.Raw = nil
	p.changes.mark(msgID, ChangeEdited)
	return nil
}

func decodeDataURI(s string) ([]byte, string, bool) {
	rest, ok := strings.CutPrefix(s, "data:")
	if !ok {
		return nil, "", false
	}
	meta, payload, ok := strings.Cut(rest, ",")
	if !ok {
		return nil, "", false
	}
	mediaType, params, _ := strings.Cut(meta, ";")
	if !strings.Contains(mediaType, "/") || !strings.Contains(";"+params+";", ";base64;") {
		return nil, "", false
	}
	decoded, err := base64.StdEncoding.DecodeString(payload)
	if err != nil || len(decoded) == 0 {
		return nil, "", false
	}
	return decoded, mediaType, true
}