blyuecode 0.3.0 → 0.3.1
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/dist/ui/ModelPicker.js +103 -29
- package/package.json +1 -1
package/dist/ui/ModelPicker.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
3
3
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
4
4
|
import { fetchModels, currentProviderLabel, } from '../core/models.js';
|
|
5
5
|
import { theme } from './theme.js';
|
|
@@ -12,6 +12,7 @@ export const ModelPicker = ({ current, onSelect, onCancel }) => {
|
|
|
12
12
|
const [error, setError] = useState(null);
|
|
13
13
|
const [idx, setIdx] = useState(0);
|
|
14
14
|
const [filter, setFilter] = useState('');
|
|
15
|
+
const [tier, setTier] = useState('all');
|
|
15
16
|
useEffect(() => {
|
|
16
17
|
let cancelled = false;
|
|
17
18
|
setLoading(true);
|
|
@@ -35,48 +36,121 @@ export const ModelPicker = ({ current, onSelect, onCancel }) => {
|
|
|
35
36
|
cancelled = true;
|
|
36
37
|
};
|
|
37
38
|
}, [current]);
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
const { free, paid } = useMemo(() => {
|
|
40
|
+
const q = filter.toLowerCase();
|
|
41
|
+
const matched = q
|
|
42
|
+
? models.filter((m) => m.id.toLowerCase().includes(q))
|
|
43
|
+
: models;
|
|
44
|
+
const free = [];
|
|
45
|
+
const paid = [];
|
|
46
|
+
for (const m of matched) {
|
|
47
|
+
if (m.isFree)
|
|
48
|
+
free.push(m);
|
|
49
|
+
else
|
|
50
|
+
paid.push(m);
|
|
51
|
+
}
|
|
52
|
+
return { free, paid };
|
|
53
|
+
}, [models, filter]);
|
|
54
|
+
const rows = useMemo(() => {
|
|
55
|
+
const out = [];
|
|
56
|
+
let abs = 0;
|
|
57
|
+
const pushSection = (label, list) => {
|
|
58
|
+
if (list.length === 0)
|
|
59
|
+
return;
|
|
60
|
+
out.push({ kind: 'header', label, count: list.length });
|
|
61
|
+
for (const m of list) {
|
|
62
|
+
out.push({ kind: 'model', model: m, absoluteIdx: abs++ });
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
if (tier === 'all' || tier === 'free')
|
|
66
|
+
pushSection('FREE', free);
|
|
67
|
+
if (tier === 'all' || tier === 'paid')
|
|
68
|
+
pushSection('PAID', paid);
|
|
69
|
+
return out;
|
|
70
|
+
}, [free, paid, tier]);
|
|
71
|
+
const selectableIdxToRow = [];
|
|
72
|
+
rows.forEach((r, i) => {
|
|
73
|
+
if (r.kind === 'model')
|
|
74
|
+
selectableIdxToRow.push(i);
|
|
75
|
+
});
|
|
76
|
+
const totalSelectable = selectableIdxToRow.length;
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (totalSelectable === 0) {
|
|
79
|
+
if (idx !== 0)
|
|
80
|
+
setIdx(0);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (idx >= totalSelectable)
|
|
84
|
+
setIdx(totalSelectable - 1);
|
|
85
|
+
}, [totalSelectable, idx]);
|
|
41
86
|
useInput((input, key) => {
|
|
42
87
|
if (key.escape)
|
|
43
88
|
return onCancel();
|
|
44
|
-
if (key.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
89
|
+
if (key.tab) {
|
|
90
|
+
setTier((t) => (t === 'all' ? 'free' : t === 'free' ? 'paid' : 'all'));
|
|
91
|
+
setIdx(0);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (key.upArrow && totalSelectable)
|
|
95
|
+
setIdx((i) => (i - 1 + totalSelectable) % totalSelectable);
|
|
96
|
+
if (key.downArrow && totalSelectable)
|
|
97
|
+
setIdx((i) => (i + 1) % totalSelectable);
|
|
98
|
+
if (key.return) {
|
|
99
|
+
const rowI = selectableIdxToRow[idx];
|
|
100
|
+
const row = rowI !== undefined ? rows[rowI] : undefined;
|
|
101
|
+
if (row && row.kind === 'model')
|
|
102
|
+
onSelect(row.model.id);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (key.backspace || key.delete) {
|
|
51
106
|
setFilter((f) => f.slice(0, -1));
|
|
107
|
+
setIdx(0);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
52
110
|
if (input && !key.ctrl && !key.meta && !key.upArrow && !key.downArrow) {
|
|
53
111
|
const clean = input.replace(/[\x00-\x1F\x7F]/g, '');
|
|
54
|
-
if (clean)
|
|
112
|
+
if (clean) {
|
|
55
113
|
setFilter((f) => f + clean);
|
|
114
|
+
setIdx(0);
|
|
115
|
+
}
|
|
56
116
|
}
|
|
57
117
|
});
|
|
58
118
|
const inner = width - 4;
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const topPad = Math.max(1, inner -
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
119
|
+
const total = free.length + paid.length;
|
|
120
|
+
const headerLabel = filter
|
|
121
|
+
? `filter: ${filter} · ${total} match`
|
|
122
|
+
: `${provider} · ${total} total · ${free.length} free`;
|
|
123
|
+
const topPad = Math.max(1, inner - headerLabel.length - 4);
|
|
124
|
+
const maxVisible = 14;
|
|
125
|
+
const cursorRowI = selectableIdxToRow[idx] ?? 0;
|
|
126
|
+
let windowStart = Math.max(0, cursorRowI - Math.floor(maxVisible / 2));
|
|
127
|
+
windowStart = Math.min(windowStart, Math.max(0, rows.length - maxVisible));
|
|
128
|
+
const windowEnd = windowStart + maxVisible;
|
|
129
|
+
const visibleRows = rows.slice(windowStart, windowEnd);
|
|
130
|
+
const cursorAbs = idx;
|
|
131
|
+
const tierBadge = tier === 'all' ? 'all' : tier === 'free' ? 'FREE only' : 'PAID only';
|
|
132
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '╭─ ' }), _jsx(Text, { color: theme.accent, bold: true, children: headerLabel }), _jsx(Text, { color: theme.muted, children: ' ' + '─'.repeat(topPad) + '╮' })] }), loading ? (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsxs(Text, { color: theme.muted, children: ["fetching ", provider, " catalog\u2026"] })] })) : error ? (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsxs(Text, { color: theme.error, children: ["\u2717 ", error.slice(0, inner - 6)] })] })) : rows.length === 0 ? (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsxs(Text, { color: theme.error, children: ["no matches", tier !== 'all' ? ` in ${tierBadge}` : ''] })] })) : (visibleRows.map((row, i) => {
|
|
133
|
+
if (row.kind === 'header') {
|
|
134
|
+
const sectionPad = Math.max(1, inner - row.label.length - row.count.toString().length - 5);
|
|
135
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: theme.accent, bold: true, children: row.label }), _jsx(Text, { color: theme.muted, children: ' ' + '─'.repeat(sectionPad) + ' ' + row.count })] }, `h-${row.label}`));
|
|
136
|
+
}
|
|
137
|
+
const m = row.model;
|
|
138
|
+
const active = row.absoluteIdx === cursorAbs;
|
|
67
139
|
const isCurrent = m.id === current;
|
|
68
140
|
const ctx = m.contextLength
|
|
69
141
|
? m.contextLength >= 1000
|
|
70
142
|
? `${Math.round(m.contextLength / 1000)}k`
|
|
71
143
|
: `${m.contextLength}`
|
|
72
144
|
: '';
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
145
|
+
const price = m.isFree
|
|
146
|
+
? 'FREE'
|
|
147
|
+
: m.pricing
|
|
148
|
+
? `$${(m.pricing.prompt * 1_000_000).toFixed(2)}/M`
|
|
149
|
+
: '';
|
|
150
|
+
const right = [ctx, price].filter(Boolean).join(' · ');
|
|
151
|
+
const maxName = Math.max(8, inner - right.length - 6);
|
|
152
|
+
const name = m.id.length > maxName ? m.id.slice(0, maxName - 1) + '…' : m.id;
|
|
153
|
+
const pad = Math.max(1, inner - name.length - right.length - 4);
|
|
154
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: active ? theme.accent : theme.muted, bold: active, children: active ? '❯ ' : ' ' }), _jsx(Text, { color: active ? theme.text : theme.muted, bold: active, children: name }), _jsx(Text, { color: theme.muted, children: ' '.repeat(pad) }), _jsx(Text, { color: m.isFree ? theme.success : theme.muted, children: right }), isCurrent && _jsx(Text, { color: theme.accent, children: " \u2713" })] }, `m-${m.id}`));
|
|
155
|
+
})), _jsx(Text, { children: _jsx(Text, { color: theme.muted, children: '╰' + '─'.repeat(inner) + '╯' }) }), _jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: " \u2191\u2193 move \u00B7 " }), _jsx(Text, { color: theme.accent, children: "tab" }), _jsxs(Text, { color: theme.muted, children: [" tier (", tierBadge, ") \u00B7 "] }), _jsx(Text, { color: theme.muted, children: "type to filter \u00B7 " }), _jsx(Text, { color: theme.accent, children: "\u21B5" }), _jsx(Text, { color: theme.muted, children: " select \u00B7 " }), _jsx(Text, { color: theme.accent, children: "esc" }), _jsx(Text, { color: theme.muted, children: " cancel" })] })] }));
|
|
82
156
|
};
|