groove-dev 0.27.142 → 0.27.143
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/gui/dist/assets/{index-BqdwIFn4.css → index-CCVvAoQn.css} +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-Bjd91ufV.js → index-DGIv_TRm.js} +35 -35
- 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-chat.jsx +74 -17
- package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/views/models.jsx +2 -3
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/gui/dist/assets/{index-BqdwIFn4.css → index-CCVvAoQn.css} +1 -1
- package/packages/gui/dist/assets/{index-Bjd91ufV.js → index-DGIv_TRm.js} +35 -35
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-chat.jsx +74 -17
- package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
- package/packages/gui/src/views/models.jsx +2 -3
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
8
|
<title>Groove GUI</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DGIv_TRm.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BYKpdS2W.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CCVvAoQn.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useState, useRef, useEffect } from 'react';
|
|
3
|
-
import { Send, Loader2, MessageSquare, HelpCircle, ArrowRight, Paperclip, Square } from 'lucide-react';
|
|
3
|
+
import { Send, Loader2, MessageSquare, HelpCircle, ArrowRight, Paperclip, Square, FileCode, Terminal as TerminalIcon, X } from 'lucide-react';
|
|
4
4
|
import { useGrooveStore } from '../../stores/groove';
|
|
5
5
|
import { cn } from '../../lib/cn';
|
|
6
6
|
import { ThinkingIndicator } from '../ui/thinking-indicator';
|
|
@@ -145,12 +145,40 @@ function TypingIndicator() {
|
|
|
145
145
|
);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
function SnippetTag({ snippet, onRemove }) {
|
|
149
|
+
const isCode = snippet.type === 'code';
|
|
150
|
+
const Icon = isCode ? FileCode : TerminalIcon;
|
|
151
|
+
const lines = snippet.code.split('\n').length;
|
|
152
|
+
let label;
|
|
153
|
+
if (isCode && snippet.filePath) {
|
|
154
|
+
const fileName = snippet.filePath.split('/').pop();
|
|
155
|
+
label = `${fileName}:${snippet.lineStart}-${snippet.lineEnd}`;
|
|
156
|
+
} else {
|
|
157
|
+
label = `${isCode ? '' : 'Terminal · '}${lines} line${lines !== 1 ? 's' : ''}`;
|
|
158
|
+
}
|
|
159
|
+
return (
|
|
160
|
+
<div className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-accent/10 border border-accent/20 text-accent">
|
|
161
|
+
<Icon size={11} className="flex-shrink-0" />
|
|
162
|
+
<span className="text-2xs font-sans font-medium truncate max-w-[160px]">{label}</span>
|
|
163
|
+
{snippet.instruction && (
|
|
164
|
+
<span className="text-2xs text-accent/60 truncate max-w-[100px]">· {snippet.instruction}</span>
|
|
165
|
+
)}
|
|
166
|
+
<button onClick={onRemove} className="p-0.5 rounded hover:bg-accent/20 cursor-pointer flex-shrink-0">
|
|
167
|
+
<X size={9} />
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
148
173
|
export function AgentChat({ agent }) {
|
|
149
174
|
const chatHistory = useGrooveStore((s) => s.chatHistory[agent.id]) || EMPTY;
|
|
150
175
|
const activityLog = useGrooveStore((s) => s.activityLog[agent.id]) || EMPTY;
|
|
151
176
|
const instructAgent = useGrooveStore((s) => s.instructAgent);
|
|
152
177
|
const isThinking = useGrooveStore((s) => s.thinkingAgents?.has(agent.id));
|
|
153
178
|
|
|
179
|
+
const pendingSnippet = useGrooveStore((s) => s.editorPendingSnippet);
|
|
180
|
+
const clearSnippet = useGrooveStore((s) => s.clearSnippet);
|
|
181
|
+
|
|
154
182
|
const storeInput = useGrooveStore((s) => s.chatInputs[agent.id] || '');
|
|
155
183
|
const setStoreInput = (val) => useGrooveStore.setState((s) => ({ chatInputs: { ...s.chatInputs, [agent.id]: val } }));
|
|
156
184
|
const input = storeInput;
|
|
@@ -162,6 +190,10 @@ export function AgentChat({ agent }) {
|
|
|
162
190
|
const fileInputRef = useRef(null);
|
|
163
191
|
const isAtBottomRef = useRef(true);
|
|
164
192
|
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (pendingSnippet) inputRef.current?.focus();
|
|
195
|
+
}, [pendingSnippet]);
|
|
196
|
+
|
|
165
197
|
useEffect(() => {
|
|
166
198
|
const el = scrollRef.current;
|
|
167
199
|
if (!el) return;
|
|
@@ -192,16 +224,30 @@ export function AgentChat({ agent }) {
|
|
|
192
224
|
|
|
193
225
|
async function handleSend() {
|
|
194
226
|
const text = input.trim();
|
|
195
|
-
if (!text || sending) return;
|
|
227
|
+
if ((!text && !pendingSnippet) || sending) return;
|
|
228
|
+
const parts = [];
|
|
229
|
+
if (text) parts.push(text);
|
|
230
|
+
if (pendingSnippet) {
|
|
231
|
+
const s = pendingSnippet;
|
|
232
|
+
if (s.type === 'code' && s.filePath) {
|
|
233
|
+
if (s.instruction && !text) parts.push(s.instruction);
|
|
234
|
+
parts.push(`File: ${s.filePath} (lines ${s.lineStart}-${s.lineEnd})`);
|
|
235
|
+
parts.push('```\n' + s.code + '\n```');
|
|
236
|
+
} else if (s.code) {
|
|
237
|
+
parts.push('```\n' + s.code + '\n```');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const message = parts.join('\n\n');
|
|
196
241
|
setInput('');
|
|
197
242
|
setAttachedFiles([]);
|
|
243
|
+
clearSnippet();
|
|
198
244
|
setSending(true);
|
|
199
245
|
isAtBottomRef.current = true;
|
|
200
246
|
requestAnimationFrame(() => {
|
|
201
247
|
if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
202
248
|
});
|
|
203
249
|
try {
|
|
204
|
-
await instructAgent(agent.id,
|
|
250
|
+
await instructAgent(agent.id, message);
|
|
205
251
|
} catch { /* toast handles */ }
|
|
206
252
|
setSending(false);
|
|
207
253
|
inputRef.current?.focus();
|
|
@@ -251,6 +297,22 @@ export function AgentChat({ agent }) {
|
|
|
251
297
|
|
|
252
298
|
{/* ── Input area ──────────────────────────────────── */}
|
|
253
299
|
<div className="border-t border-border-subtle px-3 py-2 bg-surface-1">
|
|
300
|
+
{pendingSnippet && (
|
|
301
|
+
<div className="mb-1.5">
|
|
302
|
+
<SnippetTag snippet={pendingSnippet} onRemove={clearSnippet} />
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
{input && /\[(?:save|append|update|delete|view|doc|link|read|instruct)\]/i.test(input) && (() => {
|
|
306
|
+
const cmdMatch = input.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
|
|
307
|
+
const tags = (input.match(/#[\w/.-]+/g) || []);
|
|
308
|
+
return (
|
|
309
|
+
<div className="flex items-center gap-1.5 px-3 py-1 mb-1.5 rounded-md bg-accent/5 border border-accent/10">
|
|
310
|
+
<span className="px-1.5 py-0.5 rounded bg-accent/15 text-accent font-semibold font-mono text-2xs">{cmdMatch[0]}</span>
|
|
311
|
+
{tags.map((tag, i) => <span key={i} className="text-accent font-medium text-2xs">{tag}</span>)}
|
|
312
|
+
<span className="text-2xs text-text-4 ml-auto">memory command</span>
|
|
313
|
+
</div>
|
|
314
|
+
);
|
|
315
|
+
})()}
|
|
254
316
|
<div className="flex items-end gap-1.5">
|
|
255
317
|
<input
|
|
256
318
|
ref={fileInputRef}
|
|
@@ -267,28 +329,23 @@ export function AgentChat({ agent }) {
|
|
|
267
329
|
>
|
|
268
330
|
<Paperclip size={14} />
|
|
269
331
|
</button>
|
|
270
|
-
<div className="flex-1
|
|
271
|
-
<div
|
|
272
|
-
aria-hidden
|
|
273
|
-
className="absolute inset-0 px-3 py-1.5 text-xs font-sans pointer-events-none whitespace-pre-wrap break-words overflow-hidden min-h-[32px] max-h-[120px] leading-[1.625]"
|
|
274
|
-
>
|
|
275
|
-
{input ? highlightKeeper(input) : null}
|
|
276
|
-
</div>
|
|
332
|
+
<div className="flex-1">
|
|
277
333
|
<textarea
|
|
278
334
|
ref={inputRef}
|
|
279
335
|
value={input}
|
|
280
336
|
onChange={(e) => setInput(e.target.value)}
|
|
281
337
|
onKeyDown={onKeyDown}
|
|
282
|
-
placeholder={isAlive ? 'Instruct this agent...' : 'Continue conversation...'}
|
|
338
|
+
placeholder={pendingSnippet ? 'Add a message (optional)...' : isAlive ? 'Instruct this agent...' : 'Continue conversation...'}
|
|
283
339
|
rows={1}
|
|
284
340
|
className={cn(
|
|
285
341
|
'w-full resize-none rounded-lg px-3 py-1.5 text-xs',
|
|
286
|
-
'bg-
|
|
342
|
+
'bg-surface-0 border font-sans',
|
|
287
343
|
'placeholder:text-text-4',
|
|
288
|
-
'focus:outline-none',
|
|
344
|
+
'focus:outline-none focus:ring-1',
|
|
289
345
|
'min-h-[32px] max-h-[120px]',
|
|
290
|
-
|
|
291
|
-
|
|
346
|
+
'border-border focus:ring-accent/40',
|
|
347
|
+
input && /\[(?:save|append|update|delete|view|doc|link|read|instruct)\]/i.test(input)
|
|
348
|
+
? 'text-accent'
|
|
292
349
|
: 'text-text-0',
|
|
293
350
|
)}
|
|
294
351
|
/>
|
|
@@ -304,11 +361,11 @@ export function AgentChat({ agent }) {
|
|
|
304
361
|
)}
|
|
305
362
|
<button
|
|
306
363
|
onClick={handleSend}
|
|
307
|
-
disabled={!input.trim() || sending}
|
|
364
|
+
disabled={(!input.trim() && !pendingSnippet) || sending}
|
|
308
365
|
className={cn(
|
|
309
366
|
'w-8 h-8 flex items-center justify-center rounded-lg transition-all cursor-pointer flex-shrink-0',
|
|
310
367
|
'disabled:opacity-20 disabled:cursor-not-allowed',
|
|
311
|
-
input.trim()
|
|
368
|
+
(input.trim() || pendingSnippet)
|
|
312
369
|
? 'bg-accent/15 text-accent hover:bg-accent/25 border border-accent/25'
|
|
313
370
|
: 'bg-surface-4 text-text-4',
|
|
314
371
|
)}
|
|
@@ -18,6 +18,7 @@ export function SelectionMenu({ x, y, filePath, lineStart, lineEnd, selectedCode
|
|
|
18
18
|
const sendCodeToAgent = useGrooveStore((s) => s.sendCodeToAgent);
|
|
19
19
|
const toggleAiPanel = useGrooveStore((s) => s.toggleAiPanel);
|
|
20
20
|
const aiPanelOpen = useGrooveStore((s) => s.editorAiPanelOpen);
|
|
21
|
+
const selectAgent = useGrooveStore((s) => s.selectAgent);
|
|
21
22
|
|
|
22
23
|
useEffect(() => {
|
|
23
24
|
function handleClick(e) {
|
|
@@ -51,6 +52,7 @@ export function SelectionMenu({ x, y, filePath, lineStart, lineEnd, selectedCode
|
|
|
51
52
|
if (!agentId) return;
|
|
52
53
|
sendCodeToAgent(agentId, action.instruction, filePath, lineStart, lineEnd, selectedCode);
|
|
53
54
|
if (!aiPanelOpen) toggleAiPanel();
|
|
55
|
+
selectAgent(agentId);
|
|
54
56
|
onClose();
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
|
3
|
-
import { ScrollArea } from '../components/ui/scroll-area';
|
|
4
3
|
import { Badge } from '../components/ui/badge';
|
|
5
4
|
import { Button } from '../components/ui/button';
|
|
6
5
|
import { api } from '../lib/api';
|
|
@@ -749,7 +748,7 @@ export default function ModelsView() {
|
|
|
749
748
|
</div>
|
|
750
749
|
|
|
751
750
|
{/* ════ ZONE 2 + 3: Scrollable Content ════ */}
|
|
752
|
-
<
|
|
751
|
+
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
753
752
|
<div className="p-5 space-y-6">
|
|
754
753
|
|
|
755
754
|
{/* Empty State */}
|
|
@@ -965,7 +964,7 @@ export default function ModelsView() {
|
|
|
965
964
|
)}
|
|
966
965
|
</div>
|
|
967
966
|
</div>
|
|
968
|
-
</
|
|
967
|
+
</div>
|
|
969
968
|
</div>
|
|
970
969
|
);
|
|
971
970
|
}
|