ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
88 lines
2.4 KiB
Go
88 lines
2.4 KiB
Go
package dify
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Only the backend has the bridge credential. Dify receives a short-lived step
|
|
// token, and records counts and IDs rather than elder speech or image bytes.
|
|
type Bridge struct {
|
|
BaseURL, Secret string
|
|
HTTP *http.Client
|
|
}
|
|
type Ticket struct {
|
|
ID string `json:"context_id"`
|
|
Token string `json:"step_token"`
|
|
}
|
|
|
|
func NewBridge(base, secret string) *Bridge {
|
|
return &Bridge{strings.TrimRight(base, "/"), secret, &http.Client{Timeout: 30 * time.Second}}
|
|
}
|
|
func (b *Bridge) request(ctx context.Context, method, path string, payload any, out any) error {
|
|
var body io.Reader
|
|
if payload != nil {
|
|
raw, _ := json.Marshal(payload)
|
|
body = bytes.NewReader(raw)
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, method, b.BaseURL+path, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+b.Secret)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := b.HTTP.Do(req)
|
|
if err != nil {
|
|
return ErrUnavailable
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != 200 {
|
|
return ErrUnavailable
|
|
}
|
|
if out != nil {
|
|
return json.NewDecoder(io.LimitReader(resp.Body, 4<<20)).Decode(out)
|
|
}
|
|
return nil
|
|
}
|
|
func (b *Bridge) Register(ctx context.Context, kind string, input any) (Ticket, error) {
|
|
var t Ticket
|
|
err := b.request(ctx, "POST", "/contexts", map[string]any{"kind": kind, "input": input}, &t)
|
|
return t, err
|
|
}
|
|
func (b *Bridge) Result(ctx context.Context, t Ticket, out any) error {
|
|
return b.request(ctx, "GET", "/contexts/"+t.ID, nil, out)
|
|
}
|
|
func (b *Bridge) Forget(t Ticket) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
_ = b.request(ctx, "DELETE", "/contexts/"+t.ID, nil, nil)
|
|
}
|
|
func (b *Bridge) Image(ctx context.Context, t Ticket, index int) ([]byte, error) {
|
|
path := b.BaseURL + "/contexts/" + t.ID + "/images/" + strconv.Itoa(index)
|
|
req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
|
|
req.Header.Set("Authorization", "Bearer "+b.Secret)
|
|
resp, err := b.HTTP.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == 404 {
|
|
return nil, nil
|
|
}
|
|
if resp.StatusCode != 200 {
|
|
return nil, ErrUnavailable
|
|
}
|
|
raw, err := io.ReadAll(io.LimitReader(resp.Body, (10<<20)+1))
|
|
if err != nil || len(raw) > 10<<20 || http.DetectContentType(raw) != "image/jpeg" {
|
|
return nil, errors.New("invalid workflow illustration")
|
|
}
|
|
return raw, nil
|
|
}
|