groove-dev 0.27.144 → 0.27.146

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.
Files changed (135) hide show
  1. package/CLAUDE.md +7 -0
  2. package/node_modules/@groove-dev/cli/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/api.js +12 -6
  5. package/node_modules/@groove-dev/daemon/src/conversations.js +59 -58
  6. package/node_modules/@groove-dev/daemon/src/introducer.js +20 -0
  7. package/node_modules/@groove-dev/daemon/src/process.js +262 -15
  8. package/node_modules/@groove-dev/daemon/src/providers/groove-network.js +1 -3
  9. package/node_modules/@groove-dev/daemon/src/rotator.js +15 -3
  10. package/node_modules/@groove-dev/daemon/src/routes/agents.js +49 -83
  11. package/node_modules/@groove-dev/daemon/templates/lab-general.json +12 -0
  12. package/node_modules/@groove-dev/daemon/templates/llama-cpp-setup.json +12 -0
  13. package/node_modules/@groove-dev/gui/dist/assets/index-BKbsE_hn.js +1011 -0
  14. package/node_modules/@groove-dev/gui/dist/assets/index-CEkPsSAm.css +1 -0
  15. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  16. package/node_modules/@groove-dev/gui/package.json +1 -1
  17. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +80 -95
  18. package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +2 -70
  19. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +132 -4
  20. package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +3 -8
  21. package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +199 -75
  22. package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +21 -4
  23. package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +10 -13
  24. package/node_modules/@groove-dev/gui/src/components/chat/model-picker.jsx +3 -3
  25. package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +42 -34
  26. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +9 -3
  27. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +13 -3
  28. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +66 -65
  29. package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +17 -14
  30. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +124 -127
  31. package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +10 -8
  32. package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +2 -0
  33. package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +24 -1
  34. package/node_modules/@groove-dev/gui/src/components/ui/question-modal.jsx +107 -0
  35. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +2 -2
  36. package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +8 -8
  37. package/node_modules/@groove-dev/gui/src/lib/status.js +1 -0
  38. package/node_modules/@groove-dev/gui/src/stores/groove.js +49 -2
  39. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +18 -2
  40. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +14 -14
  41. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +68 -32
  42. package/node_modules/@groove-dev/gui/src/views/models.jsx +57 -36
  43. package/node_modules/axios/CHANGELOG.md +260 -0
  44. package/node_modules/axios/README.md +595 -223
  45. package/node_modules/axios/dist/axios.js +1460 -1090
  46. package/node_modules/axios/dist/axios.js.map +1 -1
  47. package/node_modules/axios/dist/axios.min.js +3 -3
  48. package/node_modules/axios/dist/axios.min.js.map +1 -1
  49. package/node_modules/axios/dist/browser/axios.cjs +1560 -1132
  50. package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
  51. package/node_modules/axios/dist/esm/axios.js +1557 -1128
  52. package/node_modules/axios/dist/esm/axios.js.map +1 -1
  53. package/node_modules/axios/dist/esm/axios.min.js +2 -2
  54. package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
  55. package/node_modules/axios/dist/node/axios.cjs +1594 -1057
  56. package/node_modules/axios/dist/node/axios.cjs.map +1 -1
  57. package/node_modules/axios/index.d.cts +40 -41
  58. package/node_modules/axios/index.d.ts +151 -227
  59. package/node_modules/axios/index.js +2 -0
  60. package/node_modules/axios/lib/adapters/adapters.js +4 -2
  61. package/node_modules/axios/lib/adapters/fetch.js +147 -16
  62. package/node_modules/axios/lib/adapters/http.js +306 -58
  63. package/node_modules/axios/lib/adapters/xhr.js +6 -2
  64. package/node_modules/axios/lib/core/Axios.js +7 -3
  65. package/node_modules/axios/lib/core/AxiosError.js +120 -34
  66. package/node_modules/axios/lib/core/AxiosHeaders.js +27 -25
  67. package/node_modules/axios/lib/core/buildFullPath.js +1 -1
  68. package/node_modules/axios/lib/core/dispatchRequest.js +19 -7
  69. package/node_modules/axios/lib/core/mergeConfig.js +21 -4
  70. package/node_modules/axios/lib/core/settle.js +7 -11
  71. package/node_modules/axios/lib/defaults/index.js +14 -9
  72. package/node_modules/axios/lib/env/data.js +1 -1
  73. package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +1 -2
  74. package/node_modules/axios/lib/helpers/buildURL.js +1 -1
  75. package/node_modules/axios/lib/helpers/cookies.js +14 -2
  76. package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +28 -1
  77. package/node_modules/axios/lib/helpers/formDataToJSON.js +3 -1
  78. package/node_modules/axios/lib/helpers/formDataToStream.js +3 -2
  79. package/node_modules/axios/lib/helpers/parseProtocol.js +1 -1
  80. package/node_modules/axios/lib/helpers/progressEventReducer.js +5 -5
  81. package/node_modules/axios/lib/helpers/resolveConfig.js +54 -18
  82. package/node_modules/axios/lib/helpers/shouldBypassProxy.js +74 -2
  83. package/node_modules/axios/lib/helpers/toFormData.js +10 -2
  84. package/node_modules/axios/lib/helpers/validator.js +3 -1
  85. package/node_modules/axios/lib/utils.js +33 -21
  86. package/node_modules/axios/package.json +17 -24
  87. package/node_modules/follow-redirects/README.md +7 -5
  88. package/node_modules/follow-redirects/index.js +24 -1
  89. package/node_modules/follow-redirects/package.json +1 -1
  90. package/package.json +1 -1
  91. package/packages/cli/package.json +1 -1
  92. package/packages/daemon/package.json +1 -1
  93. package/packages/daemon/src/api.js +12 -6
  94. package/packages/daemon/src/conversations.js +59 -58
  95. package/packages/daemon/src/introducer.js +20 -0
  96. package/packages/daemon/src/process.js +262 -15
  97. package/packages/daemon/src/providers/groove-network.js +1 -3
  98. package/packages/daemon/src/rotator.js +15 -3
  99. package/packages/daemon/src/routes/agents.js +49 -83
  100. package/packages/daemon/templates/lab-general.json +12 -0
  101. package/packages/daemon/templates/llama-cpp-setup.json +12 -0
  102. package/packages/gui/dist/assets/index-BKbsE_hn.js +1011 -0
  103. package/packages/gui/dist/assets/index-CEkPsSAm.css +1 -0
  104. package/packages/gui/dist/index.html +2 -2
  105. package/packages/gui/package.json +1 -1
  106. package/packages/gui/src/components/agents/agent-feed.jsx +80 -95
  107. package/packages/gui/src/components/agents/agent-panel.jsx +2 -70
  108. package/packages/gui/src/components/agents/spawn-wizard.jsx +132 -4
  109. package/packages/gui/src/components/chat/chat-header.jsx +3 -8
  110. package/packages/gui/src/components/chat/chat-input.jsx +199 -75
  111. package/packages/gui/src/components/chat/chat-messages.jsx +21 -4
  112. package/packages/gui/src/components/chat/chat-view.jsx +10 -13
  113. package/packages/gui/src/components/chat/model-picker.jsx +3 -3
  114. package/packages/gui/src/components/lab/chat-playground.jsx +42 -34
  115. package/packages/gui/src/components/lab/lab-assistant.jsx +9 -3
  116. package/packages/gui/src/components/lab/metrics-panel.jsx +13 -3
  117. package/packages/gui/src/components/lab/parameter-panel.jsx +66 -65
  118. package/packages/gui/src/components/lab/preset-manager.jsx +17 -14
  119. package/packages/gui/src/components/lab/runtime-config.jsx +124 -127
  120. package/packages/gui/src/components/lab/system-prompt-editor.jsx +10 -8
  121. package/packages/gui/src/components/layout/app-shell.jsx +2 -0
  122. package/packages/gui/src/components/layout/status-bar.jsx +24 -1
  123. package/packages/gui/src/components/ui/question-modal.jsx +107 -0
  124. package/packages/gui/src/components/ui/sheet.jsx +2 -2
  125. package/packages/gui/src/components/ui/slider.jsx +8 -8
  126. package/packages/gui/src/lib/status.js +1 -0
  127. package/packages/gui/src/stores/groove.js +49 -2
  128. package/packages/gui/src/stores/slices/agents-slice.js +18 -2
  129. package/packages/gui/src/stores/slices/chat-slice.js +14 -14
  130. package/packages/gui/src/views/model-lab.jsx +68 -32
  131. package/packages/gui/src/views/models.jsx +57 -36
  132. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +0 -1012
  133. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +0 -1
  134. package/packages/gui/dist/assets/index-BcoF6_eF.js +0 -1012
  135. package/packages/gui/dist/assets/index-Dd7qhiEd.css +0 -1
@@ -1,6 +1,7 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { useState, useEffect, useRef } from 'react';
3
3
  import { useGrooveStore } from '../../stores/groove';
4
+ import { SidebarSection } from '../../views/model-lab';
4
5
  import { Button } from '../ui/button';
5
6
  import { Badge } from '../ui/badge';
6
7
  import { Input } from '../ui/input';
@@ -95,8 +96,8 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onSt
95
96
  <button
96
97
  onClick={() => onSelect(runtime.id)}
97
98
  className={cn(
98
- 'w-full flex items-center gap-2.5 px-2.5 py-2 text-left transition-colors cursor-pointer rounded-sm',
99
- active ? 'bg-accent/8 border border-accent/20' : 'border border-transparent text-text-2 hover:bg-surface-3 hover:text-text-0',
99
+ 'w-full flex items-center gap-2.5 px-2.5 py-2 text-left transition-colors cursor-pointer rounded',
100
+ active ? 'bg-accent/8 ring-1 ring-accent/20' : 'hover:bg-surface-2',
100
101
  )}
101
102
  >
102
103
  <span className={cn(
@@ -104,10 +105,10 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onSt
104
105
  online ? 'bg-success' : runtime.status === 'error' ? 'bg-danger' : 'bg-text-4',
105
106
  )} />
106
107
  <div className="flex-1 min-w-0">
107
- <div className={cn('text-xs font-sans font-medium truncate', active ? 'text-text-0' : '')}>
108
+ <div className={cn('text-[11px] font-sans font-medium truncate', active ? 'text-text-0' : 'text-text-2')}>
108
109
  {RUNTIME_TYPES.find((t) => t.value === runtime.type)?.label || runtime.type}
109
110
  </div>
110
- <div className="text-2xs text-text-4 flex items-center gap-1.5">
111
+ <div className="text-[10px] text-text-4 flex items-center gap-1.5">
111
112
  <span className={cn('font-sans', online ? 'text-success' : 'text-danger')}>
112
113
  {online ? 'Online' : 'Offline'}
113
114
  </span>
@@ -116,7 +117,7 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onSt
116
117
  )}
117
118
  </div>
118
119
  </div>
119
- <div className="flex items-center gap-0.5 flex-shrink-0">
120
+ <div className="flex items-center gap-px flex-shrink-0">
120
121
  {managed && online && (
121
122
  <Tooltip content="Stop server">
122
123
  <button
@@ -142,7 +143,7 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onSt
142
143
  onClick={(e) => { e.stopPropagation(); onTest(runtime.id); }}
143
144
  className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
144
145
  >
145
- {testing === runtime.id ? <Loader2 size={11} className="animate-spin" /> : <RotateCcw size={11} />}
146
+ {testing === runtime.id ? <Loader2 size={10} className="animate-spin" /> : <RotateCcw size={10} />}
146
147
  </button>
147
148
  </Tooltip>
148
149
  <Tooltip content="Remove runtime">
@@ -150,7 +151,7 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onSt
150
151
  onClick={(e) => { e.stopPropagation(); onRemove(runtime.id); }}
151
152
  className="p-1 text-text-4 hover:text-danger transition-colors cursor-pointer"
152
153
  >
153
- <Trash2 size={11} />
154
+ <Trash2 size={10} />
154
155
  </button>
155
156
  </Tooltip>
156
157
  </div>
@@ -172,23 +173,30 @@ const BACKENDS = [
172
173
  { id: 'tgi', label: 'TGI', subtitle: 'HuggingFace, guided setup', autoLaunch: false },
173
174
  ];
174
175
 
175
- function LaunchStatus({ phase, error }) {
176
- if (!phase) return null;
176
+ function StatusBanner({ variant, icon: Icon, children }) {
177
+ const styles = {
178
+ success: 'bg-success/8 text-success',
179
+ danger: 'bg-danger/8 text-danger',
180
+ accent: 'bg-accent/8 text-accent',
181
+ warning: 'bg-warning/8 text-warning',
182
+ };
177
183
  return (
178
- <div className={cn(
179
- 'flex items-center gap-2 px-2.5 py-1.5 text-2xs font-sans rounded-sm',
180
- phase === 'ready' && 'bg-success/8 text-success',
181
- phase === 'error' && 'bg-danger/8 text-danger',
182
- (phase === 'starting' || phase === 'checking') && 'bg-accent/8 text-accent',
183
- )}>
184
- {phase === 'starting' && <><Loader2 size={11} className="animate-spin" /> Starting server...</>}
185
- {phase === 'checking' && <><Loader2 size={11} className="animate-spin" /> Checking...</>}
186
- {phase === 'ready' && <><CheckCircle size={11} /> Server ready</>}
187
- {phase === 'error' && <><AlertTriangle size={11} /> {error || 'Launch failed'}</>}
184
+ <div className={cn('flex items-center gap-2 px-2.5 py-2 text-[11px] font-sans rounded', styles[variant])}>
185
+ <Icon size={11} className={variant === 'accent' ? 'animate-spin' : ''} />
186
+ <span>{children}</span>
188
187
  </div>
189
188
  );
190
189
  }
191
190
 
191
+ function LaunchStatus({ phase, error }) {
192
+ if (!phase) return null;
193
+ if (phase === 'starting') return <StatusBanner variant="accent" icon={Loader2}>Starting server...</StatusBanner>;
194
+ if (phase === 'checking') return <StatusBanner variant="accent" icon={Loader2}>Checking...</StatusBanner>;
195
+ if (phase === 'ready') return <StatusBanner variant="success" icon={CheckCircle}>Server ready</StatusBanner>;
196
+ if (phase === 'error') return <StatusBanner variant="danger" icon={AlertTriangle}>{error || 'Launch failed'}</StatusBanner>;
197
+ return null;
198
+ }
199
+
192
200
  function getIncompatibilityReason(modelType, backendId) {
193
201
  if (modelType === 'gguf' && backendId === 'mlx') return 'GGUF model — MLX needs MLX-format weights';
194
202
  if (modelType === 'gguf' && (backendId === 'vllm' || backendId === 'tgi')) return 'GGUF model — needs standard HuggingFace weights';
@@ -289,22 +297,20 @@ export function LaunchModel() {
289
297
  }
290
298
 
291
299
  return (
292
- <div className="space-y-3">
293
- <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Launch Model</span>
294
-
300
+ <SidebarSection label="Launch Model">
295
301
  {localModels.length === 0 ? (
296
- <div className="py-5 text-center">
297
- <HardDrive size={18} className="mx-auto text-text-4 mb-1.5" />
298
- <p className="text-xs text-text-3 font-sans">No downloaded models</p>
299
- <p className="text-2xs text-text-4 font-sans mt-0.5">Download models from the Models tab</p>
302
+ <div className="py-6 text-center rounded-md bg-surface-1/50 border border-border-subtle">
303
+ <HardDrive size={16} className="mx-auto text-text-4 mb-2" />
304
+ <p className="text-[11px] text-text-3 font-sans">No downloaded models</p>
305
+ <p className="text-[10px] text-text-4 font-sans mt-0.5">Download models from the Models tab</p>
300
306
  </div>
301
307
  ) : (
302
- <>
308
+ <div className="space-y-4">
303
309
  <div className="relative">
304
310
  <select
305
311
  value={selectedModel || ''}
306
312
  onChange={handleModelChange}
307
- className="w-full h-8 px-3 pr-8 text-xs rounded-md bg-surface-1 border border-border text-text-0 font-sans appearance-none cursor-pointer focus:outline-none focus:ring-1 focus:ring-accent"
313
+ className="w-full h-9 px-2.5 pr-7 text-[11px] rounded bg-surface-1 border border-border text-text-0 font-sans appearance-none cursor-pointer focus:outline-none focus:ring-1 focus:ring-accent/50 focus:border-accent/50 transition-colors"
308
314
  >
309
315
  <option value="">Select a model</option>
310
316
  {localModels.map((m) => {
@@ -318,75 +324,73 @@ export function LaunchModel() {
318
324
  );
319
325
  })}
320
326
  </select>
321
- <ChevronRight size={14} className="absolute right-2 top-1/2 -translate-y-1/2 text-text-3 pointer-events-none rotate-90" />
327
+ <ChevronRight size={12} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-text-4 pointer-events-none rotate-90" />
322
328
  </div>
323
329
 
324
330
  {selectedModel && (
325
- <div className="space-y-2">
326
- <span className="text-2xs font-semibold font-sans text-text-4 uppercase tracking-wider">Backend</span>
327
- <div className="space-y-px">
328
- {backendsWithCompat.map((b) => (
329
- <Tooltip key={b.id} content={b.reason} side="right">
330
- <button
331
- onClick={() => setSelectedBackend(b.id)}
332
- className={cn(
333
- 'w-full flex items-center gap-2.5 px-2.5 py-2 text-left transition-colors cursor-pointer rounded-sm',
334
- selectedBackend === b.id ? 'bg-accent/8' : 'hover:bg-surface-3',
335
- !b.compatible && 'opacity-40',
336
- )}
337
- >
338
- <span className={cn(
339
- 'w-2 h-2 rounded-full border-[1.5px] flex-shrink-0',
340
- selectedBackend === b.id ? 'border-accent bg-accent' : 'border-text-4',
341
- )} />
342
- <div className="flex-1 min-w-0">
343
- <div className="flex items-center gap-1.5">
344
- <span className={cn('text-xs font-sans font-medium', selectedBackend === b.id ? 'text-text-0' : 'text-text-2')}>
345
- {b.label}
346
- </span>
347
- {b.compatible && b.recommended && <Badge variant="success" className="text-2xs">Recommended</Badge>}
331
+ <div className="space-y-4">
332
+ <div className="space-y-2">
333
+ <span className="text-[10px] font-semibold font-sans text-text-4 uppercase tracking-widest">Backend</span>
334
+ <div className="space-y-1 rounded-md bg-surface-1/50 border border-border-subtle p-2">
335
+ {backendsWithCompat.map((b) => (
336
+ <Tooltip key={b.id} content={b.reason} side="right">
337
+ <button
338
+ onClick={() => setSelectedBackend(b.id)}
339
+ className={cn(
340
+ 'w-full flex items-center gap-2.5 px-2.5 py-2 text-left transition-colors cursor-pointer rounded',
341
+ selectedBackend === b.id ? 'bg-accent/10' : 'hover:bg-surface-3',
342
+ !b.compatible && 'opacity-40',
343
+ )}
344
+ >
345
+ <span className={cn(
346
+ 'w-2 h-2 rounded-full border-[1.5px] flex-shrink-0 transition-colors',
347
+ selectedBackend === b.id ? 'border-accent bg-accent' : 'border-text-4',
348
+ )} />
349
+ <div className="flex-1 min-w-0">
350
+ <div className="flex items-center gap-1.5">
351
+ <span className={cn('text-[11px] font-sans font-medium', selectedBackend === b.id ? 'text-text-0' : 'text-text-2')}>
352
+ {b.label}
353
+ </span>
354
+ {b.compatible && b.recommended && <Badge variant="success" className="text-[9px]">Recommended</Badge>}
355
+ </div>
356
+ <div className="text-[10px] text-text-4 font-sans">{b.subtitle}</div>
348
357
  </div>
349
- <div className="text-2xs text-text-4 font-sans">{b.subtitle}</div>
350
- </div>
351
- </button>
352
- </Tooltip>
353
- ))}
358
+ </button>
359
+ </Tooltip>
360
+ ))}
361
+ </div>
354
362
  </div>
355
363
 
356
364
  {!isCompatible && (
357
- <div className="px-2.5 py-2 bg-warning/8 rounded-sm space-y-1">
358
- <p className="text-2xs text-warning font-sans">
359
- {currentBackend?.reason}
360
- </p>
365
+ <StatusBanner variant="warning" icon={AlertTriangle}>
366
+ {currentBackend?.reason}
361
367
  {suggestion && (
362
- <p className="text-2xs text-text-2 font-sans">
363
- Try <span className="font-mono font-medium">{suggestion.repoId}</span> instead
364
- </p>
368
+ <> — try <span className="font-mono font-medium">{suggestion.repoId}</span></>
365
369
  )}
366
- </div>
370
+ </StatusBanner>
367
371
  )}
368
372
 
369
373
  {isCompatible && selectedBackend === 'llama-cpp' && (
370
- <div className="px-2.5">
374
+ <div>
371
375
  {llamaInstalled === null && (
372
- <div className="flex items-center gap-2 text-2xs text-text-3 font-sans">
376
+ <div className="flex items-center gap-2 text-[11px] text-text-3 font-sans">
373
377
  <Loader2 size={10} className="animate-spin" /> Checking llama-server...
374
378
  </div>
375
379
  )}
376
380
  {llamaInstalled === true && (
377
- <div className="flex items-center gap-2 text-2xs text-success font-sans">
381
+ <div className="flex items-center gap-2 text-[11px] text-success font-sans">
378
382
  <CheckCircle size={10} /> llama-server found
379
383
  </div>
380
384
  )}
381
385
  {llamaInstalled === false && (
382
- <div className="space-y-1.5">
383
- <div className="flex items-center gap-2 text-2xs text-danger font-sans">
386
+ <div className="space-y-2">
387
+ <div className="flex items-center gap-2 text-[11px] text-danger font-sans">
384
388
  <AlertTriangle size={10} /> llama-server not found
385
389
  </div>
386
- <code className="block text-2xs font-mono text-text-3 bg-surface-2 px-2 py-1 rounded-sm">brew install llama.cpp</code>
390
+ <code className="block text-[10px] font-mono text-text-3 bg-surface-2 px-2.5 py-1.5 rounded">brew install llama.cpp</code>
387
391
  <button
388
392
  onClick={checkLlama}
389
- className="flex items-center gap-1.5 text-2xs font-sans text-accent hover:text-accent/80 transition-colors cursor-pointer"
393
+ className="flex items-center gap-1.5 text-[11px] font-sans text-accent hover:text-accent/80 transition-colors cursor-pointer"
390
394
  >
391
395
  <RotateCcw size={10} /> Recheck after install
392
396
  </button>
@@ -399,44 +403,41 @@ export function LaunchModel() {
399
403
  <div className="space-y-2">
400
404
  {hasActiveAssistant && labAssistantBackend === selectedBackend ? (
401
405
  <div className="space-y-2">
402
- <div className={cn(
403
- 'flex items-center gap-2 px-2.5 py-2 rounded-sm text-xs font-sans',
404
- assistantRunning ? 'bg-accent/8 text-accent' : 'bg-success/8 text-success',
405
- )}>
406
- {assistantRunning ? (
407
- <><Loader2 size={12} className="animate-spin" /> Assistant is setting up {currentBackend?.label}...</>
408
- ) : (
409
- <><CheckCircle size={12} /> Setup complete</>
410
- )}
411
- </div>
406
+ {assistantRunning ? (
407
+ <StatusBanner variant="accent" icon={Loader2}>
408
+ Assistant is setting up {currentBackend?.label}...
409
+ </StatusBanner>
410
+ ) : (
411
+ <StatusBanner variant="success" icon={CheckCircle}>Setup complete</StatusBanner>
412
+ )}
412
413
  {!labAssistantMode && (
413
414
  <button
414
415
  onClick={() => setLabAssistantMode(true)}
415
- className="w-full flex items-center justify-center gap-1.5 px-3 py-2 text-xs font-sans font-medium text-text-1 bg-surface-3 hover:bg-surface-4 rounded-sm transition-colors cursor-pointer"
416
+ className="w-full flex items-center justify-center gap-1.5 h-8 text-[11px] font-sans font-medium text-text-1 bg-surface-2 hover:bg-surface-3 rounded transition-colors cursor-pointer"
416
417
  >
417
418
  View Assistant
418
419
  </button>
419
420
  )}
420
421
  </div>
421
422
  ) : (
422
- <>
423
+ <div className="space-y-2">
423
424
  <button
424
425
  onClick={handleLaunchAssistant}
425
426
  disabled={assistantLaunching}
426
427
  className={cn(
427
- 'w-full flex items-center justify-center gap-1.5 px-3 py-2 text-xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
428
+ 'w-full flex items-center justify-center gap-1.5 h-8 text-[11px] font-sans font-medium rounded transition-colors cursor-pointer',
428
429
  assistantLaunching ? 'bg-accent/20 text-accent' : 'bg-accent text-surface-0 hover:bg-accent/90',
429
430
  )}
430
431
  >
431
432
  {assistantLaunching
432
- ? <><Loader2 size={12} className="animate-spin" /> Starting Assistant...</>
433
- : <><Wrench size={12} /> Setup {currentBackend?.label} with Assistant</>
433
+ ? <><Loader2 size={11} className="animate-spin" /> Starting Assistant...</>
434
+ : <><Wrench size={11} /> Setup {currentBackend?.label} with Assistant</>
434
435
  }
435
436
  </button>
436
- <p className="text-2xs text-text-4 font-sans">
437
+ <p className="text-[10px] text-text-4 font-sans text-center">
437
438
  An AI assistant will check your system and handle the installation.
438
439
  </p>
439
- </>
440
+ </div>
440
441
  )}
441
442
  </div>
442
443
  )}
@@ -444,22 +445,20 @@ export function LaunchModel() {
444
445
  {isCompatible && currentBackend?.autoLaunch && (
445
446
  <div className="space-y-2">
446
447
  {serverRunning ? (
447
- <div className="flex items-center gap-2 px-2.5 py-2 bg-success/8 rounded-sm text-xs font-sans text-success">
448
- <CheckCircle size={12} /> Server Running
449
- </div>
448
+ <StatusBanner variant="success" icon={CheckCircle}>Server Running</StatusBanner>
450
449
  ) : (
451
450
  <button
452
451
  disabled={!canLaunch}
453
452
  onClick={handleLaunch}
454
453
  className={cn(
455
- 'w-full flex items-center justify-center gap-1.5 px-3 py-2 text-xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
454
+ 'w-full flex items-center justify-center gap-1.5 h-8 text-[11px] font-sans font-medium rounded transition-colors cursor-pointer',
456
455
  canLaunch ? 'bg-accent text-surface-0 hover:bg-accent/90' : 'bg-surface-3 text-text-4 cursor-not-allowed',
457
456
  )}
458
457
  >
459
458
  {launching ? (
460
- <><Loader2 size={12} className="animate-spin" /> Starting...</>
459
+ <><Loader2 size={11} className="animate-spin" /> Starting...</>
461
460
  ) : (
462
- <><Play size={12} /> Launch</>
461
+ <><Play size={11} /> Launch</>
463
462
  )}
464
463
  </button>
465
464
  )}
@@ -468,9 +467,9 @@ export function LaunchModel() {
468
467
  )}
469
468
  </div>
470
469
  )}
471
- </>
470
+ </div>
472
471
  )}
473
- </div>
472
+ </SidebarSection>
474
473
  );
475
474
  }
476
475
 
@@ -493,33 +492,33 @@ export function RuntimeConfig() {
493
492
  }
494
493
 
495
494
  return (
496
- <div className="space-y-2">
497
- <div className="flex items-center justify-between">
498
- <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Runtimes</span>
495
+ <SidebarSection
496
+ label="Runtimes"
497
+ action={
499
498
  <Tooltip content="Add runtime">
500
499
  <button
501
500
  onClick={() => setDialogOpen(true)}
502
501
  className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
503
502
  >
504
- <Plus size={13} />
503
+ <Plus size={12} />
505
504
  </button>
506
505
  </Tooltip>
507
- </div>
508
-
506
+ }
507
+ >
509
508
  {runtimes.length === 0 ? (
510
- <div className="py-5 text-center">
511
- <WifiOff size={18} className="mx-auto text-text-4 mb-1.5" />
512
- <p className="text-xs text-text-3 font-sans">No runtimes configured</p>
509
+ <div className="py-6 text-center rounded-md bg-surface-1/50 border border-border-subtle">
510
+ <WifiOff size={16} className="mx-auto text-text-4 mb-2" />
511
+ <p className="text-[11px] text-text-3 font-sans">No runtimes configured</p>
513
512
  <button
514
513
  onClick={() => setDialogOpen(true)}
515
- className="mt-2 flex items-center gap-1 mx-auto px-3 py-1.5 text-2xs font-sans text-text-3 hover:text-text-1 transition-colors cursor-pointer"
514
+ className="mt-2 inline-flex items-center gap-1 px-2.5 py-1 text-[10px] font-sans text-accent hover:text-accent/80 transition-colors cursor-pointer"
516
515
  >
517
- <Plus size={11} /> Add Runtime
516
+ <Plus size={10} /> Add Runtime
518
517
  </button>
519
518
  </div>
520
519
  ) : (
521
520
  <ScrollArea className="max-h-48">
522
- <div className="space-y-px">
521
+ <div className="space-y-1 rounded-md bg-surface-1/50 border border-border-subtle p-2">
523
522
  {runtimes.map((rt) => (
524
523
  <RuntimeItem
525
524
  key={rt.id}
@@ -538,7 +537,7 @@ export function RuntimeConfig() {
538
537
  )}
539
538
 
540
539
  <AddRuntimeDialog open={dialogOpen} onOpenChange={setDialogOpen} />
541
- </div>
540
+ </SidebarSection>
542
541
  );
543
542
  }
544
543
 
@@ -560,7 +559,7 @@ export function RuntimeSection() {
560
559
 
561
560
  if (!serverRunning || expanded) {
562
561
  return (
563
- <div className="space-y-5 [&>*]:pt-5 [&>*:first-child]:pt-0">
562
+ <div className="space-y-6">
564
563
  <LaunchModel />
565
564
  <RuntimeConfig />
566
565
  </div>
@@ -568,24 +567,22 @@ export function RuntimeSection() {
568
567
  }
569
568
 
570
569
  return (
571
- <div className="space-y-2">
572
- <div className="flex items-center gap-2.5 px-1 py-1.5">
573
- <span className="w-2 h-2 rounded-full bg-success flex-shrink-0" />
574
- <div className="flex-1 min-w-0">
575
- <div className="text-xs font-sans font-medium text-text-0 truncate">{runtimeLabel}</div>
576
- <div className="text-2xs text-text-4 font-sans truncate">
577
- {activeModel || 'Ready'}{activeRt?.latency != null ? ` · ${Math.round(activeRt.latency)}ms` : ''}
578
- </div>
570
+ <div className="flex items-center gap-2.5 px-2.5 py-2 rounded-md bg-surface-1/50 border border-border-subtle">
571
+ <span className="w-2 h-2 rounded-full bg-success flex-shrink-0" />
572
+ <div className="flex-1 min-w-0">
573
+ <div className="text-[11px] font-sans font-medium text-text-0 truncate">{runtimeLabel}</div>
574
+ <div className="text-[10px] text-text-4 font-sans truncate">
575
+ {activeModel || 'Ready'}{activeRt?.latency != null ? ` · ${Math.round(activeRt.latency)}ms` : ''}
579
576
  </div>
580
- <Tooltip content="Runtime settings">
581
- <button
582
- onClick={() => setExpanded(true)}
583
- className="p-1 text-text-4 hover:text-text-1 transition-colors cursor-pointer"
584
- >
585
- <Settings2 size={13} />
586
- </button>
587
- </Tooltip>
588
577
  </div>
578
+ <Tooltip content="Runtime settings">
579
+ <button
580
+ onClick={() => setExpanded(true)}
581
+ className="p-1 text-text-4 hover:text-text-1 transition-colors cursor-pointer"
582
+ >
583
+ <Settings2 size={12} />
584
+ </button>
585
+ </Tooltip>
589
586
  </div>
590
587
  );
591
588
  }
@@ -25,7 +25,7 @@ export function SystemPromptEditor() {
25
25
  const systemPrompt = useGrooveStore((s) => s.labSystemPrompt);
26
26
  const setSystemPrompt = useGrooveStore((s) => s.setLabSystemPrompt);
27
27
  const themeKey = useGrooveStore((s) => s.editorTheme);
28
- const [collapsed, setCollapsed] = useState(false);
28
+ const [collapsed, setCollapsed] = useState(true);
29
29
  const containerRef = useRef(null);
30
30
  const viewRef = useRef(null);
31
31
  const themeCompartment = useRef(new Compartment());
@@ -87,20 +87,22 @@ export function SystemPromptEditor() {
87
87
  }, [systemPrompt]);
88
88
 
89
89
  return (
90
- <div className="space-y-1.5">
90
+ <div className="space-y-3">
91
91
  <button
92
92
  onClick={() => setCollapsed(!collapsed)}
93
- className="flex items-center gap-1.5 w-full cursor-pointer group"
93
+ className="flex items-center gap-1.5 w-full h-6 cursor-pointer group"
94
94
  >
95
95
  {collapsed ? (
96
- <ChevronRight size={11} className="text-text-4 group-hover:text-text-2 transition-colors" />
96
+ <ChevronRight size={10} className="text-text-4 group-hover:text-text-2 transition-colors flex-shrink-0" />
97
97
  ) : (
98
- <ChevronDown size={11} className="text-text-4 group-hover:text-text-2 transition-colors" />
98
+ <ChevronDown size={10} className="text-text-4 group-hover:text-text-2 transition-colors flex-shrink-0" />
99
99
  )}
100
- <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider group-hover:text-text-2 transition-colors">
100
+ <span className="text-[10px] font-semibold font-sans text-text-3 uppercase tracking-widest group-hover:text-text-2 transition-colors">
101
101
  System Prompt
102
102
  </span>
103
- <span className="text-2xs font-mono text-text-4 ml-auto">{charCount > 0 ? `${charCount}` : ''}</span>
103
+ {charCount > 0 && (
104
+ <span className="text-[10px] font-mono text-text-4 ml-auto tabular-nums">{charCount}</span>
105
+ )}
104
106
  </button>
105
107
 
106
108
  <div className={cn(
@@ -109,7 +111,7 @@ export function SystemPromptEditor() {
109
111
  )}>
110
112
  <div
111
113
  ref={containerRef}
112
- className="h-full border border-border-subtle rounded-sm overflow-hidden"
114
+ className="h-full rounded-md bg-surface-1/50 border border-border-subtle overflow-hidden"
113
115
  />
114
116
  </div>
115
117
  </div>
@@ -12,6 +12,7 @@ import { StatusBar } from './status-bar';
12
12
  import { DetailPanel } from './detail-panel';
13
13
  import { CommandPalette } from './command-palette';
14
14
  import { ApprovalModal } from '../ui/approval-modal';
15
+ import { QuestionModal } from '../ui/question-modal';
15
16
  import { QuickConnect } from '../settings/quick-connect';
16
17
 
17
18
  import { TeamTabBar } from '../../views/agents';
@@ -119,6 +120,7 @@ export function AppShell({ children, detailContent, terminalContent }) {
119
120
  <CommandPalette />
120
121
  <QuickConnect />
121
122
  <ApprovalModal />
123
+ <QuestionModal />
122
124
  <ToastContainer />
123
125
  </div>
124
126
  </TooltipProvider>
@@ -1,5 +1,5 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
- import { Terminal, BookOpen, Radio, Plug, Globe, ArrowUpCircle, X, Unplug } from 'lucide-react';
2
+ import { Terminal, BookOpen, Radio, Plug, Globe, ArrowUpCircle, X, Unplug, Cpu, Square } from 'lucide-react';
3
3
  import { cn } from '../../lib/cn';
4
4
  import { StatusDot } from '../ui/status-dot';
5
5
  import { Badge } from '../ui/badge';
@@ -23,7 +23,10 @@ export function StatusBar({
23
23
  const updateProgress = useGrooveStore((s) => s.updateProgress);
24
24
  const setUpdateModalOpen = useGrooveStore((s) => s.setUpdateModalOpen);
25
25
  const navigate = useGrooveStore((s) => s.setActiveView);
26
+ const labRuntimes = useGrooveStore((s) => s.labRuntimes);
27
+ const stopLabRuntime = useGrooveStore((s) => s.stopLabRuntime);
26
28
  const activeTunnels = savedTunnels.filter((t) => t.active);
29
+ const runningRuntimes = (labRuntimes || []).filter((rt) => rt.status === 'connected');
27
30
  const electron = isElectron();
28
31
 
29
32
  return (
@@ -110,6 +113,26 @@ export function StatusBar({
110
113
  <span>Federation</span>
111
114
  </button>
112
115
  )}
116
+ {runningRuntimes.map((rt) => (
117
+ <div key={rt.id} className="flex items-center gap-1">
118
+ <button
119
+ onClick={() => navigate('model-lab')}
120
+ className="flex items-center gap-1.5 text-text-3 hover:text-text-1 cursor-pointer transition-colors"
121
+ title={`${rt.name} — running`}
122
+ >
123
+ <Cpu size={10} className="text-success" />
124
+ <span>{rt.name}</span>
125
+ <span className="w-1.5 h-1.5 rounded-full bg-success" />
126
+ </button>
127
+ <button
128
+ onClick={() => stopLabRuntime(rt.id)}
129
+ className="p-0.5 text-text-4 hover:text-danger cursor-pointer transition-colors rounded"
130
+ title={`Stop ${rt.name}`}
131
+ >
132
+ <Square size={8} />
133
+ </button>
134
+ </div>
135
+ ))}
113
136
  </div>
114
137
 
115
138
  <div className="flex-1" />