Compare commits

..

26 Commits

Author SHA1 Message Date
Patrick Devine
ad83c87454 add back in the windows terminal file 2023-11-14 16:52:34 -08:00
Patrick Devine
8627f6c66c initial commit of the readline editor replacement 2023-11-14 15:59:35 -08:00
Jeffrey Morgan
423862042a treat ollama run model < file as entire prompt, not prompt-per-line (#1126)
Previously, `ollama run` treated a non-terminal stdin (such as `ollama run model < file`) as containing one prompt per line. To run inference on a multi-line prompt, the only non-API workaround was to run `ollama run` interactively and wrap the prompt in `"""..."""`.

Now, `ollama run` treats a non-terminal stdin as containing a single prompt. For example, if `myprompt.txt` is a multi-line file, then `ollama run model < myprompt.txt` would treat `myprompt.txt`'s entire contents as the prompt.

Co-authored-by: Quinn Slack <quinn@slack.org>
2023-11-14 16:42:21 -05:00
Bruce MacDonald
df18486c35 Move /generate format to optional parameters (#1127)
This field is optional and should be under the `Advanced parameters` header
2023-11-14 16:12:30 -05:00
Jeffrey Morgan
4e612a2e92 use stdout fd for terminal size (#1125) 2023-11-14 16:09:09 -05:00
Jeffrey Morgan
6e0f686afa --format json should work in interactive mode 2023-11-14 10:22:03 -05:00
Jeffrey Morgan
c1844bbee2 add json mode to cli (#1095) 2023-11-13 21:54:02 -05:00
Huy Le
cb745965ce adding ollama.nvim for visibility (#1115) 2023-11-13 17:00:17 -05:00
Enrico Ros
8d29b6a2b6 New big-AGI integration (#1078)
* New big-AGI integration

Ollama works great in big-AGI, and this document explains how to link the two projects.

* Update README.md
2023-11-13 16:59:00 -05:00
Ilya Breitburg
724aa64bee Add Dart library to README.md (#1106) 2023-11-13 14:50:42 -05:00
Michael Yang
d91c103e74 Merge pull request #1055 from dansreis/946-fix-incorrect-base-model-name
Fixed incorrect base model name
2023-11-13 08:42:55 -08:00
Kevin Hermawan
98ec7d81e3 Add OllamaKit to the community integrations (#1085) 2023-11-11 14:41:42 -08:00
Daniel Reis
7c438f2c53 Replaced method 2023-11-10 20:22:03 +00:00
Daniel Reis
6e46338d44 Reverting previous changes 2023-11-10 20:21:35 +00:00
Jeffrey Morgan
cdddd3df65 add format to example python client 2023-11-10 10:22:21 -08:00
Daniel Hiltgen
afa61bdf45 Merge pull request #1075 from jmorganca/dhiltgen/unexpected-eof
Resume chunk download on UnexpectedEOF errors
2023-11-10 08:48:27 -08:00
Daniel Hiltgen
cc54a416c6 Resume chunk download on UnexpectedEOF errors
If the chunk download is interrupted, resume from where we left off
2023-11-10 08:29:42 -08:00
Matt Williams
c819d7f68a Merge pull request #955 from jmorganca/mattw/example-bash-compare
docs: add examples using bash to compare models
2023-11-10 08:59:32 -06:00
Daniel Reis
d17730356a Removed inline parse model path 2023-11-09 22:44:26 +00:00
Daniel Reis
32d79a6eea Using 'GetShortTagname' method instead 2023-11-09 22:40:37 +00:00
Matt Williams
13086363bd Update as per bmacd
Signed-off-by: Matt Williams <m@technovangelist.com>
2023-11-08 18:09:05 -06:00
Michael
f31961637f Update README.md 2023-11-01 12:20:55 -04:00
Matt Williams
80362fedce better readme
Signed-off-by: Matt Williams <m@technovangelist.com>
2023-10-31 12:40:46 -07:00
Matt Williams
5757925060 add a gif
Signed-off-by: Matt Williams <m@technovangelist.com>
2023-10-31 11:52:01 -07:00
Michael
4512301756 Update README.md 2023-10-31 13:25:36 -04:00
Matt Williams
2236a93efc docs: add examples using bash to compare models
Signed-off-by: Matt Williams <m@technovangelist.com>
2023-10-31 09:12:39 -07:00
19 changed files with 670 additions and 689 deletions

View File

@@ -224,6 +224,7 @@ See the [API documentation](./docs/api.md) for all endpoints.
- [Minimalistic React UI for Ollama Models](https://github.com/richawo/minimal-llm-ui)
- [Web UI](https://github.com/ollama-webui/ollama-webui)
- [Ollamac](https://github.com/kevinhermawan/Ollamac)
- [big-AGI](https://github.com/enricoros/big-agi/blob/main/docs/config-ollama.md)
### Terminal
@@ -231,6 +232,7 @@ See the [API documentation](./docs/api.md) for all endpoints.
- [Ellama Emacs client](https://github.com/s-kostyaev/ellama)
- [Emacs client](https://github.com/zweifisch/ollama)
- [gen.nvim](https://github.com/David-Kunz/gen.nvim)
- [ollama.nvim](https://github.com/nomnivore/ollama.nvim)
- [gptel Emacs client](https://github.com/karthink/gptel)
### Libraries
@@ -242,6 +244,8 @@ See the [API documentation](./docs/api.md) for all endpoints.
- [Ollama-rs for Rust](https://github.com/pepperoni21/ollama-rs)
- [Ollama4j for Java](https://github.com/amithkoujalgi/ollama4j)
- [ModelFusion Typescript Library](https://modelfusion.dev/integration/model-provider/ollama)
- [OllamaKit for Swift](https://github.com/kevinhermawan/OllamaKit)
- [Ollama for Dart](https://github.com/breitburg/dart-ollama)
### Extensions & Plugins

View File

@@ -7,7 +7,7 @@ BASE_URL = os.environ.get('OLLAMA_HOST', 'http://localhost:11434')
# Generate a response for a given prompt with a provided model. This is a streaming endpoint, so will be a series of responses.
# The final response object will include statistics and additional data from the request. Use the callback function to override
# the default handler.
def generate(model_name, prompt, system=None, template=None, context=None, options=None, callback=None):
def generate(model_name, prompt, system=None, template=None, format="", context=None, options=None, callback=None):
try:
url = f"{BASE_URL}/api/generate"
payload = {
@@ -16,7 +16,8 @@ def generate(model_name, prompt, system=None, template=None, context=None, optio
"system": system,
"template": template,
"context": context,
"options": options
"options": options,
"format": format,
}
# Remove keys with None values

View File

@@ -1,7 +1,6 @@
package cmd
import (
"bufio"
"context"
"crypto/ed25519"
"crypto/rand"
@@ -28,9 +27,9 @@ import (
"golang.org/x/term"
"github.com/jmorganca/ollama/api"
"github.com/jmorganca/ollama/editor"
"github.com/jmorganca/ollama/format"
"github.com/jmorganca/ollama/progressbar"
"github.com/jmorganca/ollama/readline"
"github.com/jmorganca/ollama/server"
"github.com/jmorganca/ollama/version"
)
@@ -350,34 +349,49 @@ func pull(model string, insecure bool) error {
}
func RunGenerate(cmd *cobra.Command, args []string) error {
if len(args) > 1 {
// join all args into a single prompt
wordWrap := false
if term.IsTerminal(int(os.Stdout.Fd())) {
wordWrap = true
}
format, err := cmd.Flags().GetString("format")
if err != nil {
return err
}
nowrap, err := cmd.Flags().GetBool("nowordwrap")
prompts := args[1:]
// prepend stdin to the prompt if provided
if !term.IsTerminal(int(os.Stdin.Fd())) {
in, err := io.ReadAll(os.Stdin)
if err != nil {
return err
}
if nowrap {
wordWrap = false
}
return generate(cmd, args[0], strings.Join(args[1:], " "), wordWrap)
prompts = append([]string{string(in)}, prompts...)
}
if readline.IsTerminal(int(os.Stdin.Fd())) {
return generateInteractive(cmd, args[0])
// output is being piped
if !term.IsTerminal(int(os.Stdout.Fd())) {
return generate(cmd, args[0], strings.Join(prompts, " "), false, format)
}
return generateBatch(cmd, args[0])
wordWrap := os.Getenv("TERM") == "xterm-256color"
nowrap, err := cmd.Flags().GetBool("nowordwrap")
if err != nil {
return err
}
if nowrap {
wordWrap = false
}
// prompts are provided via stdin or args so don't enter interactive mode
if len(prompts) > 0 {
return generate(cmd, args[0], strings.Join(prompts, " "), wordWrap, format)
}
return generateInteractive(cmd, args[0], wordWrap, format)
}
type generateContextKey string
func generate(cmd *cobra.Command, model, prompt string, wordWrap bool) error {
func generate(cmd *cobra.Command, model, prompt string, wordWrap bool, format string) error {
client, err := api.ClientFromEnvironment()
if err != nil {
return err
@@ -393,7 +407,7 @@ func generate(cmd *cobra.Command, model, prompt string, wordWrap bool) error {
generateContext = []int{}
}
termWidth, _, err := term.GetSize(int(0))
termWidth, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil {
wordWrap = false
}
@@ -414,7 +428,7 @@ func generate(cmd *cobra.Command, model, prompt string, wordWrap bool) error {
var currentLineLength int
var wordBuffer string
request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext, Format: format}
fn := func(response api.GenerateResponse) error {
if !spinner.IsFinished() {
spinner.Finish()
@@ -485,9 +499,9 @@ func generate(cmd *cobra.Command, model, prompt string, wordWrap bool) error {
return nil
}
func generateInteractive(cmd *cobra.Command, model string) error {
func generateInteractive(cmd *cobra.Command, model string, wordWrap bool, format string) error {
// load the model
if err := generate(cmd, model, "", false); err != nil {
if err := generate(cmd, model, "", false, ""); err != nil {
return err
}
@@ -508,6 +522,8 @@ func generateInteractive(cmd *cobra.Command, model string) error {
fmt.Fprintln(os.Stderr, " /set nohistory Disable history")
fmt.Fprintln(os.Stderr, " /set wordwrap Enable wordwrap")
fmt.Fprintln(os.Stderr, " /set nowordwrap Disable wordwrap")
fmt.Fprintln(os.Stderr, " /set format json Enable JSON mode")
fmt.Fprintln(os.Stderr, " /set noformat Disable formatting")
fmt.Fprintln(os.Stderr, " /set verbose Show LLM stats")
fmt.Fprintln(os.Stderr, " /set quiet Disable LLM stats")
fmt.Fprintln(os.Stderr, "")
@@ -523,45 +539,24 @@ func generateInteractive(cmd *cobra.Command, model string) error {
fmt.Fprintln(os.Stderr, "")
}
prompt := readline.Prompt{
Prompt: ">>> ",
AltPrompt: "... ",
Placeholder: "Send a message (/? for help)",
AltPlaceholder: `Use """ to end multi-line input`,
prompt := editor.Prompt{
Prompt: ">>> ",
AltPrompt: "... ",
Placeholder: "Send a message (/? for help)",
}
scanner, err := readline.New(prompt)
ed, err := editor.New(prompt)
if err != nil {
return err
}
var wordWrap bool
termType := os.Getenv("TERM")
if termType == "xterm-256color" {
wordWrap = true
}
// override wrapping if the user turned it off
nowrap, err := cmd.Flags().GetBool("nowordwrap")
if err != nil {
return err
}
if nowrap {
wordWrap = false
}
fmt.Print(readline.StartBracketedPaste)
defer fmt.Printf(readline.EndBracketedPaste)
var multiLineBuffer string
for {
line, err := scanner.Readline()
line, err := ed.HandleInput()
switch {
case errors.Is(err, io.EOF):
fmt.Println()
return nil
case errors.Is(err, readline.ErrInterrupt):
case errors.Is(err, editor.ErrInterrupt):
if line == "" {
fmt.Println("\nUse Ctrl-D or /bye to exit.")
}
@@ -574,20 +569,6 @@ func generateInteractive(cmd *cobra.Command, model string) error {
line = strings.TrimSpace(line)
switch {
case scanner.Prompt.UseAlt:
if strings.HasSuffix(line, `"""`) {
scanner.Prompt.UseAlt = false
multiLineBuffer += strings.TrimSuffix(line, `"""`)
line = multiLineBuffer
multiLineBuffer = ""
} else {
multiLineBuffer += line + " "
continue
}
case strings.HasPrefix(line, `"""`):
scanner.Prompt.UseAlt = true
multiLineBuffer = strings.TrimPrefix(line, `"""`) + " "
continue
case strings.HasPrefix(line, "/list"):
args := strings.Fields(line)
if err := ListHandler(cmd, args[1:]); err != nil {
@@ -598,9 +579,9 @@ func generateInteractive(cmd *cobra.Command, model string) error {
if len(args) > 1 {
switch args[1] {
case "history":
scanner.HistoryEnable()
//scanner.HistoryEnable()
case "nohistory":
scanner.HistoryDisable()
//scanner.HistoryDisable()
case "wordwrap":
wordWrap = true
fmt.Println("Set 'wordwrap' mode.")
@@ -613,6 +594,16 @@ func generateInteractive(cmd *cobra.Command, model string) error {
case "quiet":
cmd.Flags().Set("verbose", "false")
fmt.Println("Set 'quiet' mode.")
case "format":
if len(args) < 3 || args[2] != "json" {
fmt.Println("Invalid or missing format. For 'json' mode use '/set format json'")
} else {
format = args[2]
fmt.Printf("Set format to '%s' mode.\n", args[2])
}
case "noformat":
format = ""
fmt.Println("Disabled format.")
default:
fmt.Printf("Unknown command '/set %s'. Type /? for help\n", args[1])
}
@@ -686,26 +677,13 @@ func generateInteractive(cmd *cobra.Command, model string) error {
}
if len(line) > 0 && line[0] != '/' {
if err := generate(cmd, model, line, wordWrap); err != nil {
if err := generate(cmd, model, line, wordWrap, format); err != nil {
return err
}
}
}
}
func generateBatch(cmd *cobra.Command, model string) error {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
prompt := scanner.Text()
fmt.Printf(">>> %s\n", prompt)
if err := generate(cmd, model, prompt, false); err != nil {
return err
}
}
return nil
}
func RunServer(cmd *cobra.Command, _ []string) error {
host, port, err := net.SplitHostPort(os.Getenv("OLLAMA_HOST"))
if err != nil {
@@ -883,6 +861,7 @@ func NewCLI() *cobra.Command {
runCmd.Flags().Bool("verbose", false, "Show timings for response")
runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
runCmd.Flags().String("format", "", "Response format (e.g. json)")
serveCmd := &cobra.Command{
Use: "serve",

View File

@@ -38,10 +38,10 @@ Generate a response for a given prompt with a provided model. This is a streamin
- `model`: (required) the [model name](#model-names)
- `prompt`: the prompt to generate a response for
- `format`: the format to return a response in. Currently the only accepted value is `json`
Advanced parameters (optional):
- `format`: the format to return a response in. Currently the only accepted value is `json`
- `options`: additional model parameters listed in the documentation for the [Modelfile](./modelfile.md#valid-parameters-and-values) such as `temperature`
- `system`: system prompt to (overrides what is defined in the `Modelfile`)
- `template`: the full prompt or prompt template (overrides what is defined in the `Modelfile`)

488
editor/buffer.go Normal file
View File

@@ -0,0 +1,488 @@
package editor
import (
"fmt"
"strings"
"github.com/emirpasic/gods/lists/arraylist"
"golang.org/x/term"
)
type Buffer struct {
PosX int
PosY int
Buf []*arraylist.List
Prompt *Prompt
WordWrap int
ScreenWidth int
ScreenHeight int
}
func NewBuffer(prompt *Prompt) (*Buffer, error) {
width, height, err := term.GetSize(0)
if err != nil {
fmt.Println("Error getting size:", err)
return nil, err
}
b := &Buffer{
PosX: 0,
PosY: 0,
Buf: []*arraylist.List{arraylist.New()},
Prompt: prompt,
ScreenWidth: width,
ScreenHeight: height,
}
return b, nil
}
func (b *Buffer) LineWidth() int {
return b.ScreenWidth - len(b.Prompt.Prompt)
}
func (b *Buffer) findWordAtPos(line string, pos int) string {
return ""
}
func (b *Buffer) addLine(row int) {
if row+1 == len(b.Buf) {
b.Buf = append(b.Buf, arraylist.New())
} else {
b.Buf = append(b.Buf, nil)
copy(b.Buf[row+2:], b.Buf[row+1:])
b.Buf[row+1] = arraylist.New()
}
}
func (b *Buffer) Add(r rune) {
switch r {
case CharCtrlJ, CharEnter:
b.addLine(b.PosY)
// handle Ctrl-J in the middle of a line
var remainingText string
if b.PosX < b.Buf[b.PosY].Size() {
fmt.Print(ClearToEOL)
remainingText = b.StringLine(b.PosX, b.PosY)
for cnt := 0; cnt < len(remainingText); cnt++ {
b.Buf[b.PosY].Remove(b.Buf[b.PosY].Size() - 1)
b.Buf[b.PosY+1].Add(rune(remainingText[cnt]))
}
}
b.PosY++
b.PosX = 0
fmt.Printf("\n... " + ClearToEOL)
b.drawRemaining()
default:
if b.PosX == b.Buf[b.PosY].Size() {
fmt.Printf("%c", r)
b.PosX++
b.Buf[b.PosY].Add(r)
wrap, prefix, offset := b.splitLineInsert(b.PosY, b.PosX)
if wrap {
fmt.Print(CursorHide + cursorLeftN(len(prefix)+1) + ClearToEOL)
fmt.Printf("\n%s... %s%c", ClearToEOL, prefix, r)
b.PosY++
b.PosX = offset
b.ResetCursor()
b.drawRemaining()
fmt.Print(CursorShow)
}
} else {
fmt.Printf("%c", r)
b.Buf[b.PosY].Insert(b.PosX, r)
b.PosX++
_, prefix, offset := b.splitLineInsert(b.PosY, b.PosX)
fmt.Print(CursorHide)
if b.PosX > b.Buf[b.PosY].Size() {
if offset > 0 {
fmt.Print(cursorLeftN(offset))
}
fmt.Print(ClearToEOL + CursorDown + CursorBOL + ClearToEOL)
fmt.Printf("... %s", prefix[:offset])
b.PosY++
b.PosX = offset
b.ResetCursor()
}
b.drawRemaining()
fmt.Print(CursorShow)
}
}
}
func (b *Buffer) ResetCursor() {
fmt.Print(CursorHide + CursorBOL)
fmt.Print(cursorRightN(b.PosX + len(b.Prompt.Prompt)))
fmt.Print(CursorShow)
}
func (b *Buffer) splitLineInsert(posY, posX int) (bool, string, int) {
line := b.StringLine(0, posY)
screenEdge := b.LineWidth() - 5
// if the current line doesn't need to be reflowed, none of the other
// lines will either
if len(line) <= screenEdge {
return false, "", 0
}
// we know we're going to have to insert onto the next line, so
// add another line if there isn't one already
if posY == len(b.Buf)-1 {
b.Buf = append(b.Buf, arraylist.New())
}
// make a truncated version of the current line
currLine := line[:screenEdge]
// figure out where the last space in the line is
idx := strings.LastIndex(currLine, " ")
// deal with strings that don't have spaces in them
if idx == -1 {
idx = len(currLine) - 1
}
// if the next line already has text on it, we need
// to add a space to insert our new word
if b.Buf[posY+1].Size() > 0 {
b.Buf[posY+1].Insert(0, ' ')
}
// calculate the number of characters we need to remove
// from the current line to add to the next one
totalChars := len(line) - idx - 1
for cnt := 0; cnt < totalChars; cnt++ {
b.Buf[posY].Remove(b.Buf[posY].Size() - 1)
b.Buf[posY+1].Insert(0, rune(line[len(line)-1-cnt]))
}
// remove the trailing space
b.Buf[posY].Remove(b.Buf[posY].Size() - 1)
// wrap any further lines
if b.Buf[posY+1].Size() > b.LineWidth()-5 {
b.splitLineInsert(posY+1, 0)
}
return true, currLine[idx+1:], posX - idx - 1
}
func (b *Buffer) drawRemaining() {
remainingText := b.StringFromRow(b.PosY)
remainingText = remainingText[b.PosX:]
fmt.Print(CursorHide + ClearToEOL)
var rowCount int
for _, c := range remainingText {
fmt.Print(string(c))
if c == '\n' {
fmt.Print("... " + ClearToEOL)
rowCount++
}
}
if rowCount > 0 {
fmt.Print(cursorUpN(rowCount))
}
b.ResetCursor()
}
func (b *Buffer) findWordBeginning(posX int) int {
for {
if posX < 0 {
return -1
}
r, ok := b.Buf[b.PosY].Get(posX)
if !ok {
return -1
} else if r.(rune) == ' ' {
return posX
}
posX--
}
}
func (b *Buffer) Delete() {
if b.PosX < b.Buf[b.PosY].Size()-1 {
b.Buf[b.PosY].Remove(b.PosX)
b.drawRemaining()
} else {
b.joinLines()
}
}
func (b *Buffer) joinLines() {
lineLen := b.Buf[b.PosY].Size()
for cnt := 0; cnt < lineLen; cnt++ {
r, _ := b.Buf[b.PosY].Get(0)
b.Buf[b.PosY].Remove(0)
b.Buf[b.PosY-1].Add(r)
}
}
func (b *Buffer) Remove() {
if b.PosX > 0 {
fmt.Print(CursorLeft + " " + CursorLeft)
b.PosX--
b.Buf[b.PosY].Remove(b.PosX)
if b.PosX < b.Buf[b.PosY].Size() {
fmt.Print(ClearToEOL)
b.drawRemaining()
}
} else if b.PosX == 0 && b.PosY > 0 {
b.joinLines()
lastPos := b.Buf[b.PosY-1].Size()
var cnt int
b.PosX = lastPos
b.PosY--
fmt.Print(CursorHide)
for {
if b.PosX+cnt > b.LineWidth()-5 {
// the concatenated line won't fit, so find the beginning of the word
// and copy the rest of the string from there
idx := b.findWordBeginning(b.PosX)
lineLen := b.Buf[b.PosY].Size()
for offset := idx + 1; offset < lineLen; offset++ {
r, _ := b.Buf[b.PosY].Get(idx + 1)
b.Buf[b.PosY].Remove(idx + 1)
b.Buf[b.PosY+1].Add(r)
}
// remove the trailing space
b.Buf[b.PosY].Remove(idx)
fmt.Print(CursorUp + ClearToEOL)
b.PosX = 0
b.drawRemaining()
fmt.Print(CursorDown)
if idx > 0 {
if lastPos-idx-1 > 0 {
b.PosX = lastPos - idx - 1
b.ResetCursor()
}
}
b.PosY++
break
}
r, ok := b.Buf[b.PosY].Get(b.PosX + cnt)
if !ok {
// found the end of the string
fmt.Print(CursorUp + cursorRightN(b.PosX) + ClearToEOL)
b.drawRemaining()
break
}
if r == ' ' {
// found the end of the word
lineLen := b.Buf[b.PosY].Size()
for offset := b.PosX + cnt + 1; offset < lineLen; offset++ {
r, _ := b.Buf[b.PosY].Get(b.PosX + cnt + 1)
b.Buf[b.PosY].Remove(b.PosX + cnt + 1)
b.Buf[b.PosY+1].Add(r)
}
fmt.Print(CursorUp + cursorRightN(b.PosX) + ClearToEOL)
b.drawRemaining()
break
}
cnt++
}
fmt.Print(CursorShow)
}
}
func (b *Buffer) RemoveBefore() {
for {
if b.PosX == 0 && b.PosY == 0 {
break
}
b.Remove()
}
}
func (b *Buffer) RemoveWordBefore() {
if b.PosX > 0 || b.PosY > 0 {
var foundNonspace bool
for {
xPos := b.PosX
yPos := b.PosY
v, _ := b.Buf[yPos].Get(xPos - 1)
if v == ' ' {
if !foundNonspace {
b.Remove()
} else {
break
}
} else {
foundNonspace = true
b.Remove()
}
if xPos == 0 && yPos == 0 {
break
}
}
}
}
func (b *Buffer) StringLine(x, y int) string {
if y >= len(b.Buf) {
return ""
}
var output string
for cnt := x; cnt < b.Buf[y].Size(); cnt++ {
r, _ := b.Buf[y].Get(cnt)
output += string(r.(rune))
}
return output
}
func (b *Buffer) String() string {
return b.StringFromRow(0)
}
func (b *Buffer) StringFromRow(n int) string {
var output []string
for _, row := range b.Buf[n:] {
var currLine string
for cnt := 0; cnt < row.Size(); cnt++ {
r, _ := row.Get(cnt)
currLine += string(r.(rune))
}
currLine = strings.TrimRight(currLine, " ")
output = append(output, currLine)
}
return strings.Join(output, "\n")
}
func (b *Buffer) cursorUp() {
fmt.Print(CursorUp)
b.ResetCursor()
}
func (b *Buffer) cursorDown() {
fmt.Print(CursorDown)
b.ResetCursor()
}
func (b *Buffer) MoveUp() {
if b.PosY > 0 {
b.PosY--
if b.Buf[b.PosY].Size() < b.PosX {
b.PosX = b.Buf[b.PosY].Size()
}
b.cursorUp()
} else {
fmt.Print("\a")
}
}
func (b *Buffer) MoveDown() {
if b.PosY < len(b.Buf)-1 {
b.PosY++
if b.Buf[b.PosY].Size() < b.PosX {
b.PosX = b.Buf[b.PosY].Size()
}
b.cursorDown()
} else {
fmt.Print("\a")
}
}
func (b *Buffer) MoveLeft() {
if b.PosX > 0 {
b.PosX--
fmt.Print(CursorLeft)
} else if b.PosY > 0 {
b.PosX = b.Buf[b.PosY-1].Size()
b.PosY--
b.cursorUp()
} else if b.PosX == 0 && b.PosY == 0 {
fmt.Print("\a")
}
}
func (b *Buffer) MoveRight() {
if b.PosX < b.Buf[b.PosY].Size() {
b.PosX++
fmt.Print(CursorRight)
} else if b.PosY < len(b.Buf)-1 {
b.PosY++
b.PosX = 0
b.cursorDown()
} else {
fmt.Print("\a")
}
}
func (b *Buffer) MoveToBOL() {
if b.PosX > 0 {
b.PosX = 0
b.ResetCursor()
}
}
func (b *Buffer) MoveToEOL() {
if b.PosX < b.Buf[b.PosY].Size() {
b.PosX = b.Buf[b.PosY].Size()
b.ResetCursor()
}
}
func (b *Buffer) MoveToEnd() {
fmt.Print(CursorHide)
yDiff := len(b.Buf)-1 - b.PosY
if yDiff > 0 {
fmt.Print(cursorDownN(yDiff))
}
b.PosY = len(b.Buf)-1
b.MoveToEOL()
fmt.Print(CursorShow)
}
func cursorLeftN(n int) string {
return fmt.Sprintf(CursorLeftN, n)
}
func cursorRightN(n int) string {
return fmt.Sprintf(CursorRightN, n)
}
func cursorUpN(n int) string {
return fmt.Sprintf(CursorUpN, n)
}
func cursorDownN(n int) string {
return fmt.Sprintf(CursorDownN, n)
}
func (b *Buffer) ClearScreen() {
fmt.Printf(CursorHide + ClearScreen + CursorReset + b.Prompt.Prompt)
if b.IsEmpty() {
ph := b.Prompt.Placeholder
fmt.Printf(ColorGrey + ph + cursorLeftN(len(ph)) + ColorDefault)
} else {
currPosX := b.PosX
currPosY := b.PosY
b.PosX = 0
b.PosY = 0
b.drawRemaining()
b.PosX = currPosX
b.PosY = currPosY
fmt.Print(CursorReset + cursorRightN(len(b.Prompt.Prompt)))
if b.PosY > 0 {
fmt.Print(cursorDownN(b.PosY))
}
if b.PosX > 0 {
fmt.Print(cursorRightN(b.PosX))
}
}
fmt.Print(CursorShow)
}
func (b *Buffer) IsEmpty() bool {
return len(b.Buf) == 1 && b.Buf[0].Empty()
}

View File

@@ -1,4 +1,4 @@
package readline
package editor
import (
"bufio"
@@ -23,7 +23,6 @@ type Terminal struct {
type Instance struct {
Prompt *Prompt
Terminal *Terminal
History *History
}
func New(prompt Prompt) (*Instance, error) {
@@ -32,40 +31,33 @@ func New(prompt Prompt) (*Instance, error) {
return nil, err
}
history, err := NewHistory()
if err != nil {
return nil, err
}
return &Instance{
Prompt: &prompt,
Terminal: term,
History: history,
}, nil
}
func (i *Instance) Readline() (string, error) {
func (i *Instance) HandleInput() (string, error) {
prompt := i.Prompt.Prompt
if i.Prompt.UseAlt {
prompt = i.Prompt.AltPrompt
}
fmt.Print(prompt)
fd := int(syscall.Stdin)
termios, err := SetRawMode(fd)
termios, err := SetRawMode(syscall.Stdin)
if err != nil {
return "", err
}
defer UnsetRawMode(fd, termios)
defer UnsetRawMode(syscall.Stdin, termios)
buf, _ := NewBuffer(i.Prompt)
var esc bool
var escex bool
var metaDel bool
var pasteMode PasteMode
var currentLineBuf []rune
fmt.Print(StartBracketedPaste)
defer fmt.Printf(EndBracketedPaste)
for {
if buf.IsEmpty() {
@@ -77,33 +69,22 @@ func (i *Instance) Readline() (string, error) {
}
r, err := i.Terminal.Read()
if err != nil {
return "", io.EOF
}
if buf.IsEmpty() {
fmt.Print(ClearToEOL)
}
if err != nil {
return "", io.EOF
}
if escex {
escex = false
switch r {
case KeyUp:
if i.History.Pos > 0 {
if i.History.Pos == i.History.Size() {
currentLineBuf = []rune(buf.String())
}
buf.Replace(i.History.Prev())
}
buf.MoveUp()
case KeyDown:
if i.History.Pos < i.History.Size() {
buf.Replace(i.History.Next())
if i.History.Pos == i.History.Size() {
buf.Replace(currentLineBuf)
}
}
buf.MoveDown()
case KeyLeft:
buf.MoveLeft()
case KeyRight:
@@ -123,28 +104,16 @@ func (i *Instance) Readline() (string, error) {
} else if code == CharBracketedPasteEnd {
pasteMode = PasteModeEnd
}
case KeyDel:
if buf.Size() > 0 {
buf.Delete()
}
metaDel = true
case MetaStart:
buf.MoveToStart()
buf.MoveToBOL()
case MetaEnd:
buf.MoveToEnd()
default:
// skip any keys we don't know about
continue
buf.MoveToEOL()
}
continue
} else if esc {
esc = false
switch r {
case 'b':
buf.MoveLeftWord()
case 'f':
buf.MoveRightWord()
case CharEscapeEx:
escex = true
}
@@ -159,9 +128,9 @@ func (i *Instance) Readline() (string, error) {
case CharInterrupt:
return "", ErrInterrupt
case CharLineStart:
buf.MoveToStart()
buf.MoveToBOL()
case CharLineEnd:
buf.MoveToEnd()
buf.MoveToEOL()
case CharBackward:
buf.MoveLeft()
case CharForward:
@@ -169,56 +138,38 @@ func (i *Instance) Readline() (string, error) {
case CharBackspace, CharCtrlH:
buf.Remove()
case CharTab:
// todo: convert back to real tabs
for cnt := 0; cnt < 8; cnt++ {
buf.Add(' ')
}
case CharDelete:
if buf.Size() > 0 {
if len(buf.Buf) > 0 && buf.Buf[0].Size() > 0 {
buf.Delete()
} else {
return "", io.EOF
}
case CharKill:
buf.DeleteRemaining()
case CharCtrlU:
buf.DeleteBefore()
buf.RemoveBefore()
case CharCtrlL:
buf.ClearScreen()
case CharCtrlW:
buf.DeleteWord()
buf.RemoveWordBefore()
case CharCtrlJ:
buf.Add(r)
case CharEnter:
output := buf.String()
if output != "" {
i.History.Add([]rune(output))
if pasteMode == PasteModeStart {
buf.Add(r)
continue
}
buf.MoveToEnd()
fmt.Println()
switch pasteMode {
case PasteModeStart:
output = `"""` + output
case PasteModeEnd:
output = output + `"""`
}
return output, nil
return buf.String(), nil
default:
if metaDel {
metaDel = false
continue
}
if r >= CharSpace || r == CharEnter {
buf.Add(r)
}
}
}
}
func (i *Instance) HistoryEnable() {
i.History.Enabled = true
}
func (i *Instance) HistoryDisable() {
i.History.Enabled = false
}
func NewTerminal() (*Terminal, error) {

View File

@@ -1,4 +1,4 @@
package readline
package editor
import (
"errors"

View File

@@ -1,6 +1,6 @@
//go:build aix || darwin || dragonfly || freebsd || (linux && !appengine) || netbsd || openbsd || os400 || solaris
package readline
package editor
import (
"syscall"

View File

@@ -1,6 +1,5 @@
//go:build darwin || freebsd || netbsd || openbsd
package readline
package editor
import (
"syscall"

View File

@@ -1,6 +1,5 @@
//go:build linux || solaris
package readline
package editor
import (
"syscall"

View File

@@ -1,4 +1,4 @@
package readline
package editor
const (
CharNull = 0

View File

@@ -0,0 +1,10 @@
# Bash Shell examples
When calling `ollama`, you can pass it a file to run all the prompts in the file, one after the other:
`ollama run llama2 < sourcequestions.txt`
This concept is used in the following example.
## Compare Models
`comparemodels.sh` is a script that runs all the questions in `sourcequestions.txt` using any 4 models you choose that you have already pulled from the Ollama library or have created locally.

View File

@@ -0,0 +1,64 @@
#! /usr/bin/env bash
# Compare multiple models by running them with the same questions
NUMBEROFCHOICES=4
SELECTIONS=()
declare -a SUMS=()
# Get the list of models
CHOICES=$(ollama list | awk '{print $1}')
# Select which models to run as a comparison
echo "Select $NUMBEROFCHOICES models to compare:"
select ITEM in $CHOICES; do
if [[ -n $ITEM ]]; then
echo "You have selected $ITEM"
SELECTIONS+=("$ITEM")
((COUNT++))
if [[ $COUNT -eq $NUMBEROFCHOICES ]]; then
break
fi
else
echo "Invalid selection"
fi
done
# Loop through each of the selected models
for ITEM in "${SELECTIONS[@]}"; do
echo "--------------------------------------------------------------"
echo "Loading the model $ITEM into memory"
ollama run "$ITEM" ""
echo "--------------------------------------------------------------"
echo "Running the questions through the model $ITEM"
COMMAND_OUTPUT=$(ollama run "$ITEM" --verbose < sourcequestions.txt 2>&1| tee /dev/stderr)
# eval duration is sometimes listed in seconds and sometimes in milliseconds.
# Add up the values for each model
SUM=$(echo "$COMMAND_OUTPUT" | awk '
/eval duration:/ {
value = $3
if (index(value, "ms") > 0) {
gsub("ms", "", value)
value /= 1000
} else {
gsub("s", "", value)
}
sum += value
}
END { print sum }')
SUMS+=("All questions for $ITEM completed in $SUM seconds")
done
echo ""
echo "--------------------------------------------------------------"
echo -e "Sums of eval durations for each run:"
for val in "${SUMS[@]}"; do
echo "$val"
done
echo "--------------------------------------------------------------"
echo "Comparison complete. Now you can decide"
echo "which model is best."
echo "--------------------------------------------------------------"

View File

@@ -0,0 +1,7 @@
Why is the sky blue
What is a black hole
Explain the big bang theory like I am 5?
What is the quickest way to win a game of Monopoly with 3 others?
Why does a vacuum bottle keep my coffee hot and my milkshake cold?
What is the difference between a meteor, a meteorite, and a meteoroid?
Create an array with 5 items and print to the console. Do this in Python, C#, Typescript, and Rust.

View File

@@ -1,372 +0,0 @@
package readline
import (
"fmt"
"os"
"github.com/emirpasic/gods/lists/arraylist"
"golang.org/x/term"
)
type Buffer struct {
Pos int
Buf *arraylist.List
Prompt *Prompt
LineWidth int
Width int
Height int
}
func NewBuffer(prompt *Prompt) (*Buffer, error) {
fd := int(os.Stdout.Fd())
width, height, err := term.GetSize(fd)
if err != nil {
fmt.Println("Error getting size:", err)
return nil, err
}
lwidth := width - len(prompt.Prompt)
if prompt.UseAlt {
lwidth = width - len(prompt.AltPrompt)
}
b := &Buffer{
Pos: 0,
Buf: arraylist.New(),
Prompt: prompt,
Width: width,
Height: height,
LineWidth: lwidth,
}
return b, nil
}
func (b *Buffer) MoveLeft() {
if b.Pos > 0 {
if b.Pos%b.LineWidth == 0 {
fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width))
} else {
fmt.Print(CursorLeft)
}
b.Pos -= 1
}
}
func (b *Buffer) MoveLeftWord() {
if b.Pos > 0 {
var foundNonspace bool
for {
v, _ := b.Buf.Get(b.Pos - 1)
if v == ' ' {
if foundNonspace {
break
}
} else {
foundNonspace = true
}
b.MoveLeft()
if b.Pos == 0 {
break
}
}
}
}
func (b *Buffer) MoveRight() {
if b.Pos < b.Size() {
b.Pos += 1
if b.Pos%b.LineWidth == 0 {
fmt.Printf(CursorDown + CursorBOL + cursorRightN(b.PromptSize()))
} else {
fmt.Print(CursorRight)
}
}
}
func (b *Buffer) MoveRightWord() {
if b.Pos < b.Size() {
for {
b.MoveRight()
v, _ := b.Buf.Get(b.Pos)
if v == ' ' {
break
}
if b.Pos == b.Size() {
break
}
}
}
}
func (b *Buffer) MoveToStart() {
if b.Pos > 0 {
currLine := b.Pos / b.LineWidth
if currLine > 0 {
for cnt := 0; cnt < currLine; cnt++ {
fmt.Print(CursorUp)
}
}
fmt.Printf(CursorBOL + cursorRightN(b.PromptSize()))
b.Pos = 0
}
}
func (b *Buffer) MoveToEnd() {
if b.Pos < b.Size() {
currLine := b.Pos / b.LineWidth
totalLines := b.Size() / b.LineWidth
if currLine < totalLines {
for cnt := 0; cnt < totalLines-currLine; cnt++ {
fmt.Print(CursorDown)
}
remainder := b.Size() % b.LineWidth
fmt.Printf(CursorBOL + cursorRightN(b.PromptSize()+remainder))
} else {
fmt.Print(cursorRightN(b.Size() - b.Pos))
}
b.Pos = b.Size()
}
}
func (b *Buffer) Size() int {
return b.Buf.Size()
}
func min(n, m int) int {
if n > m {
return m
}
return n
}
func (b *Buffer) PromptSize() int {
if b.Prompt.UseAlt {
return len(b.Prompt.AltPrompt)
}
return len(b.Prompt.Prompt)
}
func (b *Buffer) Add(r rune) {
if b.Pos == b.Buf.Size() {
fmt.Printf("%c", r)
b.Buf.Add(r)
b.Pos += 1
if b.Pos > 0 && b.Pos%b.LineWidth == 0 {
fmt.Printf("\n%s", b.Prompt.AltPrompt)
}
} else {
fmt.Printf("%c", r)
b.Buf.Insert(b.Pos, r)
b.Pos += 1
if b.Pos > 0 && b.Pos%b.LineWidth == 0 {
fmt.Printf("\n%s", b.Prompt.AltPrompt)
}
b.drawRemaining()
}
}
func (b *Buffer) drawRemaining() {
var place int
remainingText := b.StringN(b.Pos)
if b.Pos > 0 {
place = b.Pos % b.LineWidth
}
fmt.Print(CursorHide)
// render the rest of the current line
currLine := remainingText[:min(b.LineWidth-place, len(remainingText))]
if len(currLine) > 0 {
fmt.Printf(ClearToEOL + currLine)
fmt.Print(cursorLeftN(len(currLine)))
} else {
fmt.Print(ClearToEOL)
}
// render the other lines
if len(remainingText) > len(currLine) {
remaining := []rune(remainingText[len(currLine):])
var totalLines int
for i, c := range remaining {
if i%b.LineWidth == 0 {
fmt.Printf("\n%s", b.Prompt.AltPrompt)
totalLines += 1
}
fmt.Printf("%c", c)
}
fmt.Print(ClearToEOL)
fmt.Print(cursorUpN(totalLines))
fmt.Printf(CursorBOL + cursorRightN(b.Width-len(currLine)))
}
fmt.Print(CursorShow)
}
func (b *Buffer) Remove() {
if b.Buf.Size() > 0 && b.Pos > 0 {
if b.Pos%b.LineWidth == 0 {
// if the user backspaces over the word boundary, do this magic to clear the line
// and move to the end of the previous line
fmt.Printf(CursorBOL + ClearToEOL)
fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width) + " " + CursorLeft)
} else {
fmt.Printf(CursorLeft + " " + CursorLeft)
}
var eraseExtraLine bool
if (b.Size()-1)%b.LineWidth == 0 {
eraseExtraLine = true
}
b.Pos -= 1
b.Buf.Remove(b.Pos)
if b.Pos < b.Size() {
b.drawRemaining()
// this erases a line which is left over when backspacing in the middle of a line and there
// are trailing characters which go over the line width boundary
if eraseExtraLine {
remainingLines := (b.Size() - b.Pos) / b.LineWidth
fmt.Printf(cursorDownN(remainingLines+1) + CursorBOL + ClearToEOL)
place := b.Pos % b.LineWidth
fmt.Printf(cursorUpN(remainingLines+1) + cursorRightN(place+len(b.Prompt.Prompt)))
}
}
}
}
func (b *Buffer) Delete() {
if b.Size() > 0 && b.Pos < b.Size() {
b.Buf.Remove(b.Pos)
b.drawRemaining()
if b.Size()%b.LineWidth == 0 {
if b.Pos != b.Size() {
remainingLines := (b.Size() - b.Pos) / b.LineWidth
fmt.Printf(cursorDownN(remainingLines) + CursorBOL + ClearToEOL)
place := b.Pos % b.LineWidth
fmt.Printf(cursorUpN(remainingLines) + cursorRightN(place+len(b.Prompt.Prompt)))
}
}
}
}
func (b *Buffer) DeleteBefore() {
if b.Pos > 0 {
for cnt := b.Pos - 1; cnt >= 0; cnt-- {
b.Remove()
}
}
}
func (b *Buffer) DeleteRemaining() {
if b.Size() > 0 && b.Pos < b.Size() {
charsToDel := b.Size() - b.Pos
for cnt := 0; cnt < charsToDel; cnt++ {
b.Delete()
}
}
}
func (b *Buffer) DeleteWord() {
if b.Buf.Size() > 0 && b.Pos > 0 {
var foundNonspace bool
for {
v, _ := b.Buf.Get(b.Pos - 1)
if v == ' ' {
if !foundNonspace {
b.Remove()
} else {
break
}
} else {
foundNonspace = true
b.Remove()
}
if b.Pos == 0 {
break
}
}
}
}
func (b *Buffer) ClearScreen() {
fmt.Printf(ClearScreen + CursorReset + b.Prompt.Prompt)
if b.IsEmpty() {
ph := b.Prompt.Placeholder
fmt.Printf(ColorGrey + ph + cursorLeftN(len(ph)) + ColorDefault)
} else {
currPos := b.Pos
b.Pos = 0
b.drawRemaining()
fmt.Printf(CursorReset + cursorRightN(len(b.Prompt.Prompt)))
if currPos > 0 {
targetLine := currPos / b.LineWidth
if targetLine > 0 {
for cnt := 0; cnt < targetLine; cnt++ {
fmt.Print(CursorDown)
}
}
remainder := currPos % b.LineWidth
if remainder > 0 {
fmt.Print(cursorRightN(remainder))
}
if currPos%b.LineWidth == 0 {
fmt.Printf(CursorBOL + b.Prompt.AltPrompt)
}
}
b.Pos = currPos
}
}
func (b *Buffer) IsEmpty() bool {
return b.Buf.Empty()
}
func (b *Buffer) Replace(r []rune) {
b.Pos = 0
b.Buf.Clear()
fmt.Printf(ClearLine + CursorBOL + b.Prompt.Prompt)
for _, c := range r {
b.Add(c)
}
}
func (b *Buffer) String() string {
return b.StringN(0)
}
func (b *Buffer) StringN(n int) string {
return b.StringNM(n, 0)
}
func (b *Buffer) StringNM(n, m int) string {
var s string
if m == 0 {
m = b.Size()
}
for cnt := n; cnt < m; cnt++ {
c, _ := b.Buf.Get(cnt)
s += string(c.(rune))
}
return s
}
func cursorLeftN(n int) string {
return fmt.Sprintf(CursorLeftN, n)
}
func cursorRightN(n int) string {
return fmt.Sprintf(CursorRightN, n)
}
func cursorUpN(n int) string {
return fmt.Sprintf(CursorUpN, n)
}
func cursorDownN(n int) string {
return fmt.Sprintf(CursorDownN, n)
}

View File

@@ -1,152 +0,0 @@
package readline
import (
"bufio"
"errors"
"io"
"os"
"path/filepath"
"strings"
"github.com/emirpasic/gods/lists/arraylist"
)
type History struct {
Buf *arraylist.List
Autosave bool
Pos int
Limit int
Filename string
Enabled bool
}
func NewHistory() (*History, error) {
h := &History{
Buf: arraylist.New(),
Limit: 100, //resizeme
Autosave: true,
Enabled: true,
}
err := h.Init()
if err != nil {
return nil, err
}
return h, nil
}
func (h *History) Init() error {
home, err := os.UserHomeDir()
if err != nil {
return err
}
path := filepath.Join(home, ".ollama", "history")
h.Filename = path
//todo check if the file exists
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDONLY, 0600)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil
}
return err
}
defer f.Close()
r := bufio.NewReader(f)
for {
line, err := r.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return err
}
line = strings.TrimSpace(line)
if len(line) == 0 {
continue
}
h.Add([]rune(line))
}
return nil
}
func (h *History) Add(l []rune) {
h.Buf.Add(l)
h.Compact()
h.Pos = h.Size()
if h.Autosave {
h.Save()
}
}
func (h *History) Compact() {
s := h.Buf.Size()
if s > h.Limit {
for cnt := 0; cnt < s-h.Limit; cnt++ {
h.Buf.Remove(0)
}
}
}
func (h *History) Clear() {
h.Buf.Clear()
}
func (h *History) Prev() []rune {
var line []rune
if h.Pos > 0 {
h.Pos -= 1
}
v, _ := h.Buf.Get(h.Pos)
line, _ = v.([]rune)
return line
}
func (h *History) Next() []rune {
var line []rune
if h.Pos < h.Buf.Size() {
h.Pos += 1
v, _ := h.Buf.Get(h.Pos)
line, _ = v.([]rune)
}
return line
}
func (h *History) Size() int {
return h.Buf.Size()
}
func (h *History) Save() error {
if !h.Enabled {
return nil
}
tmpFile := h.Filename + ".tmp"
f, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)
if err != nil {
return err
}
defer f.Close()
buf := bufio.NewWriter(f)
for cnt := 0; cnt < h.Size(); cnt++ {
v, _ := h.Buf.Get(cnt)
line, _ := v.([]rune)
buf.WriteString(string(line) + "\n")
}
buf.Flush()
f.Close()
if err = os.Rename(tmpFile, h.Filename); err != nil {
return err
}
return nil
}

View File

@@ -161,6 +161,9 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *Regis
log.Printf("%s part %d attempt %d failed: %v, retrying", b.Digest[7:19], i, try, err)
continue
default:
if try > 0 {
log.Printf("%s part %d completed after %d retries", b.Digest[7:19], i, try)
}
return nil
}
}
@@ -202,7 +205,7 @@ func (b *blobDownload) downloadChunk(ctx context.Context, requestURL *url.URL, w
defer resp.Body.Close()
n, err := io.Copy(w, io.TeeReader(resp.Body, b))
if err != nil && !errors.Is(err, context.Canceled) {
if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, io.ErrUnexpectedEOF) {
// rollback progress
b.Completed.Add(-n)
return err
@@ -213,7 +216,7 @@ func (b *blobDownload) downloadChunk(ctx context.Context, requestURL *url.URL, w
return err
}
// return nil or context.Canceled
// return nil or context.Canceled or UnexpectedEOF (resumable)
return err
}

View File

@@ -397,7 +397,7 @@ func CreateModel(ctx context.Context, name string, path string, fn func(resp api
if err != nil {
return err
}
newLayer.From = mp.GetNamespaceRepository()
newLayer.From = mp.GetShortTagname()
layers = append(layers, newLayer)
}
}