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.
- package/CLAUDE.md +7 -0
- 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 +12 -6
- package/node_modules/@groove-dev/daemon/src/conversations.js +59 -58
- package/node_modules/@groove-dev/daemon/src/introducer.js +20 -0
- package/node_modules/@groove-dev/daemon/src/process.js +262 -15
- package/node_modules/@groove-dev/daemon/src/providers/groove-network.js +1 -3
- package/node_modules/@groove-dev/daemon/src/rotator.js +15 -3
- package/node_modules/@groove-dev/daemon/src/routes/agents.js +49 -83
- package/node_modules/@groove-dev/daemon/templates/lab-general.json +12 -0
- package/node_modules/@groove-dev/daemon/templates/llama-cpp-setup.json +12 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BKbsE_hn.js +1011 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CEkPsSAm.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/components/agents/agent-feed.jsx +80 -95
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +2 -70
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +132 -4
- package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +3 -8
- package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +199 -75
- package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +21 -4
- package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +10 -13
- package/node_modules/@groove-dev/gui/src/components/chat/model-picker.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +42 -34
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +9 -3
- package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +13 -3
- package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +66 -65
- 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 +124 -127
- package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +10 -8
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +24 -1
- package/node_modules/@groove-dev/gui/src/components/ui/question-modal.jsx +107 -0
- package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +8 -8
- package/node_modules/@groove-dev/gui/src/lib/status.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +49 -2
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +18 -2
- package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +14 -14
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +68 -32
- package/node_modules/@groove-dev/gui/src/views/models.jsx +57 -36
- 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 +12 -6
- package/packages/daemon/src/conversations.js +59 -58
- package/packages/daemon/src/introducer.js +20 -0
- package/packages/daemon/src/process.js +262 -15
- package/packages/daemon/src/providers/groove-network.js +1 -3
- package/packages/daemon/src/rotator.js +15 -3
- package/packages/daemon/src/routes/agents.js +49 -83
- package/packages/daemon/templates/lab-general.json +12 -0
- package/packages/daemon/templates/llama-cpp-setup.json +12 -0
- package/packages/gui/dist/assets/index-BKbsE_hn.js +1011 -0
- package/packages/gui/dist/assets/index-CEkPsSAm.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-feed.jsx +80 -95
- package/packages/gui/src/components/agents/agent-panel.jsx +2 -70
- package/packages/gui/src/components/agents/spawn-wizard.jsx +132 -4
- package/packages/gui/src/components/chat/chat-header.jsx +3 -8
- package/packages/gui/src/components/chat/chat-input.jsx +199 -75
- package/packages/gui/src/components/chat/chat-messages.jsx +21 -4
- package/packages/gui/src/components/chat/chat-view.jsx +10 -13
- package/packages/gui/src/components/chat/model-picker.jsx +3 -3
- package/packages/gui/src/components/lab/chat-playground.jsx +42 -34
- package/packages/gui/src/components/lab/lab-assistant.jsx +9 -3
- package/packages/gui/src/components/lab/metrics-panel.jsx +13 -3
- package/packages/gui/src/components/lab/parameter-panel.jsx +66 -65
- package/packages/gui/src/components/lab/preset-manager.jsx +17 -14
- package/packages/gui/src/components/lab/runtime-config.jsx +124 -127
- package/packages/gui/src/components/lab/system-prompt-editor.jsx +10 -8
- package/packages/gui/src/components/layout/app-shell.jsx +2 -0
- package/packages/gui/src/components/layout/status-bar.jsx +24 -1
- package/packages/gui/src/components/ui/question-modal.jsx +107 -0
- package/packages/gui/src/components/ui/sheet.jsx +2 -2
- package/packages/gui/src/components/ui/slider.jsx +8 -8
- package/packages/gui/src/lib/status.js +1 -0
- package/packages/gui/src/stores/groove.js +49 -2
- package/packages/gui/src/stores/slices/agents-slice.js +18 -2
- package/packages/gui/src/stores/slices/chat-slice.js +14 -14
- package/packages/gui/src/views/model-lab.jsx +68 -32
- package/packages/gui/src/views/models.jsx +57 -36
- package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +0 -1012
- package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +0 -1
- package/packages/gui/dist/assets/index-BcoF6_eF.js +0 -1012
- package/packages/gui/dist/assets/index-Dd7qhiEd.css +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { useState, useRef
|
|
2
|
+
import { useState, useRef } from 'react';
|
|
3
3
|
import { useGrooveStore } from '../../stores/groove';
|
|
4
4
|
import { Badge } from '../ui/badge';
|
|
5
5
|
import { AgentFeed } from './agent-feed';
|
|
6
6
|
import { AgentConfig } from './agent-config';
|
|
7
7
|
import { AgentTelemetry } from './agent-telemetry';
|
|
8
8
|
import { AgentMdFiles } from './agent-mdfiles';
|
|
9
|
-
import { MessageSquare, Settings, Activity, FileText, Pencil, Check, X
|
|
9
|
+
import { MessageSquare, Settings, Activity, FileText, Pencil, Check, X } from 'lucide-react';
|
|
10
10
|
import { fmtNum, fmtUptime } from '../../lib/format';
|
|
11
11
|
import { cn } from '../../lib/cn';
|
|
12
12
|
import { roleColor } from '../../lib/status';
|
|
@@ -77,56 +77,6 @@ function InlineName({ agent }) {
|
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function useRoutingSuggestion(agentId, isAlive) {
|
|
81
|
-
const [suggestion, setSuggestion] = useState(null);
|
|
82
|
-
const [dismissed, setDismissed] = useState(false);
|
|
83
|
-
|
|
84
|
-
useEffect(() => {
|
|
85
|
-
if (!agentId || !isAlive || dismissed) { setSuggestion(null); return; }
|
|
86
|
-
let cancelled = false;
|
|
87
|
-
async function poll() {
|
|
88
|
-
try {
|
|
89
|
-
const res = await fetch(`/api/agents/${agentId}/routing/suggestion`);
|
|
90
|
-
if (cancelled) return;
|
|
91
|
-
if (res.status === 204 || !res.ok) { setSuggestion(null); return; }
|
|
92
|
-
const data = await res.json();
|
|
93
|
-
setSuggestion(data);
|
|
94
|
-
} catch { setSuggestion(null); }
|
|
95
|
-
}
|
|
96
|
-
poll();
|
|
97
|
-
const id = setInterval(poll, 30000);
|
|
98
|
-
return () => { cancelled = true; clearInterval(id); };
|
|
99
|
-
}, [agentId, isAlive, dismissed]);
|
|
100
|
-
|
|
101
|
-
const dismiss = useCallback(() => setDismissed(true), []);
|
|
102
|
-
const reset = useCallback(() => setDismissed(false), []);
|
|
103
|
-
|
|
104
|
-
return { suggestion: dismissed ? null : suggestion, dismiss, reset };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function DownshiftPill({ suggestion, onAccept, onDismiss }) {
|
|
108
|
-
if (!suggestion) return null;
|
|
109
|
-
const { suggestedModel } = suggestion;
|
|
110
|
-
return (
|
|
111
|
-
<div className="flex items-center gap-1 px-1.5 py-0.5 rounded bg-success/10 border border-success/20 text-2xs font-mono animate-in fade-in slide-in-from-left-1 duration-200">
|
|
112
|
-
<TrendingDown size={10} className="text-success flex-shrink-0" />
|
|
113
|
-
<span className="text-success/90 truncate max-w-[80px]">{suggestedModel.name}</span>
|
|
114
|
-
<button
|
|
115
|
-
onClick={onAccept}
|
|
116
|
-
className="px-1 py-px rounded bg-success/20 text-success font-semibold hover:bg-success/30 transition-colors cursor-pointer"
|
|
117
|
-
>
|
|
118
|
-
Switch
|
|
119
|
-
</button>
|
|
120
|
-
<button
|
|
121
|
-
onClick={onDismiss}
|
|
122
|
-
className="p-0.5 text-text-4 hover:text-text-1 cursor-pointer"
|
|
123
|
-
>
|
|
124
|
-
<X size={8} />
|
|
125
|
-
</button>
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
80
|
export function AgentPanel() {
|
|
131
81
|
const detailPanel = useGrooveStore((s) => s.detailPanel);
|
|
132
82
|
const agents = useGrooveStore((s) => s.agents);
|
|
@@ -141,7 +91,6 @@ export function AgentPanel() {
|
|
|
141
91
|
else if (cachedAgentRef.current && cachedAgentRef.current.id !== agentId) cachedAgentRef.current = null;
|
|
142
92
|
const agent = liveAgent || cachedAgentRef.current;
|
|
143
93
|
const isAlive = liveAgent?.status === 'running' || liveAgent?.status === 'starting';
|
|
144
|
-
const { suggestion, dismiss: dismissSuggestion } = useRoutingSuggestion(agentId, isAlive);
|
|
145
94
|
|
|
146
95
|
if (!agent) return null;
|
|
147
96
|
if (activeTeamId && agent.teamId && agent.teamId !== activeTeamId) return null;
|
|
@@ -151,17 +100,6 @@ export function AgentPanel() {
|
|
|
151
100
|
const uptime = spawned ? Math.floor((Date.now() - new Date(spawned).getTime()) / 1000) : 0;
|
|
152
101
|
const colors = roleColor(agent.role);
|
|
153
102
|
|
|
154
|
-
async function acceptSuggestion() {
|
|
155
|
-
if (!suggestion) return;
|
|
156
|
-
try {
|
|
157
|
-
await api.patch(`/agents/${agent.id}`, { model: suggestion.suggestedModel.id });
|
|
158
|
-
addToast('success', `Model → ${suggestion.suggestedModel.name}`);
|
|
159
|
-
dismissSuggestion();
|
|
160
|
-
} catch (err) {
|
|
161
|
-
addToast('error', 'Model switch failed', err.message);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
103
|
return (
|
|
166
104
|
<div className="flex flex-col h-full">
|
|
167
105
|
{/* ── Header ─────────────────────────────────────────── */}
|
|
@@ -194,12 +132,6 @@ export function AgentPanel() {
|
|
|
194
132
|
)}
|
|
195
133
|
<span className="text-text-4">·</span>
|
|
196
134
|
<span>{fmtUptime(uptime)}</span>
|
|
197
|
-
{suggestion && (
|
|
198
|
-
<>
|
|
199
|
-
<span className="text-text-4">·</span>
|
|
200
|
-
<DownshiftPill suggestion={suggestion} onAccept={acceptSuggestion} onDismiss={dismissSuggestion} />
|
|
201
|
-
</>
|
|
202
|
-
)}
|
|
203
135
|
</div>
|
|
204
136
|
</div>
|
|
205
137
|
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
Shield, Database, Megaphone, Calculator, UserCheck,
|
|
13
13
|
Headphones, BarChart3, Rocket, ChevronDown, Pen, Presentation,
|
|
14
14
|
Sparkles, X, Search, AlertTriangle, Plug, MessageCircle, GitBranch, Globe,
|
|
15
|
-
Check,
|
|
15
|
+
Check, Users, Plus,
|
|
16
16
|
} from 'lucide-react';
|
|
17
17
|
import { api } from '../../lib/api';
|
|
18
18
|
import { Dialog, DialogContent } from '../ui/dialog';
|
|
@@ -53,6 +53,8 @@ export function SpawnWizard() {
|
|
|
53
53
|
const closeDetail = useGrooveStore((s) => s.closeDetail);
|
|
54
54
|
const spawnAgent = useGrooveStore((s) => s.spawnAgent);
|
|
55
55
|
const fetchProviders = useGrooveStore((s) => s.fetchProviders);
|
|
56
|
+
const teams = useGrooveStore((s) => s.teams);
|
|
57
|
+
const createTeam = useGrooveStore((s) => s.createTeam);
|
|
56
58
|
|
|
57
59
|
const open = detailPanel?.type === 'spawn';
|
|
58
60
|
const [role, setRole] = useState('');
|
|
@@ -80,6 +82,9 @@ export function SpawnWizard() {
|
|
|
80
82
|
const [preflightDialog, setPreflightDialog] = useState(null);
|
|
81
83
|
const [ollamaInstalled, setOllamaInstalled] = useState([]);
|
|
82
84
|
const [ollamaServerRunning, setOllamaServerRunning] = useState(false);
|
|
85
|
+
const [teamMode, setTeamMode] = useState('new');
|
|
86
|
+
const [newTeamName, setNewTeamName] = useState('');
|
|
87
|
+
const [selectedTeamId, setSelectedTeamId] = useState('');
|
|
83
88
|
const federation = useGrooveStore((s) => s.federation);
|
|
84
89
|
const ollamaRunningModels = useGrooveStore((s) => s.ollamaRunningModels);
|
|
85
90
|
|
|
@@ -102,6 +107,25 @@ export function SpawnWizard() {
|
|
|
102
107
|
const best = priority.find((pid) => installed.some((p) => p.id === pid)) || installed[0].id;
|
|
103
108
|
setProvider(best);
|
|
104
109
|
}
|
|
110
|
+
if (_presetProvider) setProvider(_presetProvider);
|
|
111
|
+
if (_presetModel) {
|
|
112
|
+
const prov = list.find((p) => p.id === (_presetProvider || provider));
|
|
113
|
+
const models = prov?.models || [];
|
|
114
|
+
const exact = models.find((m) => m.id === _presetModel);
|
|
115
|
+
if (exact) {
|
|
116
|
+
setModel(_presetModel);
|
|
117
|
+
} else {
|
|
118
|
+
const parts = _presetModel.split(':');
|
|
119
|
+
const modelName = parts.length >= 3 ? parts.slice(2).join(':') : parts[parts.length - 1];
|
|
120
|
+
const match = models.find((m) =>
|
|
121
|
+
m.id === _presetModel ||
|
|
122
|
+
m.id === `gguf:${modelName}` ||
|
|
123
|
+
m.id.endsWith(`:${modelName}`) ||
|
|
124
|
+
m.name === modelName
|
|
125
|
+
);
|
|
126
|
+
setModel(match?.id || _presetModel);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
105
129
|
}).catch(() => {});
|
|
106
130
|
api.get('/integrations/installed').then((data) => {
|
|
107
131
|
setInstalledIntegrations(Array.isArray(data) ? data : []);
|
|
@@ -114,6 +138,10 @@ export function SpawnWizard() {
|
|
|
114
138
|
}).catch(() => {});
|
|
115
139
|
setRole(''); setCustomRole(''); setName('');
|
|
116
140
|
setProvider(_presetProvider); setModel(_presetModel);
|
|
141
|
+
setTeamMode('new'); setSelectedTeamId('');
|
|
142
|
+
setNewTeamName(_presetModel
|
|
143
|
+
? _presetModel.split(':').pop().replace(/[-_]/g, ' ')
|
|
144
|
+
: '');
|
|
117
145
|
setPrompt('');
|
|
118
146
|
setSelectedIntegrations([]);
|
|
119
147
|
setIntegrationApproval('manual');
|
|
@@ -153,6 +181,14 @@ export function SpawnWizard() {
|
|
|
153
181
|
async function runSpawn() {
|
|
154
182
|
setSpawning(true);
|
|
155
183
|
try {
|
|
184
|
+
let teamId;
|
|
185
|
+
if (teamMode === 'new') {
|
|
186
|
+
const teamName = newTeamName.trim() || selectedRole || 'New Team';
|
|
187
|
+
const team = await createTeam(teamName);
|
|
188
|
+
teamId = team.id;
|
|
189
|
+
} else if (teamMode === 'existing' && selectedTeamId) {
|
|
190
|
+
teamId = selectedTeamId;
|
|
191
|
+
}
|
|
156
192
|
const config = {
|
|
157
193
|
role: selectedRole,
|
|
158
194
|
...(name && { name: name.replace(/\s+/g, '-') }),
|
|
@@ -164,6 +200,7 @@ export function SpawnWizard() {
|
|
|
164
200
|
...(selectedRepos.length > 0 && { repos: selectedRepos }),
|
|
165
201
|
...(selectedPersonality && { personality: selectedPersonality }),
|
|
166
202
|
...(selectedRole === 'ambassador' && selectedPeerId && { peerId: selectedPeerId }),
|
|
203
|
+
...(teamId && { teamId }),
|
|
167
204
|
};
|
|
168
205
|
await spawnAgent(config);
|
|
169
206
|
closeDetail();
|
|
@@ -415,7 +452,36 @@ export function SpawnWizard() {
|
|
|
415
452
|
}
|
|
416
453
|
</optgroup>
|
|
417
454
|
</>
|
|
418
|
-
) : (
|
|
455
|
+
) : provider === 'local' ? (() => {
|
|
456
|
+
const runtimeModels = availableModels.filter((m) => m.source === 'runtime');
|
|
457
|
+
const ggufModels = availableModels.filter((m) => m.source === 'gguf');
|
|
458
|
+
const ollamaMs = availableModels.filter((m) => m.source === 'ollama' || !m.source);
|
|
459
|
+
return (
|
|
460
|
+
<>
|
|
461
|
+
{runtimeModels.length > 0 && (
|
|
462
|
+
<optgroup label="Lab Runtimes">
|
|
463
|
+
{runtimeModels.map((m) => (
|
|
464
|
+
<option key={m.id} value={m.id}>{m.name}{m.runtimeType ? ` (${m.runtimeType})` : ''}</option>
|
|
465
|
+
))}
|
|
466
|
+
</optgroup>
|
|
467
|
+
)}
|
|
468
|
+
{ggufModels.length > 0 && (
|
|
469
|
+
<optgroup label="GGUF Models">
|
|
470
|
+
{ggufModels.map((m) => (
|
|
471
|
+
<option key={m.id} value={m.id}>{m.name}{m.hasRuntime ? '' : ' (no runtime)'}</option>
|
|
472
|
+
))}
|
|
473
|
+
</optgroup>
|
|
474
|
+
)}
|
|
475
|
+
{ollamaMs.length > 0 && (
|
|
476
|
+
<optgroup label="Ollama">
|
|
477
|
+
{ollamaMs.map((m) => (
|
|
478
|
+
<option key={m.id} value={m.id}>{m.name}</option>
|
|
479
|
+
))}
|
|
480
|
+
</optgroup>
|
|
481
|
+
)}
|
|
482
|
+
</>
|
|
483
|
+
);
|
|
484
|
+
})() : (
|
|
419
485
|
availableModels.map((m) => (
|
|
420
486
|
<option key={m.id} value={m.id}>{m.name}</option>
|
|
421
487
|
))
|
|
@@ -459,6 +525,64 @@ export function SpawnWizard() {
|
|
|
459
525
|
</div>
|
|
460
526
|
)}
|
|
461
527
|
|
|
528
|
+
{/* Team */}
|
|
529
|
+
<div className="space-y-1.5">
|
|
530
|
+
<label className="text-xs font-medium text-text-2 font-sans">Team</label>
|
|
531
|
+
<div className="flex gap-1.5">
|
|
532
|
+
<button
|
|
533
|
+
onClick={() => setTeamMode('new')}
|
|
534
|
+
className={cn(
|
|
535
|
+
'flex-1 flex items-center gap-2 px-3 py-2 rounded-md border text-left transition-all cursor-pointer',
|
|
536
|
+
teamMode === 'new'
|
|
537
|
+
? 'border-accent bg-accent/5'
|
|
538
|
+
: 'border-border-subtle bg-surface-1 hover:border-border',
|
|
539
|
+
)}
|
|
540
|
+
>
|
|
541
|
+
<Plus size={13} className={teamMode === 'new' ? 'text-accent' : 'text-text-3'} />
|
|
542
|
+
<div>
|
|
543
|
+
<div className="text-2xs font-semibold text-text-0 font-sans">New Team</div>
|
|
544
|
+
</div>
|
|
545
|
+
</button>
|
|
546
|
+
<button
|
|
547
|
+
onClick={() => setTeamMode('existing')}
|
|
548
|
+
className={cn(
|
|
549
|
+
'flex-1 flex items-center gap-2 px-3 py-2 rounded-md border text-left transition-all cursor-pointer',
|
|
550
|
+
teamMode === 'existing'
|
|
551
|
+
? 'border-accent bg-accent/5'
|
|
552
|
+
: 'border-border-subtle bg-surface-1 hover:border-border',
|
|
553
|
+
)}
|
|
554
|
+
>
|
|
555
|
+
<Users size={13} className={teamMode === 'existing' ? 'text-accent' : 'text-text-3'} />
|
|
556
|
+
<div>
|
|
557
|
+
<div className="text-2xs font-semibold text-text-0 font-sans">Existing</div>
|
|
558
|
+
</div>
|
|
559
|
+
</button>
|
|
560
|
+
</div>
|
|
561
|
+
{teamMode === 'new' && (
|
|
562
|
+
<Input
|
|
563
|
+
value={newTeamName}
|
|
564
|
+
onChange={(e) => setNewTeamName(e.target.value)}
|
|
565
|
+
placeholder="Team name..."
|
|
566
|
+
className="text-xs"
|
|
567
|
+
/>
|
|
568
|
+
)}
|
|
569
|
+
{teamMode === 'existing' && (
|
|
570
|
+
<div className="relative">
|
|
571
|
+
<select
|
|
572
|
+
value={selectedTeamId}
|
|
573
|
+
onChange={(e) => setSelectedTeamId(e.target.value)}
|
|
574
|
+
className="w-full h-8 px-3 pr-8 text-sm 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"
|
|
575
|
+
>
|
|
576
|
+
<option value="">Select team...</option>
|
|
577
|
+
{teams.filter((t) => !t.archived).map((t) => (
|
|
578
|
+
<option key={t.id} value={t.id}>{t.name}</option>
|
|
579
|
+
))}
|
|
580
|
+
</select>
|
|
581
|
+
<ChevronDown size={14} className="absolute right-2 top-1/2 -translate-y-1/2 text-text-3 pointer-events-none" />
|
|
582
|
+
</div>
|
|
583
|
+
)}
|
|
584
|
+
</div>
|
|
585
|
+
|
|
462
586
|
{/* Integrations */}
|
|
463
587
|
<div className="space-y-1.5">
|
|
464
588
|
<label className="text-xs font-medium text-text-2 font-sans">Integrations</label>
|
|
@@ -756,18 +880,22 @@ export function SpawnWizard() {
|
|
|
756
880
|
</div>
|
|
757
881
|
)}
|
|
758
882
|
{selectedRole && installedProviders.length > 0 && (
|
|
759
|
-
<div className="flex items-center gap-2 mb-3 text-xs text-text-3 font-sans">
|
|
883
|
+
<div className="flex items-center gap-2 mb-3 text-xs text-text-3 font-sans flex-wrap">
|
|
760
884
|
<span>Spawning</span>
|
|
761
885
|
<Badge variant="accent">{selectedRole}</Badge>
|
|
762
886
|
{provider && <span>on {selectedProvider?.name || provider}</span>}
|
|
763
887
|
{name && <span>as {name.replace(/\s+/g, '-')}</span>}
|
|
888
|
+
{teamMode === 'new' && <span>in new team{newTeamName ? ` "${newTeamName}"` : ''}</span>}
|
|
889
|
+
{teamMode === 'existing' && selectedTeamId && (
|
|
890
|
+
<span>in {teams.find((t) => t.id === selectedTeamId)?.name || 'team'}</span>
|
|
891
|
+
)}
|
|
764
892
|
</div>
|
|
765
893
|
)}
|
|
766
894
|
<Button
|
|
767
895
|
variant="primary"
|
|
768
896
|
size="lg"
|
|
769
897
|
onClick={handleSpawn}
|
|
770
|
-
disabled={!selectedRole || spawning || installedProviders.length === 0}
|
|
898
|
+
disabled={!selectedRole || spawning || installedProviders.length === 0 || (teamMode === 'existing' && !selectedTeamId)}
|
|
771
899
|
className="w-full"
|
|
772
900
|
>
|
|
773
901
|
{spawning ? 'Spawning...' : 'Spawn Agent'}
|
|
@@ -3,11 +3,12 @@ import { useState, useRef, useEffect } from 'react';
|
|
|
3
3
|
import { Pencil, Pin, PinOff, Trash2, Hash, MoreHorizontal, ChevronDown, X } from 'lucide-react';
|
|
4
4
|
import { useGrooveStore } from '../../stores/groove';
|
|
5
5
|
import { fmtNum } from '../../lib/format';
|
|
6
|
-
import { ModelPicker, formatModelName } from './model-picker';
|
|
7
6
|
import { cn } from '../../lib/cn';
|
|
8
7
|
import { roleColor } from '../../lib/status';
|
|
9
8
|
|
|
10
9
|
const CHAT_ROLES = [
|
|
10
|
+
{ id: 'chat', label: 'Chat', desc: 'General conversation' },
|
|
11
|
+
{ id: 'research', label: 'Research Assistant', desc: 'Explore ideas, web search' },
|
|
11
12
|
{ id: 'fullstack', label: 'Fullstack', desc: 'End-to-end engineering' },
|
|
12
13
|
{ id: 'backend', label: 'Backend', desc: 'APIs, services, databases' },
|
|
13
14
|
{ id: 'frontend', label: 'Frontend', desc: 'UI, components, styling' },
|
|
@@ -24,7 +25,7 @@ const CHAT_ROLES = [
|
|
|
24
25
|
{ id: 'support', label: 'Support', desc: 'Customer support, FAQs' },
|
|
25
26
|
];
|
|
26
27
|
|
|
27
|
-
export function ChatHeader({ conversation,
|
|
28
|
+
export function ChatHeader({ conversation, role, onRoleChange, sidebarCollapsed }) {
|
|
28
29
|
const renameConversation = useGrooveStore((s) => s.renameConversation);
|
|
29
30
|
const pinConversation = useGrooveStore((s) => s.pinConversation);
|
|
30
31
|
const deleteConversation = useGrooveStore((s) => s.deleteConversation);
|
|
@@ -155,12 +156,6 @@ export function ChatHeader({ conversation, model, onModelChange, role, onRoleCha
|
|
|
155
156
|
)}
|
|
156
157
|
</div>
|
|
157
158
|
|
|
158
|
-
<ModelPicker
|
|
159
|
-
value={model || { provider: conversation.provider, model: conversation.model }}
|
|
160
|
-
onChange={onModelChange}
|
|
161
|
-
disabled={false}
|
|
162
|
-
/>
|
|
163
|
-
|
|
164
159
|
{tokens > 0 && (
|
|
165
160
|
<span className="text-2xs text-text-3 font-mono tabular-nums">{fmtNum(tokens)} tok</span>
|
|
166
161
|
)}
|