groove-dev 0.24.9 → 0.24.10

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.
@@ -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-C7dW1iUw.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-Df8BnR7l.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-DjxlUkR9.css">
13
+ <link rel="stylesheet" crossorigin href="/assets/index-BwmIyTkm.css">
14
14
  </head>
15
15
  <body>
16
16
  <div id="root"></div>
@@ -242,6 +242,72 @@ function ModelRow({ model, isInstalled, isRecommended, canRun, onPull, onDelete,
242
242
  );
243
243
  }
244
244
 
245
+ /* ── Server Status Bar ────────────────────────────────────────── */
246
+
247
+ function ServerStatusBar({ onStopped }) {
248
+ const [action, setAction] = useState(null); // 'stopping' | 'restarting'
249
+ const addToast = useGrooveStore((s) => s.addToast);
250
+
251
+ async function handleStop() {
252
+ setAction('stopping');
253
+ try {
254
+ const result = await api.post('/providers/ollama/stop');
255
+ if (result.ok) {
256
+ addToast('info', 'Ollama server stopped');
257
+ if (onStopped) onStopped();
258
+ } else {
259
+ addToast('error', 'Could not stop server');
260
+ }
261
+ } catch (err) {
262
+ addToast('error', 'Stop failed', err.message);
263
+ }
264
+ setAction(null);
265
+ }
266
+
267
+ async function handleRestart() {
268
+ setAction('restarting');
269
+ try {
270
+ const result = await api.post('/providers/ollama/restart');
271
+ if (result.ok) {
272
+ addToast('success', 'Ollama server restarted');
273
+ } else {
274
+ addToast('error', 'Restart failed');
275
+ }
276
+ } catch (err) {
277
+ addToast('error', 'Restart failed', err.message);
278
+ }
279
+ setAction(null);
280
+ }
281
+
282
+ return (
283
+ <div className="flex items-center gap-2 bg-success/8 border border-success/20 rounded-lg px-3 py-2">
284
+ <span className="relative flex-shrink-0 w-[6px] h-[6px]">
285
+ <span className="absolute inset-0 rounded-full bg-success" />
286
+ <span className="absolute inset-[-2px] rounded-full bg-success opacity-20 animate-pulse" />
287
+ </span>
288
+ <span className="text-xs font-sans text-success font-semibold">Server Running</span>
289
+ <span className="text-2xs font-mono text-text-4">:11434</span>
290
+ <div className="flex-1" />
291
+ <button
292
+ onClick={handleRestart}
293
+ disabled={!!action}
294
+ className="flex items-center gap-1 text-2xs font-sans text-text-3 hover:text-accent cursor-pointer transition-colors disabled:opacity-40"
295
+ >
296
+ <RefreshCw size={10} className={action === 'restarting' ? 'animate-spin' : ''} />
297
+ {action === 'restarting' ? 'Restarting...' : 'Restart'}
298
+ </button>
299
+ <button
300
+ onClick={handleStop}
301
+ disabled={!!action}
302
+ className="flex items-center gap-1 text-2xs font-sans text-text-3 hover:text-danger cursor-pointer transition-colors disabled:opacity-40"
303
+ >
304
+ <AlertCircle size={10} />
305
+ {action === 'stopping' ? 'Stopping...' : 'Stop'}
306
+ </button>
307
+ </div>
308
+ );
309
+ }
310
+
245
311
  /* ── Model Browser (installed) ─────────────────────────────── */
246
312
 
247
313
  function ModelBrowser({ onModelChange }) {
@@ -249,6 +315,7 @@ function ModelBrowser({ onModelChange }) {
249
315
  const [pulling, setPulling] = useState(null);
250
316
  const [category, setCategory] = useState('code');
251
317
  const [showAll, setShowAll] = useState(false);
318
+ const [serverStopped, setServerStopped] = useState(false);
252
319
  const addToast = useGrooveStore((s) => s.addToast);
253
320
 
254
321
  const retried = useRef(false);
@@ -256,7 +323,6 @@ function ModelBrowser({ onModelChange }) {
256
323
  function load() {
257
324
  api.get('/providers/ollama/models').then((result) => {
258
325
  setData(result);
259
- // If no installed models on first load, retry once after 2s (server may still be warming up)
260
326
  if (!retried.current && result.installed?.length === 0) {
261
327
  retried.current = true;
262
328
  setTimeout(load, 2000);
@@ -300,8 +366,44 @@ function ModelBrowser({ onModelChange }) {
300
366
  const filtered = catalog.filter((m) => m.category === category);
301
367
  const visible = showAll ? filtered : filtered.filter((m) => m.ramGb <= maxRam);
302
368
 
369
+ if (serverStopped) {
370
+ return (
371
+ <div className="space-y-3 p-3">
372
+ <div className="flex items-center gap-2 bg-warning/8 border border-warning/20 rounded-lg px-3 py-2.5">
373
+ <AlertCircle size={14} className="text-warning flex-shrink-0" />
374
+ <span className="text-xs font-sans text-text-2">
375
+ <span className="text-warning font-semibold">Ollama server stopped.</span>
376
+ {' '}Start it again to pull and use models.
377
+ </span>
378
+ </div>
379
+ <Button
380
+ variant="primary"
381
+ size="md"
382
+ onClick={async () => {
383
+ try {
384
+ const result = await api.post('/providers/ollama/serve');
385
+ if (result.ok) {
386
+ addToast('success', 'Ollama server started!');
387
+ setServerStopped(false);
388
+ retried.current = false;
389
+ setTimeout(load, 2000);
390
+ }
391
+ } catch (err) {
392
+ addToast('error', 'Could not start server', err.message);
393
+ }
394
+ }}
395
+ className="w-full gap-1.5"
396
+ >
397
+ <Zap size={12} />
398
+ Start Ollama Server
399
+ </Button>
400
+ </div>
401
+ );
402
+ }
403
+
303
404
  return (
304
405
  <div className="space-y-2 p-3">
406
+ <ServerStatusBar onStopped={() => setServerStopped(true)} />
305
407
  <HardwareBar hardware={hardware} />
306
408
 
307
409
  {/* Installed count */}