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 */}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.24.9",
3
+ "version": "0.24.10",
4
4
  "description": "Open-source agent orchestration layer — the AI company OS. MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
@@ -199,6 +199,35 @@ export function createApi(app, daemon) {
199
199
  }
200
200
  });
201
201
 
202
+ app.post('/api/providers/ollama/stop', async (req, res) => {
203
+ if (!OllamaProvider.isInstalled()) return res.status(400).json({ error: 'Ollama is not installed' });
204
+ const running = await OllamaProvider.isServerRunning();
205
+ if (!running) return res.json({ ok: true, alreadyStopped: true });
206
+ const result = OllamaProvider.stopServer();
207
+ await new Promise((r) => setTimeout(r, 1000));
208
+ const stillRunning = await OllamaProvider.isServerRunning();
209
+ res.json({ ok: !stillRunning, method: result.method });
210
+ });
211
+
212
+ app.post('/api/providers/ollama/restart', async (req, res) => {
213
+ if (!OllamaProvider.isInstalled()) return res.status(400).json({ error: 'Ollama is not installed' });
214
+ // Stop
215
+ const running = await OllamaProvider.isServerRunning();
216
+ if (running) {
217
+ OllamaProvider.stopServer();
218
+ await new Promise((r) => setTimeout(r, 1500));
219
+ }
220
+ // Start
221
+ const result = OllamaProvider.startServer();
222
+ if (result.started) {
223
+ await new Promise((r) => setTimeout(r, 2000));
224
+ const nowRunning = await OllamaProvider.isServerRunning();
225
+ res.json({ ok: nowRunning, method: result.method });
226
+ } else {
227
+ res.status(500).json({ error: 'Could not restart server' });
228
+ }
229
+ });
230
+
202
231
  // --- Credentials ---
203
232
 
204
233
  app.get('/api/credentials', (req, res) => {
@@ -107,6 +107,29 @@ export class OllamaProvider extends Provider {
107
107
  }
108
108
  }
109
109
 
110
+ static stopServer() {
111
+ const platform = process.platform;
112
+ if (platform === 'darwin') {
113
+ try {
114
+ execSync('brew services stop ollama', { stdio: 'ignore', timeout: 10000 });
115
+ return { stopped: true, method: 'brew services' };
116
+ } catch { /* fall through */ }
117
+ }
118
+ // Kill ollama serve process
119
+ try {
120
+ execSync('pkill -f "ollama serve"', { stdio: 'ignore', timeout: 5000 });
121
+ return { stopped: true, method: 'pkill' };
122
+ } catch {
123
+ // Also try killing by port
124
+ try {
125
+ execSync("lsof -ti:11434 | xargs kill", { stdio: 'ignore', timeout: 5000 });
126
+ return { stopped: true, method: 'port-kill' };
127
+ } catch {
128
+ return { stopped: false };
129
+ }
130
+ }
131
+ }
132
+
110
133
  static hardwareRequirements() {
111
134
  return {
112
135
  minRAM: 4,