groove-dev 0.27.144 → 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/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/conversations.js +18 -48
- package/node_modules/@groove-dev/daemon/src/routes/agents.js +6 -83
- package/node_modules/@groove-dev/gui/dist/assets/{index-BcoF6_eF.js → index-Bxc0gU06.js} +232 -238
- 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/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/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/lab/chat-playground.jsx +39 -31
- 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 +126 -127
- package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +10 -8
- 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 +17 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +8 -1
- package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +13 -14
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +41 -10
- 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/conversations.js +18 -48
- package/packages/daemon/src/routes/agents.js +6 -83
- package/packages/gui/dist/assets/{index-BcoF6_eF.js → index-Bxc0gU06.js} +232 -238
- 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/components/agents/agent-feed.jsx +80 -95
- package/packages/gui/src/components/agents/agent-panel.jsx +2 -70
- 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/lab/chat-playground.jsx +39 -31
- 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 +126 -127
- package/packages/gui/src/components/lab/system-prompt-editor.jsx +10 -8
- 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 +17 -0
- package/packages/gui/src/stores/slices/agents-slice.js +8 -1
- package/packages/gui/src/stores/slices/chat-slice.js +13 -14
- package/packages/gui/src/views/model-lab.jsx +41 -10
- package/packages/gui/src/views/models.jsx +57 -36
- package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +0 -1
- 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
|
|
|
@@ -8,6 +8,8 @@ import { cn } from '../../lib/cn';
|
|
|
8
8
|
import { roleColor } from '../../lib/status';
|
|
9
9
|
|
|
10
10
|
const CHAT_ROLES = [
|
|
11
|
+
{ id: 'chat', label: 'Chat', desc: 'General conversation' },
|
|
12
|
+
{ id: 'research', label: 'Research Assistant', desc: 'Explore ideas, web search' },
|
|
11
13
|
{ id: 'fullstack', label: 'Fullstack', desc: 'End-to-end engineering' },
|
|
12
14
|
{ id: 'backend', label: 'Backend', desc: 'APIs, services, databases' },
|
|
13
15
|
{ id: 'frontend', label: 'Frontend', desc: 'UI, components, styling' },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { SendHorizontal, Loader2, Square, Paperclip, Image as ImageIcon, Zap, Bot, GripHorizontal } from 'lucide-react';
|
|
4
4
|
import { cn } from '../../lib/cn';
|
|
5
5
|
import { formatModelName } from './model-picker';
|
|
6
6
|
|
|
@@ -19,7 +19,7 @@ const VERBOSITY_OPTIONS = [
|
|
|
19
19
|
|
|
20
20
|
export function ChatInput({ onSend, onStop, sending, streaming, disabled, isImageModel, currentModel, replyContext, onClearReply, role, isCodex, reasoningEffort, onReasoningEffortChange, verbosity, onVerbosityChange, mode, onModeChange, modeChanging }) {
|
|
21
21
|
const [input, setInput] = useState('');
|
|
22
|
-
const [inputHeight, setInputHeight] = useState(
|
|
22
|
+
const [inputHeight, setInputHeight] = useState(88);
|
|
23
23
|
const textareaRef = useRef(null);
|
|
24
24
|
const fileInputRef = useRef(null);
|
|
25
25
|
|
|
@@ -32,7 +32,6 @@ export function ChatInput({ onSend, onStop, sending, streaming, disabled, isImag
|
|
|
32
32
|
if (!text || sending || disabled) return;
|
|
33
33
|
onSend(text);
|
|
34
34
|
setInput('');
|
|
35
|
-
setInputHeight(40);
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
function onKeyDown(e) {
|
|
@@ -54,7 +53,7 @@ export function ChatInput({ onSend, onStop, sending, streaming, disabled, isImag
|
|
|
54
53
|
e.preventDefault();
|
|
55
54
|
const startY = e.clientY;
|
|
56
55
|
const startH = inputHeight;
|
|
57
|
-
const onMove = (ev) => setInputHeight(Math.min(Math.max(
|
|
56
|
+
const onMove = (ev) => setInputHeight(Math.min(Math.max(56, startH - (ev.clientY - startY)), 400));
|
|
58
57
|
const onUp = () => {
|
|
59
58
|
window.removeEventListener('mousemove', onMove);
|
|
60
59
|
window.removeEventListener('mouseup', onUp);
|
|
@@ -76,95 +75,97 @@ export function ChatInput({ onSend, onStop, sending, streaming, disabled, isImag
|
|
|
76
75
|
: 'Send a message...';
|
|
77
76
|
|
|
78
77
|
return (
|
|
79
|
-
<div className="
|
|
78
|
+
<div className="px-4 pb-3">
|
|
80
79
|
<div
|
|
81
80
|
onMouseDown={onDragStart}
|
|
82
|
-
className="flex items-center justify-center h-
|
|
81
|
+
className="flex items-center justify-center h-5 cursor-row-resize group"
|
|
83
82
|
>
|
|
84
|
-
<GripHorizontal size={
|
|
83
|
+
<GripHorizontal size={12} className="text-text-4 group-hover:text-text-2 transition-colors" />
|
|
85
84
|
</div>
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
ref={
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
rows={1}
|
|
106
|
-
style={{ height: inputHeight }}
|
|
107
|
-
className={cn(
|
|
108
|
-
'w-full resize-none rounded-xl px-4 py-2.5 text-sm',
|
|
109
|
-
'bg-surface-0 border text-text-0 font-sans',
|
|
110
|
-
'placeholder:text-text-4',
|
|
111
|
-
'focus:outline-none focus:ring-1',
|
|
112
|
-
'border-border focus:ring-accent/40',
|
|
113
|
-
'disabled:opacity-50 disabled:cursor-not-allowed',
|
|
114
|
-
)}
|
|
86
|
+
{replyContext && (
|
|
87
|
+
<div className="flex items-center gap-2 mb-2 px-3 py-2 rounded-lg bg-accent/5 border border-accent/15">
|
|
88
|
+
<ImageIcon size={12} className="text-accent flex-shrink-0" />
|
|
89
|
+
<span className="flex-1 text-2xs text-text-2 font-sans truncate">Iterating: "{replyContext.prompt}"</span>
|
|
90
|
+
<button onClick={onClearReply} className="text-text-4 hover:text-text-1 cursor-pointer flex-shrink-0">
|
|
91
|
+
<Square size={10} />
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
<div className="flex flex-col rounded-lg border border-border-subtle bg-surface-0 transition-colors overflow-hidden focus-within:border-text-4/40">
|
|
97
|
+
<input
|
|
98
|
+
ref={fileInputRef}
|
|
99
|
+
type="file"
|
|
100
|
+
multiple
|
|
101
|
+
accept=".pdf,.png,.jpg,.jpeg,.gif,.svg,.csv,.txt,.md,.json,.yaml,.yml,.docx,.pptx,.xlsx"
|
|
102
|
+
onChange={handleFileSelect}
|
|
103
|
+
className="hidden"
|
|
115
104
|
/>
|
|
116
105
|
|
|
117
|
-
<div className="
|
|
118
|
-
<
|
|
119
|
-
ref={
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
106
|
+
<div className="px-1">
|
|
107
|
+
<textarea
|
|
108
|
+
ref={textareaRef}
|
|
109
|
+
value={input}
|
|
110
|
+
onChange={(e) => setInput(e.target.value)}
|
|
111
|
+
onKeyDown={onKeyDown}
|
|
112
|
+
placeholder={placeholder}
|
|
113
|
+
disabled={disabled}
|
|
114
|
+
rows={1}
|
|
115
|
+
className={cn(
|
|
116
|
+
'w-full resize-none px-3 py-2.5 text-[13px]',
|
|
117
|
+
'bg-transparent font-sans text-text-0',
|
|
118
|
+
'placeholder:text-text-4',
|
|
119
|
+
'focus:outline-none',
|
|
120
|
+
'disabled:opacity-40 disabled:cursor-not-allowed',
|
|
121
|
+
)}
|
|
122
|
+
style={{ height: inputHeight }}
|
|
125
123
|
/>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<div className="flex items-center gap-1.5 px-1.5 pb-1.5 pt-0.5">
|
|
126
127
|
<button
|
|
127
128
|
onClick={() => fileInputRef.current?.click()}
|
|
128
129
|
disabled={disabled}
|
|
129
|
-
className="w-
|
|
130
|
+
className="w-7 h-7 flex items-center justify-center rounded-md text-text-4 hover:text-text-1 transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
|
|
130
131
|
title="Attach file"
|
|
131
132
|
>
|
|
132
133
|
<Paperclip size={14} />
|
|
133
134
|
</button>
|
|
134
135
|
|
|
135
|
-
<div className="flex items-center h-
|
|
136
|
+
<div className="flex items-center h-6 rounded-md bg-surface-3 border border-border-subtle p-0.5">
|
|
136
137
|
<button
|
|
137
138
|
onClick={() => onModeChange?.('api')}
|
|
138
139
|
disabled={modeChanging}
|
|
139
140
|
className={cn(
|
|
140
|
-
'flex items-center gap-1 h-
|
|
141
|
+
'flex items-center gap-1 h-5 px-2 rounded text-2xs font-semibold font-sans transition-colors cursor-pointer',
|
|
141
142
|
'disabled:opacity-50 disabled:cursor-not-allowed',
|
|
142
|
-
currentMode === 'api' ? 'bg-accent/15 text-accent
|
|
143
|
+
currentMode === 'api' ? 'bg-accent/15 text-accent' : 'text-text-3 hover:text-text-1',
|
|
143
144
|
)}
|
|
144
145
|
title="Lightweight — fast and cheap, no tools"
|
|
145
146
|
>
|
|
146
|
-
<Zap size={
|
|
147
|
+
<Zap size={10} /> Chat
|
|
147
148
|
</button>
|
|
148
149
|
<button
|
|
149
150
|
onClick={() => onModeChange?.('agent')}
|
|
150
151
|
disabled={modeChanging}
|
|
151
152
|
className={cn(
|
|
152
|
-
'flex items-center gap-1 h-
|
|
153
|
+
'flex items-center gap-1 h-5 px-2 rounded text-2xs font-semibold font-sans transition-colors cursor-pointer',
|
|
153
154
|
'disabled:opacity-50 disabled:cursor-not-allowed',
|
|
154
|
-
currentMode === 'agent' ? 'bg-purple/15 text-purple
|
|
155
|
+
currentMode === 'agent' ? 'bg-purple/15 text-purple' : 'text-text-3 hover:text-text-1',
|
|
155
156
|
)}
|
|
156
157
|
title="Full agent — tools, files, session resume"
|
|
157
158
|
>
|
|
158
|
-
<Bot size={
|
|
159
|
+
<Bot size={10} /> Agent
|
|
159
160
|
</button>
|
|
160
161
|
</div>
|
|
161
162
|
|
|
162
163
|
{currentModel && (
|
|
163
164
|
<div className={cn(
|
|
164
|
-
'flex items-center gap-1 h-
|
|
165
|
+
'flex items-center gap-1 h-5 px-2 rounded text-2xs font-mono',
|
|
165
166
|
isImageModel
|
|
166
|
-
? 'bg-purple/8
|
|
167
|
-
: '
|
|
167
|
+
? 'bg-purple/8 text-purple'
|
|
168
|
+
: 'text-text-3',
|
|
168
169
|
)}>
|
|
169
170
|
{isImageModel && <ImageIcon size={9} />}
|
|
170
171
|
<span className="max-w-[80px] truncate">{formatModelName(currentModel)}</span>
|
|
@@ -173,13 +174,13 @@ export function ChatInput({ onSend, onStop, sending, streaming, disabled, isImag
|
|
|
173
174
|
|
|
174
175
|
{isCodex && (
|
|
175
176
|
<>
|
|
176
|
-
<div className="flex items-center h-
|
|
177
|
+
<div className="flex items-center h-5 rounded bg-surface-3 border border-border-subtle p-0.5">
|
|
177
178
|
{EFFORT_OPTIONS.map((opt) => (
|
|
178
179
|
<button
|
|
179
180
|
key={opt.value}
|
|
180
181
|
onClick={() => onReasoningEffortChange?.(opt.value)}
|
|
181
182
|
className={cn(
|
|
182
|
-
'h-
|
|
183
|
+
'h-4 px-1.5 rounded text-2xs font-semibold font-sans transition-colors cursor-pointer',
|
|
183
184
|
reasoningEffort === opt.value
|
|
184
185
|
? 'bg-accent/15 text-accent'
|
|
185
186
|
: 'text-text-4 hover:text-text-1',
|
|
@@ -191,13 +192,13 @@ export function ChatInput({ onSend, onStop, sending, streaming, disabled, isImag
|
|
|
191
192
|
))}
|
|
192
193
|
</div>
|
|
193
194
|
|
|
194
|
-
<div className="flex items-center h-
|
|
195
|
+
<div className="flex items-center h-5 rounded bg-surface-3 border border-border-subtle p-0.5">
|
|
195
196
|
{VERBOSITY_OPTIONS.map((opt) => (
|
|
196
197
|
<button
|
|
197
198
|
key={opt.value}
|
|
198
199
|
onClick={() => onVerbosityChange?.(opt.value)}
|
|
199
200
|
className={cn(
|
|
200
|
-
'h-
|
|
201
|
+
'h-4 px-1.5 rounded text-2xs font-semibold font-sans transition-colors cursor-pointer',
|
|
201
202
|
verbosity === opt.value
|
|
202
203
|
? 'bg-accent/15 text-accent'
|
|
203
204
|
: 'text-text-4 hover:text-text-1',
|
|
@@ -216,24 +217,25 @@ export function ChatInput({ onSend, onStop, sending, streaming, disabled, isImag
|
|
|
216
217
|
{isActive ? (
|
|
217
218
|
<button
|
|
218
219
|
onClick={onStop}
|
|
219
|
-
className="w-8 h-8 flex items-center justify-center rounded-lg bg-danger/80 text-white hover:bg-danger transition-all cursor-pointer shadow-lg shadow-danger/20 flex-shrink-0"
|
|
220
220
|
title="Stop generation"
|
|
221
|
+
className="group w-7 h-7 flex items-center justify-center rounded-md transition-colors cursor-pointer"
|
|
221
222
|
>
|
|
222
|
-
<
|
|
223
|
+
<span className="relative flex items-center justify-center w-3.5 h-3.5">
|
|
224
|
+
<span className="absolute inset-0 rounded-full bg-accent/30 group-hover:bg-red-500/30 animate-ping [animation-duration:2s] transition-colors" />
|
|
225
|
+
<span className="relative w-2.5 h-2.5 rounded-full bg-accent group-hover:bg-red-500 transition-colors" />
|
|
226
|
+
</span>
|
|
223
227
|
</button>
|
|
224
228
|
) : (
|
|
225
229
|
<button
|
|
226
230
|
onClick={handleSend}
|
|
227
231
|
disabled={!canSend}
|
|
228
232
|
className={cn(
|
|
229
|
-
'w-
|
|
230
|
-
'disabled:opacity-
|
|
231
|
-
canSend
|
|
232
|
-
? 'bg-accent/15 text-accent hover:bg-accent/25 border border-accent/25'
|
|
233
|
-
: 'bg-surface-4 text-text-4',
|
|
233
|
+
'w-7 h-7 flex items-center justify-center rounded-md transition-colors cursor-pointer',
|
|
234
|
+
'disabled:opacity-15 disabled:cursor-not-allowed',
|
|
235
|
+
canSend ? 'text-text-0 hover:text-text-1' : 'text-text-4',
|
|
234
236
|
)}
|
|
235
237
|
>
|
|
236
|
-
{sending ? <Loader2 size={
|
|
238
|
+
{sending ? <Loader2 size={15} className="animate-spin" /> : <SendHorizontal size={15} />}
|
|
237
239
|
</button>
|
|
238
240
|
)}
|
|
239
241
|
</div>
|
|
@@ -37,7 +37,7 @@ export function ChatView() {
|
|
|
37
37
|
const setActiveConversation = useGrooveStore((s) => s.setActiveConversation);
|
|
38
38
|
const sendChatMessage = useGrooveStore((s) => s.sendChatMessage);
|
|
39
39
|
const sendImageMessage = useGrooveStore((s) => s.sendImageMessage);
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
const stopChatStreaming = useGrooveStore((s) => s.stopChatStreaming);
|
|
42
42
|
const setConversationMode = useGrooveStore((s) => s.setConversationMode);
|
|
43
43
|
const setConversationModel = useGrooveStore((s) => s.setConversationModel);
|
|
@@ -54,7 +54,7 @@ export function ChatView() {
|
|
|
54
54
|
const [replyContext, setReplyContext] = useState(null);
|
|
55
55
|
const [modeChanging, setModeChanging] = useState(false);
|
|
56
56
|
|
|
57
|
-
const activeRole = activeConversationId ? (conversationRoles?.[activeConversationId] ||
|
|
57
|
+
const activeRole = activeConversationId ? (conversationRoles?.[activeConversationId] || 'chat') : 'chat';
|
|
58
58
|
const activeReasoningEffort = activeConversationId ? (conversationReasoningEffort?.[activeConversationId] || 'medium') : 'medium';
|
|
59
59
|
const activeVerbosity = activeConversationId ? (conversationVerbosity?.[activeConversationId] || 'medium') : 'medium';
|
|
60
60
|
|
|
@@ -101,12 +101,8 @@ export function ChatView() {
|
|
|
101
101
|
|
|
102
102
|
const handleStop = useCallback(() => {
|
|
103
103
|
if (!activeConversation) return;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} else {
|
|
107
|
-
stopChatStreaming(activeConversationId);
|
|
108
|
-
}
|
|
109
|
-
}, [activeConversation, activeConversationId, stopAgent, stopChatStreaming]);
|
|
104
|
+
stopChatStreaming(activeConversationId);
|
|
105
|
+
}, [activeConversation, activeConversationId, stopChatStreaming]);
|
|
110
106
|
|
|
111
107
|
const handleModelChange = useCallback(async (selection) => {
|
|
112
108
|
if (activeConversationId) {
|
|
@@ -5,7 +5,7 @@ import { ScrollArea } from '../ui/scroll-area';
|
|
|
5
5
|
import { Button } from '../ui/button';
|
|
6
6
|
import { Tooltip } from '../ui/tooltip';
|
|
7
7
|
import { cn } from '../../lib/cn';
|
|
8
|
-
import {
|
|
8
|
+
import { SendHorizontal, Plus, ChevronDown, Clock, Zap, Bot } from 'lucide-react';
|
|
9
9
|
|
|
10
10
|
function MessageMetrics({ metrics }) {
|
|
11
11
|
if (!metrics) return null;
|
|
@@ -217,44 +217,52 @@ export function ChatPlayground() {
|
|
|
217
217
|
|
|
218
218
|
{/* Input */}
|
|
219
219
|
<div className="flex-shrink-0 px-4 py-3">
|
|
220
|
-
<div className="flex
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
220
|
+
<div className="flex flex-col rounded-lg border border-border-subtle bg-surface-0 transition-colors overflow-hidden focus-within:border-text-4/40">
|
|
221
|
+
<div className="px-1">
|
|
222
|
+
<textarea
|
|
223
|
+
ref={inputRef}
|
|
224
|
+
value={input}
|
|
225
|
+
onChange={(e) => setInput(e.target.value)}
|
|
226
|
+
onKeyDown={handleKeyDown}
|
|
227
|
+
placeholder={!activeRuntime ? 'Select a runtime first' : !activeModel ? 'Select a model first' : 'Type a message...'}
|
|
228
|
+
disabled={!activeRuntime || !activeModel}
|
|
229
|
+
rows={1}
|
|
230
|
+
className={cn(
|
|
231
|
+
'w-full resize-none px-3 py-2.5 text-[13px]',
|
|
232
|
+
'bg-transparent font-sans text-text-0',
|
|
233
|
+
'placeholder:text-text-4',
|
|
234
|
+
'focus:outline-none',
|
|
235
|
+
'disabled:opacity-40 disabled:cursor-not-allowed',
|
|
236
|
+
)}
|
|
237
|
+
style={{ height: 88 }}
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
<div className="flex items-center gap-1 px-1.5 pb-1.5 pt-0.5">
|
|
241
|
+
<div className="flex-1" />
|
|
242
|
+
{streaming && (
|
|
243
|
+
<button
|
|
244
|
+
onClick={() => useGrooveStore.getState().stopLabInference()}
|
|
245
|
+
title="Stop generation"
|
|
246
|
+
className="group w-7 h-7 flex items-center justify-center rounded-md transition-colors cursor-pointer"
|
|
247
|
+
>
|
|
248
|
+
<span className="relative flex items-center justify-center w-3.5 h-3.5">
|
|
249
|
+
<span className="absolute inset-0 rounded-full bg-accent/30 group-hover:bg-red-500/30 animate-ping [animation-duration:2s] transition-colors" />
|
|
250
|
+
<span className="relative w-2.5 h-2.5 rounded-full bg-accent group-hover:bg-red-500 transition-colors" />
|
|
251
|
+
</span>
|
|
252
|
+
</button>
|
|
235
253
|
)}
|
|
236
|
-
style={{ height: 'auto', overflowY: input.split('\n').length > 4 ? 'auto' : 'hidden' }}
|
|
237
|
-
onInput={(e) => { e.target.style.height = 'auto'; e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`; }}
|
|
238
|
-
/>
|
|
239
|
-
{streaming ? (
|
|
240
|
-
<button
|
|
241
|
-
onClick={() => useGrooveStore.getState().stopLabInference()}
|
|
242
|
-
className="flex-shrink-0 w-7 h-7 flex items-center justify-center rounded-sm bg-danger/15 text-danger hover:bg-danger/25 transition-colors cursor-pointer"
|
|
243
|
-
>
|
|
244
|
-
<Square size={12} />
|
|
245
|
-
</button>
|
|
246
|
-
) : (
|
|
247
254
|
<button
|
|
248
255
|
disabled={!canSend}
|
|
249
256
|
onClick={handleSend}
|
|
250
257
|
className={cn(
|
|
251
|
-
'
|
|
252
|
-
|
|
258
|
+
'w-7 h-7 flex items-center justify-center rounded-md transition-colors cursor-pointer',
|
|
259
|
+
'disabled:opacity-15 disabled:cursor-not-allowed',
|
|
260
|
+
canSend ? 'text-text-0 hover:text-text-1' : 'text-text-4',
|
|
253
261
|
)}
|
|
254
262
|
>
|
|
255
|
-
<
|
|
263
|
+
<SendHorizontal size={15} />
|
|
256
264
|
</button>
|
|
257
|
-
|
|
265
|
+
</div>
|
|
258
266
|
</div>
|
|
259
267
|
</div>
|
|
260
268
|
</div>
|