v0.3.29 f3d8eb6

llama_dialog_test.go RawHistoryFolder

1package modes
2
3import (
4	"fmt"
5	"net/http"
6	"net/http/httptest"
7	"strings"
8	"testing"
9	"time"
10
11	"github.com/patriceckhart/zot/packages/provider"
12	"github.com/patriceckhart/zot/packages/tui"
13)
14
15func TestLlamaDialogSearchesAsUserTypes(t *testing.T) {
16	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17		if r.URL.Path != "/api/models" || r.URL.Query().Get("search") != "qw" {
18			http.Error(w, "unexpected request", http.StatusBadRequest)
19			return
20		}
21		fmt.Fprint(w, `[{"id":"owner/qwen-GGUF","downloads":42}]`)
22	}))
23	defer server.Close()
24
25	hf := provider.NewHuggingFaceClient("")
26	hf.BaseURL = server.URL
27	updated := make(chan struct{}, 1)
28	d := newLlamaDialog()
29	d.step = llamaSearch
30	d.hf = hf
31	d.invalidate = func() {
32		select {
33		case updated <- struct{}{}:
34		default:
35		}
36	}
37	d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'q'})
38	d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'w'})
39
40	select {
41	case <-updated:
42	case <-time.After(2 * time.Second):
43		t.Fatal("timed out waiting for live search")
44	}
45	d.mu.Lock()
46	defer d.mu.Unlock()
47	if len(d.results) != 1 || d.results[0].ID != "owner/qwen-GGUF" {
48		t.Fatalf("results = %#v", d.results)
49	}
50	if d.message != "" {
51		t.Fatalf("message = %q", d.message)
52	}
53}
54
55func TestLlamaDialogSearchAcceptsPastedText(t *testing.T) {
56	d := newLlamaDialog()
57	d.step = llamaSearch
58
59	d.HandleKey(tui.Key{Kind: tui.KeyPaste, Paste: "unsloth/Qwen3.5-4B-GGUF\n"})
60	defer d.Close()
61
62	if d.query != "unsloth/Qwen3.5-4B-GGUF" {
63		t.Fatalf("query = %q", d.query)
64	}
65}
66
67func TestLlamaDialogSearchCursorTracksQuery(t *testing.T) {
68	d := newLlamaDialog()
69	d.step = llamaSearch
70	d.query = "qwen"
71	row, col := d.CursorPos()
72	if row != 5 || col != 6 {
73		t.Fatalf("cursor = (%d, %d)", row, col)
74	}
75	d.message = "searching Hugging Face..."
76	row, _ = d.CursorPos()
77	if row != 6 {
78		t.Fatalf("cursor row with status = %d", row)
79	}
80}
81
82func TestLlamaDialogLoadedModelRequiresUnloadConfirmation(t *testing.T) {
83	d := newLlamaDialog()
84	var loaded provider.LlamaCPPModel
85	loaded.ID = "local-model"
86	loaded.Status.Value = "loaded"
87	d.step = llamaModels
88	d.models = []provider.LlamaCPPModel{loaded}
89
90	d.HandleKey(tui.Key{Kind: tui.KeyEnter})
91	if d.step != llamaConfirmUnload || d.selected != "local-model" {
92		t.Fatalf("step = %v, selected = %q", d.step, d.selected)
93	}
94	lines := d.Render(tui.Theme{}, 80)
95	if !strings.Contains(strings.Join(lines, "\n"), "Unload local-model?") {
96		t.Fatalf("render = %q", lines)
97	}
98}
99
100func TestLlamaDialogRemovableModelRequiresDiskRemovalConfirmation(t *testing.T) {
101	d := newLlamaDialog()
102	var cached provider.LlamaCPPModel
103	cached.ID = "owner/model:Q4_K_M"
104	cached.Status.Value = "unloaded"
105	cached.CanRemove = true
106	d.step = llamaModels
107	d.models = []provider.LlamaCPPModel{cached}
108
109	d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'd'})
110	if d.step != llamaConfirmRemove || d.selected != cached.ID {
111		t.Fatalf("step = %v, selected = %q", d.step, d.selected)
112	}
113	lines := d.Render(tui.Theme{}, 80)
114	if !strings.Contains(strings.Join(lines, "\n"), "Remove cached model "+cached.ID+"?") {
115		t.Fatalf("render = %q", lines)
116	}
117
118	d.HandleKey(tui.Key{Kind: tui.KeyEsc})
119	if d.step != llamaModels {
120		t.Fatalf("step after escape = %v", d.step)
121	}
122}
123
124func TestLlamaDialogDoesNotRemoveModelsDirEntry(t *testing.T) {
125	d := newLlamaDialog()
126	var local provider.LlamaCPPModel
127	local.ID = "local-model"
128	local.Status.Value = "unloaded"
129	d.step = llamaModels
130	d.models = []provider.LlamaCPPModel{local}
131
132	d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'd'})
133	if d.step != llamaModels {
134		t.Fatalf("step = %v", d.step)
135	}
136	if !strings.Contains(d.message, "only router-downloaded cache models") {
137		t.Fatalf("message = %q", d.message)
138	}
139}
140
141func TestLlamaDialogDownloadSearchReturnsToModels(t *testing.T) {
142	d := newLlamaDialog()
143	d.step = llamaModels
144	d.HandleKey(tui.Key{Kind: tui.KeyEnter})
145	if d.step != llamaSearch {
146		t.Fatalf("step = %v", d.step)
147	}
148	d.HandleKey(tui.Key{Kind: tui.KeyEsc})
149	if d.step != llamaModels {
150		t.Fatalf("step after escape = %v", d.step)
151	}
152}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close