groove-dev 0.26.8 → 0.26.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@groove-dev/daemon/src/agent-loop.js +10 -7
- package/node_modules/@groove-dev/gui/dist/assets/{index-DVRmIjTA.css → index-C4cVCdfw.css} +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-UCdYAYEi.js +633 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/stores/groove.js +12 -8
- package/node_modules/@groove-dev/gui/src/views/models.jsx +45 -22
- package/package.json +1 -1
- package/packages/daemon/src/agent-loop.js +10 -7
- package/packages/gui/dist/assets/{index-DVRmIjTA.css → index-C4cVCdfw.css} +1 -1
- package/packages/gui/dist/assets/index-UCdYAYEi.js +633 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/src/stores/groove.js +12 -8
- package/packages/gui/src/views/models.jsx +45 -22
- package/node_modules/@groove-dev/gui/dist/assets/index-PPbrScja.js +0 -633
- package/packages/gui/dist/assets/index-PPbrScja.js +0 -633
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
7
7
|
<title>Groove GUI</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-UCdYAYEi.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C4cVCdfw.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="root"></div>
|
|
@@ -157,11 +157,13 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
157
157
|
set({ agents });
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
// Text responses → chat bubbles
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
// Text responses → chat bubbles
|
|
161
|
+
// Skip pure token-level stream chunks (subtype='stream') — too granular
|
|
162
|
+
// Show: subtype='assistant' (Claude Code), subtype='text' (agent loop), type='result',
|
|
163
|
+
// and plain activity events with string data (Gemini/Codex/Ollama CLI)
|
|
164
|
+
const isTokenStream = data.subtype === 'stream';
|
|
165
|
+
const showAsChat = chatText && chatText.trim() && !isTokenStream && (
|
|
166
|
+
data.subtype === 'assistant' || data.subtype === 'text' || data.type === 'result' ||
|
|
165
167
|
(data.type === 'activity' && typeof data.data === 'string')
|
|
166
168
|
);
|
|
167
169
|
if (showAsChat) {
|
|
@@ -171,9 +173,11 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
171
173
|
const last = arr[arr.length - 1];
|
|
172
174
|
const isRecent = last && last.from === 'agent' && (Date.now() - last.timestamp) < 8000;
|
|
173
175
|
|
|
174
|
-
if (isRecent
|
|
175
|
-
//
|
|
176
|
-
|
|
176
|
+
if (isRecent) {
|
|
177
|
+
// Append to the last agent message (streaming from any provider)
|
|
178
|
+
// Claude Code blocks use \n\n separator; plain text uses space
|
|
179
|
+
const sep = data.subtype === 'assistant' ? '\n\n' : ' ';
|
|
180
|
+
arr[arr.length - 1] = { ...last, text: last.text + sep + chatText.trim(), timestamp: Date.now() };
|
|
177
181
|
} else {
|
|
178
182
|
// New message bubble
|
|
179
183
|
arr.push({ from: 'agent', text: chatText.trim(), timestamp: Date.now() });
|
|
@@ -200,13 +200,16 @@ function FilePicker({ repoId, onDownload, systemRamGb }) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
// ---- Recommended Model Card ----
|
|
203
|
-
function RecommendedModel({ model, systemRamGb, onPull, pulling }) {
|
|
203
|
+
function RecommendedModel({ model, systemRamGb, onPull, pulling, isInstalled }) {
|
|
204
204
|
const tierColors = { light: 'text-green-400', medium: 'text-blue-400', heavy: 'text-orange-400' };
|
|
205
205
|
const categoryIcons = { code: '{}', general: 'AI' };
|
|
206
206
|
const headroom = systemRamGb ? Math.round((1 - model.ramGb / systemRamGb) * 100) : null;
|
|
207
207
|
|
|
208
208
|
return (
|
|
209
|
-
<div className=
|
|
209
|
+
<div className={cn(
|
|
210
|
+
'flex items-center gap-3 px-4 py-3 border rounded-lg transition-colors',
|
|
211
|
+
isInstalled ? 'bg-success/5 border-success/20' : 'bg-surface-1 border-border-subtle hover:border-accent/20',
|
|
212
|
+
)}>
|
|
210
213
|
<div className="w-9 h-9 rounded-lg bg-surface-3 flex items-center justify-center text-xs font-mono text-text-2 flex-shrink-0">
|
|
211
214
|
{categoryIcons[model.category] || 'AI'}
|
|
212
215
|
</div>
|
|
@@ -214,6 +217,7 @@ function RecommendedModel({ model, systemRamGb, onPull, pulling }) {
|
|
|
214
217
|
<div className="flex items-center gap-2">
|
|
215
218
|
<span className="text-sm font-mono font-bold text-text-0 truncate">{model.name}</span>
|
|
216
219
|
<span className={cn('text-2xs font-semibold capitalize', tierColors[model.tier])}>{model.tier}</span>
|
|
220
|
+
{isInstalled && <Badge variant="success" className="text-2xs gap-1"><Check size={8} /> Installed</Badge>}
|
|
217
221
|
</div>
|
|
218
222
|
<div className="text-2xs text-text-3 font-sans mt-0.5">{model.description}</div>
|
|
219
223
|
<div className="flex items-center gap-3 mt-1 text-2xs font-sans">
|
|
@@ -222,14 +226,18 @@ function RecommendedModel({ model, systemRamGb, onPull, pulling }) {
|
|
|
222
226
|
{headroom !== null && <span className="text-text-4">{headroom}% headroom</span>}
|
|
223
227
|
</div>
|
|
224
228
|
</div>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
{isInstalled ? (
|
|
230
|
+
<span className="text-xs text-success font-sans font-medium px-3 py-1.5">Ready</span>
|
|
231
|
+
) : (
|
|
232
|
+
<button
|
|
233
|
+
onClick={() => onPull(model.id)}
|
|
234
|
+
disabled={pulling === model.id}
|
|
235
|
+
className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-sans font-medium bg-accent/10 text-accent hover:bg-accent/20 transition-colors cursor-pointer disabled:opacity-40"
|
|
236
|
+
>
|
|
237
|
+
{pulling === model.id ? <Loader2 size={12} className="animate-spin" /> : <Download size={12} />}
|
|
238
|
+
Pull
|
|
239
|
+
</button>
|
|
240
|
+
)}
|
|
233
241
|
</div>
|
|
234
242
|
);
|
|
235
243
|
}
|
|
@@ -246,6 +254,7 @@ export default function ModelsView() {
|
|
|
246
254
|
const [hardware, setHardware] = useState(null);
|
|
247
255
|
const [expandedResult, setExpandedResult] = useState(null);
|
|
248
256
|
const [pulling, setPulling] = useState(null);
|
|
257
|
+
const [ollamaModels, setOllamaModels] = useState([]);
|
|
249
258
|
const toast = useToast();
|
|
250
259
|
|
|
251
260
|
// Fetch installed models
|
|
@@ -255,7 +264,13 @@ export default function ModelsView() {
|
|
|
255
264
|
}).catch(() => {});
|
|
256
265
|
}, []);
|
|
257
266
|
|
|
258
|
-
|
|
267
|
+
const fetchOllamaModels = useCallback(() => {
|
|
268
|
+
api.get('/providers/ollama/models').then((data) => {
|
|
269
|
+
setOllamaModels((data.installed || []).map((m) => m.id));
|
|
270
|
+
}).catch(() => {});
|
|
271
|
+
}, []);
|
|
272
|
+
|
|
273
|
+
// Fetch hardware info + recommended models + Ollama installed
|
|
259
274
|
useEffect(() => {
|
|
260
275
|
api.get('/providers/ollama/hardware').then(setHardware).catch(() => {});
|
|
261
276
|
api.get('/models/recommended').then((data) => {
|
|
@@ -263,14 +278,16 @@ export default function ModelsView() {
|
|
|
263
278
|
if (!hardware && data.hardware) setHardware(data.hardware);
|
|
264
279
|
}).catch(() => {});
|
|
265
280
|
fetchInstalled();
|
|
266
|
-
|
|
281
|
+
fetchOllamaModels();
|
|
282
|
+
}, [fetchInstalled, fetchOllamaModels]);
|
|
267
283
|
|
|
268
284
|
async function handlePull(modelId) {
|
|
269
285
|
setPulling(modelId);
|
|
270
286
|
try {
|
|
271
287
|
await api.post('/providers/ollama/pull', { model: modelId });
|
|
272
|
-
toast.success(`${modelId}
|
|
288
|
+
toast.success(`${modelId} ready to use`);
|
|
273
289
|
fetchInstalled();
|
|
290
|
+
fetchOllamaModels();
|
|
274
291
|
} catch (err) {
|
|
275
292
|
toast.error(`Pull failed: ${err.message}`);
|
|
276
293
|
}
|
|
@@ -419,15 +436,21 @@ export default function ModelsView() {
|
|
|
419
436
|
<div className="text-xs text-text-3 font-sans mb-2">
|
|
420
437
|
Top models for your system ({hardware?.totalRamGb || '?'} GB RAM). Click Pull to download via Ollama.
|
|
421
438
|
</div>
|
|
422
|
-
{recommended.map((m) =>
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
439
|
+
{recommended.map((m) => {
|
|
440
|
+
// Check if this model (or a variant) is already installed in Ollama
|
|
441
|
+
const baseId = m.id.split(':')[0];
|
|
442
|
+
const isInstalled = ollamaModels.some((id) => id === m.id || id.startsWith(baseId + ':') || id === baseId);
|
|
443
|
+
return (
|
|
444
|
+
<RecommendedModel
|
|
445
|
+
key={m.id}
|
|
446
|
+
model={m}
|
|
447
|
+
systemRamGb={hardware?.totalRamGb}
|
|
448
|
+
onPull={handlePull}
|
|
449
|
+
pulling={pulling}
|
|
450
|
+
isInstalled={isInstalled}
|
|
451
|
+
/>
|
|
452
|
+
);
|
|
453
|
+
})}
|
|
431
454
|
</>
|
|
432
455
|
)}
|
|
433
456
|
</>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.10",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -126,18 +126,21 @@ export class AgentLoop extends EventEmitter {
|
|
|
126
126
|
}
|
|
127
127
|
this.messages.push(assistantMsg);
|
|
128
128
|
|
|
129
|
-
//
|
|
130
|
-
if (content) {
|
|
131
|
-
this._writeLog({ type: 'assistant', content: content.slice(0, 2000) });
|
|
132
|
-
this.emit('output', { type: 'activity', subtype: 'text', data: content });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// No tool calls → turn complete, go idle
|
|
129
|
+
// No tool calls → turn complete, broadcast final text and go idle
|
|
136
130
|
if (!toolCalls || toolCalls.length === 0) {
|
|
131
|
+
if (content) {
|
|
132
|
+
this._writeLog({ type: 'assistant', content: content.slice(0, 2000) });
|
|
133
|
+
}
|
|
137
134
|
this.emit('output', { type: 'result', data: content || 'Turn complete', turns: this.turns });
|
|
138
135
|
break;
|
|
139
136
|
}
|
|
140
137
|
|
|
138
|
+
// Has tool calls — broadcast text before executing tools (if model sent text + tools)
|
|
139
|
+
if (content) {
|
|
140
|
+
this._writeLog({ type: 'assistant', content: content.slice(0, 2000) });
|
|
141
|
+
this.emit('output', { type: 'activity', subtype: 'text', data: content });
|
|
142
|
+
}
|
|
143
|
+
|
|
141
144
|
// Execute each tool call
|
|
142
145
|
for (const call of toolCalls) {
|
|
143
146
|
if (!this.running) break;
|