groove-dev 0.26.5 → 0.26.7
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/node_modules/@groove-dev/daemon/src/api.js +12 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-DvNB4K83.css → index-DVRmIjTA.css} +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-BiwUzEwA.js → index-PPbrScja.js} +50 -50
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/views/models.jsx +94 -10
- package/package.json +1 -1
- package/packages/daemon/src/api.js +12 -0
- package/packages/gui/dist/assets/{index-DvNB4K83.css → index-DVRmIjTA.css} +1 -1
- package/packages/gui/dist/assets/{index-BiwUzEwA.js → index-PPbrScja.js} +50 -50
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/src/views/models.jsx +94 -10
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
7
7
|
<title>Groove GUI</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-PPbrScja.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DVRmIjTA.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="root"></div>
|
|
@@ -162,8 +162,8 @@ function FilePicker({ repoId, onDownload, systemRamGb }) {
|
|
|
162
162
|
return (
|
|
163
163
|
<div className="pl-6 pr-4 pb-2 space-y-1.5">
|
|
164
164
|
{files.map((f) => {
|
|
165
|
-
const canRun = !f.estimatedRamGb || !systemRamGb || f.estimatedRamGb <= systemRamGb
|
|
166
|
-
const tight = f.estimatedRamGb && systemRamGb && f.estimatedRamGb > systemRamGb * 0.
|
|
165
|
+
const canRun = !f.estimatedRamGb || !systemRamGb || f.estimatedRamGb <= systemRamGb;
|
|
166
|
+
const tight = f.estimatedRamGb && systemRamGb && f.estimatedRamGb > systemRamGb * 0.8 && canRun;
|
|
167
167
|
return (
|
|
168
168
|
<div key={f.filename} className={cn(
|
|
169
169
|
'flex items-center gap-2 py-1.5 px-3 rounded-md text-xs font-sans',
|
|
@@ -199,16 +199,53 @@ function FilePicker({ repoId, onDownload, systemRamGb }) {
|
|
|
199
199
|
);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
// ---- Recommended Model Card ----
|
|
203
|
+
function RecommendedModel({ model, systemRamGb, onPull, pulling }) {
|
|
204
|
+
const tierColors = { light: 'text-green-400', medium: 'text-blue-400', heavy: 'text-orange-400' };
|
|
205
|
+
const categoryIcons = { code: '{}', general: 'AI' };
|
|
206
|
+
const headroom = systemRamGb ? Math.round((1 - model.ramGb / systemRamGb) * 100) : null;
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div className="flex items-center gap-3 px-4 py-3 bg-surface-1 border border-border-subtle rounded-lg hover:border-accent/20 transition-colors">
|
|
210
|
+
<div className="w-9 h-9 rounded-lg bg-surface-3 flex items-center justify-center text-xs font-mono text-text-2 flex-shrink-0">
|
|
211
|
+
{categoryIcons[model.category] || 'AI'}
|
|
212
|
+
</div>
|
|
213
|
+
<div className="flex-1 min-w-0">
|
|
214
|
+
<div className="flex items-center gap-2">
|
|
215
|
+
<span className="text-sm font-mono font-bold text-text-0 truncate">{model.name}</span>
|
|
216
|
+
<span className={cn('text-2xs font-semibold capitalize', tierColors[model.tier])}>{model.tier}</span>
|
|
217
|
+
</div>
|
|
218
|
+
<div className="text-2xs text-text-3 font-sans mt-0.5">{model.description}</div>
|
|
219
|
+
<div className="flex items-center gap-3 mt-1 text-2xs font-sans">
|
|
220
|
+
<span className="text-text-2">{model.sizeGb} GB download</span>
|
|
221
|
+
<span className="text-green-400 font-medium">{model.ramGb} GB RAM</span>
|
|
222
|
+
{headroom !== null && <span className="text-text-4">{headroom}% headroom</span>}
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
<button
|
|
226
|
+
onClick={() => onPull(model.id)}
|
|
227
|
+
disabled={pulling === model.id}
|
|
228
|
+
className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-sans font-medium bg-accent/10 text-accent hover:bg-accent/20 transition-colors cursor-pointer disabled:opacity-40"
|
|
229
|
+
>
|
|
230
|
+
{pulling === model.id ? <Loader2 size={12} className="animate-spin" /> : <Download size={12} />}
|
|
231
|
+
Pull
|
|
232
|
+
</button>
|
|
233
|
+
</div>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
202
237
|
// ---- Main View ----
|
|
203
238
|
export default function ModelsView() {
|
|
204
|
-
const [tab, setTab] = useState('
|
|
239
|
+
const [tab, setTab] = useState('recommended'); // recommended | installed | search
|
|
205
240
|
const [searchQuery, setSearchQuery] = useState('');
|
|
206
241
|
const [searchResults, setSearchResults] = useState([]);
|
|
207
242
|
const [searching, setSearching] = useState(false);
|
|
208
243
|
const [installed, setInstalled] = useState([]);
|
|
244
|
+
const [recommended, setRecommended] = useState([]);
|
|
209
245
|
const [downloads, setDownloads] = useState([]);
|
|
210
246
|
const [hardware, setHardware] = useState(null);
|
|
211
247
|
const [expandedResult, setExpandedResult] = useState(null);
|
|
248
|
+
const [pulling, setPulling] = useState(null);
|
|
212
249
|
const toast = useToast();
|
|
213
250
|
|
|
214
251
|
// Fetch installed models
|
|
@@ -218,12 +255,28 @@ export default function ModelsView() {
|
|
|
218
255
|
}).catch(() => {});
|
|
219
256
|
}, []);
|
|
220
257
|
|
|
221
|
-
// Fetch hardware info
|
|
258
|
+
// Fetch hardware info + recommended models
|
|
222
259
|
useEffect(() => {
|
|
223
260
|
api.get('/providers/ollama/hardware').then(setHardware).catch(() => {});
|
|
261
|
+
api.get('/models/recommended').then((data) => {
|
|
262
|
+
setRecommended(data.models || []);
|
|
263
|
+
if (!hardware && data.hardware) setHardware(data.hardware);
|
|
264
|
+
}).catch(() => {});
|
|
224
265
|
fetchInstalled();
|
|
225
266
|
}, [fetchInstalled]);
|
|
226
267
|
|
|
268
|
+
async function handlePull(modelId) {
|
|
269
|
+
setPulling(modelId);
|
|
270
|
+
try {
|
|
271
|
+
await api.post('/providers/ollama/pull', { model: modelId });
|
|
272
|
+
toast.success(`${modelId} pulled successfully`);
|
|
273
|
+
fetchInstalled();
|
|
274
|
+
} catch (err) {
|
|
275
|
+
toast.error(`Pull failed: ${err.message}`);
|
|
276
|
+
}
|
|
277
|
+
setPulling(null);
|
|
278
|
+
}
|
|
279
|
+
|
|
227
280
|
// Listen for download progress via WebSocket
|
|
228
281
|
useEffect(() => {
|
|
229
282
|
const unsub = useGrooveStore.subscribe((state, prev) => {
|
|
@@ -323,16 +376,20 @@ export default function ModelsView() {
|
|
|
323
376
|
|
|
324
377
|
{/* Tabs */}
|
|
325
378
|
<div className="flex gap-1">
|
|
326
|
-
{[
|
|
379
|
+
{[
|
|
380
|
+
{ id: 'recommended', label: `Recommended (${recommended.length})` },
|
|
381
|
+
{ id: 'installed', label: `Installed (${installed.length})` },
|
|
382
|
+
{ id: 'search', label: `Search (${searchResults.length})` },
|
|
383
|
+
].map((t) => (
|
|
327
384
|
<button
|
|
328
|
-
key={t}
|
|
329
|
-
onClick={() => setTab(t)}
|
|
385
|
+
key={t.id}
|
|
386
|
+
onClick={() => setTab(t.id)}
|
|
330
387
|
className={cn(
|
|
331
|
-
'px-3 py-1 rounded-md text-xs font-sans font-medium transition-colors cursor-pointer
|
|
332
|
-
tab === t ? 'bg-accent/12 text-accent' : 'text-text-3 hover:text-text-1 hover:bg-surface-3',
|
|
388
|
+
'px-3 py-1 rounded-md text-xs font-sans font-medium transition-colors cursor-pointer',
|
|
389
|
+
tab === t.id ? 'bg-accent/12 text-accent' : 'text-text-3 hover:text-text-1 hover:bg-surface-3',
|
|
333
390
|
)}
|
|
334
391
|
>
|
|
335
|
-
{t
|
|
392
|
+
{t.label}
|
|
336
393
|
</button>
|
|
337
394
|
))}
|
|
338
395
|
</div>
|
|
@@ -349,6 +406,33 @@ export default function ModelsView() {
|
|
|
349
406
|
{/* Content */}
|
|
350
407
|
<ScrollArea className="flex-1">
|
|
351
408
|
<div className="px-5 py-4 space-y-2">
|
|
409
|
+
{tab === 'recommended' && (
|
|
410
|
+
<>
|
|
411
|
+
{recommended.length === 0 ? (
|
|
412
|
+
<div className="text-center py-12">
|
|
413
|
+
<Cpu size={40} className="mx-auto text-text-4 mb-3" />
|
|
414
|
+
<p className="text-sm text-text-2 font-sans font-medium">Detecting hardware...</p>
|
|
415
|
+
<p className="text-xs text-text-3 font-sans mt-1">Make sure Ollama is installed so we can check your system.</p>
|
|
416
|
+
</div>
|
|
417
|
+
) : (
|
|
418
|
+
<>
|
|
419
|
+
<div className="text-xs text-text-3 font-sans mb-2">
|
|
420
|
+
Top models for your system ({hardware?.totalRamGb || '?'} GB RAM). Click Pull to download via Ollama.
|
|
421
|
+
</div>
|
|
422
|
+
{recommended.map((m) => (
|
|
423
|
+
<RecommendedModel
|
|
424
|
+
key={m.id}
|
|
425
|
+
model={m}
|
|
426
|
+
systemRamGb={hardware?.totalRamGb}
|
|
427
|
+
onPull={handlePull}
|
|
428
|
+
pulling={pulling}
|
|
429
|
+
/>
|
|
430
|
+
))}
|
|
431
|
+
</>
|
|
432
|
+
)}
|
|
433
|
+
</>
|
|
434
|
+
)}
|
|
435
|
+
|
|
352
436
|
{tab === 'installed' && (
|
|
353
437
|
<>
|
|
354
438
|
{installed.length === 0 ? (
|