Compare commits

...

13 Commits

Author SHA1 Message Date
Patrick Devine
3168f51125 change error handler behavior and fix error when a model isn't found 2023-07-21 22:44:04 -07:00
Michael Yang
37324a0a00 Merge pull request #172 from jmorganca/set-vars-first
fix vars.First
2023-07-21 20:55:06 -07:00
Michael Yang
20a5d99f77 fix vars.First 2023-07-21 20:45:32 -07:00
Patrick Devine
3b43cc019a fix extended tag names (#171) 2023-07-21 20:27:25 -07:00
Patrick Devine
b8421dce3d get the proper path for blobs to delete (#168) 2023-07-21 17:30:40 -07:00
Patrick Devine
9f6e97865c allow pushing/pulling to insecure registries (#157) 2023-07-21 15:42:19 -07:00
Bruce MacDonald
f5f0da06d9 Merge pull request #166 from jmorganca/brucemacd/dev-cgo 2023-07-21 22:48:10 +02:00
Bruce MacDonald
52f04e39f2 Note that CGO must be enabled in dev docs 2023-07-21 22:36:36 +02:00
Jeffrey Morgan
3c8f4c03d7 web: tweak homepage text 2023-07-21 09:57:57 -07:00
Bruce MacDonald
7ba1308595 Merge pull request #147 from jmorganca/brucemacd/cli-err-display
Improve CLI error display
2023-07-21 16:10:19 +02:00
Jeffrey Morgan
91cd54016c add basic REST api documentation 2023-07-21 00:47:17 -07:00
Bruce MacDonald
09dc6273e3 suppress error when running list before pulling image 2023-07-20 20:53:09 +02:00
Bruce MacDonald
ebaa33ac28 display gin api errors in cli 2023-07-20 20:45:12 +02:00
9 changed files with 160 additions and 96 deletions

View File

@@ -125,3 +125,13 @@ Finally, run a model!
``` ```
./ollama run llama2 ./ollama run llama2
``` ```
## REST API
### `POST /api/generate`
Generate text from a model.
```
curl -X POST http://localhost:11434/api/generate -d '{"model": "llama2", "prompt":"Why is the sky blue?"}'
```

View File

@@ -27,7 +27,7 @@ func checkError(resp *http.Response, body []byte) error {
err := json.Unmarshal(body, &apiError) err := json.Unmarshal(body, &apiError)
if err != nil { if err != nil {
// Use the full body as the message if we fail to decode a response. // Use the full body as the message if we fail to decode a response.
apiError.Message = string(body) apiError.ErrorMessage = string(body)
} }
return apiError return apiError
@@ -92,7 +92,6 @@ func (c *Client) do(ctx context.Context, method, path string, reqData, respData
} }
} }
return nil return nil
} }
func (c *Client) stream(ctx context.Context, method, path string, data any, fn func([]byte) error) error { func (c *Client) stream(ctx context.Context, method, path string, data any, fn func([]byte) error) error {
@@ -137,9 +136,9 @@ func (c *Client) stream(ctx context.Context, method, path string, data any, fn f
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
return StatusError{ return StatusError{
StatusCode: response.StatusCode, StatusCode: response.StatusCode,
Status: response.Status, Status: response.Status,
Message: errorResponse.Error, ErrorMessage: errorResponse.Error,
} }
} }
@@ -211,15 +210,9 @@ func (c *Client) List(ctx context.Context) (*ListResponse, error) {
return &lr, nil return &lr, nil
} }
type DeleteProgressFunc func(ProgressResponse) error func (c *Client) Delete(ctx context.Context, req *DeleteRequest) error {
if err := c.do(ctx, http.MethodDelete, "/api/delete", req, nil); err != nil {
func (c *Client) Delete(ctx context.Context, req *DeleteRequest, fn DeleteProgressFunc) error { return err
return c.stream(ctx, http.MethodDelete, "/api/delete", req, func(bts []byte) error { }
var resp ProgressResponse return nil
if err := json.Unmarshal(bts, &resp); err != nil {
return err
}
return fn(resp)
})
} }

View File

@@ -8,16 +8,23 @@ import (
) )
type StatusError struct { type StatusError struct {
StatusCode int StatusCode int
Status string Status string
Message string ErrorMessage string `json:"error"`
} }
func (e StatusError) Error() string { func (e StatusError) Error() string {
if e.Message != "" { switch {
return fmt.Sprintf("%s: %s", e.Status, e.Message) case e.Status != "" && e.ErrorMessage != "":
return fmt.Sprintf("%s: %s", e.Status, e.ErrorMessage)
case e.Status != "":
return e.Status
case e.ErrorMessage != "":
return e.ErrorMessage
default:
// this should not happen
return "something went wrong, please see the ollama server logs for details"
} }
return e.Status
} }
type GenerateRequest struct { type GenerateRequest struct {
@@ -43,6 +50,7 @@ type DeleteRequest struct {
type PullRequest struct { type PullRequest struct {
Name string `json:"name"` Name string `json:"name"`
Insecure bool `json:"insecure,omitempty"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
} }
@@ -56,6 +64,7 @@ type ProgressResponse struct {
type PushRequest struct { type PushRequest struct {
Name string `json:"name"` Name string `json:"name"`
Insecure bool `json:"insecure,omitempty"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
} }

View File

@@ -69,7 +69,7 @@ func RunHandler(cmd *cobra.Command, args []string) error {
_, err = os.Stat(fp) _, err = os.Stat(fp)
switch { switch {
case errors.Is(err, os.ErrNotExist): case errors.Is(err, os.ErrNotExist):
if err := pull(args[0]); err != nil { if err := pull(args[0], false); err != nil {
var apiStatusError api.StatusError var apiStatusError api.StatusError
if !errors.As(err, &apiStatusError) { if !errors.As(err, &apiStatusError) {
return err return err
@@ -89,7 +89,12 @@ func RunHandler(cmd *cobra.Command, args []string) error {
func PushHandler(cmd *cobra.Command, args []string) error { func PushHandler(cmd *cobra.Command, args []string) error {
client := api.NewClient() client := api.NewClient()
request := api.PushRequest{Name: args[0]} insecure, err := cmd.Flags().GetBool("insecure")
if err != nil {
return err
}
request := api.PushRequest{Name: args[0], Insecure: insecure}
fn := func(resp api.ProgressResponse) error { fn := func(resp api.ProgressResponse) error {
fmt.Println(resp.Status) fmt.Println(resp.Status)
return nil return nil
@@ -135,28 +140,29 @@ func DeleteHandler(cmd *cobra.Command, args []string) error {
client := api.NewClient() client := api.NewClient()
request := api.DeleteRequest{Name: args[0]} request := api.DeleteRequest{Name: args[0]}
fn := func(resp api.ProgressResponse) error { if err := client.Delete(context.Background(), &request); err != nil {
fmt.Println(resp.Status)
return nil
}
if err := client.Delete(context.Background(), &request, fn); err != nil {
return err return err
} }
fmt.Printf("deleted '%s'\n", args[0])
return nil return nil
} }
func PullHandler(cmd *cobra.Command, args []string) error { func PullHandler(cmd *cobra.Command, args []string) error {
return pull(args[0]) insecure, err := cmd.Flags().GetBool("insecure")
if err != nil {
return err
}
return pull(args[0], insecure)
} }
func pull(model string) error { func pull(model string, insecure bool) error {
client := api.NewClient() client := api.NewClient()
var currentDigest string var currentDigest string
var bar *progressbar.ProgressBar var bar *progressbar.ProgressBar
request := api.PullRequest{Name: model} request := api.PullRequest{Name: model, Insecure: insecure}
fn := func(resp api.ProgressResponse) error { fn := func(resp api.ProgressResponse) error {
if resp.Digest != currentDigest && resp.Digest != "" { if resp.Digest != currentDigest && resp.Digest != "" {
currentDigest = resp.Digest currentDigest = resp.Digest
@@ -430,6 +436,8 @@ func NewCLI() *cobra.Command {
RunE: PullHandler, RunE: PullHandler,
} }
pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")
pushCmd := &cobra.Command{ pushCmd := &cobra.Command{
Use: "push MODEL", Use: "push MODEL",
Short: "Push a model to a registry", Short: "Push a model to a registry",
@@ -437,11 +445,13 @@ func NewCLI() *cobra.Command {
RunE: PushHandler, RunE: PushHandler,
} }
pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")
listCmd := &cobra.Command{ listCmd := &cobra.Command{
Use: "list", Use: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Short: "List models", Short: "List models",
RunE: ListHandler, RunE: ListHandler,
} }
deleteCmd := &cobra.Command{ deleteCmd := &cobra.Command{

View File

@@ -6,6 +6,12 @@ Install required tools:
brew install go brew install go
``` ```
Enable CGO:
```
export CGO_ENABLED=1
```
Then build ollama: Then build ollama:
``` ```

View File

@@ -22,6 +22,12 @@ import (
"github.com/jmorganca/ollama/parser" "github.com/jmorganca/ollama/parser"
) )
type RegistryOptions struct {
Insecure bool
Username string
Password string
}
type Model struct { type Model struct {
Name string `json:"name"` Name string `json:"name"`
ModelPath string ModelPath string
@@ -45,7 +51,7 @@ func (m *Model) Prompt(request api.GenerateRequest) (string, error) {
Context []int Context []int
} }
vars.First = len(vars.Context) == 0 vars.First = len(request.Context) == 0
vars.System = m.System vars.System = m.System
vars.Prompt = request.Prompt vars.Prompt = request.Prompt
vars.Context = request.Context vars.Context = request.Context
@@ -102,8 +108,8 @@ func GetManifest(mp ModelPath) (*ManifestV2, error) {
return nil, err return nil, err
} }
if _, err = os.Stat(fp); err != nil && !errors.Is(err, os.ErrNotExist) { if _, err = os.Stat(fp); err != nil {
return nil, fmt.Errorf("couldn't find model '%s'", mp.GetShortTagname()) return nil, err
} }
var manifest *ManifestV2 var manifest *ManifestV2
@@ -487,12 +493,11 @@ func CreateLayer(f io.ReadSeeker) (*LayerReader, error) {
return layer, nil return layer, nil
} }
func DeleteModel(name string, fn func(api.ProgressResponse)) error { func DeleteModel(name string) error {
mp := ParseModelPath(name) mp := ParseModelPath(name)
manifest, err := GetManifest(mp) manifest, err := GetManifest(mp)
if err != nil { if err != nil {
fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
return err return err
} }
deleteMap := make(map[string]bool) deleteMap := make(map[string]bool)
@@ -503,12 +508,10 @@ func DeleteModel(name string, fn func(api.ProgressResponse)) error {
fp, err := GetManifestPath() fp, err := GetManifestPath()
if err != nil { if err != nil {
fn(api.ProgressResponse{Status: "problem getting manifest path"})
return err return err
} }
err = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error { err = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
fn(api.ProgressResponse{Status: "problem walking manifest dir"})
return err return err
} }
if !info.IsDir() { if !info.IsDir() {
@@ -542,9 +545,13 @@ func DeleteModel(name string, fn func(api.ProgressResponse)) error {
// only delete the files which are still in the deleteMap // only delete the files which are still in the deleteMap
for k, v := range deleteMap { for k, v := range deleteMap {
if v { if v {
err := os.Remove(k) fp, err := GetBlobsPath(k)
if err != nil { if err != nil {
log.Printf("couldn't remove file '%s': %v", k, err) log.Printf("couldn't get file path for '%s': %v", k, err)
continue
}
if err := os.Remove(fp); err != nil {
log.Printf("couldn't remove file '%s': %v", fp, err)
continue continue
} }
} }
@@ -559,12 +566,11 @@ func DeleteModel(name string, fn func(api.ProgressResponse)) error {
log.Printf("couldn't remove manifest file '%s': %v", fp, err) log.Printf("couldn't remove manifest file '%s': %v", fp, err)
return err return err
} }
fn(api.ProgressResponse{Status: fmt.Sprintf("deleted '%s'", name)})
return nil return nil
} }
func PushModel(name, username, password string, fn func(api.ProgressResponse)) error { func PushModel(name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
mp := ParseModelPath(name) mp := ParseModelPath(name)
fn(api.ProgressResponse{Status: "retrieving manifest"}) fn(api.ProgressResponse{Status: "retrieving manifest"})
@@ -586,7 +592,7 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
total += manifest.Config.Size total += manifest.Config.Size
for _, layer := range layers { for _, layer := range layers {
exists, err := checkBlobExistence(mp, layer.Digest, username, password) exists, err := checkBlobExistence(mp, layer.Digest, regOpts)
if err != nil { if err != nil {
return err return err
} }
@@ -609,13 +615,13 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
Completed: completed, Completed: completed,
}) })
location, err := startUpload(mp, username, password) location, err := startUpload(mp, regOpts)
if err != nil { if err != nil {
log.Printf("couldn't start upload: %v", err) log.Printf("couldn't start upload: %v", err)
return err return err
} }
err = uploadBlob(location, layer, username, password) err = uploadBlob(location, layer, regOpts)
if err != nil { if err != nil {
log.Printf("error uploading blob: %v", err) log.Printf("error uploading blob: %v", err)
return err return err
@@ -634,7 +640,7 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
Total: total, Total: total,
Completed: completed, Completed: completed,
}) })
url := fmt.Sprintf("%s://%s/v2/%s/manifests/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), mp.Tag) url := fmt.Sprintf("%s/v2/%s/manifests/%s", mp.Registry, mp.GetNamespaceRepository(), mp.Tag)
headers := map[string]string{ headers := map[string]string{
"Content-Type": "application/vnd.docker.distribution.manifest.v2+json", "Content-Type": "application/vnd.docker.distribution.manifest.v2+json",
} }
@@ -644,7 +650,7 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
return err return err
} }
resp, err := makeRequest("PUT", url, headers, bytes.NewReader(manifestJSON), username, password) resp, err := makeRequest("PUT", url, headers, bytes.NewReader(manifestJSON), regOpts)
if err != nil { if err != nil {
return err return err
} }
@@ -665,12 +671,12 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
return nil return nil
} }
func PullModel(name, username, password string, fn func(api.ProgressResponse)) error { func PullModel(name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
mp := ParseModelPath(name) mp := ParseModelPath(name)
fn(api.ProgressResponse{Status: "pulling manifest"}) fn(api.ProgressResponse{Status: "pulling manifest"})
manifest, err := pullModelManifest(mp, username, password) manifest, err := pullModelManifest(mp, regOpts)
if err != nil { if err != nil {
return fmt.Errorf("pull model manifest: %q", err) return fmt.Errorf("pull model manifest: %q", err)
} }
@@ -680,7 +686,7 @@ func PullModel(name, username, password string, fn func(api.ProgressResponse)) e
layers = append(layers, &manifest.Config) layers = append(layers, &manifest.Config)
for _, layer := range layers { for _, layer := range layers {
if err := downloadBlob(mp, layer.Digest, username, password, fn); err != nil { if err := downloadBlob(mp, layer.Digest, regOpts, fn); err != nil {
return err return err
} }
} }
@@ -715,13 +721,13 @@ func PullModel(name, username, password string, fn func(api.ProgressResponse)) e
return nil return nil
} }
func pullModelManifest(mp ModelPath, username, password string) (*ManifestV2, error) { func pullModelManifest(mp ModelPath, regOpts *RegistryOptions) (*ManifestV2, error) {
url := fmt.Sprintf("%s://%s/v2/%s/manifests/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), mp.Tag) url := fmt.Sprintf("%s/v2/%s/manifests/%s", mp.Registry, mp.GetNamespaceRepository(), mp.Tag)
headers := map[string]string{ headers := map[string]string{
"Accept": "application/vnd.docker.distribution.manifest.v2+json", "Accept": "application/vnd.docker.distribution.manifest.v2+json",
} }
resp, err := makeRequest("GET", url, headers, nil, username, password) resp, err := makeRequest("GET", url, headers, nil, regOpts)
if err != nil { if err != nil {
log.Printf("couldn't get manifest: %v", err) log.Printf("couldn't get manifest: %v", err)
return nil, err return nil, err
@@ -782,10 +788,10 @@ func GetSHA256Digest(r io.Reader) (string, int) {
return fmt.Sprintf("sha256:%x", h.Sum(nil)), int(n) return fmt.Sprintf("sha256:%x", h.Sum(nil)), int(n)
} }
func startUpload(mp ModelPath, username string, password string) (string, error) { func startUpload(mp ModelPath, regOpts *RegistryOptions) (string, error) {
url := fmt.Sprintf("%s://%s/v2/%s/blobs/uploads/", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository()) url := fmt.Sprintf("%s/v2/%s/blobs/uploads/", mp.Registry, mp.GetNamespaceRepository())
resp, err := makeRequest("POST", url, nil, nil, username, password) resp, err := makeRequest("POST", url, nil, nil, regOpts)
if err != nil { if err != nil {
log.Printf("couldn't start upload: %v", err) log.Printf("couldn't start upload: %v", err)
return "", err return "", err
@@ -808,10 +814,10 @@ func startUpload(mp ModelPath, username string, password string) (string, error)
} }
// Function to check if a blob already exists in the Docker registry // Function to check if a blob already exists in the Docker registry
func checkBlobExistence(mp ModelPath, digest string, username string, password string) (bool, error) { func checkBlobExistence(mp ModelPath, digest string, regOpts *RegistryOptions) (bool, error) {
url := fmt.Sprintf("%s://%s/v2/%s/blobs/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), digest) url := fmt.Sprintf("%s/v2/%s/blobs/%s", mp.Registry, mp.GetNamespaceRepository(), digest)
resp, err := makeRequest("HEAD", url, nil, nil, username, password) resp, err := makeRequest("HEAD", url, nil, nil, regOpts)
if err != nil { if err != nil {
log.Printf("couldn't check for blob: %v", err) log.Printf("couldn't check for blob: %v", err)
return false, err return false, err
@@ -822,7 +828,7 @@ func checkBlobExistence(mp ModelPath, digest string, username string, password s
return resp.StatusCode == http.StatusOK, nil return resp.StatusCode == http.StatusOK, nil
} }
func uploadBlob(location string, layer *Layer, username string, password string) error { func uploadBlob(location string, layer *Layer, regOpts *RegistryOptions) error {
// Create URL // Create URL
url := fmt.Sprintf("%s&digest=%s", location, layer.Digest) url := fmt.Sprintf("%s&digest=%s", location, layer.Digest)
@@ -845,7 +851,7 @@ func uploadBlob(location string, layer *Layer, username string, password string)
return err return err
} }
resp, err := makeRequest("PUT", url, headers, f, username, password) resp, err := makeRequest("PUT", url, headers, f, regOpts)
if err != nil { if err != nil {
log.Printf("couldn't upload blob: %v", err) log.Printf("couldn't upload blob: %v", err)
return err return err
@@ -861,7 +867,7 @@ func uploadBlob(location string, layer *Layer, username string, password string)
return nil return nil
} }
func downloadBlob(mp ModelPath, digest string, username, password string, fn func(api.ProgressResponse)) error { func downloadBlob(mp ModelPath, digest string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
fp, err := GetBlobsPath(digest) fp, err := GetBlobsPath(digest)
if err != nil { if err != nil {
return err return err
@@ -890,12 +896,12 @@ func downloadBlob(mp ModelPath, digest string, username, password string, fn fun
size = fi.Size() size = fi.Size()
} }
url := fmt.Sprintf("%s://%s/v2/%s/blobs/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), digest) url := fmt.Sprintf("%s/v2/%s/blobs/%s", mp.Registry, mp.GetNamespaceRepository(), digest)
headers := map[string]string{ headers := map[string]string{
"Range": fmt.Sprintf("bytes=%d-", size), "Range": fmt.Sprintf("bytes=%d-", size),
} }
resp, err := makeRequest("GET", url, headers, nil, username, password) resp, err := makeRequest("GET", url, headers, nil, regOpts)
if err != nil { if err != nil {
log.Printf("couldn't download blob: %v", err) log.Printf("couldn't download blob: %v", err)
return err return err
@@ -959,7 +965,17 @@ func downloadBlob(mp ModelPath, digest string, username, password string, fn fun
return nil return nil
} }
func makeRequest(method, url string, headers map[string]string, body io.Reader, username, password string) (*http.Response, error) { func makeRequest(method, url string, headers map[string]string, body io.Reader, regOpts *RegistryOptions) (*http.Response, error) {
if !strings.HasPrefix(url, "http") {
if regOpts.Insecure {
url = "http://" + url
} else {
url = "https://" + url
}
}
log.Printf("url = %s", url)
req, err := http.NewRequest(method, url, body) req, err := http.NewRequest(method, url, body)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -970,8 +986,8 @@ func makeRequest(method, url string, headers map[string]string, body io.Reader,
} }
// TODO: better auth // TODO: better auth
if username != "" && password != "" { if regOpts.Username != "" && regOpts.Password != "" {
req.SetBasicAuth(username, password) req.SetBasicAuth(regOpts.Username, regOpts.Password)
} }
client := &http.Client{ client := &http.Client{

View File

@@ -45,7 +45,7 @@ func ParseModelPath(name string) ModelPath {
return ModelPath{} return ModelPath{}
} }
colonParts := strings.Split(name, ":") colonParts := strings.Split(slashParts[len(slashParts)-1], ":")
if len(colonParts) == 2 { if len(colonParts) == 2 {
tag = colonParts[1] tag = colonParts[1]
} else { } else {
@@ -70,10 +70,13 @@ func (mp ModelPath) GetFullTagname() string {
} }
func (mp ModelPath) GetShortTagname() string { func (mp ModelPath) GetShortTagname() string {
if mp.Registry == DefaultRegistry && mp.Namespace == DefaultNamespace { if mp.Registry == DefaultRegistry {
return fmt.Sprintf("%s:%s", mp.Repository, mp.Tag) if mp.Namespace == DefaultNamespace {
return fmt.Sprintf("%s:%s", mp.Repository, mp.Tag)
}
return fmt.Sprintf("%s/%s:%s", mp.Namespace, mp.Repository, mp.Tag)
} }
return fmt.Sprintf("%s/%s:%s", mp.Namespace, mp.Repository, mp.Tag) return fmt.Sprintf("%s/%s/%s:%s", mp.Registry, mp.Namespace, mp.Repository, mp.Tag)
} }
func (mp ModelPath) GetManifestPath(createDir bool) (string, error) { func (mp ModelPath) GetManifestPath(createDir bool) (string, error) {

View File

@@ -2,6 +2,8 @@ package server
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt"
"io" "io"
"log" "log"
"net" "net"
@@ -92,7 +94,13 @@ func PullModelHandler(c *gin.Context) {
ch <- r ch <- r
} }
if err := PullModel(req.Name, req.Username, req.Password, fn); err != nil { regOpts := &RegistryOptions{
Insecure: req.Insecure,
Username: req.Username,
Password: req.Password,
}
if err := PullModel(req.Name, regOpts, fn); err != nil {
ch <- gin.H{"error": err.Error()} ch <- gin.H{"error": err.Error()}
} }
}() }()
@@ -114,7 +122,13 @@ func PushModelHandler(c *gin.Context) {
ch <- r ch <- r
} }
if err := PushModel(req.Name, req.Username, req.Password, fn); err != nil { regOpts := &RegistryOptions{
Insecure: req.Insecure,
Username: req.Username,
Password: req.Password,
}
if err := PushModel(req.Name, regOpts, fn); err != nil {
ch <- gin.H{"error": err.Error()} ch <- gin.H{"error": err.Error()}
} }
}() }()
@@ -153,20 +167,14 @@ func DeleteModelHandler(c *gin.Context) {
return return
} }
ch := make(chan any) if err := DeleteModel(req.Name); err != nil {
go func() { if os.IsNotExist(err) {
defer close(ch) c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Name)})
fn := func(r api.ProgressResponse) { } else {
ch <- r
}
if err := DeleteModel(req.Name, fn); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
} }
}() return
}
streamResponse(c, ch)
} }
func ListModelsHandler(c *gin.Context) { func ListModelsHandler(c *gin.Context) {
@@ -178,6 +186,10 @@ func ListModelsHandler(c *gin.Context) {
} }
err = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error { err = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
if errors.Is(err, os.ErrNotExist) {
log.Printf("manifest file does not exist: %s", fp)
return nil
}
return err return err
} }
if !info.IsDir() { if !info.IsDir() {

View File

@@ -11,18 +11,23 @@ export default async function Home() {
<Image src='/ollama.png' width={64} height={64} alt='ollamaIcon' /> <Image src='/ollama.png' width={64} height={64} alt='ollamaIcon' />
<section className='my-12 text-center'> <section className='my-12 text-center'>
<div className='flex flex-col space-y-2'> <div className='flex flex-col space-y-2'>
<h2 className='md:max-w-[18rem] mx-auto my-2 text-3xl tracking-tight'>Portable large language models</h2> <h2 className='md:max-w-md mx-auto my-2 text-3xl tracking-tight'>
Get up and running with large language models, locally.
</h2>
<h3 className='md:max-w-xs mx-auto text-base text-neutral-500'> <h3 className='md:max-w-xs mx-auto text-base text-neutral-500'>
Bundle a models weights, configuration, prompts, data and more into self-contained packages that run anywhere. Run Llama 2 and other models on macOS. Customize and create your own.
</h3> </h3>
</div> </div>
<div className='mx-auto flex flex-col space-y-4 mt-12'> <div className='mx-auto max-w-xs flex flex-col space-y-4 mt-12'>
<Link href='/download' className='md:mx-10 lg:mx-14 bg-black text-white rounded-full px-4 py-2 focus:outline-none cursor-pointer'> <Link
href='/download'
className='md:mx-10 lg:mx-14 bg-black text-white rounded-full px-4 py-2 focus:outline-none cursor-pointer'
>
Download Download
</Link> </Link>
<p className='text-neutral-500 text-sm '> <p className='text-neutral-500 text-sm '>
Available for macOS with Apple Silicon <br /> Available for macOS with Apple Silicon <br />
Windows & Linux support coming soon. Windows & Linux support coming soon.
</p> </p>
</div> </div>
</section> </section>