groove-dev 0.27.142 → 0.27.144
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/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
- package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
- package/node_modules/@groove-dev/daemon/src/index.js +3 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
- package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
- package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
- package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
- package/node_modules/@groove-dev/daemon/src/process.js +2 -2
- package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
- package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
- package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
- package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
- package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
- package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
- package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
- package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
- package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
- package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
- package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
- package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
- package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
- package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
- package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
- package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
- package/node_modules/@groove-dev/gui/src/app.css +35 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
- package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
- package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
- package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
- package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
- package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
- package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
- package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
- package/node_modules/@groove-dev/gui/src/views/models.jsx +410 -509
- package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +1086 -6532
- package/packages/daemon/src/gateways/manager.js +35 -1
- package/packages/daemon/src/index.js +3 -0
- package/packages/daemon/src/journalist.js +23 -13
- package/packages/daemon/src/mlx-server.js +365 -0
- package/packages/daemon/src/model-lab.js +308 -12
- package/packages/daemon/src/pm.js +1 -1
- package/packages/daemon/src/process.js +2 -2
- package/packages/daemon/src/providers/local.js +36 -8
- package/packages/daemon/src/registry.js +21 -5
- package/packages/daemon/src/routes/agents.js +889 -0
- package/packages/daemon/src/routes/coordination.js +318 -0
- package/packages/daemon/src/routes/files.js +751 -0
- package/packages/daemon/src/routes/integrations.js +485 -0
- package/packages/daemon/src/routes/network.js +1784 -0
- package/packages/daemon/src/routes/providers.js +755 -0
- package/packages/daemon/src/routes/schedules.js +110 -0
- package/packages/daemon/src/routes/teams.js +650 -0
- package/packages/daemon/src/scheduler.js +456 -24
- package/packages/daemon/src/teams.js +1 -1
- package/packages/daemon/src/validate.js +38 -1
- package/packages/daemon/templates/mlx-setup.json +12 -0
- package/packages/daemon/templates/tgi-setup.json +1 -1
- package/packages/daemon/templates/vllm-setup.json +1 -1
- package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
- package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/App.jsx} +0 -2
- package/packages/gui/src/app.css +35 -0
- package/packages/gui/src/components/agents/agent-config.jsx +1 -128
- package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
- package/packages/gui/src/components/agents/agent-node.jsx +8 -13
- package/packages/gui/src/components/agents/code-review.jsx +159 -122
- package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/packages/gui/src/components/automations/automation-card.jsx +274 -0
- package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
- package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
- package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
- package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
- package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/packages/gui/src/components/network/network-health.jsx +2 -2
- package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/packages/gui/src/components/ui/sheet.jsx +5 -2
- package/packages/gui/src/lib/cron.js +64 -0
- package/packages/gui/src/lib/status.js +24 -24
- package/packages/gui/src/lib/theme-hex.js +1 -0
- package/packages/gui/src/stores/groove.js +34 -3144
- package/packages/gui/src/stores/helpers.js +10 -0
- package/packages/gui/src/stores/slices/agents-slice.js +452 -0
- package/packages/gui/src/stores/slices/automations-slice.js +96 -0
- package/packages/gui/src/stores/slices/chat-slice.js +227 -0
- package/packages/gui/src/stores/slices/editor-slice.js +285 -0
- package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/packages/gui/src/stores/slices/network-slice.js +361 -0
- package/packages/gui/src/stores/slices/preview-slice.js +109 -0
- package/packages/gui/src/stores/slices/providers-slice.js +897 -0
- package/packages/gui/src/stores/slices/teams-slice.js +413 -0
- package/packages/gui/src/stores/slices/ui-slice.js +98 -0
- package/packages/gui/src/views/agents.jsx +5 -5
- package/packages/gui/src/views/dashboard.jsx +12 -13
- package/packages/gui/src/views/marketplace.jsx +191 -3
- package/packages/gui/src/views/model-lab.jsx +17 -6
- package/packages/gui/src/views/models.jsx +410 -509
- package/packages/gui/src/views/network.jsx +3 -3
- package/packages/gui/src/views/settings.jsx +81 -94
- package/packages/gui/src/views/teams.jsx +40 -483
- package/SECURITY_SWEEP.md +0 -228
- package/TRAINING_DATA_v4.md +0 -6
- package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
- package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
- package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
- package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
- package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
- package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
- package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
- package/packages/gui/src/views/preview.jsx +0 -6
- package/packages/gui/src/views/subscription-panel.jsx +0 -327
- package/test.py +0 -571
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { useGrooveStore } from '../../stores/groove';
|
|
4
4
|
import { Button } from '../ui/button';
|
|
5
5
|
import { Badge } from '../ui/badge';
|
|
@@ -8,13 +8,16 @@ import { Dialog, DialogContent } from '../ui/dialog';
|
|
|
8
8
|
import { Select, SelectTrigger, SelectContent, SelectItem } from '../ui/select';
|
|
9
9
|
import { Tooltip } from '../ui/tooltip';
|
|
10
10
|
import { ScrollArea } from '../ui/scroll-area';
|
|
11
|
-
import { Plus, Trash2, Loader2, WifiOff, RotateCcw, HardDrive, Play, CheckCircle, AlertTriangle, ChevronRight, Wrench } from 'lucide-react';
|
|
11
|
+
import { Plus, Trash2, Loader2, WifiOff, RotateCcw, HardDrive, Play, Square, CheckCircle, AlertTriangle, ChevronRight, Wrench, Settings2 } from 'lucide-react';
|
|
12
12
|
import { cn } from '../../lib/cn';
|
|
13
13
|
|
|
14
|
+
const IS_APPLE = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform || '');
|
|
15
|
+
|
|
14
16
|
const RUNTIME_TYPES = [
|
|
15
17
|
{ value: 'ollama', label: 'Ollama' },
|
|
16
18
|
{ value: 'vllm', label: 'vLLM' },
|
|
17
19
|
{ value: 'llama-cpp', label: 'llama.cpp' },
|
|
20
|
+
{ value: 'mlx', label: 'MLX', suffix: 'Apple Silicon', appleOnly: true },
|
|
18
21
|
{ value: 'tgi', label: 'TGI' },
|
|
19
22
|
{ value: 'openai-compatible', label: 'OpenAI Compatible' },
|
|
20
23
|
];
|
|
@@ -23,6 +26,7 @@ const DEFAULT_ENDPOINTS = {
|
|
|
23
26
|
ollama: 'http://localhost:11434',
|
|
24
27
|
vllm: 'http://localhost:8000',
|
|
25
28
|
'llama-cpp': 'http://localhost:8080',
|
|
29
|
+
mlx: 'http://localhost:8080',
|
|
26
30
|
tgi: 'http://localhost:8080',
|
|
27
31
|
'openai-compatible': 'http://localhost:8000',
|
|
28
32
|
};
|
|
@@ -64,8 +68,8 @@ function AddRuntimeDialog({ open, onOpenChange }) {
|
|
|
64
68
|
<Select value={type} onValueChange={handleTypeChange}>
|
|
65
69
|
<SelectTrigger placeholder="Select type" />
|
|
66
70
|
<SelectContent>
|
|
67
|
-
{RUNTIME_TYPES.map((rt) => (
|
|
68
|
-
<SelectItem key={rt.value} value={rt.value}>{rt.label}</SelectItem>
|
|
71
|
+
{RUNTIME_TYPES.filter(rt => !rt.appleOnly || IS_APPLE).map((rt) => (
|
|
72
|
+
<SelectItem key={rt.value} value={rt.value}>{rt.label}{rt.suffix ? ` (${rt.suffix})` : ''}</SelectItem>
|
|
69
73
|
))}
|
|
70
74
|
</SelectContent>
|
|
71
75
|
</Select>
|
|
@@ -84,30 +88,54 @@ function AddRuntimeDialog({ open, onOpenChange }) {
|
|
|
84
88
|
);
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, testing }) {
|
|
91
|
+
function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onStart, testing }) {
|
|
92
|
+
const online = runtime.status === 'connected';
|
|
93
|
+
const managed = !!(runtime._localModelId || runtime._mlxModelId || runtime.launchConfig || runtime.type === 'mlx' || runtime.type === 'llama-cpp');
|
|
88
94
|
return (
|
|
89
95
|
<button
|
|
90
96
|
onClick={() => onSelect(runtime.id)}
|
|
91
97
|
className={cn(
|
|
92
98
|
'w-full flex items-center gap-2.5 px-2.5 py-2 text-left transition-colors cursor-pointer rounded-sm',
|
|
93
|
-
active ? 'bg-accent/8
|
|
99
|
+
active ? 'bg-accent/8 border border-accent/20' : 'border border-transparent text-text-2 hover:bg-surface-3 hover:text-text-0',
|
|
94
100
|
)}
|
|
95
101
|
>
|
|
96
102
|
<span className={cn(
|
|
97
103
|
'w-1.5 h-1.5 rounded-full flex-shrink-0',
|
|
98
|
-
|
|
104
|
+
online ? 'bg-success' : runtime.status === 'error' ? 'bg-danger' : 'bg-text-4',
|
|
99
105
|
)} />
|
|
100
106
|
<div className="flex-1 min-w-0">
|
|
101
|
-
<div className=
|
|
107
|
+
<div className={cn('text-xs font-sans font-medium truncate', active ? 'text-text-0' : '')}>
|
|
108
|
+
{RUNTIME_TYPES.find((t) => t.value === runtime.type)?.label || runtime.type}
|
|
109
|
+
</div>
|
|
102
110
|
<div className="text-2xs text-text-4 flex items-center gap-1.5">
|
|
103
|
-
<span className=
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
<span className={cn('font-sans', online ? 'text-success' : 'text-danger')}>
|
|
112
|
+
{online ? 'Online' : 'Offline'}
|
|
113
|
+
</span>
|
|
114
|
+
{runtime.latency != null && online && (
|
|
115
|
+
<span className="font-mono">{Math.round(runtime.latency)}ms</span>
|
|
116
|
+
)}
|
|
106
117
|
</div>
|
|
107
118
|
</div>
|
|
108
119
|
<div className="flex items-center gap-0.5 flex-shrink-0">
|
|
109
|
-
{
|
|
110
|
-
<
|
|
120
|
+
{managed && online && (
|
|
121
|
+
<Tooltip content="Stop server">
|
|
122
|
+
<button
|
|
123
|
+
onClick={(e) => { e.stopPropagation(); onStop(runtime.id); }}
|
|
124
|
+
className="p-1 text-text-4 hover:text-danger transition-colors cursor-pointer"
|
|
125
|
+
>
|
|
126
|
+
<Square size={10} />
|
|
127
|
+
</button>
|
|
128
|
+
</Tooltip>
|
|
129
|
+
)}
|
|
130
|
+
{managed && !online && (
|
|
131
|
+
<Tooltip content="Start server">
|
|
132
|
+
<button
|
|
133
|
+
onClick={(e) => { e.stopPropagation(); onStart(runtime.id); }}
|
|
134
|
+
className="p-1 text-text-4 hover:text-success transition-colors cursor-pointer"
|
|
135
|
+
>
|
|
136
|
+
<Play size={10} />
|
|
137
|
+
</button>
|
|
138
|
+
</Tooltip>
|
|
111
139
|
)}
|
|
112
140
|
<Tooltip content="Test connection">
|
|
113
141
|
<button
|
|
@@ -117,7 +145,7 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, testing }) {
|
|
|
117
145
|
{testing === runtime.id ? <Loader2 size={11} className="animate-spin" /> : <RotateCcw size={11} />}
|
|
118
146
|
</button>
|
|
119
147
|
</Tooltip>
|
|
120
|
-
<Tooltip content="Remove">
|
|
148
|
+
<Tooltip content="Remove runtime">
|
|
121
149
|
<button
|
|
122
150
|
onClick={(e) => { e.stopPropagation(); onRemove(runtime.id); }}
|
|
123
151
|
className="p-1 text-text-4 hover:text-danger transition-colors cursor-pointer"
|
|
@@ -138,7 +166,8 @@ function formatSize(bytes) {
|
|
|
138
166
|
}
|
|
139
167
|
|
|
140
168
|
const BACKENDS = [
|
|
141
|
-
{ id: '
|
|
169
|
+
...(IS_APPLE ? [{ id: 'mlx', label: 'MLX', subtitle: 'Apple Silicon optimized', recommended: true, autoLaunch: true, appleOnly: true }] : []),
|
|
170
|
+
{ id: 'llama-cpp', label: 'llama.cpp', subtitle: 'CPU + GPU, auto-managed', recommended: !IS_APPLE, autoLaunch: true },
|
|
142
171
|
{ id: 'vllm', label: 'vLLM', subtitle: 'GPU-optimized, guided setup', autoLaunch: false },
|
|
143
172
|
{ id: 'tgi', label: 'TGI', subtitle: 'HuggingFace, guided setup', autoLaunch: false },
|
|
144
173
|
];
|
|
@@ -160,6 +189,26 @@ function LaunchStatus({ phase, error }) {
|
|
|
160
189
|
);
|
|
161
190
|
}
|
|
162
191
|
|
|
192
|
+
function getIncompatibilityReason(modelType, backendId) {
|
|
193
|
+
if (modelType === 'gguf' && backendId === 'mlx') return 'GGUF model — MLX needs MLX-format weights';
|
|
194
|
+
if (modelType === 'gguf' && (backendId === 'vllm' || backendId === 'tgi')) return 'GGUF model — needs standard HuggingFace weights';
|
|
195
|
+
if (modelType === 'mlx' && backendId === 'llama-cpp') return 'MLX model — llama.cpp needs a GGUF file';
|
|
196
|
+
if (modelType === 'mlx' && (backendId === 'vllm' || backendId === 'tgi')) return 'MLX model — needs standard HuggingFace weights';
|
|
197
|
+
if (modelType === 'hf' && backendId === 'mlx') return 'HF model — MLX needs MLX-converted weights';
|
|
198
|
+
if (modelType === 'hf' && backendId === 'llama-cpp') return 'HF model — llama.cpp needs a GGUF file';
|
|
199
|
+
return 'Incompatible format';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function getBackendCompat(model, backends) {
|
|
203
|
+
if (!model) return backends.map((b) => ({ ...b, compatible: true, reason: null }));
|
|
204
|
+
const compat = model.compatibleBackends || (model.type === 'gguf' ? ['llama-cpp'] : model.type === 'mlx' ? ['mlx'] : ['vllm', 'tgi']);
|
|
205
|
+
return backends.map((b) => ({
|
|
206
|
+
...b,
|
|
207
|
+
compatible: compat.includes(b.id),
|
|
208
|
+
reason: compat.includes(b.id) ? null : getIncompatibilityReason(model.type, b.id),
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
|
|
163
212
|
export function LaunchModel() {
|
|
164
213
|
const localModels = useGrooveStore((s) => s.labLocalModels);
|
|
165
214
|
const fetchLocalModels = useGrooveStore((s) => s.fetchLabLocalModels);
|
|
@@ -170,15 +219,58 @@ export function LaunchModel() {
|
|
|
170
219
|
const launchPhase = useGrooveStore((s) => s.labLaunchPhase);
|
|
171
220
|
const launchError = useGrooveStore((s) => s.labLaunchError);
|
|
172
221
|
const launchLabAssistant = useGrooveStore((s) => s.launchLabAssistant);
|
|
222
|
+
const labAssistantAgentId = useGrooveStore((s) => s.labAssistantAgentId);
|
|
223
|
+
const labAssistantBackend = useGrooveStore((s) => s.labAssistantBackend);
|
|
224
|
+
const labAssistantMode = useGrooveStore((s) => s.labAssistantMode);
|
|
225
|
+
const setLabAssistantMode = useGrooveStore((s) => s.setLabAssistantMode);
|
|
226
|
+
const agents = useGrooveStore((s) => s.agents);
|
|
227
|
+
const runtimes = useGrooveStore((s) => s.labRuntimes);
|
|
228
|
+
const activeRuntime = useGrooveStore((s) => s.labActiveRuntime);
|
|
173
229
|
|
|
174
230
|
const [selectedModel, setSelectedModel] = useState(null);
|
|
175
|
-
const [selectedBackend, setSelectedBackend] = useState('llama-cpp');
|
|
231
|
+
const [selectedBackend, setSelectedBackend] = useState(IS_APPLE ? 'mlx' : 'llama-cpp');
|
|
176
232
|
const [assistantLaunching, setAssistantLaunching] = useState(false);
|
|
233
|
+
const [suggestion, setSuggestion] = useState(null);
|
|
177
234
|
|
|
178
235
|
useEffect(() => { fetchLocalModels(); checkLlama(); }, [fetchLocalModels, checkLlama]);
|
|
179
236
|
|
|
180
|
-
const
|
|
181
|
-
const
|
|
237
|
+
const selectedModelObj = localModels.find((m) => m.id === selectedModel);
|
|
238
|
+
const backendsWithCompat = getBackendCompat(selectedModelObj, BACKENDS);
|
|
239
|
+
const currentBackend = backendsWithCompat.find((b) => b.id === selectedBackend);
|
|
240
|
+
const isCompatible = currentBackend?.compatible ?? true;
|
|
241
|
+
|
|
242
|
+
const backendReady = selectedBackend === 'mlx' || selectedBackend === 'llama-cpp' ? (selectedBackend === 'mlx' || llamaInstalled) : true;
|
|
243
|
+
const canLaunch = selectedModel && currentBackend?.autoLaunch && backendReady && !launching && isCompatible;
|
|
244
|
+
|
|
245
|
+
const assistantAgent = labAssistantAgentId ? agents.find((a) => a.id === labAssistantAgentId) : null;
|
|
246
|
+
const assistantRunning = assistantAgent?.status === 'running';
|
|
247
|
+
const assistantComplete = assistantAgent && assistantAgent.status !== 'running';
|
|
248
|
+
const hasActiveAssistant = !!(labAssistantAgentId && (assistantRunning || assistantComplete));
|
|
249
|
+
|
|
250
|
+
const activeRt = activeRuntime ? runtimes.find((r) => r.id === activeRuntime) : null;
|
|
251
|
+
const serverRunning = activeRt?.status === 'connected';
|
|
252
|
+
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
if (!selectedModel || !selectedBackend || isCompatible) { setSuggestion(null); return; }
|
|
255
|
+
let cancelled = false;
|
|
256
|
+
fetch(`/api/lab/suggest-model?modelId=${encodeURIComponent(selectedModel)}&targetBackend=${selectedBackend}`)
|
|
257
|
+
.then((r) => r.ok ? r.json() : null)
|
|
258
|
+
.then((data) => { if (!cancelled) setSuggestion(data?.suggestion || null); })
|
|
259
|
+
.catch(() => { if (!cancelled) setSuggestion(null); });
|
|
260
|
+
return () => { cancelled = true; };
|
|
261
|
+
}, [selectedModel, selectedBackend, isCompatible]);
|
|
262
|
+
|
|
263
|
+
function handleModelChange(e) {
|
|
264
|
+
const id = e.target.value || null;
|
|
265
|
+
setSelectedModel(id);
|
|
266
|
+
if (!id) return;
|
|
267
|
+
const model = localModels.find((m) => m.id === id);
|
|
268
|
+
if (!model) return;
|
|
269
|
+
const compat = model.compatibleBackends || [];
|
|
270
|
+
const preferred = IS_APPLE ? ['mlx', 'llama-cpp', 'vllm', 'tgi'] : ['llama-cpp', 'vllm', 'tgi'];
|
|
271
|
+
const best = preferred.find((b) => compat.includes(b));
|
|
272
|
+
if (best) setSelectedBackend(best);
|
|
273
|
+
}
|
|
182
274
|
|
|
183
275
|
function handleLaunch() {
|
|
184
276
|
if (!canLaunch) return;
|
|
@@ -189,7 +281,8 @@ export function LaunchModel() {
|
|
|
189
281
|
if (assistantLaunching) return;
|
|
190
282
|
setAssistantLaunching(true);
|
|
191
283
|
try {
|
|
192
|
-
|
|
284
|
+
const model = localModels.find((m) => m.id === selectedModel);
|
|
285
|
+
await launchLabAssistant(currentBackend.id, model || undefined);
|
|
193
286
|
} finally {
|
|
194
287
|
setAssistantLaunching(false);
|
|
195
288
|
}
|
|
@@ -203,69 +296,77 @@ export function LaunchModel() {
|
|
|
203
296
|
<div className="py-5 text-center">
|
|
204
297
|
<HardDrive size={18} className="mx-auto text-text-4 mb-1.5" />
|
|
205
298
|
<p className="text-xs text-text-3 font-sans">No downloaded models</p>
|
|
206
|
-
<p className="text-2xs text-text-4 font-sans mt-0.5">Download
|
|
299
|
+
<p className="text-2xs text-text-4 font-sans mt-0.5">Download models from the Models tab</p>
|
|
207
300
|
</div>
|
|
208
301
|
) : (
|
|
209
302
|
<>
|
|
210
|
-
<
|
|
211
|
-
<
|
|
212
|
-
{
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
232
|
-
{selectedModel === m.id && <ChevronRight size={11} className="text-accent flex-shrink-0" />}
|
|
233
|
-
</button>
|
|
234
|
-
))}
|
|
235
|
-
</div>
|
|
236
|
-
</ScrollArea>
|
|
303
|
+
<div className="relative">
|
|
304
|
+
<select
|
|
305
|
+
value={selectedModel || ''}
|
|
306
|
+
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"
|
|
308
|
+
>
|
|
309
|
+
<option value="">Select a model</option>
|
|
310
|
+
{localModels.map((m) => {
|
|
311
|
+
const label = m.filename?.replace(/\.gguf$/i, '') || m.id;
|
|
312
|
+
const tag = m.type === 'mlx' ? 'MLX' : m.type === 'hf' ? 'HF' : 'GGUF';
|
|
313
|
+
const meta = [tag, m.quantization, m.parameters, m.sizeBytes ? formatSize(m.sizeBytes) : null].filter(Boolean).join(' · ');
|
|
314
|
+
return (
|
|
315
|
+
<option key={m.id} value={m.id}>
|
|
316
|
+
{label}{meta ? ` (${meta})` : ''}
|
|
317
|
+
</option>
|
|
318
|
+
);
|
|
319
|
+
})}
|
|
320
|
+
</select>
|
|
321
|
+
<ChevronRight size={14} className="absolute right-2 top-1/2 -translate-y-1/2 text-text-3 pointer-events-none rotate-90" />
|
|
322
|
+
</div>
|
|
237
323
|
|
|
238
324
|
{selectedModel && (
|
|
239
325
|
<div className="space-y-2">
|
|
240
326
|
<span className="text-2xs font-semibold font-sans text-text-4 uppercase tracking-wider">Backend</span>
|
|
241
327
|
<div className="space-y-px">
|
|
242
|
-
{
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
<div className="flex
|
|
257
|
-
<
|
|
258
|
-
{b.
|
|
259
|
-
|
|
260
|
-
|
|
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>}
|
|
348
|
+
</div>
|
|
349
|
+
<div className="text-2xs text-text-4 font-sans">{b.subtitle}</div>
|
|
261
350
|
</div>
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
</button>
|
|
351
|
+
</button>
|
|
352
|
+
</Tooltip>
|
|
265
353
|
))}
|
|
266
354
|
</div>
|
|
267
355
|
|
|
268
|
-
{
|
|
356
|
+
{!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>
|
|
361
|
+
{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>
|
|
365
|
+
)}
|
|
366
|
+
</div>
|
|
367
|
+
)}
|
|
368
|
+
|
|
369
|
+
{isCompatible && selectedBackend === 'llama-cpp' && (
|
|
269
370
|
<div className="px-2.5">
|
|
270
371
|
{llamaInstalled === null && (
|
|
271
372
|
<div className="flex items-center gap-2 text-2xs text-text-3 font-sans">
|
|
@@ -294,43 +395,74 @@ export function LaunchModel() {
|
|
|
294
395
|
</div>
|
|
295
396
|
)}
|
|
296
397
|
|
|
297
|
-
{!currentBackend?.autoLaunch && (
|
|
398
|
+
{isCompatible && !currentBackend?.autoLaunch && (
|
|
298
399
|
<div className="space-y-2">
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
400
|
+
{hasActiveAssistant && labAssistantBackend === selectedBackend ? (
|
|
401
|
+
<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>
|
|
412
|
+
{!labAssistantMode && (
|
|
413
|
+
<button
|
|
414
|
+
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
|
+
>
|
|
417
|
+
View Assistant
|
|
418
|
+
</button>
|
|
419
|
+
)}
|
|
420
|
+
</div>
|
|
421
|
+
) : (
|
|
422
|
+
<>
|
|
423
|
+
<button
|
|
424
|
+
onClick={handleLaunchAssistant}
|
|
425
|
+
disabled={assistantLaunching}
|
|
426
|
+
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
|
+
assistantLaunching ? 'bg-accent/20 text-accent' : 'bg-accent text-surface-0 hover:bg-accent/90',
|
|
429
|
+
)}
|
|
430
|
+
>
|
|
431
|
+
{assistantLaunching
|
|
432
|
+
? <><Loader2 size={12} className="animate-spin" /> Starting Assistant...</>
|
|
433
|
+
: <><Wrench size={12} /> Setup {currentBackend?.label} with Assistant</>
|
|
434
|
+
}
|
|
435
|
+
</button>
|
|
436
|
+
<p className="text-2xs text-text-4 font-sans">
|
|
437
|
+
An AI assistant will check your system and handle the installation.
|
|
438
|
+
</p>
|
|
439
|
+
</>
|
|
440
|
+
)}
|
|
315
441
|
</div>
|
|
316
442
|
)}
|
|
317
443
|
|
|
318
|
-
{currentBackend?.autoLaunch && (
|
|
444
|
+
{isCompatible && currentBackend?.autoLaunch && (
|
|
319
445
|
<div className="space-y-2">
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
446
|
+
{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>
|
|
450
|
+
) : (
|
|
451
|
+
<button
|
|
452
|
+
disabled={!canLaunch}
|
|
453
|
+
onClick={handleLaunch}
|
|
454
|
+
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',
|
|
456
|
+
canLaunch ? 'bg-accent text-surface-0 hover:bg-accent/90' : 'bg-surface-3 text-text-4 cursor-not-allowed',
|
|
457
|
+
)}
|
|
458
|
+
>
|
|
459
|
+
{launching ? (
|
|
460
|
+
<><Loader2 size={12} className="animate-spin" /> Starting...</>
|
|
461
|
+
) : (
|
|
462
|
+
<><Play size={12} /> Launch</>
|
|
463
|
+
)}
|
|
464
|
+
</button>
|
|
465
|
+
)}
|
|
334
466
|
<LaunchStatus phase={launchPhase} error={launchError} />
|
|
335
467
|
</div>
|
|
336
468
|
)}
|
|
@@ -348,6 +480,8 @@ export function RuntimeConfig() {
|
|
|
348
480
|
const setActiveRuntime = useGrooveStore((s) => s.setLabActiveRuntime);
|
|
349
481
|
const testRuntime = useGrooveStore((s) => s.testLabRuntime);
|
|
350
482
|
const removeRuntime = useGrooveStore((s) => s.removeLabRuntime);
|
|
483
|
+
const stopRuntime = useGrooveStore((s) => s.stopLabRuntime);
|
|
484
|
+
const startRuntime = useGrooveStore((s) => s.startLabRuntime);
|
|
351
485
|
|
|
352
486
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
353
487
|
const [testing, setTesting] = useState(null);
|
|
@@ -394,6 +528,8 @@ export function RuntimeConfig() {
|
|
|
394
528
|
onSelect={setActiveRuntime}
|
|
395
529
|
onTest={handleTest}
|
|
396
530
|
onRemove={removeRuntime}
|
|
531
|
+
onStop={stopRuntime}
|
|
532
|
+
onStart={startRuntime}
|
|
397
533
|
testing={testing}
|
|
398
534
|
/>
|
|
399
535
|
))}
|
|
@@ -405,3 +541,51 @@ export function RuntimeConfig() {
|
|
|
405
541
|
</div>
|
|
406
542
|
);
|
|
407
543
|
}
|
|
544
|
+
|
|
545
|
+
export function RuntimeSection() {
|
|
546
|
+
const runtimes = useGrooveStore((s) => s.labRuntimes);
|
|
547
|
+
const activeRuntime = useGrooveStore((s) => s.labActiveRuntime);
|
|
548
|
+
const activeModel = useGrooveStore((s) => s.labActiveModel);
|
|
549
|
+
const [expanded, setExpanded] = useState(true);
|
|
550
|
+
const wasRunning = useRef(false);
|
|
551
|
+
|
|
552
|
+
const activeRt = activeRuntime ? runtimes.find((r) => r.id === activeRuntime) : null;
|
|
553
|
+
const serverRunning = activeRt?.status === 'connected';
|
|
554
|
+
const runtimeLabel = activeRt ? (RUNTIME_TYPES.find((t) => t.value === activeRt.type)?.label || activeRt.type) : null;
|
|
555
|
+
|
|
556
|
+
useEffect(() => {
|
|
557
|
+
if (serverRunning && !wasRunning.current) setExpanded(false);
|
|
558
|
+
wasRunning.current = serverRunning;
|
|
559
|
+
}, [serverRunning]);
|
|
560
|
+
|
|
561
|
+
if (!serverRunning || expanded) {
|
|
562
|
+
return (
|
|
563
|
+
<div className="space-y-5 [&>*]:pt-5 [&>*:first-child]:pt-0">
|
|
564
|
+
<LaunchModel />
|
|
565
|
+
<RuntimeConfig />
|
|
566
|
+
</div>
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
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>
|
|
579
|
+
</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
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { Network, Code2, ChartSpline, Puzzle, Users, Box, FlaskConical, Newspaper, Settings, Globe, MessageCircle,
|
|
2
|
+
import { Network, Code2, ChartSpline, Puzzle, Users, Box, FlaskConical, Newspaper, Settings, Globe, MessageCircle, BookOpen } from 'lucide-react';
|
|
3
3
|
import { cn } from '../../lib/cn';
|
|
4
4
|
import { Tooltip } from '../ui/tooltip';
|
|
5
5
|
import { useGrooveStore } from '../../stores/groove';
|
|
@@ -18,7 +18,6 @@ const BASE_NAV_ITEMS = [
|
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
const NETWORK_NAV_ITEM = { id: 'network', icon: Globe, label: 'Network' };
|
|
21
|
-
const PREVIEW_NAV_ITEM = { id: 'preview', icon: Eye, label: 'Preview' };
|
|
22
21
|
|
|
23
22
|
const UTIL_ITEMS = [
|
|
24
23
|
{ id: 'journalist', icon: Newspaper, label: 'Journalist', panel: true },
|
|
@@ -28,8 +27,7 @@ const UTIL_ITEMS = [
|
|
|
28
27
|
export function ActivityBar({ activeView, detailPanel, onNavigate, onTogglePanel }) {
|
|
29
28
|
const darwinTrafficLights = isElectron() && getPlatform() === 'darwin';
|
|
30
29
|
const networkUnlocked = useGrooveStore((s) => s.networkUnlocked);
|
|
31
|
-
|
|
32
|
-
let NAV_ITEMS = previewUrl ? [...BASE_NAV_ITEMS, PREVIEW_NAV_ITEM] : BASE_NAV_ITEMS;
|
|
30
|
+
let NAV_ITEMS = BASE_NAV_ITEMS;
|
|
33
31
|
if (networkUnlocked) NAV_ITEMS = [...NAV_ITEMS, NETWORK_NAV_ITEM];
|
|
34
32
|
|
|
35
33
|
return (
|
|
@@ -107,7 +107,9 @@ export function TerminalPanel({
|
|
|
107
107
|
const startY = useRef(0);
|
|
108
108
|
const startH = useRef(0);
|
|
109
109
|
|
|
110
|
-
const
|
|
110
|
+
const detailPanel = useGrooveStore((s) => s.detailPanel);
|
|
111
|
+
const workspaceAgentId = useGrooveStore((s) => s.workspaceAgentId);
|
|
112
|
+
const activeAgent = detailPanel?.type === 'agent' ? detailPanel.agentId : workspaceAgentId || null;
|
|
111
113
|
const agents = useGrooveStore((s) => s.agents);
|
|
112
114
|
const attachSnippet = useGrooveStore((s) => s.attachSnippet);
|
|
113
115
|
|
|
@@ -141,8 +143,8 @@ export function TerminalPanel({
|
|
|
141
143
|
function sendToAgent(agentId) {
|
|
142
144
|
if (!agentId || !selectedText?.trim()) return;
|
|
143
145
|
setShowPicker(false);
|
|
144
|
-
useGrooveStore.setState({ editorSelectedAgent: agentId });
|
|
145
146
|
attachSnippet({ type: 'terminal', code: selectedText.trim() });
|
|
147
|
+
useGrooveStore.getState().selectAgent(agentId);
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
function handleSendClick() {
|