groove-dev 0.27.143 → 0.27.145
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/CLAUDE.md +0 -7
- 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/conversations.js +18 -48
- 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 +812 -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-Bxc0gU06.js +1006 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-C0pztKBn.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/node_modules/@groove-dev/gui/src/{app.jsx → 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 +210 -112
- package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +2 -70
- 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/chat/chat-header.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +68 -66
- package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +4 -8
- 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/lab/chat-playground.jsx +39 -31
- 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 +200 -18
- package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +17 -14
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +335 -152
- package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +10 -8
- 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/components/ui/slider.jsx +8 -8
- package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +25 -24
- package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +51 -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 +459 -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 +226 -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 +54 -12
- package/node_modules/@groove-dev/gui/src/views/models.jsx +419 -496
- 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/node_modules/axios/CHANGELOG.md +260 -0
- package/node_modules/axios/README.md +595 -223
- package/node_modules/axios/dist/axios.js +1460 -1090
- package/node_modules/axios/dist/axios.js.map +1 -1
- package/node_modules/axios/dist/axios.min.js +3 -3
- package/node_modules/axios/dist/axios.min.js.map +1 -1
- package/node_modules/axios/dist/browser/axios.cjs +1560 -1132
- package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
- package/node_modules/axios/dist/esm/axios.js +1557 -1128
- package/node_modules/axios/dist/esm/axios.js.map +1 -1
- package/node_modules/axios/dist/esm/axios.min.js +2 -2
- package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
- package/node_modules/axios/dist/node/axios.cjs +1594 -1057
- package/node_modules/axios/dist/node/axios.cjs.map +1 -1
- package/node_modules/axios/index.d.cts +40 -41
- package/node_modules/axios/index.d.ts +151 -227
- package/node_modules/axios/index.js +2 -0
- package/node_modules/axios/lib/adapters/adapters.js +4 -2
- package/node_modules/axios/lib/adapters/fetch.js +147 -16
- package/node_modules/axios/lib/adapters/http.js +306 -58
- package/node_modules/axios/lib/adapters/xhr.js +6 -2
- package/node_modules/axios/lib/core/Axios.js +7 -3
- package/node_modules/axios/lib/core/AxiosError.js +120 -34
- package/node_modules/axios/lib/core/AxiosHeaders.js +27 -25
- package/node_modules/axios/lib/core/buildFullPath.js +1 -1
- package/node_modules/axios/lib/core/dispatchRequest.js +19 -7
- package/node_modules/axios/lib/core/mergeConfig.js +21 -4
- package/node_modules/axios/lib/core/settle.js +7 -11
- package/node_modules/axios/lib/defaults/index.js +14 -9
- package/node_modules/axios/lib/env/data.js +1 -1
- package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +1 -2
- package/node_modules/axios/lib/helpers/buildURL.js +1 -1
- package/node_modules/axios/lib/helpers/cookies.js +14 -2
- package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +28 -1
- package/node_modules/axios/lib/helpers/formDataToJSON.js +3 -1
- package/node_modules/axios/lib/helpers/formDataToStream.js +3 -2
- package/node_modules/axios/lib/helpers/parseProtocol.js +1 -1
- package/node_modules/axios/lib/helpers/progressEventReducer.js +5 -5
- package/node_modules/axios/lib/helpers/resolveConfig.js +54 -18
- package/node_modules/axios/lib/helpers/shouldBypassProxy.js +74 -2
- package/node_modules/axios/lib/helpers/toFormData.js +10 -2
- package/node_modules/axios/lib/helpers/validator.js +3 -1
- package/node_modules/axios/lib/utils.js +33 -21
- package/node_modules/axios/package.json +17 -24
- package/node_modules/follow-redirects/README.md +7 -5
- package/node_modules/follow-redirects/index.js +24 -1
- package/node_modules/follow-redirects/package.json +1 -1
- 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/conversations.js +18 -48
- 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 +812 -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-Bxc0gU06.js +1006 -0
- package/packages/gui/dist/assets/index-C0pztKBn.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/{app.jsx → 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 +210 -112
- package/packages/gui/src/components/agents/agent-node.jsx +8 -13
- package/packages/gui/src/components/agents/agent-panel.jsx +2 -70
- 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/chat/chat-header.jsx +2 -0
- package/packages/gui/src/components/chat/chat-input.jsx +68 -66
- package/packages/gui/src/components/chat/chat-view.jsx +4 -8
- 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/lab/chat-playground.jsx +39 -31
- 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 +200 -18
- package/packages/gui/src/components/lab/preset-manager.jsx +17 -14
- package/packages/gui/src/components/lab/runtime-config.jsx +335 -152
- package/packages/gui/src/components/lab/system-prompt-editor.jsx +10 -8
- 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/components/ui/slider.jsx +8 -8
- package/packages/gui/src/lib/cron.js +64 -0
- package/packages/gui/src/lib/status.js +25 -24
- package/packages/gui/src/lib/theme-hex.js +1 -0
- package/packages/gui/src/stores/groove.js +51 -3144
- package/packages/gui/src/stores/helpers.js +10 -0
- package/packages/gui/src/stores/slices/agents-slice.js +459 -0
- package/packages/gui/src/stores/slices/automations-slice.js +96 -0
- package/packages/gui/src/stores/slices/chat-slice.js +226 -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 +54 -12
- package/packages/gui/src/views/models.jsx +419 -496
- 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-CCVvAoQn.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +0 -984
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -379
- 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-CCVvAoQn.css +0 -1
- package/packages/gui/dist/assets/index-DGIv_TRm.js +0 -984
- package/packages/gui/src/components/agents/agent-chat.jsx +0 -379
- 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,6 +1,7 @@
|
|
|
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
|
+
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';
|
|
@@ -8,13 +9,16 @@ import { Dialog, DialogContent } from '../ui/dialog';
|
|
|
8
9
|
import { Select, SelectTrigger, SelectContent, SelectItem } from '../ui/select';
|
|
9
10
|
import { Tooltip } from '../ui/tooltip';
|
|
10
11
|
import { ScrollArea } from '../ui/scroll-area';
|
|
11
|
-
import { Plus, Trash2, Loader2, WifiOff, RotateCcw, HardDrive, Play, CheckCircle, AlertTriangle, ChevronRight, Wrench } from 'lucide-react';
|
|
12
|
+
import { Plus, Trash2, Loader2, WifiOff, RotateCcw, HardDrive, Play, Square, CheckCircle, AlertTriangle, ChevronRight, Wrench, Settings2 } from 'lucide-react';
|
|
12
13
|
import { cn } from '../../lib/cn';
|
|
13
14
|
|
|
15
|
+
const IS_APPLE = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform || '');
|
|
16
|
+
|
|
14
17
|
const RUNTIME_TYPES = [
|
|
15
18
|
{ value: 'ollama', label: 'Ollama' },
|
|
16
19
|
{ value: 'vllm', label: 'vLLM' },
|
|
17
20
|
{ value: 'llama-cpp', label: 'llama.cpp' },
|
|
21
|
+
{ value: 'mlx', label: 'MLX', suffix: 'Apple Silicon', appleOnly: true },
|
|
18
22
|
{ value: 'tgi', label: 'TGI' },
|
|
19
23
|
{ value: 'openai-compatible', label: 'OpenAI Compatible' },
|
|
20
24
|
];
|
|
@@ -23,6 +27,7 @@ const DEFAULT_ENDPOINTS = {
|
|
|
23
27
|
ollama: 'http://localhost:11434',
|
|
24
28
|
vllm: 'http://localhost:8000',
|
|
25
29
|
'llama-cpp': 'http://localhost:8080',
|
|
30
|
+
mlx: 'http://localhost:8080',
|
|
26
31
|
tgi: 'http://localhost:8080',
|
|
27
32
|
'openai-compatible': 'http://localhost:8000',
|
|
28
33
|
};
|
|
@@ -64,8 +69,8 @@ function AddRuntimeDialog({ open, onOpenChange }) {
|
|
|
64
69
|
<Select value={type} onValueChange={handleTypeChange}>
|
|
65
70
|
<SelectTrigger placeholder="Select type" />
|
|
66
71
|
<SelectContent>
|
|
67
|
-
{RUNTIME_TYPES.map((rt) => (
|
|
68
|
-
<SelectItem key={rt.value} value={rt.value}>{rt.label}</SelectItem>
|
|
72
|
+
{RUNTIME_TYPES.filter(rt => !rt.appleOnly || IS_APPLE).map((rt) => (
|
|
73
|
+
<SelectItem key={rt.value} value={rt.value}>{rt.label}{rt.suffix ? ` (${rt.suffix})` : ''}</SelectItem>
|
|
69
74
|
))}
|
|
70
75
|
</SelectContent>
|
|
71
76
|
</Select>
|
|
@@ -84,45 +89,69 @@ function AddRuntimeDialog({ open, onOpenChange }) {
|
|
|
84
89
|
);
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, testing }) {
|
|
92
|
+
function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onStart, testing }) {
|
|
93
|
+
const online = runtime.status === 'connected';
|
|
94
|
+
const managed = !!(runtime._localModelId || runtime._mlxModelId || runtime.launchConfig || runtime.type === 'mlx' || runtime.type === 'llama-cpp');
|
|
88
95
|
return (
|
|
89
96
|
<button
|
|
90
97
|
onClick={() => onSelect(runtime.id)}
|
|
91
98
|
className={cn(
|
|
92
|
-
'w-full flex items-center gap-2.5 px-2.5 py-2 text-left transition-colors cursor-pointer rounded
|
|
93
|
-
active ? 'bg-accent/8
|
|
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',
|
|
94
101
|
)}
|
|
95
102
|
>
|
|
96
103
|
<span className={cn(
|
|
97
104
|
'w-1.5 h-1.5 rounded-full flex-shrink-0',
|
|
98
|
-
|
|
105
|
+
online ? 'bg-success' : runtime.status === 'error' ? 'bg-danger' : 'bg-text-4',
|
|
99
106
|
)} />
|
|
100
107
|
<div className="flex-1 min-w-0">
|
|
101
|
-
<div className=
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
{
|
|
108
|
+
<div className={cn('text-[11px] font-sans font-medium truncate', active ? 'text-text-0' : 'text-text-2')}>
|
|
109
|
+
{RUNTIME_TYPES.find((t) => t.value === runtime.type)?.label || runtime.type}
|
|
110
|
+
</div>
|
|
111
|
+
<div className="text-[10px] text-text-4 flex items-center gap-1.5">
|
|
112
|
+
<span className={cn('font-sans', online ? 'text-success' : 'text-danger')}>
|
|
113
|
+
{online ? 'Online' : 'Offline'}
|
|
114
|
+
</span>
|
|
115
|
+
{runtime.latency != null && online && (
|
|
116
|
+
<span className="font-mono">{Math.round(runtime.latency)}ms</span>
|
|
117
|
+
)}
|
|
106
118
|
</div>
|
|
107
119
|
</div>
|
|
108
|
-
<div className="flex items-center gap-
|
|
109
|
-
{
|
|
110
|
-
<
|
|
120
|
+
<div className="flex items-center gap-px flex-shrink-0">
|
|
121
|
+
{managed && online && (
|
|
122
|
+
<Tooltip content="Stop server">
|
|
123
|
+
<button
|
|
124
|
+
onClick={(e) => { e.stopPropagation(); onStop(runtime.id); }}
|
|
125
|
+
className="p-1 text-text-4 hover:text-danger transition-colors cursor-pointer"
|
|
126
|
+
>
|
|
127
|
+
<Square size={10} />
|
|
128
|
+
</button>
|
|
129
|
+
</Tooltip>
|
|
130
|
+
)}
|
|
131
|
+
{managed && !online && (
|
|
132
|
+
<Tooltip content="Start server">
|
|
133
|
+
<button
|
|
134
|
+
onClick={(e) => { e.stopPropagation(); onStart(runtime.id); }}
|
|
135
|
+
className="p-1 text-text-4 hover:text-success transition-colors cursor-pointer"
|
|
136
|
+
>
|
|
137
|
+
<Play size={10} />
|
|
138
|
+
</button>
|
|
139
|
+
</Tooltip>
|
|
111
140
|
)}
|
|
112
141
|
<Tooltip content="Test connection">
|
|
113
142
|
<button
|
|
114
143
|
onClick={(e) => { e.stopPropagation(); onTest(runtime.id); }}
|
|
115
144
|
className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
|
|
116
145
|
>
|
|
117
|
-
{testing === runtime.id ? <Loader2 size={
|
|
146
|
+
{testing === runtime.id ? <Loader2 size={10} className="animate-spin" /> : <RotateCcw size={10} />}
|
|
118
147
|
</button>
|
|
119
148
|
</Tooltip>
|
|
120
|
-
<Tooltip content="Remove">
|
|
149
|
+
<Tooltip content="Remove runtime">
|
|
121
150
|
<button
|
|
122
151
|
onClick={(e) => { e.stopPropagation(); onRemove(runtime.id); }}
|
|
123
152
|
className="p-1 text-text-4 hover:text-danger transition-colors cursor-pointer"
|
|
124
153
|
>
|
|
125
|
-
<Trash2 size={
|
|
154
|
+
<Trash2 size={10} />
|
|
126
155
|
</button>
|
|
127
156
|
</Tooltip>
|
|
128
157
|
</div>
|
|
@@ -138,28 +167,56 @@ function formatSize(bytes) {
|
|
|
138
167
|
}
|
|
139
168
|
|
|
140
169
|
const BACKENDS = [
|
|
141
|
-
{ id: '
|
|
170
|
+
...(IS_APPLE ? [{ id: 'mlx', label: 'MLX', subtitle: 'Apple Silicon optimized', recommended: true, autoLaunch: true, appleOnly: true }] : []),
|
|
171
|
+
{ id: 'llama-cpp', label: 'llama.cpp', subtitle: 'CPU + GPU, auto-managed', recommended: !IS_APPLE, autoLaunch: true },
|
|
142
172
|
{ id: 'vllm', label: 'vLLM', subtitle: 'GPU-optimized, guided setup', autoLaunch: false },
|
|
143
173
|
{ id: 'tgi', label: 'TGI', subtitle: 'HuggingFace, guided setup', autoLaunch: false },
|
|
144
174
|
];
|
|
145
175
|
|
|
146
|
-
function
|
|
147
|
-
|
|
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
|
+
};
|
|
148
183
|
return (
|
|
149
|
-
<div className={cn(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
phase === 'error' && 'bg-danger/8 text-danger',
|
|
153
|
-
(phase === 'starting' || phase === 'checking') && 'bg-accent/8 text-accent',
|
|
154
|
-
)}>
|
|
155
|
-
{phase === 'starting' && <><Loader2 size={11} className="animate-spin" /> Starting server...</>}
|
|
156
|
-
{phase === 'checking' && <><Loader2 size={11} className="animate-spin" /> Checking...</>}
|
|
157
|
-
{phase === 'ready' && <><CheckCircle size={11} /> Server ready</>}
|
|
158
|
-
{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>
|
|
159
187
|
</div>
|
|
160
188
|
);
|
|
161
189
|
}
|
|
162
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
|
+
|
|
200
|
+
function getIncompatibilityReason(modelType, backendId) {
|
|
201
|
+
if (modelType === 'gguf' && backendId === 'mlx') return 'GGUF model — MLX needs MLX-format weights';
|
|
202
|
+
if (modelType === 'gguf' && (backendId === 'vllm' || backendId === 'tgi')) return 'GGUF model — needs standard HuggingFace weights';
|
|
203
|
+
if (modelType === 'mlx' && backendId === 'llama-cpp') return 'MLX model — llama.cpp needs a GGUF file';
|
|
204
|
+
if (modelType === 'mlx' && (backendId === 'vllm' || backendId === 'tgi')) return 'MLX model — needs standard HuggingFace weights';
|
|
205
|
+
if (modelType === 'hf' && backendId === 'mlx') return 'HF model — MLX needs MLX-converted weights';
|
|
206
|
+
if (modelType === 'hf' && backendId === 'llama-cpp') return 'HF model — llama.cpp needs a GGUF file';
|
|
207
|
+
return 'Incompatible format';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function getBackendCompat(model, backends) {
|
|
211
|
+
if (!model) return backends.map((b) => ({ ...b, compatible: true, reason: null }));
|
|
212
|
+
const compat = model.compatibleBackends || (model.type === 'gguf' ? ['llama-cpp'] : model.type === 'mlx' ? ['mlx'] : ['vllm', 'tgi']);
|
|
213
|
+
return backends.map((b) => ({
|
|
214
|
+
...b,
|
|
215
|
+
compatible: compat.includes(b.id),
|
|
216
|
+
reason: compat.includes(b.id) ? null : getIncompatibilityReason(model.type, b.id),
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
|
|
163
220
|
export function LaunchModel() {
|
|
164
221
|
const localModels = useGrooveStore((s) => s.labLocalModels);
|
|
165
222
|
const fetchLocalModels = useGrooveStore((s) => s.fetchLabLocalModels);
|
|
@@ -170,15 +227,58 @@ export function LaunchModel() {
|
|
|
170
227
|
const launchPhase = useGrooveStore((s) => s.labLaunchPhase);
|
|
171
228
|
const launchError = useGrooveStore((s) => s.labLaunchError);
|
|
172
229
|
const launchLabAssistant = useGrooveStore((s) => s.launchLabAssistant);
|
|
230
|
+
const labAssistantAgentId = useGrooveStore((s) => s.labAssistantAgentId);
|
|
231
|
+
const labAssistantBackend = useGrooveStore((s) => s.labAssistantBackend);
|
|
232
|
+
const labAssistantMode = useGrooveStore((s) => s.labAssistantMode);
|
|
233
|
+
const setLabAssistantMode = useGrooveStore((s) => s.setLabAssistantMode);
|
|
234
|
+
const agents = useGrooveStore((s) => s.agents);
|
|
235
|
+
const runtimes = useGrooveStore((s) => s.labRuntimes);
|
|
236
|
+
const activeRuntime = useGrooveStore((s) => s.labActiveRuntime);
|
|
173
237
|
|
|
174
238
|
const [selectedModel, setSelectedModel] = useState(null);
|
|
175
|
-
const [selectedBackend, setSelectedBackend] = useState('llama-cpp');
|
|
239
|
+
const [selectedBackend, setSelectedBackend] = useState(IS_APPLE ? 'mlx' : 'llama-cpp');
|
|
176
240
|
const [assistantLaunching, setAssistantLaunching] = useState(false);
|
|
241
|
+
const [suggestion, setSuggestion] = useState(null);
|
|
177
242
|
|
|
178
243
|
useEffect(() => { fetchLocalModels(); checkLlama(); }, [fetchLocalModels, checkLlama]);
|
|
179
244
|
|
|
180
|
-
const
|
|
181
|
-
const
|
|
245
|
+
const selectedModelObj = localModels.find((m) => m.id === selectedModel);
|
|
246
|
+
const backendsWithCompat = getBackendCompat(selectedModelObj, BACKENDS);
|
|
247
|
+
const currentBackend = backendsWithCompat.find((b) => b.id === selectedBackend);
|
|
248
|
+
const isCompatible = currentBackend?.compatible ?? true;
|
|
249
|
+
|
|
250
|
+
const backendReady = selectedBackend === 'mlx' || selectedBackend === 'llama-cpp' ? (selectedBackend === 'mlx' || llamaInstalled) : true;
|
|
251
|
+
const canLaunch = selectedModel && currentBackend?.autoLaunch && backendReady && !launching && isCompatible;
|
|
252
|
+
|
|
253
|
+
const assistantAgent = labAssistantAgentId ? agents.find((a) => a.id === labAssistantAgentId) : null;
|
|
254
|
+
const assistantRunning = assistantAgent?.status === 'running';
|
|
255
|
+
const assistantComplete = assistantAgent && assistantAgent.status !== 'running';
|
|
256
|
+
const hasActiveAssistant = !!(labAssistantAgentId && (assistantRunning || assistantComplete));
|
|
257
|
+
|
|
258
|
+
const activeRt = activeRuntime ? runtimes.find((r) => r.id === activeRuntime) : null;
|
|
259
|
+
const serverRunning = activeRt?.status === 'connected';
|
|
260
|
+
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
if (!selectedModel || !selectedBackend || isCompatible) { setSuggestion(null); return; }
|
|
263
|
+
let cancelled = false;
|
|
264
|
+
fetch(`/api/lab/suggest-model?modelId=${encodeURIComponent(selectedModel)}&targetBackend=${selectedBackend}`)
|
|
265
|
+
.then((r) => r.ok ? r.json() : null)
|
|
266
|
+
.then((data) => { if (!cancelled) setSuggestion(data?.suggestion || null); })
|
|
267
|
+
.catch(() => { if (!cancelled) setSuggestion(null); });
|
|
268
|
+
return () => { cancelled = true; };
|
|
269
|
+
}, [selectedModel, selectedBackend, isCompatible]);
|
|
270
|
+
|
|
271
|
+
function handleModelChange(e) {
|
|
272
|
+
const id = e.target.value || null;
|
|
273
|
+
setSelectedModel(id);
|
|
274
|
+
if (!id) return;
|
|
275
|
+
const model = localModels.find((m) => m.id === id);
|
|
276
|
+
if (!model) return;
|
|
277
|
+
const compat = model.compatibleBackends || [];
|
|
278
|
+
const preferred = IS_APPLE ? ['mlx', 'llama-cpp', 'vllm', 'tgi'] : ['llama-cpp', 'vllm', 'tgi'];
|
|
279
|
+
const best = preferred.find((b) => compat.includes(b));
|
|
280
|
+
if (best) setSelectedBackend(best);
|
|
281
|
+
}
|
|
182
282
|
|
|
183
283
|
function handleLaunch() {
|
|
184
284
|
if (!canLaunch) return;
|
|
@@ -189,103 +289,108 @@ export function LaunchModel() {
|
|
|
189
289
|
if (assistantLaunching) return;
|
|
190
290
|
setAssistantLaunching(true);
|
|
191
291
|
try {
|
|
192
|
-
|
|
292
|
+
const model = localModels.find((m) => m.id === selectedModel);
|
|
293
|
+
await launchLabAssistant(currentBackend.id, model || undefined);
|
|
193
294
|
} finally {
|
|
194
295
|
setAssistantLaunching(false);
|
|
195
296
|
}
|
|
196
297
|
}
|
|
197
298
|
|
|
198
299
|
return (
|
|
199
|
-
<
|
|
200
|
-
<span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Launch Model</span>
|
|
201
|
-
|
|
300
|
+
<SidebarSection label="Launch Model" collapsible defaultOpen={false}>
|
|
202
301
|
{localModels.length === 0 ? (
|
|
203
|
-
<div className="py-
|
|
204
|
-
<HardDrive size={
|
|
205
|
-
<p className="text-
|
|
206
|
-
<p className="text-
|
|
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>
|
|
207
306
|
</div>
|
|
208
307
|
) : (
|
|
209
|
-
|
|
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>
|
|
308
|
+
<div className="space-y-4">
|
|
309
|
+
<div className="relative">
|
|
310
|
+
<select
|
|
311
|
+
value={selectedModel || ''}
|
|
312
|
+
onChange={handleModelChange}
|
|
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"
|
|
314
|
+
>
|
|
315
|
+
<option value="">Select a model</option>
|
|
316
|
+
{localModels.map((m) => {
|
|
317
|
+
const label = m.filename?.replace(/\.gguf$/i, '') || m.id;
|
|
318
|
+
const tag = m.type === 'mlx' ? 'MLX' : m.type === 'hf' ? 'HF' : 'GGUF';
|
|
319
|
+
const meta = [tag, m.quantization, m.parameters, m.sizeBytes ? formatSize(m.sizeBytes) : null].filter(Boolean).join(' · ');
|
|
320
|
+
return (
|
|
321
|
+
<option key={m.id} value={m.id}>
|
|
322
|
+
{label}{meta ? ` (${meta})` : ''}
|
|
323
|
+
</option>
|
|
324
|
+
);
|
|
325
|
+
})}
|
|
326
|
+
</select>
|
|
327
|
+
<ChevronRight size={12} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-text-4 pointer-events-none rotate-90" />
|
|
328
|
+
</div>
|
|
237
329
|
|
|
238
330
|
{selectedModel && (
|
|
239
|
-
<div className="space-y-
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
key={b.id}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
<
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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>
|
|
357
|
+
</div>
|
|
358
|
+
</button>
|
|
359
|
+
</Tooltip>
|
|
360
|
+
))}
|
|
361
|
+
</div>
|
|
266
362
|
</div>
|
|
267
363
|
|
|
268
|
-
{
|
|
269
|
-
<
|
|
364
|
+
{!isCompatible && (
|
|
365
|
+
<StatusBanner variant="warning" icon={AlertTriangle}>
|
|
366
|
+
{currentBackend?.reason}
|
|
367
|
+
{suggestion && (
|
|
368
|
+
<> — try <span className="font-mono font-medium">{suggestion.repoId}</span></>
|
|
369
|
+
)}
|
|
370
|
+
</StatusBanner>
|
|
371
|
+
)}
|
|
372
|
+
|
|
373
|
+
{isCompatible && selectedBackend === 'llama-cpp' && (
|
|
374
|
+
<div>
|
|
270
375
|
{llamaInstalled === null && (
|
|
271
|
-
<div className="flex items-center gap-2 text-
|
|
376
|
+
<div className="flex items-center gap-2 text-[11px] text-text-3 font-sans">
|
|
272
377
|
<Loader2 size={10} className="animate-spin" /> Checking llama-server...
|
|
273
378
|
</div>
|
|
274
379
|
)}
|
|
275
380
|
{llamaInstalled === true && (
|
|
276
|
-
<div className="flex items-center gap-2 text-
|
|
381
|
+
<div className="flex items-center gap-2 text-[11px] text-success font-sans">
|
|
277
382
|
<CheckCircle size={10} /> llama-server found
|
|
278
383
|
</div>
|
|
279
384
|
)}
|
|
280
385
|
{llamaInstalled === false && (
|
|
281
|
-
<div className="space-y-
|
|
282
|
-
<div className="flex items-center gap-2 text-
|
|
386
|
+
<div className="space-y-2">
|
|
387
|
+
<div className="flex items-center gap-2 text-[11px] text-danger font-sans">
|
|
283
388
|
<AlertTriangle size={10} /> llama-server not found
|
|
284
389
|
</div>
|
|
285
|
-
<code className="block text-
|
|
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>
|
|
286
391
|
<button
|
|
287
392
|
onClick={checkLlama}
|
|
288
|
-
className="flex items-center gap-1.5 text-
|
|
393
|
+
className="flex items-center gap-1.5 text-[11px] font-sans text-accent hover:text-accent/80 transition-colors cursor-pointer"
|
|
289
394
|
>
|
|
290
395
|
<RotateCcw size={10} /> Recheck after install
|
|
291
396
|
</button>
|
|
@@ -294,51 +399,77 @@ export function LaunchModel() {
|
|
|
294
399
|
</div>
|
|
295
400
|
)}
|
|
296
401
|
|
|
297
|
-
{!currentBackend?.autoLaunch && (
|
|
402
|
+
{isCompatible && !currentBackend?.autoLaunch && (
|
|
298
403
|
<div className="space-y-2">
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
404
|
+
{hasActiveAssistant && labAssistantBackend === selectedBackend ? (
|
|
405
|
+
<div className="space-y-2">
|
|
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
|
+
)}
|
|
413
|
+
{!labAssistantMode && (
|
|
414
|
+
<button
|
|
415
|
+
onClick={() => setLabAssistantMode(true)}
|
|
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"
|
|
417
|
+
>
|
|
418
|
+
View Assistant
|
|
419
|
+
</button>
|
|
420
|
+
)}
|
|
421
|
+
</div>
|
|
422
|
+
) : (
|
|
423
|
+
<div className="space-y-2">
|
|
424
|
+
<button
|
|
425
|
+
onClick={handleLaunchAssistant}
|
|
426
|
+
disabled={assistantLaunching}
|
|
427
|
+
className={cn(
|
|
428
|
+
'w-full flex items-center justify-center gap-1.5 h-8 text-[11px] font-sans font-medium rounded transition-colors cursor-pointer',
|
|
429
|
+
assistantLaunching ? 'bg-accent/20 text-accent' : 'bg-accent text-surface-0 hover:bg-accent/90',
|
|
430
|
+
)}
|
|
431
|
+
>
|
|
432
|
+
{assistantLaunching
|
|
433
|
+
? <><Loader2 size={11} className="animate-spin" /> Starting Assistant...</>
|
|
434
|
+
: <><Wrench size={11} /> Setup {currentBackend?.label} with Assistant</>
|
|
435
|
+
}
|
|
436
|
+
</button>
|
|
437
|
+
<p className="text-[10px] text-text-4 font-sans text-center">
|
|
438
|
+
An AI assistant will check your system and handle the installation.
|
|
439
|
+
</p>
|
|
440
|
+
</div>
|
|
441
|
+
)}
|
|
315
442
|
</div>
|
|
316
443
|
)}
|
|
317
444
|
|
|
318
|
-
{currentBackend?.autoLaunch && (
|
|
445
|
+
{isCompatible && currentBackend?.autoLaunch && (
|
|
319
446
|
<div className="space-y-2">
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
447
|
+
{serverRunning ? (
|
|
448
|
+
<StatusBanner variant="success" icon={CheckCircle}>Server Running</StatusBanner>
|
|
449
|
+
) : (
|
|
450
|
+
<button
|
|
451
|
+
disabled={!canLaunch}
|
|
452
|
+
onClick={handleLaunch}
|
|
453
|
+
className={cn(
|
|
454
|
+
'w-full flex items-center justify-center gap-1.5 h-8 text-[11px] font-sans font-medium rounded transition-colors cursor-pointer',
|
|
455
|
+
canLaunch ? 'bg-accent text-surface-0 hover:bg-accent/90' : 'bg-surface-3 text-text-4 cursor-not-allowed',
|
|
456
|
+
)}
|
|
457
|
+
>
|
|
458
|
+
{launching ? (
|
|
459
|
+
<><Loader2 size={11} className="animate-spin" /> Starting...</>
|
|
460
|
+
) : (
|
|
461
|
+
<><Play size={11} /> Launch</>
|
|
462
|
+
)}
|
|
463
|
+
</button>
|
|
464
|
+
)}
|
|
334
465
|
<LaunchStatus phase={launchPhase} error={launchError} />
|
|
335
466
|
</div>
|
|
336
467
|
)}
|
|
337
468
|
</div>
|
|
338
469
|
)}
|
|
339
|
-
|
|
470
|
+
</div>
|
|
340
471
|
)}
|
|
341
|
-
</
|
|
472
|
+
</SidebarSection>
|
|
342
473
|
);
|
|
343
474
|
}
|
|
344
475
|
|
|
@@ -348,6 +479,8 @@ export function RuntimeConfig() {
|
|
|
348
479
|
const setActiveRuntime = useGrooveStore((s) => s.setLabActiveRuntime);
|
|
349
480
|
const testRuntime = useGrooveStore((s) => s.testLabRuntime);
|
|
350
481
|
const removeRuntime = useGrooveStore((s) => s.removeLabRuntime);
|
|
482
|
+
const stopRuntime = useGrooveStore((s) => s.stopLabRuntime);
|
|
483
|
+
const startRuntime = useGrooveStore((s) => s.startLabRuntime);
|
|
351
484
|
|
|
352
485
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
353
486
|
const [testing, setTesting] = useState(null);
|
|
@@ -359,33 +492,35 @@ export function RuntimeConfig() {
|
|
|
359
492
|
}
|
|
360
493
|
|
|
361
494
|
return (
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
495
|
+
<SidebarSection
|
|
496
|
+
label="Runtimes"
|
|
497
|
+
collapsible
|
|
498
|
+
defaultOpen={false}
|
|
499
|
+
action={
|
|
365
500
|
<Tooltip content="Add runtime">
|
|
366
501
|
<button
|
|
367
502
|
onClick={() => setDialogOpen(true)}
|
|
368
503
|
className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
|
|
369
504
|
>
|
|
370
|
-
<Plus size={
|
|
505
|
+
<Plus size={12} />
|
|
371
506
|
</button>
|
|
372
507
|
</Tooltip>
|
|
373
|
-
|
|
374
|
-
|
|
508
|
+
}
|
|
509
|
+
>
|
|
375
510
|
{runtimes.length === 0 ? (
|
|
376
|
-
<div className="py-
|
|
377
|
-
<WifiOff size={
|
|
378
|
-
<p className="text-
|
|
511
|
+
<div className="py-6 text-center rounded-md bg-surface-1/50 border border-border-subtle">
|
|
512
|
+
<WifiOff size={16} className="mx-auto text-text-4 mb-2" />
|
|
513
|
+
<p className="text-[11px] text-text-3 font-sans">No runtimes configured</p>
|
|
379
514
|
<button
|
|
380
515
|
onClick={() => setDialogOpen(true)}
|
|
381
|
-
className="mt-2 flex items-center gap-1
|
|
516
|
+
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"
|
|
382
517
|
>
|
|
383
|
-
<Plus size={
|
|
518
|
+
<Plus size={10} /> Add Runtime
|
|
384
519
|
</button>
|
|
385
520
|
</div>
|
|
386
521
|
) : (
|
|
387
522
|
<ScrollArea className="max-h-48">
|
|
388
|
-
<div className="space-y-
|
|
523
|
+
<div className="space-y-1 rounded-md bg-surface-1/50 border border-border-subtle p-2">
|
|
389
524
|
{runtimes.map((rt) => (
|
|
390
525
|
<RuntimeItem
|
|
391
526
|
key={rt.id}
|
|
@@ -394,6 +529,8 @@ export function RuntimeConfig() {
|
|
|
394
529
|
onSelect={setActiveRuntime}
|
|
395
530
|
onTest={handleTest}
|
|
396
531
|
onRemove={removeRuntime}
|
|
532
|
+
onStop={stopRuntime}
|
|
533
|
+
onStart={startRuntime}
|
|
397
534
|
testing={testing}
|
|
398
535
|
/>
|
|
399
536
|
))}
|
|
@@ -402,6 +539,52 @@ export function RuntimeConfig() {
|
|
|
402
539
|
)}
|
|
403
540
|
|
|
404
541
|
<AddRuntimeDialog open={dialogOpen} onOpenChange={setDialogOpen} />
|
|
542
|
+
</SidebarSection>
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export function RuntimeSection() {
|
|
547
|
+
const runtimes = useGrooveStore((s) => s.labRuntimes);
|
|
548
|
+
const activeRuntime = useGrooveStore((s) => s.labActiveRuntime);
|
|
549
|
+
const activeModel = useGrooveStore((s) => s.labActiveModel);
|
|
550
|
+
const [expanded, setExpanded] = useState(true);
|
|
551
|
+
const wasRunning = useRef(false);
|
|
552
|
+
|
|
553
|
+
const activeRt = activeRuntime ? runtimes.find((r) => r.id === activeRuntime) : null;
|
|
554
|
+
const serverRunning = activeRt?.status === 'connected';
|
|
555
|
+
const runtimeLabel = activeRt ? (RUNTIME_TYPES.find((t) => t.value === activeRt.type)?.label || activeRt.type) : null;
|
|
556
|
+
|
|
557
|
+
useEffect(() => {
|
|
558
|
+
if (serverRunning && !wasRunning.current) setExpanded(false);
|
|
559
|
+
wasRunning.current = serverRunning;
|
|
560
|
+
}, [serverRunning]);
|
|
561
|
+
|
|
562
|
+
if (!serverRunning || expanded) {
|
|
563
|
+
return (
|
|
564
|
+
<div className="space-y-6">
|
|
565
|
+
<LaunchModel />
|
|
566
|
+
<RuntimeConfig />
|
|
567
|
+
</div>
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return (
|
|
572
|
+
<div className="flex items-center gap-2.5 px-2.5 py-2 rounded-md bg-surface-1/50 border border-border-subtle">
|
|
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-[11px] font-sans font-medium text-text-0 truncate">{runtimeLabel}</div>
|
|
576
|
+
<div className="text-[10px] 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={12} />
|
|
586
|
+
</button>
|
|
587
|
+
</Tooltip>
|
|
405
588
|
</div>
|
|
406
589
|
);
|
|
407
590
|
}
|