@tianmucreations/jeeves 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/bin/jeeves +2 -0
- package/dist/agent/context.js +50 -0
- package/dist/agent/errors.js +41 -0
- package/dist/agent/loop.js +84 -0
- package/dist/agent/permissions.js +27 -0
- package/dist/app.js +68 -0
- package/dist/commands/clear.js +9 -0
- package/dist/commands/help.js +17 -0
- package/dist/commands/keys.js +15 -0
- package/dist/commands/model.js +4 -0
- package/dist/commands/verbose.js +8 -0
- package/dist/components/AlternateScreen.js +74 -0
- package/dist/components/Footer.js +114 -0
- package/dist/components/Header.js +6 -0
- package/dist/components/HelpView.js +14 -0
- package/dist/components/Input.js +76 -0
- package/dist/components/KeysManager.js +281 -0
- package/dist/components/ModelPicker.js +457 -0
- package/dist/components/ProjectPicker.js +334 -0
- package/dist/components/TrafficLight.js +116 -0
- package/dist/components/Transcript.js +23 -0
- package/dist/components/UsageBar.js +35 -0
- package/dist/components/transcript-layout.js +103 -0
- package/dist/index.js +53 -0
- package/dist/ink/AlternateScreen.js +106 -0
- package/dist/keys/store.js +58 -0
- package/dist/models/filter.js +4 -0
- package/dist/models/registry.js +112 -0
- package/dist/platform/config.js +60 -0
- package/dist/platform/paths.js +60 -0
- package/dist/platform/shell.js +9 -0
- package/dist/providers/index.js +165 -0
- package/dist/providers/ollama.js +103 -0
- package/dist/providers/openrouter.js +109 -0
- package/dist/providers/types.js +1 -0
- package/dist/providers/zai.js +104 -0
- package/dist/state/session.js +315 -0
- package/dist/tools/index.js +106 -0
- package/dist/tools/listDir.js +55 -0
- package/dist/tools/readFile.js +15 -0
- package/dist/tools/runBash.js +22 -0
- package/dist/tools/writeFile.js +15 -0
- package/package.json +62 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
import Fuse from 'fuse.js';
|
|
6
|
+
import { session, useSession } from '../state/session.js';
|
|
7
|
+
import { isToolCapable } from '../models/filter.js';
|
|
8
|
+
import { compactContext, compactPrice, isFastModel, resolveCurated, cleanModelName, } from '../models/registry.js';
|
|
9
|
+
import { setFavorites, setRecents, setDefaultModel, setDefaultProvider } from '../platform/config.js';
|
|
10
|
+
import { summariseHistory } from '../agent/context.js';
|
|
11
|
+
import { hasCredentialsFor, PROVIDER_ROWS } from '../providers/index.js';
|
|
12
|
+
import { listLocalOllamaModels, isOllamaOnline } from '../providers/ollama.js';
|
|
13
|
+
import { ZAI_MODELS } from '../providers/zai.js';
|
|
14
|
+
import { resetStickySession } from '../providers/openrouter.js';
|
|
15
|
+
const TABS = ['favorites', 'recent', 'all', 'tools'];
|
|
16
|
+
const TAB_LABELS = {
|
|
17
|
+
favorites: 'Favorites',
|
|
18
|
+
recent: 'Recent',
|
|
19
|
+
all: 'All',
|
|
20
|
+
tools: 'Tool-capable',
|
|
21
|
+
};
|
|
22
|
+
// The calm first step uses the shared provider list; keys live in the OS keychain (Phase 7).
|
|
23
|
+
const PROVIDER_ORDER = ['openrouter', 'zai', 'anthropic', 'openai', 'google', 'x-ai', 'groq', 'mistral', 'ollama'];
|
|
24
|
+
const PROVIDER_LABELS = {
|
|
25
|
+
openrouter: 'OpenRouter',
|
|
26
|
+
zai: 'Z.ai',
|
|
27
|
+
anthropic: 'Anthropic',
|
|
28
|
+
openai: 'OpenAI',
|
|
29
|
+
google: 'Google',
|
|
30
|
+
'x-ai': 'xAI',
|
|
31
|
+
groq: 'Groq',
|
|
32
|
+
mistral: 'Mistral',
|
|
33
|
+
ollama: 'Ollama',
|
|
34
|
+
};
|
|
35
|
+
function providerLabel(provider) {
|
|
36
|
+
return PROVIDER_LABELS[provider] ?? provider;
|
|
37
|
+
}
|
|
38
|
+
function groupModels(models) {
|
|
39
|
+
const groups = new Map();
|
|
40
|
+
for (const model of models) {
|
|
41
|
+
const list = groups.get(model.provider);
|
|
42
|
+
if (list) {
|
|
43
|
+
list.push(model);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
groups.set(model.provider, [model]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return groups;
|
|
50
|
+
}
|
|
51
|
+
function orderedProviders(groups) {
|
|
52
|
+
const rest = [...groups.keys()].filter((provider) => !PROVIDER_ORDER.includes(provider)).sort();
|
|
53
|
+
return [...PROVIDER_ORDER.filter((provider) => groups.has(provider)), ...rest];
|
|
54
|
+
}
|
|
55
|
+
function column(text, width) {
|
|
56
|
+
return text.length >= width ? text.slice(0, width - 1) + '…' : text.padEnd(width);
|
|
57
|
+
}
|
|
58
|
+
function rowText(model) {
|
|
59
|
+
return (column(model.name, 30) +
|
|
60
|
+
column(model.provider, 11) +
|
|
61
|
+
column(compactContext(model.contextLength), 7) +
|
|
62
|
+
column(compactPrice(model.promptPrice, model.completionPrice, model.priceLabel), 20) +
|
|
63
|
+
(isToolCapable(model) ? '✓' : '✗') +
|
|
64
|
+
(isFastModel(model) ? ' »' : ''));
|
|
65
|
+
}
|
|
66
|
+
function poolFor(tab, models, favorites, recents) {
|
|
67
|
+
const byId = new Map(models.map((model) => [model.id, model]));
|
|
68
|
+
if (tab === 'favorites')
|
|
69
|
+
return favorites.map((id) => byId.get(id)).filter((m) => m !== undefined);
|
|
70
|
+
if (tab === 'recent')
|
|
71
|
+
return recents.map((id) => byId.get(id)).filter((m) => m !== undefined);
|
|
72
|
+
if (tab === 'tools')
|
|
73
|
+
return models.filter(isToolCapable);
|
|
74
|
+
return models;
|
|
75
|
+
}
|
|
76
|
+
function fuzzyMatch(pool, query) {
|
|
77
|
+
if (!query)
|
|
78
|
+
return pool;
|
|
79
|
+
const fuse = new Fuse(pool, { keys: ['name', 'id'], threshold: 0.4, ignoreLocation: true });
|
|
80
|
+
return fuse.search(query).map((result) => result.item);
|
|
81
|
+
}
|
|
82
|
+
// The cursor skips group headers; back, show-all, and model rows are all selectable.
|
|
83
|
+
function resolveIndex(items, cursor) {
|
|
84
|
+
const start = cursor < 0 ? 0 : cursor;
|
|
85
|
+
for (let i = start; i < items.length; i++) {
|
|
86
|
+
if (items[i].kind !== 'header')
|
|
87
|
+
return i;
|
|
88
|
+
}
|
|
89
|
+
for (let i = Math.min(start, items.length - 1); i >= 0; i--) {
|
|
90
|
+
if (items[i].kind !== 'header')
|
|
91
|
+
return i;
|
|
92
|
+
}
|
|
93
|
+
return -1;
|
|
94
|
+
}
|
|
95
|
+
function stepItem(items, from, delta) {
|
|
96
|
+
let i = from;
|
|
97
|
+
do {
|
|
98
|
+
i += delta;
|
|
99
|
+
} while (i >= 0 && i < items.length && items[i].kind === 'header');
|
|
100
|
+
return i >= 0 && i < items.length ? i : from;
|
|
101
|
+
}
|
|
102
|
+
export function ModelPicker({ rows, columns }) {
|
|
103
|
+
const s = useSession();
|
|
104
|
+
const [step, setStep] = useState('providers');
|
|
105
|
+
// The remembered provider starts highlighted so one Enter continues where you left off.
|
|
106
|
+
const [providerCursor, setProviderCursor] = useState(() => {
|
|
107
|
+
const remembered = PROVIDER_ROWS.findIndex((row) => row.id === session.providerId);
|
|
108
|
+
return remembered >= 0 ? remembered : 0;
|
|
109
|
+
});
|
|
110
|
+
const [providerChoice, setProviderChoice] = useState('openrouter');
|
|
111
|
+
const [ollamaOnline, setOllamaOnline] = useState(null);
|
|
112
|
+
const [ollamaModels, setOllamaModels] = useState(null);
|
|
113
|
+
const [tab, setTab] = useState('all');
|
|
114
|
+
const [query, setQuery] = useState('');
|
|
115
|
+
const [cursor, setCursor] = useState(0);
|
|
116
|
+
const [phase, setPhase] = useState('browse');
|
|
117
|
+
const [pending, setPending] = useState(null);
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
let cancelled = false;
|
|
120
|
+
void isOllamaOnline().then((online) => {
|
|
121
|
+
if (!cancelled)
|
|
122
|
+
setOllamaOnline(online);
|
|
123
|
+
});
|
|
124
|
+
return () => {
|
|
125
|
+
cancelled = true;
|
|
126
|
+
};
|
|
127
|
+
}, []);
|
|
128
|
+
const catalog = providerChoice === 'ollama' ? (ollamaModels ?? []) : providerChoice === 'zai' ? ZAI_MODELS : s.models;
|
|
129
|
+
const listHeight = Math.max(1, rows - 5);
|
|
130
|
+
const items = useMemo(() => {
|
|
131
|
+
if (step === 'curated') {
|
|
132
|
+
const picks = resolveCurated(catalog);
|
|
133
|
+
const flat = [{ kind: 'back' }, { kind: 'header', label: 'Recommended' }];
|
|
134
|
+
for (const pick of picks) {
|
|
135
|
+
flat.push({ kind: 'model', model: pick.model, blurb: pick.blurb });
|
|
136
|
+
}
|
|
137
|
+
flat.push({ kind: 'header', label: '──────────' }, { kind: 'show-all' });
|
|
138
|
+
return flat;
|
|
139
|
+
}
|
|
140
|
+
if (step === 'full') {
|
|
141
|
+
const pool = poolFor(tab, catalog, s.favorites, s.recents);
|
|
142
|
+
const matched = fuzzyMatch(pool, query);
|
|
143
|
+
const flat = [{ kind: 'back' }];
|
|
144
|
+
if (providerChoice === 'ollama') {
|
|
145
|
+
flat.push({ kind: 'header', label: 'Ollama (local)' });
|
|
146
|
+
for (const model of matched)
|
|
147
|
+
flat.push({ kind: 'model', model });
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const groups = groupModels(matched);
|
|
151
|
+
for (const provider of orderedProviders(groups)) {
|
|
152
|
+
flat.push({ kind: 'header', label: providerLabel(provider) });
|
|
153
|
+
for (const model of groups.get(provider) ?? []) {
|
|
154
|
+
flat.push({ kind: 'model', model });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return flat;
|
|
159
|
+
}
|
|
160
|
+
return [];
|
|
161
|
+
}, [step, tab, query, catalog, s.favorites, s.recents, providerChoice]);
|
|
162
|
+
const resolved = resolveIndex(items, cursor);
|
|
163
|
+
const half = Math.floor(listHeight / 2);
|
|
164
|
+
const start = Math.max(0, Math.min(items.length - listHeight, resolved - half));
|
|
165
|
+
const visible = items.slice(start, start + listHeight);
|
|
166
|
+
const highlighted = resolved >= 0 && items[resolved]?.kind === 'model' ? items[resolved].model : null;
|
|
167
|
+
function providerEnabled(rowId) {
|
|
168
|
+
if (rowId === 'openrouter')
|
|
169
|
+
return hasCredentialsFor('openrouter');
|
|
170
|
+
if (rowId === 'zai')
|
|
171
|
+
return hasCredentialsFor('zai');
|
|
172
|
+
if (rowId === 'ollama')
|
|
173
|
+
return ollamaOnline === true;
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
function providerHint(rowId) {
|
|
177
|
+
if (rowId === 'ollama') {
|
|
178
|
+
return ollamaOnline === null ? 'checking…' : 'not running - start the Ollama app';
|
|
179
|
+
}
|
|
180
|
+
return 'add key with /keys';
|
|
181
|
+
}
|
|
182
|
+
function enterModelStep(forProvider) {
|
|
183
|
+
setProviderChoice(forProvider);
|
|
184
|
+
setQuery('');
|
|
185
|
+
setTab('all');
|
|
186
|
+
// Big catalogs get the curated shortlist first; small ones go straight to the full list.
|
|
187
|
+
const big = forProvider === 'openrouter' ? s.models.length > 8 : false;
|
|
188
|
+
if (big) {
|
|
189
|
+
// The remembered model starts highlighted so one Enter accepts it.
|
|
190
|
+
const picks = resolveCurated(s.models);
|
|
191
|
+
const remembered = picks.findIndex((pick) => pick.model.id === s.model);
|
|
192
|
+
setCursor(remembered >= 0 ? 2 + remembered : 1);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
setCursor(1);
|
|
196
|
+
}
|
|
197
|
+
setStep(big ? 'curated' : 'full');
|
|
198
|
+
}
|
|
199
|
+
function chooseProvider() {
|
|
200
|
+
const row = PROVIDER_ROWS[providerCursor];
|
|
201
|
+
if (!row)
|
|
202
|
+
return;
|
|
203
|
+
if (row.id === 'openrouter') {
|
|
204
|
+
if (!hasCredentialsFor('openrouter'))
|
|
205
|
+
return;
|
|
206
|
+
enterModelStep('openrouter');
|
|
207
|
+
}
|
|
208
|
+
else if (row.id === 'zai') {
|
|
209
|
+
if (!hasCredentialsFor('zai'))
|
|
210
|
+
return;
|
|
211
|
+
enterModelStep('zai');
|
|
212
|
+
}
|
|
213
|
+
else if (row.id === 'ollama') {
|
|
214
|
+
if (ollamaOnline !== true)
|
|
215
|
+
return;
|
|
216
|
+
enterModelStep('ollama');
|
|
217
|
+
void listLocalOllamaModels()
|
|
218
|
+
.then(setOllamaModels)
|
|
219
|
+
.catch(() => setOllamaModels([]));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function goBack() {
|
|
223
|
+
if (step === 'full' && providerChoice === 'openrouter') {
|
|
224
|
+
setQuery('');
|
|
225
|
+
setCursor(1);
|
|
226
|
+
setStep('curated');
|
|
227
|
+
}
|
|
228
|
+
else if (step === 'curated' || step === 'full') {
|
|
229
|
+
setStep('providers');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function backLabel() {
|
|
233
|
+
return step === 'full' && providerChoice === 'openrouter' ? '← Back to recommended' : '← Back to providers';
|
|
234
|
+
}
|
|
235
|
+
function applyModel(model) {
|
|
236
|
+
const provider = providerChoice;
|
|
237
|
+
s.setProvider(provider);
|
|
238
|
+
s.setModel(model.id);
|
|
239
|
+
setDefaultProvider(provider);
|
|
240
|
+
setDefaultModel(model.id);
|
|
241
|
+
const updatedRecents = [model.id, ...s.recents.filter((id) => id !== model.id)].slice(0, 10);
|
|
242
|
+
s.setRecents(updatedRecents);
|
|
243
|
+
setRecents(updatedRecents);
|
|
244
|
+
}
|
|
245
|
+
function toggleFavorite() {
|
|
246
|
+
if (!highlighted)
|
|
247
|
+
return;
|
|
248
|
+
const updated = s.favorites.includes(highlighted.id)
|
|
249
|
+
? s.favorites.filter((id) => id !== highlighted.id)
|
|
250
|
+
: [...s.favorites, highlighted.id];
|
|
251
|
+
s.setFavorites(updated);
|
|
252
|
+
setFavorites(updated);
|
|
253
|
+
}
|
|
254
|
+
function selectHighlighted() {
|
|
255
|
+
if (resolved < 0)
|
|
256
|
+
return;
|
|
257
|
+
const current = items[resolved];
|
|
258
|
+
if (!current || current.kind === 'header')
|
|
259
|
+
return;
|
|
260
|
+
if (current.kind === 'back') {
|
|
261
|
+
goBack();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (current.kind === 'show-all') {
|
|
265
|
+
setStep('full');
|
|
266
|
+
setQuery('');
|
|
267
|
+
setTab('all');
|
|
268
|
+
setCursor(1);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const model = current.model;
|
|
272
|
+
if (model.id === s.model && providerChoice === s.providerId) {
|
|
273
|
+
s.closePicker();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (!isToolCapable(model)) {
|
|
277
|
+
setPending(model);
|
|
278
|
+
setPhase('tool-warning');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (s.history.length > 0) {
|
|
282
|
+
setPending(model);
|
|
283
|
+
setPhase('switch-confirm');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
applyModel(model);
|
|
287
|
+
s.addNotice(`Switched to ${model.name}.`);
|
|
288
|
+
s.closePicker();
|
|
289
|
+
}
|
|
290
|
+
useInput((input, key) => {
|
|
291
|
+
if (phase === 'tool-warning') {
|
|
292
|
+
if (key.return) {
|
|
293
|
+
if (pending) {
|
|
294
|
+
applyModel(pending);
|
|
295
|
+
s.addNotice('Chat-only mode - this model cannot call tools.');
|
|
296
|
+
}
|
|
297
|
+
s.closePicker();
|
|
298
|
+
}
|
|
299
|
+
else if (key.escape) {
|
|
300
|
+
setPhase('browse');
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (phase === 'switch-confirm') {
|
|
305
|
+
if (input === 'k') {
|
|
306
|
+
if (pending) {
|
|
307
|
+
applyModel(pending);
|
|
308
|
+
s.addNotice(`Switched to ${pending.name} - conversation kept.`);
|
|
309
|
+
}
|
|
310
|
+
s.closePicker();
|
|
311
|
+
}
|
|
312
|
+
else if (input === 's') {
|
|
313
|
+
if (pending) {
|
|
314
|
+
applyModel(pending);
|
|
315
|
+
s.addNotice(`Switched to ${pending.name} - summarising the conversation.`);
|
|
316
|
+
}
|
|
317
|
+
s.closePicker();
|
|
318
|
+
void summariseHistory();
|
|
319
|
+
}
|
|
320
|
+
else if (input === 'f') {
|
|
321
|
+
if (pending) {
|
|
322
|
+
applyModel(pending);
|
|
323
|
+
s.addNotice(`Switched to ${pending.name} - starting fresh.`);
|
|
324
|
+
}
|
|
325
|
+
s.setHistory([]);
|
|
326
|
+
resetStickySession();
|
|
327
|
+
s.closePicker();
|
|
328
|
+
}
|
|
329
|
+
else if (key.escape) {
|
|
330
|
+
setPhase('browse');
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (step === 'providers') {
|
|
335
|
+
if (key.escape) {
|
|
336
|
+
s.closePicker();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (key.upArrow) {
|
|
340
|
+
setProviderCursor((current) => Math.max(0, current - 1));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (key.downArrow) {
|
|
344
|
+
setProviderCursor((current) => Math.min(PROVIDER_ROWS.length - 1, current + 1));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (key.return) {
|
|
348
|
+
chooseProvider();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (key.escape) {
|
|
354
|
+
goBack();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (key.upArrow) {
|
|
358
|
+
setCursor(stepItem(items, resolved, -1));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (key.downArrow) {
|
|
362
|
+
setCursor(stepItem(items, resolved, 1));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (key.return) {
|
|
366
|
+
selectHighlighted();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (key.tab && step === 'full') {
|
|
370
|
+
const next = TABS[(TABS.indexOf(tab) + 1) % TABS.length];
|
|
371
|
+
setTab(next);
|
|
372
|
+
setCursor(1);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (key.backspace || key.delete) {
|
|
376
|
+
if (step === 'full')
|
|
377
|
+
setQuery((current) => current.slice(0, -1));
|
|
378
|
+
setCursor(1);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (input === '+') {
|
|
382
|
+
toggleFavorite();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (!input || key.ctrl || key.meta)
|
|
386
|
+
return;
|
|
387
|
+
if (step === 'full') {
|
|
388
|
+
setQuery((current) => current + input);
|
|
389
|
+
setCursor(1);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
if (step === 'providers') {
|
|
393
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Text, { dimColor: true, children: "Choose an AI provider" }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: PROVIDER_ROWS.map((row, index) => {
|
|
394
|
+
const enabled = providerEnabled(row.id);
|
|
395
|
+
const selected = index === providerCursor;
|
|
396
|
+
return (_jsxs(Text, { inverse: selected, dimColor: !enabled && !selected, children: [column(' ' + row.label, 16), enabled ? _jsx(Text, { dimColor: true, children: row.description }) : _jsx(Text, { children: providerHint(row.id) })] }, row.id));
|
|
397
|
+
}) }), _jsx(Text, { dimColor: true, children: "\u2191\u2193 move \u00B7 Enter choose \u00B7 Esc close" })] }));
|
|
398
|
+
}
|
|
399
|
+
if (step === 'curated') {
|
|
400
|
+
const picks = resolveCurated(catalog);
|
|
401
|
+
const hint = `↑↓ move · + favorite · Enter select · Esc back`;
|
|
402
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Box, { flexGrow: 1, flexDirection: "column", justifyContent: "center", minHeight: listHeight, children: s.models.length === 0 ? (_jsxs(Text, { dimColor: true, children: [_jsx(Spinner, { type: "dots" }), " Loading the model list\u2026"] })) : (visible.map((item, index) => {
|
|
403
|
+
const absoluteIndex = start + index;
|
|
404
|
+
const selected = absoluteIndex === resolved;
|
|
405
|
+
if (item.kind === 'header') {
|
|
406
|
+
return (_jsx(Text, { dimColor: true, children: item.label ? `${item.label}` : '' }, `h${absoluteIndex}`));
|
|
407
|
+
}
|
|
408
|
+
if (item.kind === 'back') {
|
|
409
|
+
return (_jsx(Text, { inverse: selected, dimColor: !selected, children: backLabel() }, "back"));
|
|
410
|
+
}
|
|
411
|
+
if (item.kind === 'show-all') {
|
|
412
|
+
return (_jsxs(Text, { inverse: selected, dimColor: !selected, children: ["Show all ", catalog.length, " models \u2192"] }, "showall"));
|
|
413
|
+
}
|
|
414
|
+
const tools = isToolCapable(item.model) ? '✓' : '✗';
|
|
415
|
+
return (_jsxs(Text, { inverse: selected, children: [' ', cleanModelName(item.model.name), ' ', _jsxs(Text, { dimColor: true, children: ["\u2014 ", compactContext(item.model.contextLength), " ctx \u2014 ", item.blurb ?? '', " \u2014 ", tools, " tools"] })] }, `m${item.model.id}`));
|
|
416
|
+
})) }), _jsx(Text, { dimColor: true, children: hint })] }));
|
|
417
|
+
}
|
|
418
|
+
const emptyMessage = providerChoice === 'ollama'
|
|
419
|
+
? ollamaModels === null
|
|
420
|
+
? 'Loading local models…'
|
|
421
|
+
: ollamaModels.length === 0
|
|
422
|
+
? 'Could not reach Ollama - is the app still running?'
|
|
423
|
+
: ''
|
|
424
|
+
: s.models.length === 0 && s.modelsNote
|
|
425
|
+
? s.modelsNote
|
|
426
|
+
: s.models.length === 0
|
|
427
|
+
? 'Loading the model list…'
|
|
428
|
+
: items.length <= 1
|
|
429
|
+
? query
|
|
430
|
+
? `No models match "${query}".`
|
|
431
|
+
: tab === 'favorites'
|
|
432
|
+
? 'No favorites yet - highlight a model and press +'
|
|
433
|
+
: tab === 'recent'
|
|
434
|
+
? 'No recently used models yet.'
|
|
435
|
+
: 'No models.'
|
|
436
|
+
: '';
|
|
437
|
+
const hint = phase === 'tool-warning'
|
|
438
|
+
? `This model can't call tools, so reading files and running commands won't work. Enter: continue in chat-only mode · Esc: pick another`
|
|
439
|
+
: phase === 'switch-confirm'
|
|
440
|
+
? `Keep this conversation (k) · Summarise it first (s) · Start fresh (f) · Esc: cancel`
|
|
441
|
+
: `Tab list · ↑↓ move · type to search · + favorite · Enter select · Esc back`;
|
|
442
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsxs(Text, { dimColor: true, children: [providerLabel(providerChoice), " \u2014 all models"] }), _jsx(Box, { children: TABS.map((name) => (_jsxs(React.Fragment, { children: [_jsxs(Text, { inverse: name === tab, dimColor: name !== tab, children: [' ', TAB_LABELS[name], " (", catalog.length && name === 'tools' ? catalog.filter(isToolCapable).length : name === 'all' ? catalog.length : poolFor(name, catalog, s.favorites, s.recents).length, ")", ' '] }), _jsx(Text, { children: " " })] }, name))) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "search: " }), _jsx(Text, { children: query }), _jsx(Text, { dimColor: true, children: query ? '' : 'type to filter' })] }), _jsx(Text, { dimColor: true, children: "context in tokens \u00B7 prices per million tokens \u00B7 \u2713 tools \u00B7 \u00BB fast" }), _jsx(Box, { flexDirection: "column", height: listHeight, children: emptyMessage ? (_jsxs(Text, { dimColor: true, children: [_jsx(Spinner, { type: "dots" }), " ", emptyMessage] })) : (visible.map((item, index) => {
|
|
443
|
+
const absoluteIndex = start + index;
|
|
444
|
+
if (item.kind === 'header') {
|
|
445
|
+
return (_jsx(Text, { dimColor: true, children: `── ${item.label} ──` }, `h${absoluteIndex}`));
|
|
446
|
+
}
|
|
447
|
+
if (item.kind === 'back') {
|
|
448
|
+
const selected = absoluteIndex === resolved;
|
|
449
|
+
return (_jsx(Text, { inverse: selected, dimColor: !selected, children: backLabel() }, "back"));
|
|
450
|
+
}
|
|
451
|
+
if (item.kind === 'show-all') {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
const selected = absoluteIndex === resolved;
|
|
455
|
+
return (_jsx(Text, { inverse: selected, wrap: "truncate-end", children: rowText(item.model) }, `m${item.model.id}`));
|
|
456
|
+
})) }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { dimColor: true, children: hint }), _jsx(Text, { dimColor: true, children: s.modelsNote })] })] }));
|
|
457
|
+
}
|