feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s
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.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user