client.go 2.99 KB
Newer Older
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1
2
3
package api

import (
Jeffrey Morgan's avatar
Jeffrey Morgan committed
4
	"bufio"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
5
6
7
	"bytes"
	"context"
	"encoding/json"
8
	"fmt"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
9
	"net/http"
Michael Yang's avatar
Michael Yang committed
10
	"net/url"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
11
12
)

Michael Yang's avatar
Michael Yang committed
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type StatusError struct {
	StatusCode int
	Status     string
	Message    string
}

func (e StatusError) Error() string {
	if e.Message != "" {
		return fmt.Sprintf("%s: %s", e.Status, e.Message)
	}

	return e.Status
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
27
type Client struct {
Michael Yang's avatar
Michael Yang committed
28
29
30
31
32
33
34
35
36
37
38
39
	base url.URL
}

func NewClient(hosts ...string) *Client {
	host := "127.0.0.1:11434"
	if len(hosts) > 0 {
		host = hosts[0]
	}

	return &Client{
		base: url.URL{Scheme: "http", Host: host},
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
40
41
}

Michael Yang's avatar
Michael Yang committed
42
func (c *Client) stream(ctx context.Context, method, path string, data any, fn func([]byte) error) error {
43
44
45
46
47
48
	var buf *bytes.Buffer
	if data != nil {
		bts, err := json.Marshal(data)
		if err != nil {
			return err
		}
Michael Yang's avatar
Michael Yang committed
49

50
		buf = bytes.NewBuffer(bts)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
51
52
	}

53
	request, err := http.NewRequestWithContext(ctx, method, c.base.JoinPath(path).String(), buf)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
54
55
56
57
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
58
59
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Accept", "application/json")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
60

Michael Yang's avatar
Michael Yang committed
61
	response, err := http.DefaultClient.Do(request)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
62
63
64
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
65
	defer response.Body.Close()
Jeffrey Morgan's avatar
Jeffrey Morgan committed
66

67
68
69
	scanner := bufio.NewScanner(response.Body)
	for scanner.Scan() {
		var errorResponse struct {
Michael Yang's avatar
Michael Yang committed
70
			Error string `json:"error,omitempty"`
71
72
73
74
75
76
77
		}

		bts := scanner.Bytes()
		if err := json.Unmarshal(bts, &errorResponse); err != nil {
			return fmt.Errorf("unmarshal: %w", err)
		}

Michael Yang's avatar
Michael Yang committed
78
79
80
81
82
83
		if response.StatusCode >= 400 {
			return StatusError{
				StatusCode: response.StatusCode,
				Status:     response.Status,
				Message:    errorResponse.Error,
			}
84
85
		}

Michael Yang's avatar
Michael Yang committed
86
		if err := fn(bts); err != nil {
87
			return err
Jeffrey Morgan's avatar
Jeffrey Morgan committed
88
89
90
		}
	}

Michael Yang's avatar
Michael Yang committed
91
92
	return nil
}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
93

Michael Yang's avatar
Michael Yang committed
94
type GenerateResponseFunc func(GenerateResponse) error
Jeffrey Morgan's avatar
Jeffrey Morgan committed
95

Michael Yang's avatar
Michael Yang committed
96
func (c *Client) Generate(ctx context.Context, req *GenerateRequest, fn GenerateResponseFunc) error {
97
98
99
100
101
102
103
104
	return c.stream(ctx, http.MethodPost, "/api/generate", req, func(bts []byte) error {
		var resp GenerateResponse
		if err := json.Unmarshal(bts, &resp); err != nil {
			return err
		}

		return fn(resp)
	})
Jeffrey Morgan's avatar
Jeffrey Morgan committed
105
}
Bruce MacDonald's avatar
Bruce MacDonald committed
106

Michael Yang's avatar
Michael Yang committed
107
108
109
type PullProgressFunc func(PullProgress) error

func (c *Client) Pull(ctx context.Context, req *PullRequest, fn PullProgressFunc) error {
110
111
112
113
114
115
116
117
	return c.stream(ctx, http.MethodPost, "/api/pull", req, func(bts []byte) error {
		var resp PullProgress
		if err := json.Unmarshal(bts, &resp); err != nil {
			return err
		}

		return fn(resp)
	})
Bruce MacDonald's avatar
Bruce MacDonald committed
118
}
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

type PushProgressFunc func(PushProgress) error

func (c *Client) Push(ctx context.Context, req *PushRequest, fn PushProgressFunc) error {
	return c.stream(ctx, http.MethodPost, "/api/push", req, func(bts []byte) error {
		var resp PushProgress
		if err := json.Unmarshal(bts, &resp); err != nil {
			return err
		}

		return fn(resp)
	})
}

type CreateProgressFunc func(CreateProgress) error

func (c *Client) Create(ctx context.Context, req *CreateRequest, fn CreateProgressFunc) error {
	return c.stream(ctx, http.MethodPost, "/api/create", req, func(bts []byte) error {
		var resp CreateProgress
		if err := json.Unmarshal(bts, &resp); err != nil {
			return err
		}

		return fn(resp)
	})
}