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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
4
|
+
import { MessageCircleQuestion, Send } from 'lucide-react';
|
|
5
|
+
import { Button } from '../ui/button';
|
|
6
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
7
|
+
|
|
8
|
+
export function QuestionModal() {
|
|
9
|
+
const pendingQuestions = useGrooveStore((s) => s.pendingQuestions);
|
|
10
|
+
const answerQuestion = useGrooveStore((s) => s.answerQuestion);
|
|
11
|
+
const [answers, setAnswers] = useState({});
|
|
12
|
+
|
|
13
|
+
if (!pendingQuestions?.length) return null;
|
|
14
|
+
|
|
15
|
+
function handleSubmit(q) {
|
|
16
|
+
const questionAnswers = {};
|
|
17
|
+
for (const qItem of q.questions) {
|
|
18
|
+
const key = qItem.question || qItem.header || `q${q.questions.indexOf(qItem)}`;
|
|
19
|
+
questionAnswers[key] = answers[`${q.agentId}:${key}`] || '';
|
|
20
|
+
}
|
|
21
|
+
answerQuestion(q.agentId, questionAnswers);
|
|
22
|
+
setAnswers((prev) => {
|
|
23
|
+
const next = { ...prev };
|
|
24
|
+
for (const key of Object.keys(next)) {
|
|
25
|
+
if (key.startsWith(q.agentId + ':')) delete next[key];
|
|
26
|
+
}
|
|
27
|
+
return next;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="fixed bottom-10 left-1/2 -translate-x-1/2 z-50 w-full max-w-lg flex flex-col gap-2 px-4">
|
|
33
|
+
<AnimatePresence>
|
|
34
|
+
{pendingQuestions.map((q) => (
|
|
35
|
+
<motion.div
|
|
36
|
+
key={q.id}
|
|
37
|
+
initial={{ y: 20, opacity: 0 }}
|
|
38
|
+
animate={{ y: 0, opacity: 1 }}
|
|
39
|
+
exit={{ y: 20, opacity: 0 }}
|
|
40
|
+
transition={{ duration: 0.2 }}
|
|
41
|
+
className="rounded-lg border border-accent/30 bg-surface-2/95 backdrop-blur-md shadow-xl shadow-accent/5 overflow-hidden"
|
|
42
|
+
>
|
|
43
|
+
<div className="px-4 py-3 flex items-start gap-3">
|
|
44
|
+
<MessageCircleQuestion size={16} className="text-accent shrink-0 mt-0.5" />
|
|
45
|
+
<div className="flex-1 min-w-0">
|
|
46
|
+
<p className="text-sm font-semibold text-text-0 font-sans">
|
|
47
|
+
{q.agentName || 'Agent'} has a question
|
|
48
|
+
</p>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div className="px-4 pb-3 flex flex-col gap-2">
|
|
52
|
+
{q.questions.map((qItem, i) => {
|
|
53
|
+
const key = qItem.question || qItem.header || `q${i}`;
|
|
54
|
+
const inputKey = `${q.agentId}:${key}`;
|
|
55
|
+
return (
|
|
56
|
+
<div key={i}>
|
|
57
|
+
<p className="text-2xs text-text-2 font-sans mb-1">{qItem.question || key}</p>
|
|
58
|
+
{qItem.options?.length > 0 ? (
|
|
59
|
+
<div className="flex flex-wrap gap-1">
|
|
60
|
+
{qItem.options.map((opt) => {
|
|
61
|
+
const label = typeof opt === 'string' ? opt : opt.label;
|
|
62
|
+
const selected = answers[inputKey] === label;
|
|
63
|
+
return (
|
|
64
|
+
<button
|
|
65
|
+
key={label}
|
|
66
|
+
onClick={() => setAnswers((p) => ({ ...p, [inputKey]: label }))}
|
|
67
|
+
className={`px-2 py-1 text-2xs rounded border font-sans transition-colors ${
|
|
68
|
+
selected
|
|
69
|
+
? 'border-accent bg-accent/20 text-text-0'
|
|
70
|
+
: 'border-border-subtle bg-surface-1 text-text-2 hover:border-accent/50'
|
|
71
|
+
}`}
|
|
72
|
+
>
|
|
73
|
+
{label}
|
|
74
|
+
</button>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</div>
|
|
78
|
+
) : (
|
|
79
|
+
<input
|
|
80
|
+
type="text"
|
|
81
|
+
className="w-full px-2 py-1.5 text-xs rounded border border-border-subtle bg-surface-1 text-text-0 font-sans placeholder:text-text-3 focus:outline-none focus:border-accent"
|
|
82
|
+
placeholder="Type your answer..."
|
|
83
|
+
value={answers[inputKey] || ''}
|
|
84
|
+
onChange={(e) => setAnswers((p) => ({ ...p, [inputKey]: e.target.value }))}
|
|
85
|
+
onKeyDown={(e) => e.key === 'Enter' && handleSubmit(q)}
|
|
86
|
+
/>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
})}
|
|
91
|
+
</div>
|
|
92
|
+
<div className="px-4 py-2.5 border-t border-border-subtle flex items-center justify-end">
|
|
93
|
+
<Button
|
|
94
|
+
size="sm"
|
|
95
|
+
variant="accent"
|
|
96
|
+
onClick={() => handleSubmit(q)}
|
|
97
|
+
>
|
|
98
|
+
<Send size={14} className="mr-1" />
|
|
99
|
+
Answer
|
|
100
|
+
</Button>
|
|
101
|
+
</div>
|
|
102
|
+
</motion.div>
|
|
103
|
+
))}
|
|
104
|
+
</AnimatePresence>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -30,12 +30,12 @@ export function SheetContent({ children, className, title, side = 'right', width
|
|
|
30
30
|
<DialogPrimitive.Title className="text-base font-semibold text-text-0 font-sans">
|
|
31
31
|
{title}
|
|
32
32
|
</DialogPrimitive.Title>
|
|
33
|
-
<
|
|
33
|
+
<button
|
|
34
34
|
onClick={onClose}
|
|
35
35
|
className="p-1 rounded-md text-text-3 hover:text-text-0 hover:bg-surface-5 transition-colors cursor-pointer"
|
|
36
36
|
>
|
|
37
37
|
<X size={16} />
|
|
38
|
-
</
|
|
38
|
+
</button>
|
|
39
39
|
</div>
|
|
40
40
|
)}
|
|
41
41
|
<DialogPrimitive.Description className="sr-only">Panel</DialogPrimitive.Description>
|
|
@@ -10,13 +10,13 @@ export function TuningSlider({
|
|
|
10
10
|
const display = typeof fmt === 'function' ? fmt(value) : (typeof fmt === 'string' ? fmt : value);
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
|
-
<div className={cn('group flex items-center gap-2
|
|
14
|
-
<span className="text-
|
|
13
|
+
<div className={cn('group flex items-center gap-2.5 h-8', disabled && 'opacity-40 pointer-events-none', className)}>
|
|
14
|
+
<span className="text-[11px] text-text-2 font-sans w-[76px] shrink-0 truncate">{label}</span>
|
|
15
15
|
<div className="relative flex-1 flex items-center h-5">
|
|
16
16
|
<div className="absolute inset-y-0 flex items-center w-full pointer-events-none">
|
|
17
|
-
<div className="w-full h-
|
|
17
|
+
<div className="w-full h-[3px] rounded-full bg-surface-5">
|
|
18
18
|
<div
|
|
19
|
-
className="h-full rounded-full bg-accent/
|
|
19
|
+
className="h-full rounded-full bg-accent/60 group-hover:bg-accent transition-colors"
|
|
20
20
|
style={{ width: `${pct}%` }}
|
|
21
21
|
/>
|
|
22
22
|
</div>
|
|
@@ -30,20 +30,20 @@ export function TuningSlider({
|
|
|
30
30
|
disabled={disabled}
|
|
31
31
|
onChange={(e) => onChange(Number(e.target.value))}
|
|
32
32
|
className="relative w-full h-5 appearance-none bg-transparent cursor-pointer
|
|
33
|
-
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-
|
|
33
|
+
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-2.5 [&::-webkit-slider-thumb]:h-2.5
|
|
34
34
|
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent
|
|
35
35
|
[&::-webkit-slider-thumb]:shadow-[0_0_0_2px_var(--color-surface-1)]
|
|
36
|
-
[&::-webkit-slider-thumb]:hover:
|
|
36
|
+
[&::-webkit-slider-thumb]:hover:shadow-[0_0_0_2px_var(--color-surface-1),0_0_6px_rgba(51,175,188,0.35)]
|
|
37
37
|
[&::-webkit-slider-thumb]:active:scale-110
|
|
38
38
|
[&::-webkit-slider-thumb]:transition-all
|
|
39
|
-
[&::-moz-range-thumb]:w-
|
|
39
|
+
[&::-moz-range-thumb]:w-2.5 [&::-moz-range-thumb]:h-2.5 [&::-moz-range-thumb]:rounded-full
|
|
40
40
|
[&::-moz-range-thumb]:bg-accent [&::-moz-range-thumb]:border-none
|
|
41
41
|
[&::-moz-range-thumb]:shadow-[0_0_0_2px_var(--color-surface-1)]
|
|
42
42
|
[&::-moz-range-track]:bg-transparent
|
|
43
43
|
disabled:cursor-not-allowed"
|
|
44
44
|
/>
|
|
45
45
|
</div>
|
|
46
|
-
<span className="text-
|
|
46
|
+
<span className="text-[11px] text-accent font-mono font-medium w-9 text-right shrink-0 tabular-nums">{display}</span>
|
|
47
47
|
</div>
|
|
48
48
|
);
|
|
49
49
|
}
|
|
@@ -91,6 +91,11 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
91
91
|
if (data) set({ subscription: { ...get().subscription, ...data } });
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
+
if (window.groove?.update?.onUpdateAvailable) {
|
|
95
|
+
window.groove.update.onUpdateAvailable((data) => {
|
|
96
|
+
set({ updateProgress: { percent: 0, version: data.version } });
|
|
97
|
+
});
|
|
98
|
+
}
|
|
94
99
|
if (window.groove?.update?.onUpdateProgress) {
|
|
95
100
|
window.groove.update.onUpdateProgress((data) => {
|
|
96
101
|
set({ updateProgress: data });
|
|
@@ -101,6 +106,18 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
101
106
|
set({ updateReady: data.version, updateModalOpen: true, updateProgress: null });
|
|
102
107
|
});
|
|
103
108
|
}
|
|
109
|
+
if (window.groove?.update?.getUpdateStatus) {
|
|
110
|
+
window.groove.update.getUpdateStatus().then((state) => {
|
|
111
|
+
if (!state) return;
|
|
112
|
+
if (state.downloaded) {
|
|
113
|
+
set({ updateReady: state.downloaded.version, updateProgress: null });
|
|
114
|
+
} else if (state.progress) {
|
|
115
|
+
set({ updateProgress: state.progress });
|
|
116
|
+
} else if (state.available) {
|
|
117
|
+
set({ updateProgress: { percent: 0, version: state.available.version } });
|
|
118
|
+
}
|
|
119
|
+
}).catch(() => {});
|
|
120
|
+
}
|
|
104
121
|
};
|
|
105
122
|
|
|
106
123
|
ws.onmessage = (event) => {
|
|
@@ -297,6 +314,11 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
297
314
|
}
|
|
298
315
|
|
|
299
316
|
case 'agent:exit': {
|
|
317
|
+
if (msg.status === 'waiting_for_input') {
|
|
318
|
+
const waitAgent = get().agents.find((a) => a.id === msg.agentId);
|
|
319
|
+
get().addToast('info', `${waitAgent?.name || msg.agentId.slice(0, 8)} needs your input`, 'Check the question popup below');
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
300
322
|
const agent = get().agents.find((a) => a.id === msg.agentId);
|
|
301
323
|
const name = agent?.name || msg.agentId;
|
|
302
324
|
const isKill = msg.status === 'killed' || msg.code === 143 || msg.code === 137;
|
|
@@ -398,6 +420,14 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
398
420
|
get().addChatMessage(msg.agentId, 'system', 'Agent is working — message will be delivered when it finishes.');
|
|
399
421
|
break;
|
|
400
422
|
|
|
423
|
+
case 'agent:question':
|
|
424
|
+
set((s) => ({ pendingQuestions: [...s.pendingQuestions, msg.data] }));
|
|
425
|
+
break;
|
|
426
|
+
|
|
427
|
+
case 'agent:question:resolved':
|
|
428
|
+
set((s) => ({ pendingQuestions: s.pendingQuestions.filter((q) => q.agentId !== msg.agentId) }));
|
|
429
|
+
break;
|
|
430
|
+
|
|
401
431
|
case 'ollama:pull:progress':
|
|
402
432
|
set({ ollamaPullProgress: { ...get().ollamaPullProgress, [msg.model]: { status: 'pulling', progress: msg.progress } } });
|
|
403
433
|
break;
|
|
@@ -433,6 +463,8 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
433
463
|
case 'lab:runtime:added':
|
|
434
464
|
case 'lab:runtime:updated':
|
|
435
465
|
case 'lab:runtime:removed':
|
|
466
|
+
case 'lab:runtime:started':
|
|
467
|
+
case 'lab:runtime:stopped':
|
|
436
468
|
case 'llama:server:stopped':
|
|
437
469
|
get().fetchLabRuntimes();
|
|
438
470
|
break;
|
|
@@ -871,6 +903,17 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
871
903
|
break;
|
|
872
904
|
}
|
|
873
905
|
|
|
906
|
+
case 'conversation:tool': {
|
|
907
|
+
const { conversationId, name, summary } = msg.data || msg;
|
|
908
|
+
if (!conversationId) break;
|
|
909
|
+
set((s) => {
|
|
910
|
+
const tools = { ...s.conversationActiveTools };
|
|
911
|
+
tools[conversationId] = { name: name || 'Tool', summary: summary || null, timestamp: Date.now() };
|
|
912
|
+
return { conversationActiveTools: tools };
|
|
913
|
+
});
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
|
|
874
917
|
case 'conversation:chunk': {
|
|
875
918
|
const { conversationId, text } = msg.data || msg;
|
|
876
919
|
if (!conversationId || !text) break;
|
|
@@ -885,7 +928,9 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
885
928
|
arr.push({ from: 'assistant', text, timestamp: Date.now() });
|
|
886
929
|
}
|
|
887
930
|
msgs[conversationId] = arr.slice(-200);
|
|
888
|
-
|
|
931
|
+
const tools = { ...s.conversationActiveTools };
|
|
932
|
+
delete tools[conversationId];
|
|
933
|
+
return { conversationMessages: msgs, streamingConversationId: conversationId, conversationActiveTools: tools };
|
|
889
934
|
});
|
|
890
935
|
break;
|
|
891
936
|
}
|
|
@@ -893,7 +938,9 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
893
938
|
case 'conversation:complete': {
|
|
894
939
|
const { conversationId } = msg.data || msg;
|
|
895
940
|
if (conversationId && get().streamingConversationId === conversationId) {
|
|
896
|
-
|
|
941
|
+
const tools = { ...get().conversationActiveTools };
|
|
942
|
+
delete tools[conversationId];
|
|
943
|
+
set({ sendingMessage: false, streamingConversationId: null, conversationActiveTools: tools });
|
|
897
944
|
}
|
|
898
945
|
if (conversationId) persistJSON('groove:conversationMessages', get().conversationMessages);
|
|
899
946
|
break;
|
|
@@ -14,6 +14,15 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
14
14
|
// Track which agents are thinking (sent a message, waiting for response)
|
|
15
15
|
thinkingAgents: new Set(),
|
|
16
16
|
|
|
17
|
+
// Pending questions from agents awaiting user input
|
|
18
|
+
pendingQuestions: [],
|
|
19
|
+
|
|
20
|
+
answerQuestion(agentId, answers) {
|
|
21
|
+
return api.post(`/agents/${agentId}/answer`, { answers }).then(() => {
|
|
22
|
+
set((s) => ({ pendingQuestions: s.pendingQuestions.filter((q) => q.agentId !== agentId) }));
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
|
|
17
26
|
// ── Workspace Mode ────────────────────────────────────────
|
|
18
27
|
workspaceMode: localStorage.getItem('groove:workspaceMode') === 'true',
|
|
19
28
|
workspaceAgentId: null,
|
|
@@ -38,7 +47,7 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
38
47
|
|
|
39
48
|
async spawnAgent(config) {
|
|
40
49
|
try {
|
|
41
|
-
const teamId = get().activeTeamId;
|
|
50
|
+
const teamId = config.teamId || get().activeTeamId;
|
|
42
51
|
const agent = await api.post('/agents', { ...config, teamId });
|
|
43
52
|
get().addToast('success', `Spawned ${agent.name}`);
|
|
44
53
|
return agent;
|
|
@@ -407,15 +416,22 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
407
416
|
|
|
408
417
|
case 'read': {
|
|
409
418
|
if (tags.length === 0) { addSystemMsg('Usage: [read] #tag1 #tag2 ...'); return true; }
|
|
419
|
+
const userText = rest.replace(/#[\w/.-]+/g, '').trim();
|
|
410
420
|
get().addChatMessage(agentId, 'user', message, false);
|
|
411
421
|
const readBrief = await api.post('/keeper/pull', { tags });
|
|
412
422
|
if (readBrief?.brief) {
|
|
423
|
+
const memoryBlock = `\n\n---\nContext from memories (${tags.map(t => '#' + t).join(', ')}):\n\n${readBrief.brief}`;
|
|
424
|
+
set((s) => ({ thinkingAgents: new Set([...s.thinkingAgents, agentId]) }));
|
|
413
425
|
await api.post(`/agents/${encodeURIComponent(agentId)}/instruct`, {
|
|
414
|
-
message: `Here is context from my tagged memories:\n\n${readBrief.brief}`,
|
|
426
|
+
message: userText ? `${userText}${memoryBlock}` : `Here is context from my tagged memories:\n\n${readBrief.brief}`,
|
|
415
427
|
});
|
|
416
428
|
addSystemMsg(`Sent ${tags.map(t => '#' + t).join(', ')} to agent`);
|
|
417
429
|
} else {
|
|
418
430
|
addSystemMsg(`No memories found for ${tags.map(t => '#' + t).join(', ')}`);
|
|
431
|
+
if (userText) {
|
|
432
|
+
set((s) => ({ thinkingAgents: new Set([...s.thinkingAgents, agentId]) }));
|
|
433
|
+
await api.post(`/agents/${encodeURIComponent(agentId)}/instruct`, { message: userText });
|
|
434
|
+
}
|
|
419
435
|
}
|
|
420
436
|
return true;
|
|
421
437
|
}
|
|
@@ -10,6 +10,7 @@ export const createChatSlice = (set, get) => ({
|
|
|
10
10
|
conversations: [],
|
|
11
11
|
activeConversationId: localStorage.getItem('groove:activeConversationId') || null,
|
|
12
12
|
conversationMessages: loadJSON('groove:conversationMessages'),
|
|
13
|
+
conversationActiveTools: {},
|
|
13
14
|
sendingMessage: false,
|
|
14
15
|
streamingConversationId: null,
|
|
15
16
|
conversationRoles: loadJSON('groove:conversationRoles'),
|
|
@@ -166,20 +167,19 @@ export const createChatSlice = (set, get) => ({
|
|
|
166
167
|
|
|
167
168
|
try {
|
|
168
169
|
const body = { message };
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
170
|
+
const history = get().conversationMessages[conversationId] || [];
|
|
171
|
+
body.history = history.slice(0, -1);
|
|
172
|
+
|
|
173
|
+
const role = get().conversationRoles?.[conversationId];
|
|
174
|
+
const rules = ['Never use emojis in your responses.', 'Be professional, concise, and direct.'];
|
|
175
|
+
if (role && role !== 'chat') rules.unshift(`You are a professional ${role}. Respond with deep expertise in that domain.`);
|
|
176
|
+
if (role === 'research') rules.unshift('You are a research assistant. Help explore ideas, synthesize information, and provide thorough analysis with sources when possible.');
|
|
177
|
+
const systemCtx = rules.join(' ');
|
|
178
|
+
body.history = [
|
|
179
|
+
{ from: 'user', text: `Instructions: ${systemCtx}` },
|
|
180
|
+
{ from: 'assistant', text: 'Understood.' },
|
|
181
|
+
...body.history,
|
|
182
|
+
];
|
|
183
183
|
const effort = get().conversationReasoningEffort?.[conversationId] || 'medium';
|
|
184
184
|
const verbosity = get().conversationVerbosity?.[conversationId] || 'medium';
|
|
185
185
|
if (conv.provider === 'codex') {
|
|
@@ -12,7 +12,7 @@ import { LabAssistant } from '../components/lab/lab-assistant';
|
|
|
12
12
|
import { MetricsPanel } from '../components/lab/metrics-panel';
|
|
13
13
|
import { PresetManager } from '../components/lab/preset-manager';
|
|
14
14
|
import { cn } from '../lib/cn';
|
|
15
|
-
import { FlaskConical, PanelLeftClose, PanelLeftOpen, PanelRightClose, PanelRightOpen, Box } from 'lucide-react';
|
|
15
|
+
import { FlaskConical, PanelLeftClose, PanelLeftOpen, PanelRightClose, PanelRightOpen, Box, ChevronRight } from 'lucide-react';
|
|
16
16
|
|
|
17
17
|
const LEFT_DEFAULT = 280;
|
|
18
18
|
const LEFT_MIN = 220;
|
|
@@ -21,6 +21,39 @@ const RIGHT_DEFAULT = 240;
|
|
|
21
21
|
const RIGHT_MIN = 200;
|
|
22
22
|
const RIGHT_MAX = 360;
|
|
23
23
|
|
|
24
|
+
export function SidebarSection({ label, action, children, className, collapsible = false, defaultOpen = true }) {
|
|
25
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
26
|
+
const isOpen = collapsible ? open : true;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={cn('space-y-3', className)}>
|
|
30
|
+
{label && (
|
|
31
|
+
<div className="flex items-center justify-between h-6">
|
|
32
|
+
{collapsible ? (
|
|
33
|
+
<button
|
|
34
|
+
onClick={() => setOpen(!open)}
|
|
35
|
+
className="flex items-center gap-1.5 cursor-pointer group"
|
|
36
|
+
>
|
|
37
|
+
<ChevronRight
|
|
38
|
+
size={10}
|
|
39
|
+
className={cn(
|
|
40
|
+
'text-text-4 transition-transform duration-150 flex-shrink-0',
|
|
41
|
+
isOpen && 'rotate-90',
|
|
42
|
+
)}
|
|
43
|
+
/>
|
|
44
|
+
<span className="text-[10px] font-semibold font-sans text-text-3 uppercase tracking-widest group-hover:text-text-2 transition-colors">{label}</span>
|
|
45
|
+
</button>
|
|
46
|
+
) : (
|
|
47
|
+
<span className="text-[10px] font-semibold font-sans text-text-3 uppercase tracking-widest">{label}</span>
|
|
48
|
+
)}
|
|
49
|
+
{isOpen && action}
|
|
50
|
+
</div>
|
|
51
|
+
)}
|
|
52
|
+
{isOpen && children}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
24
57
|
function ModelSelector() {
|
|
25
58
|
const models = useGrooveStore((s) => s.labModels);
|
|
26
59
|
const runtimes = useGrooveStore((s) => s.labRuntimes);
|
|
@@ -35,18 +68,16 @@ function ModelSelector() {
|
|
|
35
68
|
|
|
36
69
|
if (!online) {
|
|
37
70
|
return (
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
</div>
|
|
71
|
+
<SidebarSection label="Model">
|
|
72
|
+
<p className="text-2xs text-text-4 font-sans">Start the runtime to select a model</p>
|
|
73
|
+
</SidebarSection>
|
|
42
74
|
);
|
|
43
75
|
}
|
|
44
76
|
|
|
45
77
|
if (models.length <= 1 && activeModel) return null;
|
|
46
78
|
|
|
47
79
|
return (
|
|
48
|
-
<
|
|
49
|
-
<span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Model</span>
|
|
80
|
+
<SidebarSection label="Model">
|
|
50
81
|
<Combobox
|
|
51
82
|
value={activeModel || ''}
|
|
52
83
|
onChange={setActiveModel}
|
|
@@ -60,7 +91,7 @@ function ModelSelector() {
|
|
|
60
91
|
</div>
|
|
61
92
|
)}
|
|
62
93
|
/>
|
|
63
|
-
</
|
|
94
|
+
</SidebarSection>
|
|
64
95
|
);
|
|
65
96
|
}
|
|
66
97
|
|
|
@@ -104,6 +135,7 @@ export default function ModelLabView() {
|
|
|
104
135
|
const labAssistantAgentId = useGrooveStore((s) => s.labAssistantAgentId);
|
|
105
136
|
const labAssistantMode = useGrooveStore((s) => s.labAssistantMode);
|
|
106
137
|
const setLabAssistantMode = useGrooveStore((s) => s.setLabAssistantMode);
|
|
138
|
+
const launchLabAssistant = useGrooveStore((s) => s.launchLabAssistant);
|
|
107
139
|
|
|
108
140
|
useEffect(() => { fetchLabRuntimes(); }, [fetchLabRuntimes]);
|
|
109
141
|
|
|
@@ -175,7 +207,7 @@ export default function ModelLabView() {
|
|
|
175
207
|
style={leftCollapsed ? undefined : { width: leftWidth }}
|
|
176
208
|
>
|
|
177
209
|
<div className="h-full flex flex-col">
|
|
178
|
-
<div className="flex-shrink-0 flex items-center justify-between px-4 h-10">
|
|
210
|
+
<div className="flex-shrink-0 flex items-center justify-between px-4 h-10 border-b border-border-subtle">
|
|
179
211
|
<div className="flex items-center gap-2">
|
|
180
212
|
<FlaskConical size={13} className="text-accent" />
|
|
181
213
|
<span className="text-xs font-semibold font-sans text-text-1">Model Lab</span>
|
|
@@ -183,7 +215,7 @@ export default function ModelLabView() {
|
|
|
183
215
|
<PanelToggle collapsed={false} onClick={() => setLeftCollapsed(true)} side="left" />
|
|
184
216
|
</div>
|
|
185
217
|
<ScrollArea className="flex-1 min-h-0">
|
|
186
|
-
<div className="px-4
|
|
218
|
+
<div className="px-4 py-4 space-y-6">
|
|
187
219
|
<RuntimeSection />
|
|
188
220
|
<ModelSelector />
|
|
189
221
|
<ParameterPanel />
|
|
@@ -203,28 +235,32 @@ export default function ModelLabView() {
|
|
|
203
235
|
{leftCollapsed && (
|
|
204
236
|
<PanelToggle collapsed onClick={() => setLeftCollapsed(false)} side="left" />
|
|
205
237
|
)}
|
|
206
|
-
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
238
|
+
<div className="flex items-center gap-px bg-surface-2 rounded p-px">
|
|
239
|
+
<button
|
|
240
|
+
onClick={() => setLabAssistantMode(false)}
|
|
241
|
+
className={cn(
|
|
242
|
+
'px-3 py-1 text-2xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
|
|
243
|
+
!labAssistantMode ? 'text-text-0 bg-surface-4' : 'text-text-3 hover:text-text-1',
|
|
244
|
+
)}
|
|
245
|
+
>
|
|
246
|
+
Playground
|
|
247
|
+
</button>
|
|
248
|
+
<button
|
|
249
|
+
onClick={() => {
|
|
250
|
+
if (labAssistantAgentId) {
|
|
251
|
+
setLabAssistantMode(true);
|
|
252
|
+
} else {
|
|
253
|
+
launchLabAssistant('lab-general');
|
|
254
|
+
}
|
|
255
|
+
}}
|
|
256
|
+
className={cn(
|
|
257
|
+
'px-3 py-1 text-2xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
|
|
258
|
+
labAssistantMode ? 'text-text-0 bg-surface-4' : 'text-text-3 hover:text-text-1',
|
|
259
|
+
)}
|
|
260
|
+
>
|
|
261
|
+
Assistant
|
|
262
|
+
</button>
|
|
263
|
+
</div>
|
|
228
264
|
<div className="flex-1" />
|
|
229
265
|
{rightCollapsed && (
|
|
230
266
|
<PanelToggle collapsed onClick={() => setRightCollapsed(false)} side="right" />
|