mirror of https://github.com/ollama/ollama
Merge 2d83bb607a into 903b1fc97f
This commit is contained in:
commit
bed5b5e5c7
76
cmd/cmd.go
76
cmd/cmd.go
|
|
@ -49,6 +49,20 @@ import (
|
|||
|
||||
const ConnectInstructions = "To sign in, navigate to:\n %s\n\n"
|
||||
|
||||
type ListSortOrder int
|
||||
const (
|
||||
ListByTime ListSortOrder = iota // -t
|
||||
ListBySize // -S
|
||||
ListByName // -U
|
||||
ListByID // -i
|
||||
)
|
||||
var listSortByTime bool
|
||||
var listSortBySize bool
|
||||
var listSortByName bool
|
||||
var listSortByID bool
|
||||
var listSortReverse bool
|
||||
var listSortGrouped bool
|
||||
|
||||
// ensureThinkingSupport emits a warning if the model does not advertise thinking support
|
||||
func ensureThinkingSupport(ctx context.Context, client *api.Client, name string) {
|
||||
if name == "" {
|
||||
|
|
@ -702,9 +716,11 @@ func ListHandler(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
sorted := sortListModels(models)
|
||||
|
||||
var data [][]string
|
||||
|
||||
for _, m := range models.Models {
|
||||
for _, m := range sorted {
|
||||
if len(args) == 0 || strings.HasPrefix(strings.ToLower(m.Name), strings.ToLower(args[0])) {
|
||||
var size string
|
||||
if m.RemoteModel != "" {
|
||||
|
|
@ -731,6 +747,58 @@ func ListHandler(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func sortListModels(models *api.ListResponse) []*api.ListModelResponse {
|
||||
sorted := make([]*api.ListModelResponse, len(models.Models))
|
||||
for i := 0; i < len(models.Models); i++ {
|
||||
sorted[i] = &models.Models[i]
|
||||
}
|
||||
|
||||
var sortFn func(i, j int) bool
|
||||
switch {
|
||||
case listSortByName:
|
||||
sortFn = func(i, j int) bool {
|
||||
return sorted[i].Name < sorted[j].Name
|
||||
}
|
||||
case listSortBySize:
|
||||
sortFn = func(i, j int) bool {
|
||||
return sorted[i].Size < sorted[j].Size
|
||||
}
|
||||
case listSortByID:
|
||||
sortFn = func(i, j int) bool {
|
||||
return sorted[i].Digest < sorted[j].Digest
|
||||
}
|
||||
case listSortByTime:
|
||||
listSortReverse = !listSortReverse
|
||||
}
|
||||
|
||||
if sortFn != nil {
|
||||
sort.Slice(sorted, sortFn)
|
||||
}
|
||||
if listSortReverse {
|
||||
slices.Reverse(sorted)
|
||||
}
|
||||
if listSortGrouped {
|
||||
groups := make([]string, 0, len(sorted))
|
||||
grouped := make(map[string][]*api.ListModelResponse)
|
||||
for _, model := range sorted {
|
||||
group := strings.Split(model.Name, ":")[0]
|
||||
ms, ok := grouped[group]
|
||||
if !ok {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
grouped[group] = append(ms, model)
|
||||
}
|
||||
i := 0
|
||||
for _, group := range groups {
|
||||
for _, model := range grouped[group] {
|
||||
sorted[i] = model
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
return sorted
|
||||
}
|
||||
|
||||
func ListRunningHandler(cmd *cobra.Command, args []string) error {
|
||||
client, err := api.ClientFromEnvironment()
|
||||
if err != nil {
|
||||
|
|
@ -1811,6 +1879,12 @@ func NewCLI() *cobra.Command {
|
|||
PreRunE: checkServerHeartbeat,
|
||||
RunE: ListHandler,
|
||||
}
|
||||
listCmd.Flags().BoolVarP(&listSortByTime, "time", "t", false, "Sort by date/time, chronologically (default reversed).")
|
||||
listCmd.Flags().BoolVarP(&listSortBySize, "size", "S", false, "Sort by file size, smallest first.")
|
||||
listCmd.Flags().BoolVarP(&listSortByName, "name", "U", false, "Sort by name in alphabetical order.")
|
||||
listCmd.Flags().BoolVarP(&listSortByID, "id", "i", false, "Sort by ID in alphabetical order.")
|
||||
listCmd.Flags().BoolVarP(&listSortReverse, "reverse", "r", false, "Reverse the sort order.")
|
||||
listCmd.Flags().BoolVarP(&listSortGrouped, "grouped", "g", false, "Group models.")
|
||||
|
||||
psCmd := &cobra.Command{
|
||||
Use: "ps",
|
||||
|
|
|
|||
135
cmd/cmd_test.go
135
cmd/cmd_test.go
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -962,6 +963,93 @@ func TestListHandler(t *testing.T) {
|
|||
expectedOutput: "NAME ID SIZE MODIFIED \n" +
|
||||
"model1 sha256:abc12 1.0 KB 24 hours ago \n",
|
||||
},
|
||||
{
|
||||
name: "list all models ordered by name",
|
||||
args: []string{"--name"},
|
||||
serverResponse: []api.ListModelResponse{
|
||||
{Name: "model1", Digest: "sha256:abc123", Size: 1024, ModifiedAt: time.Now().Add(-24 * time.Hour)},
|
||||
{Name: "model2", Digest: "sha256:def456", Size: 2048, ModifiedAt: time.Now().Add(-48 * time.Hour)},
|
||||
{Name: "model0", Digest: "sha256:bcd789", Size: 1536, ModifiedAt: time.Now().Add(-72 * time.Hour)},
|
||||
},
|
||||
expectedOutput: "NAME ID SIZE MODIFIED \n" +
|
||||
"model0 sha256:bcd78 1.5 KB 3 days ago \n" +
|
||||
"model1 sha256:abc12 1.0 KB 24 hours ago \n" +
|
||||
"model2 sha256:def45 2.0 KB 2 days ago \n",
|
||||
},
|
||||
{
|
||||
name: "list all models ordered by ID",
|
||||
args: []string{"--id"},
|
||||
serverResponse: []api.ListModelResponse{
|
||||
{Name: "model1", Digest: "sha256:abc123", Size: 1024, ModifiedAt: time.Now().Add(-24 * time.Hour)},
|
||||
{Name: "model2", Digest: "sha256:def456", Size: 2048, ModifiedAt: time.Now().Add(-48 * time.Hour)},
|
||||
{Name: "model0", Digest: "sha256:bcd789", Size: 1536, ModifiedAt: time.Now().Add(-72 * time.Hour)},
|
||||
},
|
||||
expectedOutput: "NAME ID SIZE MODIFIED \n" +
|
||||
"model1 sha256:abc12 1.0 KB 24 hours ago \n" +
|
||||
"model0 sha256:bcd78 1.5 KB 3 days ago \n" +
|
||||
"model2 sha256:def45 2.0 KB 2 days ago \n",
|
||||
},
|
||||
{
|
||||
name: "list all models ordered by size",
|
||||
args: []string{"--size"},
|
||||
serverResponse: []api.ListModelResponse{
|
||||
{Name: "model1", Digest: "sha256:abc123", Size: 1024, ModifiedAt: time.Now().Add(-24 * time.Hour)},
|
||||
{Name: "model2", Digest: "sha256:def456", Size: 2048, ModifiedAt: time.Now().Add(-48 * time.Hour)},
|
||||
{Name: "model0", Digest: "sha256:bcd789", Size: 1536, ModifiedAt: time.Now().Add(-72 * time.Hour)},
|
||||
},
|
||||
expectedOutput: "NAME ID SIZE MODIFIED \n" +
|
||||
"model1 sha256:abc12 1.0 KB 24 hours ago \n" +
|
||||
"model0 sha256:bcd78 1.5 KB 3 days ago \n" +
|
||||
"model2 sha256:def45 2.0 KB 2 days ago \n",
|
||||
},
|
||||
{
|
||||
name: "list all models reversed",
|
||||
args: []string{"--reverse"},
|
||||
serverResponse: []api.ListModelResponse{
|
||||
{Name: "model1", Digest: "sha256:abc123", Size: 1024, ModifiedAt: time.Now().Add(-24 * time.Hour)},
|
||||
{Name: "model2", Digest: "sha256:def456", Size: 2048, ModifiedAt: time.Now().Add(-48 * time.Hour)},
|
||||
{Name: "model0", Digest: "sha256:bcd789", Size: 1536, ModifiedAt: time.Now().Add(-72 * time.Hour)},
|
||||
},
|
||||
expectedOutput: "NAME ID SIZE MODIFIED \n" +
|
||||
"model0 sha256:bcd78 1.5 KB 3 days ago \n" +
|
||||
"model2 sha256:def45 2.0 KB 2 days ago \n" +
|
||||
"model1 sha256:abc12 1.0 KB 24 hours ago \n",
|
||||
},
|
||||
{
|
||||
name: "list all models grouped",
|
||||
args: []string{"--grouped"},
|
||||
serverResponse: []api.ListModelResponse{
|
||||
{Name: "b:model4", Digest: "sha256:cde357", Size: 3072, ModifiedAt: time.Now().Add(-12 * time.Hour)},
|
||||
{Name: "a:model1", Digest: "sha256:abc123", Size: 1024, ModifiedAt: time.Now().Add(-24 * time.Hour)},
|
||||
{Name: "a:model2", Digest: "sha256:def456", Size: 2048, ModifiedAt: time.Now().Add(-48 * time.Hour)},
|
||||
{Name: "b:model0", Digest: "sha256:bcd789", Size: 1536, ModifiedAt: time.Now().Add(-72 * time.Hour)},
|
||||
},
|
||||
expectedOutput: "NAME ID SIZE MODIFIED \n" +
|
||||
"b:model4 sha256:cde35 3.1 KB 12 hours ago \n" +
|
||||
"b:model0 sha256:bcd78 1.5 KB 3 days ago \n" +
|
||||
"a:model1 sha256:abc12 1.0 KB 24 hours ago \n" +
|
||||
"a:model2 sha256:def45 2.0 KB 2 days ago \n",
|
||||
},
|
||||
{
|
||||
name: "list help",
|
||||
args: []string{"--help"},
|
||||
expectedOutput: "Usage:\n" +
|
||||
" ollama list [flags]\n" +
|
||||
"\n" +
|
||||
"Aliases:\n" +
|
||||
" list, ls\n" +
|
||||
"\n" +
|
||||
"Flags:\n" +
|
||||
" -g, --grouped Group models.\n" +
|
||||
" -i, --id Sort by ID in alphabetical order.\n" +
|
||||
" -U, --name Sort by name in alphabetical order.\n" +
|
||||
" -r, --reverse Reverse the sort order.\n" +
|
||||
" -S, --size Sort by file size, smallest first.\n" +
|
||||
" -t, --time Sort by date/time, chronologically (default reversed).\n" +
|
||||
"\n" +
|
||||
"Environment Variables:\n" +
|
||||
" OLLAMA_HOST IP Address for the ollama server (default 127.0.0.1:11434)\n",
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
args: []string{},
|
||||
|
|
@ -992,20 +1080,47 @@ func TestListHandler(t *testing.T) {
|
|||
|
||||
t.Setenv("OLLAMA_HOST", mockServer.URL)
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
cmd := func() *cobra.Command {
|
||||
cli := NewCLI()
|
||||
listCmd, _, err := cli.Find([]string{"list"})
|
||||
if err != nil || listCmd == nil {
|
||||
return nil
|
||||
}
|
||||
return listCmd
|
||||
}()
|
||||
if cmd == nil {
|
||||
t.Fatal("list command not found - did cmd package change?")
|
||||
}
|
||||
isHelp := slices.Equal(tt.args, []string{"--help"})
|
||||
var args []string
|
||||
if !isHelp {
|
||||
if err := cmd.ParseFlags(tt.args); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
args = cmd.Flags().Args()
|
||||
}
|
||||
cmd.SetContext(t.Context())
|
||||
|
||||
// Capture stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
var output []byte
|
||||
var err error
|
||||
if isHelp {
|
||||
var buf bytes.Buffer
|
||||
cmd.SetOut(&buf)
|
||||
err = cmd.Usage()
|
||||
output = buf.Bytes()
|
||||
} else {
|
||||
// Capture stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
err := ListHandler(cmd, tt.args)
|
||||
err = ListHandler(cmd, args)
|
||||
|
||||
// Restore stdout and get output
|
||||
w.Close()
|
||||
os.Stdout = oldStdout
|
||||
output, _ := io.ReadAll(r)
|
||||
// Restore stdout and get output
|
||||
w.Close()
|
||||
os.Stdout = oldStdout
|
||||
output, _ = io.ReadAll(r)
|
||||
}
|
||||
|
||||
if tt.expectedError == "" {
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue