groove-dev 0.27.134 → 0.27.136
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/moe-training/client/domain-tagger.js +1 -1
- package/moe-training/scripts/retag-delegate-yield.js +303 -0
- package/moe-training/test/shared/envelope-schema.test.js +3 -3
- 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/adaptive.js +77 -0
- package/node_modules/@groove-dev/daemon/src/api.js +35 -5
- package/node_modules/@groove-dev/daemon/src/journalist.js +28 -12
- package/node_modules/@groove-dev/daemon/src/model-lab.js +53 -76
- package/node_modules/@groove-dev/daemon/src/process.js +91 -2
- package/node_modules/@groove-dev/daemon/src/rotator.js +45 -3
- package/node_modules/@groove-dev/gui/dist/assets/{index-Dozp69tK.js → index-BrZHF7pK.js} +1770 -1766
- package/node_modules/@groove-dev/gui/dist/assets/index-DIfiwdKl.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-chat.jsx +60 -18
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +42 -20
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +2 -22
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +9 -9
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +7 -0
- package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +59 -51
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +48 -48
- package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +39 -38
- package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +4 -5
- package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +11 -11
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +66 -62
- package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +13 -13
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/preview/preview-workspace.jsx +62 -22
- package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +16 -17
- package/node_modules/@groove-dev/gui/src/components/ui/table-tree.jsx +38 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +23 -9
- package/node_modules/@groove-dev/gui/src/views/editor.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +101 -87
- package/node_modules/moe-training/client/domain-tagger.js +1 -1
- package/node_modules/moe-training/scripts/retag-delegate-yield.js +303 -0
- package/node_modules/moe-training/test/shared/envelope-schema.test.js +3 -3
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/adaptive.js +77 -0
- package/packages/daemon/src/api.js +35 -5
- package/packages/daemon/src/journalist.js +28 -12
- package/packages/daemon/src/model-lab.js +53 -76
- package/packages/daemon/src/process.js +91 -2
- package/packages/daemon/src/rotator.js +45 -3
- package/packages/gui/dist/assets/{index-Dozp69tK.js → index-BrZHF7pK.js} +1770 -1766
- package/packages/gui/dist/assets/index-DIfiwdKl.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-chat.jsx +60 -18
- package/packages/gui/src/components/agents/agent-feed.jsx +42 -20
- package/packages/gui/src/components/agents/agent-file-tree.jsx +1 -1
- package/packages/gui/src/components/agents/workspace-mode.jsx +1 -1
- package/packages/gui/src/components/chat/chat-messages.jsx +2 -22
- package/packages/gui/src/components/editor/code-editor.jsx +9 -9
- package/packages/gui/src/components/editor/file-tree.jsx +1 -1
- package/packages/gui/src/components/editor/terminal.jsx +7 -0
- package/packages/gui/src/components/lab/chat-playground.jsx +59 -51
- package/packages/gui/src/components/lab/lab-assistant.jsx +48 -48
- package/packages/gui/src/components/lab/metrics-panel.jsx +39 -38
- package/packages/gui/src/components/lab/parameter-panel.jsx +4 -5
- package/packages/gui/src/components/lab/preset-manager.jsx +11 -11
- package/packages/gui/src/components/lab/runtime-config.jsx +66 -62
- package/packages/gui/src/components/lab/system-prompt-editor.jsx +13 -13
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
- package/packages/gui/src/components/preview/preview-workspace.jsx +62 -22
- package/packages/gui/src/components/ui/slider.jsx +16 -17
- package/packages/gui/src/components/ui/table-tree.jsx +38 -0
- package/packages/gui/src/stores/groove.js +23 -9
- package/packages/gui/src/views/editor.jsx +1 -1
- package/packages/gui/src/views/model-lab.jsx +101 -87
- package/plan_files/DELEGATE_YIELD_TRAINING_TAGS.md +135 -0
- package/plan_files/session-quality-rotation-fixes.md +218 -0
- package/test.py +571 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BgQL4bNl.css +0 -1
- package/packages/gui/dist/assets/index-BgQL4bNl.css +0 -1
- /package/{AGENT_ORCHESTRATION.md → plan_files/AGENT_ORCHESTRATION.md} +0 -0
- /package/{DYNAMIC_LEAF_ARCH.md → plan_files/DYNAMIC_LEAF_ARCH.md} +0 -0
- /package/{EMBEDDING_DIAGNOSTIC.md → plan_files/EMBEDDING_DIAGNOSTIC.md} +0 -0
- /package/{EMBEDDING_SERVICE_BUILD_PLAN.md → plan_files/EMBEDDING_SERVICE_BUILD_PLAN.md} +0 -0
- /package/{MOE_TRAINING_PIPELINE.md → plan_files/MOE_TRAINING_PIPELINE.md} +0 -0
|
@@ -27,24 +27,24 @@ const LANGS = {
|
|
|
27
27
|
const grooveHighlightStyle = HighlightStyle.define([
|
|
28
28
|
{ tag: t.keyword, color: '#b07fd5' },
|
|
29
29
|
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#d4d8e0' },
|
|
30
|
-
{ tag: [t.function(t.variableName), t.labelName], color: '#
|
|
31
|
-
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#
|
|
30
|
+
{ tag: [t.function(t.variableName), t.labelName], color: '#dcc9a0' },
|
|
31
|
+
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#d4a07a' },
|
|
32
32
|
{ tag: [t.definition(t.name), t.separator], color: '#bcc2cd' },
|
|
33
|
-
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#
|
|
34
|
-
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.special(t.string)], color: '#
|
|
33
|
+
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#e0c589' },
|
|
34
|
+
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.special(t.string)], color: '#89b4c4' },
|
|
35
35
|
{ tag: [t.meta, t.comment], color: '#6e7681', fontStyle: 'italic' },
|
|
36
36
|
{ tag: t.strong, fontWeight: 'bold' },
|
|
37
37
|
{ tag: t.emphasis, fontStyle: 'italic' },
|
|
38
38
|
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
|
39
39
|
{ tag: t.link, color: '#7ab0df', textDecoration: 'underline' },
|
|
40
40
|
{ tag: t.heading, fontWeight: '400', color: '#bcc2cd' },
|
|
41
|
-
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#
|
|
42
|
-
{ tag: [t.processingInstruction, t.string, t.inserted], color: '#
|
|
41
|
+
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d4a07a' },
|
|
42
|
+
{ tag: [t.processingInstruction, t.string, t.inserted], color: '#95b2b8' },
|
|
43
43
|
{ tag: t.invalid, color: '#d4736e' },
|
|
44
|
-
{ tag: t.propertyName, color: '#
|
|
44
|
+
{ tag: t.propertyName, color: '#dcc9a0' },
|
|
45
45
|
{ tag: [t.tagName], color: '#d4736e' },
|
|
46
|
-
{ tag: t.attributeName, color: '#
|
|
47
|
-
{ tag: t.attributeValue, color: '#
|
|
46
|
+
{ tag: t.attributeName, color: '#e0c589' },
|
|
47
|
+
{ tag: t.attributeValue, color: '#95b2b8' },
|
|
48
48
|
]);
|
|
49
49
|
|
|
50
50
|
// Custom theme overrides to match our design tokens
|
|
@@ -130,7 +130,7 @@ function TreeNode({ entry, depth = 0, activePath, onFileClick, onDirToggle, expa
|
|
|
130
130
|
onDoubleClick={handleContextMenu}
|
|
131
131
|
onContextMenu={handleContextMenu}
|
|
132
132
|
className={cn(
|
|
133
|
-
'w-full flex items-center gap-1.5 py-[3px] text-
|
|
133
|
+
'w-full flex items-center gap-1.5 py-[3px] text-xs font-sans cursor-pointer',
|
|
134
134
|
'hover:bg-surface-5 transition-colors text-left select-none',
|
|
135
135
|
isActive && 'bg-accent/10 text-text-0',
|
|
136
136
|
!isActive && 'text-text-1',
|
|
@@ -29,6 +29,8 @@ function TerminalInstance({ tabId, visible, registerKill }) {
|
|
|
29
29
|
const termIdRef = useRef(null);
|
|
30
30
|
const handlerRef = useRef(null);
|
|
31
31
|
const mountedRef = useRef(false);
|
|
32
|
+
const visibleRef = useRef(visible);
|
|
33
|
+
const lastSizeRef = useRef({ cols: 0, rows: 0 });
|
|
32
34
|
|
|
33
35
|
useEffect(() => {
|
|
34
36
|
registerKill?.(tabId, () => {
|
|
@@ -105,6 +107,9 @@ function TerminalInstance({ tabId, visible, registerKill }) {
|
|
|
105
107
|
});
|
|
106
108
|
|
|
107
109
|
term.onResize(({ cols, rows }) => {
|
|
110
|
+
if (cols === lastSizeRef.current.cols && rows === lastSizeRef.current.rows) return;
|
|
111
|
+
if (cols < 2 || rows < 2) return;
|
|
112
|
+
lastSizeRef.current = { cols, rows };
|
|
108
113
|
const ws = useGrooveStore.getState().ws;
|
|
109
114
|
if (ws?.readyState === WebSocket.OPEN && termIdRef.current) {
|
|
110
115
|
ws.send(JSON.stringify({ type: 'terminal:resize', id: termIdRef.current, rows, cols }));
|
|
@@ -119,6 +124,7 @@ function TerminalInstance({ tabId, visible, registerKill }) {
|
|
|
119
124
|
});
|
|
120
125
|
|
|
121
126
|
const observer = new ResizeObserver(() => {
|
|
127
|
+
if (!visibleRef.current) return;
|
|
122
128
|
requestAnimationFrame(() => { try { fitAddon.fit(); } catch {} });
|
|
123
129
|
});
|
|
124
130
|
observer.observe(containerRef.current);
|
|
@@ -135,6 +141,7 @@ function TerminalInstance({ tabId, visible, registerKill }) {
|
|
|
135
141
|
}, []);
|
|
136
142
|
|
|
137
143
|
useEffect(() => {
|
|
144
|
+
visibleRef.current = visible;
|
|
138
145
|
if (visible && fitRef.current) {
|
|
139
146
|
requestAnimationFrame(() => {
|
|
140
147
|
try { fitRef.current.fit(); } catch {}
|
|
@@ -5,23 +5,23 @@ 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 { Send, Plus, ChevronDown,
|
|
8
|
+
import { Send, Plus, ChevronDown, Square, Clock, Zap, Bot } from 'lucide-react';
|
|
9
9
|
|
|
10
10
|
function MessageMetrics({ metrics }) {
|
|
11
11
|
if (!metrics) return null;
|
|
12
12
|
return (
|
|
13
|
-
<div className="flex items-center gap-3 mt-1
|
|
13
|
+
<div className="flex items-center gap-3 mt-1 ml-5">
|
|
14
14
|
{metrics.ttft != null && (
|
|
15
15
|
<Tooltip content="Time to first token">
|
|
16
16
|
<span className="text-2xs font-mono text-text-4 flex items-center gap-1">
|
|
17
|
-
<Clock size={
|
|
17
|
+
<Clock size={9} /> {Math.round(metrics.ttft)}ms
|
|
18
18
|
</span>
|
|
19
19
|
</Tooltip>
|
|
20
20
|
)}
|
|
21
21
|
{metrics.tokensPerSec != null && (
|
|
22
22
|
<Tooltip content="Tokens per second">
|
|
23
23
|
<span className="text-2xs font-mono text-text-4 flex items-center gap-1">
|
|
24
|
-
<Zap size={
|
|
24
|
+
<Zap size={9} /> {metrics.tokensPerSec.toFixed(1)} t/s
|
|
25
25
|
</span>
|
|
26
26
|
</Tooltip>
|
|
27
27
|
)}
|
|
@@ -37,9 +37,9 @@ function MessageMetrics({ metrics }) {
|
|
|
37
37
|
|
|
38
38
|
function UserMessage({ msg }) {
|
|
39
39
|
return (
|
|
40
|
-
<div className="flex justify-end">
|
|
41
|
-
<div className="max-w-[
|
|
42
|
-
<div className="px-3 py-2
|
|
40
|
+
<div className="flex justify-end animate-chat-fade-in">
|
|
41
|
+
<div className="max-w-[80%]">
|
|
42
|
+
<div className="px-3.5 py-2 bg-accent/8 rounded rounded-br-none">
|
|
43
43
|
<p className="text-xs text-text-0 font-sans whitespace-pre-wrap break-words leading-relaxed">{msg.content}</p>
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
@@ -51,35 +51,35 @@ function AssistantMessage({ msg, streaming }) {
|
|
|
51
51
|
const isStreaming = streaming && !msg.content && !msg.reasoning && !msg.error;
|
|
52
52
|
const isReasoning = streaming && msg.reasoning && !msg.content;
|
|
53
53
|
return (
|
|
54
|
-
<div>
|
|
55
|
-
<div className="
|
|
56
|
-
<
|
|
54
|
+
<div className="animate-chat-fade-in">
|
|
55
|
+
<div className="flex items-center gap-1.5 mb-1">
|
|
56
|
+
<div className="w-4 h-4 rounded-sm bg-surface-4 flex items-center justify-center">
|
|
57
|
+
<Bot size={10} className="text-text-3" />
|
|
58
|
+
</div>
|
|
59
|
+
<span className="text-2xs text-text-3 font-sans font-medium">Assistant</span>
|
|
57
60
|
</div>
|
|
58
61
|
{msg.reasoning && (
|
|
59
|
-
<div className="border-l
|
|
60
|
-
<div className="text-2xs font-sans text-text-
|
|
62
|
+
<div className="ml-5 mb-1.5 pl-3 border-l border-text-4/20 py-1">
|
|
63
|
+
<div className="text-2xs font-sans text-text-4 italic whitespace-pre-wrap break-words leading-relaxed">
|
|
61
64
|
{msg.reasoning}
|
|
62
|
-
{isReasoning && <span className="inline-block w-1 h-3 bg-text-4/
|
|
65
|
+
{isReasoning && <span className="inline-block w-1 h-3 bg-text-4/40 ml-0.5 animate-pulse" />}
|
|
63
66
|
</div>
|
|
64
67
|
</div>
|
|
65
68
|
)}
|
|
66
|
-
<div className=
|
|
67
|
-
'border-l-2 pl-3 py-0.5',
|
|
68
|
-
msg.error ? 'border-danger/40' : 'border-accent/40',
|
|
69
|
-
)}>
|
|
69
|
+
<div className="ml-5">
|
|
70
70
|
{msg.content ? (
|
|
71
71
|
<div className={cn(
|
|
72
72
|
'text-xs font-sans whitespace-pre-wrap break-words leading-relaxed',
|
|
73
73
|
msg.error ? 'text-danger' : 'text-text-1',
|
|
74
74
|
)}>
|
|
75
75
|
{msg.content}
|
|
76
|
-
{streaming && !msg.error && <span className="inline-block w-1.5 h-3.5 bg-accent/
|
|
76
|
+
{streaming && !msg.error && <span className="inline-block w-1.5 h-3.5 bg-accent/60 ml-0.5 animate-pulse" />}
|
|
77
77
|
</div>
|
|
78
78
|
) : isStreaming ? (
|
|
79
|
-
<div className="flex items-center gap-1 py-1">
|
|
80
|
-
<span className="w-1
|
|
81
|
-
<span className="w-1
|
|
82
|
-
<span className="w-1
|
|
79
|
+
<div className="flex items-center gap-1.5 py-1">
|
|
80
|
+
<span className="w-1 h-1 rounded-full bg-text-4 animate-pulse" />
|
|
81
|
+
<span className="w-1 h-1 rounded-full bg-text-4 animate-pulse" style={{ animationDelay: '150ms' }} />
|
|
82
|
+
<span className="w-1 h-1 rounded-full bg-text-4 animate-pulse" style={{ animationDelay: '300ms' }} />
|
|
83
83
|
</div>
|
|
84
84
|
) : null}
|
|
85
85
|
</div>
|
|
@@ -99,19 +99,19 @@ function SessionSelector({ sessions, activeSession, onSelect, onNew }) {
|
|
|
99
99
|
<div className="relative">
|
|
100
100
|
<button
|
|
101
101
|
onClick={() => setOpen(!open)}
|
|
102
|
-
className="flex items-center gap-1
|
|
102
|
+
className="flex items-center gap-1 px-2 py-1 text-2xs font-sans text-text-3 hover:text-text-1 transition-colors cursor-pointer"
|
|
103
103
|
>
|
|
104
104
|
{label} <ChevronDown size={10} />
|
|
105
105
|
</button>
|
|
106
106
|
{open && (
|
|
107
|
-
<div className="absolute top-full left-0 mt-1 z-20 min-w-40 bg-surface-
|
|
107
|
+
<div className="absolute top-full left-0 mt-1 z-20 min-w-40 bg-surface-2 border border-border rounded py-1 shadow-xl">
|
|
108
108
|
{sessions.map((sess, i) => (
|
|
109
109
|
<button
|
|
110
110
|
key={sess.id}
|
|
111
111
|
onClick={() => { onSelect(sess.id); setOpen(false); }}
|
|
112
112
|
className={cn(
|
|
113
113
|
'w-full text-left px-3 py-1.5 text-xs font-sans cursor-pointer transition-colors',
|
|
114
|
-
sess.id === activeSession ? 'text-accent bg-accent/
|
|
114
|
+
sess.id === activeSession ? 'text-accent bg-accent/8' : 'text-text-2 hover:bg-surface-4 hover:text-text-0',
|
|
115
115
|
)}
|
|
116
116
|
>
|
|
117
117
|
Session {i + 1}
|
|
@@ -165,11 +165,10 @@ export function ChatPlayground() {
|
|
|
165
165
|
const canSend = input.trim() && activeRuntime && activeModel && !streaming;
|
|
166
166
|
|
|
167
167
|
return (
|
|
168
|
-
<div className="h-full flex flex-col
|
|
169
|
-
{/*
|
|
170
|
-
<div className="flex items-center justify-between px-
|
|
171
|
-
<div className="flex items-center gap-
|
|
172
|
-
<span className="text-xs font-semibold font-sans text-text-1">Playground</span>
|
|
168
|
+
<div className="h-full flex flex-col">
|
|
169
|
+
{/* Toolbar */}
|
|
170
|
+
<div className="flex-shrink-0 flex items-center justify-between px-4 pb-2">
|
|
171
|
+
<div className="flex items-center gap-1">
|
|
173
172
|
<SessionSelector
|
|
174
173
|
sessions={sessions}
|
|
175
174
|
activeSession={activeSession}
|
|
@@ -178,20 +177,25 @@ export function ChatPlayground() {
|
|
|
178
177
|
/>
|
|
179
178
|
</div>
|
|
180
179
|
<Tooltip content="New session">
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
<button
|
|
181
|
+
onClick={newSession}
|
|
182
|
+
className="flex items-center gap-1 px-2 py-1 text-2xs font-sans text-text-3 hover:text-text-1 transition-colors cursor-pointer"
|
|
183
|
+
>
|
|
184
|
+
<Plus size={11} /> New
|
|
185
|
+
</button>
|
|
184
186
|
</Tooltip>
|
|
185
187
|
</div>
|
|
186
188
|
|
|
187
189
|
{/* Messages */}
|
|
188
190
|
<ScrollArea ref={scrollRef} className="flex-1 min-h-0">
|
|
189
|
-
<div className="px-4 py-
|
|
191
|
+
<div className="px-4 py-3 space-y-5">
|
|
190
192
|
{messages.length === 0 ? (
|
|
191
|
-
<div className="flex flex-col items-center justify-center py-
|
|
192
|
-
<
|
|
193
|
+
<div className="flex flex-col items-center justify-center py-20 text-center">
|
|
194
|
+
<div className="w-10 h-10 rounded bg-surface-2 flex items-center justify-center mb-3">
|
|
195
|
+
<Bot size={20} className="text-text-4" />
|
|
196
|
+
</div>
|
|
193
197
|
<p className="text-sm text-text-2 font-sans font-medium">Start a conversation</p>
|
|
194
|
-
<p className="text-xs text-text-
|
|
198
|
+
<p className="text-xs text-text-4 font-sans mt-1">
|
|
195
199
|
{!activeRuntime ? 'Select a runtime to get started' : !activeModel ? 'Select a model to get started' : 'Send a message to test your model'}
|
|
196
200
|
</p>
|
|
197
201
|
</div>
|
|
@@ -212,8 +216,8 @@ export function ChatPlayground() {
|
|
|
212
216
|
</ScrollArea>
|
|
213
217
|
|
|
214
218
|
{/* Input */}
|
|
215
|
-
<div className="flex-shrink-0 px-
|
|
216
|
-
<div className="flex items-end gap-2">
|
|
219
|
+
<div className="flex-shrink-0 px-4 py-3">
|
|
220
|
+
<div className="flex items-end gap-2 bg-surface-1 border border-border-subtle rounded-md p-1.5 focus-within:border-accent/30 transition-colors">
|
|
217
221
|
<textarea
|
|
218
222
|
ref={inputRef}
|
|
219
223
|
value={input}
|
|
@@ -223,29 +227,33 @@ export function ChatPlayground() {
|
|
|
223
227
|
disabled={!activeRuntime || !activeModel}
|
|
224
228
|
rows={1}
|
|
225
229
|
className={cn(
|
|
226
|
-
'flex-1 resize-none bg-
|
|
230
|
+
'flex-1 resize-none bg-transparent px-2 py-1.5',
|
|
227
231
|
'text-xs text-text-0 font-sans placeholder:text-text-4',
|
|
228
|
-
'focus:outline-none
|
|
232
|
+
'focus:outline-none',
|
|
229
233
|
'disabled:opacity-40 disabled:cursor-not-allowed',
|
|
230
|
-
'min-h-[
|
|
234
|
+
'min-h-[28px] max-h-32',
|
|
231
235
|
)}
|
|
232
236
|
style={{ height: 'auto', overflowY: input.split('\n').length > 4 ? 'auto' : 'hidden' }}
|
|
233
237
|
onInput={(e) => { e.target.style.height = 'auto'; e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`; }}
|
|
234
238
|
/>
|
|
235
239
|
{streaming ? (
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
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>
|
|
239
246
|
) : (
|
|
240
|
-
<
|
|
241
|
-
variant="primary"
|
|
242
|
-
size="icon"
|
|
243
|
-
className="flex-shrink-0"
|
|
247
|
+
<button
|
|
244
248
|
disabled={!canSend}
|
|
245
249
|
onClick={handleSend}
|
|
250
|
+
className={cn(
|
|
251
|
+
'flex-shrink-0 w-7 h-7 flex items-center justify-center rounded-sm transition-colors cursor-pointer',
|
|
252
|
+
canSend ? 'bg-accent text-surface-0 hover:bg-accent/90' : 'bg-surface-3 text-text-4 cursor-not-allowed',
|
|
253
|
+
)}
|
|
246
254
|
>
|
|
247
|
-
<Send size={
|
|
248
|
-
</
|
|
255
|
+
<Send size={12} />
|
|
256
|
+
</button>
|
|
249
257
|
)}
|
|
250
258
|
</div>
|
|
251
259
|
</div>
|
|
@@ -2,21 +2,20 @@
|
|
|
2
2
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import { useGrooveStore } from '../../stores/groove';
|
|
4
4
|
import { ScrollArea } from '../ui/scroll-area';
|
|
5
|
-
import { Button } from '../ui/button';
|
|
6
5
|
import { Badge } from '../ui/badge';
|
|
7
6
|
import { cn } from '../../lib/cn';
|
|
8
|
-
import { Send, X, Bot, ArrowRight
|
|
7
|
+
import { Send, X, Bot, ArrowRight } from 'lucide-react';
|
|
9
8
|
|
|
10
9
|
function AssistantMessage({ msg }) {
|
|
11
10
|
return (
|
|
12
|
-
<div>
|
|
13
|
-
<div className="
|
|
14
|
-
<
|
|
11
|
+
<div className="animate-chat-fade-in">
|
|
12
|
+
<div className="flex items-center gap-1.5 mb-1">
|
|
13
|
+
<div className="w-4 h-4 rounded-sm bg-surface-4 flex items-center justify-center">
|
|
14
|
+
<Bot size={10} className="text-text-3" />
|
|
15
|
+
</div>
|
|
16
|
+
<span className="text-2xs text-text-3 font-sans font-medium">Lab Assistant</span>
|
|
15
17
|
</div>
|
|
16
|
-
<div className=
|
|
17
|
-
'border-l-2 pl-3 py-0.5',
|
|
18
|
-
msg.error ? 'border-danger/40' : 'border-accent/40',
|
|
19
|
-
)}>
|
|
18
|
+
<div className="ml-5">
|
|
20
19
|
<div className={cn(
|
|
21
20
|
'text-xs font-sans whitespace-pre-wrap break-words leading-relaxed',
|
|
22
21
|
msg.error ? 'text-danger' : 'text-text-1',
|
|
@@ -30,9 +29,9 @@ function AssistantMessage({ msg }) {
|
|
|
30
29
|
|
|
31
30
|
function UserMessage({ msg }) {
|
|
32
31
|
return (
|
|
33
|
-
<div className="flex justify-end">
|
|
34
|
-
<div className="max-w-[
|
|
35
|
-
<div className="px-3 py-2
|
|
32
|
+
<div className="flex justify-end animate-chat-fade-in">
|
|
33
|
+
<div className="max-w-[80%]">
|
|
34
|
+
<div className="px-3.5 py-2 bg-accent/8 rounded rounded-br-none">
|
|
36
35
|
<p className="text-xs text-text-0 font-sans whitespace-pre-wrap break-words leading-relaxed">{msg.text}</p>
|
|
37
36
|
</div>
|
|
38
37
|
</div>
|
|
@@ -84,13 +83,12 @@ export function LabAssistant() {
|
|
|
84
83
|
if (!agentId) return null;
|
|
85
84
|
|
|
86
85
|
return (
|
|
87
|
-
<div className="h-full flex flex-col
|
|
86
|
+
<div className="h-full flex flex-col">
|
|
88
87
|
{/* Header */}
|
|
89
|
-
<div className="flex items-center justify-between px-
|
|
88
|
+
<div className="flex-shrink-0 flex items-center justify-between px-4 pb-2">
|
|
90
89
|
<div className="flex items-center gap-2">
|
|
91
|
-
<span className="text-xs font-semibold font-sans text-text-1">Lab Assistant</span>
|
|
92
90
|
{backend && (
|
|
93
|
-
<
|
|
91
|
+
<span className="text-2xs font-mono text-text-3">{backend}</span>
|
|
94
92
|
)}
|
|
95
93
|
{agent && (
|
|
96
94
|
<Badge
|
|
@@ -103,7 +101,7 @@ export function LabAssistant() {
|
|
|
103
101
|
</div>
|
|
104
102
|
<button
|
|
105
103
|
onClick={dismissLabAssistant}
|
|
106
|
-
className="p-1
|
|
104
|
+
className="p-1 text-text-4 hover:text-text-1 transition-colors cursor-pointer"
|
|
107
105
|
>
|
|
108
106
|
<X size={14} />
|
|
109
107
|
</button>
|
|
@@ -111,12 +109,14 @@ export function LabAssistant() {
|
|
|
111
109
|
|
|
112
110
|
{/* Messages */}
|
|
113
111
|
<ScrollArea ref={scrollRef} className="flex-1 min-h-0">
|
|
114
|
-
<div className="px-4 py-
|
|
112
|
+
<div className="px-4 py-3 space-y-5">
|
|
115
113
|
{messages.length === 0 && !isThinking ? (
|
|
116
|
-
<div className="flex flex-col items-center justify-center py-
|
|
117
|
-
<
|
|
114
|
+
<div className="flex flex-col items-center justify-center py-20 text-center">
|
|
115
|
+
<div className="w-10 h-10 rounded bg-surface-2 flex items-center justify-center mb-3">
|
|
116
|
+
<Bot size={20} className="text-text-4" />
|
|
117
|
+
</div>
|
|
118
118
|
<p className="text-sm text-text-2 font-sans font-medium">Setting up {backend}</p>
|
|
119
|
-
<p className="text-xs text-text-
|
|
119
|
+
<p className="text-xs text-text-4 font-sans mt-1">The assistant is starting up...</p>
|
|
120
120
|
</div>
|
|
121
121
|
) : (
|
|
122
122
|
messages.map((msg, i) =>
|
|
@@ -128,18 +128,18 @@ export function LabAssistant() {
|
|
|
128
128
|
)
|
|
129
129
|
)}
|
|
130
130
|
|
|
131
|
-
{/* Thinking indicator */}
|
|
132
131
|
{isThinking && (
|
|
133
|
-
<div>
|
|
134
|
-
<div className="
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
<div className="border-l-2 border-accent/40 pl-3 py-0.5">
|
|
138
|
-
<div className="flex items-center gap-1 py-1">
|
|
139
|
-
<span className="w-1.5 h-1.5 rounded-full bg-text-3 animate-pulse" />
|
|
140
|
-
<span className="w-1.5 h-1.5 rounded-full bg-text-3 animate-pulse" style={{ animationDelay: '150ms' }} />
|
|
141
|
-
<span className="w-1.5 h-1.5 rounded-full bg-text-3 animate-pulse" style={{ animationDelay: '300ms' }} />
|
|
132
|
+
<div className="animate-chat-fade-in">
|
|
133
|
+
<div className="flex items-center gap-1.5 mb-1">
|
|
134
|
+
<div className="w-4 h-4 rounded-sm bg-surface-4 flex items-center justify-center">
|
|
135
|
+
<Bot size={10} className="text-text-3" />
|
|
142
136
|
</div>
|
|
137
|
+
<span className="text-2xs text-text-3 font-sans font-medium">Lab Assistant</span>
|
|
138
|
+
</div>
|
|
139
|
+
<div className="ml-5 flex items-center gap-1.5 py-1">
|
|
140
|
+
<span className="w-1 h-1 rounded-full bg-text-4 animate-pulse" />
|
|
141
|
+
<span className="w-1 h-1 rounded-full bg-text-4 animate-pulse" style={{ animationDelay: '150ms' }} />
|
|
142
|
+
<span className="w-1 h-1 rounded-full bg-text-4 animate-pulse" style={{ animationDelay: '300ms' }} />
|
|
143
143
|
</div>
|
|
144
144
|
</div>
|
|
145
145
|
)}
|
|
@@ -148,23 +148,22 @@ export function LabAssistant() {
|
|
|
148
148
|
|
|
149
149
|
{/* Completion banner */}
|
|
150
150
|
{isComplete && messages.length > 0 && (
|
|
151
|
-
<div className="flex-shrink-0 px-
|
|
151
|
+
<div className="flex-shrink-0 px-4 py-2 bg-success/5 border-t border-success/10">
|
|
152
152
|
<div className="flex items-center justify-between">
|
|
153
153
|
<span className="text-xs font-sans text-success font-medium">Setup complete</span>
|
|
154
|
-
<
|
|
155
|
-
variant="primary"
|
|
156
|
-
size="sm"
|
|
154
|
+
<button
|
|
157
155
|
onClick={() => setLabAssistantMode(false)}
|
|
156
|
+
className="flex items-center gap-1 px-3 py-1.5 text-xs font-sans font-medium text-surface-0 bg-accent hover:bg-accent/90 rounded-sm transition-colors cursor-pointer"
|
|
158
157
|
>
|
|
159
|
-
<ArrowRight size={12}
|
|
160
|
-
</
|
|
158
|
+
<ArrowRight size={12} /> Playground
|
|
159
|
+
</button>
|
|
161
160
|
</div>
|
|
162
161
|
</div>
|
|
163
162
|
)}
|
|
164
163
|
|
|
165
164
|
{/* Input */}
|
|
166
|
-
<div className="flex-shrink-0 px-
|
|
167
|
-
<div className="flex items-end gap-2">
|
|
165
|
+
<div className="flex-shrink-0 px-4 py-3">
|
|
166
|
+
<div className="flex items-end gap-2 bg-surface-1 border border-border-subtle rounded-md p-1.5 focus-within:border-accent/30 transition-colors">
|
|
168
167
|
<textarea
|
|
169
168
|
ref={inputRef}
|
|
170
169
|
value={input}
|
|
@@ -174,24 +173,25 @@ export function LabAssistant() {
|
|
|
174
173
|
disabled={!isRunning}
|
|
175
174
|
rows={1}
|
|
176
175
|
className={cn(
|
|
177
|
-
'flex-1 resize-none bg-
|
|
176
|
+
'flex-1 resize-none bg-transparent px-2 py-1.5',
|
|
178
177
|
'text-xs text-text-0 font-sans placeholder:text-text-4',
|
|
179
|
-
'focus:outline-none
|
|
178
|
+
'focus:outline-none',
|
|
180
179
|
'disabled:opacity-40 disabled:cursor-not-allowed',
|
|
181
|
-
'min-h-[
|
|
180
|
+
'min-h-[28px] max-h-32',
|
|
182
181
|
)}
|
|
183
182
|
style={{ height: 'auto', overflowY: input.split('\n').length > 4 ? 'auto' : 'hidden' }}
|
|
184
183
|
onInput={(e) => { e.target.style.height = 'auto'; e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`; }}
|
|
185
184
|
/>
|
|
186
|
-
<
|
|
187
|
-
variant="primary"
|
|
188
|
-
size="icon"
|
|
189
|
-
className="flex-shrink-0"
|
|
185
|
+
<button
|
|
190
186
|
disabled={!input.trim() || !isRunning}
|
|
191
187
|
onClick={handleSend}
|
|
188
|
+
className={cn(
|
|
189
|
+
'flex-shrink-0 w-7 h-7 flex items-center justify-center rounded-sm transition-colors cursor-pointer',
|
|
190
|
+
input.trim() && isRunning ? 'bg-accent text-surface-0 hover:bg-accent/90' : 'bg-surface-3 text-text-4 cursor-not-allowed',
|
|
191
|
+
)}
|
|
192
192
|
>
|
|
193
|
-
<Send size={
|
|
194
|
-
</
|
|
193
|
+
<Send size={12} />
|
|
194
|
+
</button>
|
|
195
195
|
</div>
|
|
196
196
|
</div>
|
|
197
197
|
</div>
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useGrooveStore } from '../../stores/groove';
|
|
3
|
-
import { Button } from '../ui/button';
|
|
4
3
|
import { Tooltip } from '../ui/tooltip';
|
|
5
|
-
import { Badge } from '../ui/badge';
|
|
6
4
|
import { cn } from '../../lib/cn';
|
|
7
5
|
import { fmtNum } from '../../lib/format';
|
|
8
6
|
import { HEX, hexAlpha } from '../../lib/theme-hex';
|
|
@@ -11,24 +9,24 @@ import { Zap, Clock, Cpu, Hash, Timer, Link } from 'lucide-react';
|
|
|
11
9
|
function TtftGauge({ value }) {
|
|
12
10
|
if (value == null) {
|
|
13
11
|
return (
|
|
14
|
-
<div className="text-center py-
|
|
15
|
-
<div className="text-2xl font-mono font-bold text-text-4"
|
|
16
|
-
<div className="text-2xs text-text-4 font-sans mt-
|
|
12
|
+
<div className="text-center py-4">
|
|
13
|
+
<div className="text-2xl font-mono font-bold text-text-4 tabular-nums">--</div>
|
|
14
|
+
<div className="text-2xs text-text-4 font-sans mt-1">TTFT</div>
|
|
17
15
|
</div>
|
|
18
16
|
);
|
|
19
17
|
}
|
|
20
|
-
const color = value < 200 ? 'success' : value < 500 ? 'warning' : 'danger';
|
|
18
|
+
const color = value < 200 ? 'text-success' : value < 500 ? 'text-warning' : 'text-danger';
|
|
21
19
|
return (
|
|
22
|
-
<div className="text-center py-
|
|
23
|
-
<div className={cn('text-2xl font-mono font-bold',
|
|
20
|
+
<div className="text-center py-4">
|
|
21
|
+
<div className={cn('text-2xl font-mono font-bold tabular-nums', color)}>
|
|
24
22
|
{Math.round(value)}
|
|
25
23
|
</div>
|
|
26
|
-
<div className="text-2xs text-text-3 font-sans mt-
|
|
24
|
+
<div className="text-2xs text-text-3 font-sans mt-1">ms TTFT</div>
|
|
27
25
|
</div>
|
|
28
26
|
);
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
function Sparkline({ data, width =
|
|
29
|
+
function Sparkline({ data, width = 140, height = 32, color = HEX.accent }) {
|
|
32
30
|
if (!data || data.length < 2) {
|
|
33
31
|
return <div className="flex-shrink-0" style={{ width, height }} />;
|
|
34
32
|
}
|
|
@@ -49,22 +47,22 @@ function Sparkline({ data, width = 120, height = 28, color = HEX.accent }) {
|
|
|
49
47
|
const fillPoints = `0,${height} ${points} ${width},${height}`;
|
|
50
48
|
|
|
51
49
|
return (
|
|
52
|
-
<svg width={width} height={height} className="
|
|
53
|
-
<polyline points={fillPoints} fill={hexAlpha(color, 0.
|
|
54
|
-
<polyline points={points} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round" strokeOpacity="0.
|
|
50
|
+
<svg width={width} height={height} className="w-full">
|
|
51
|
+
<polyline points={fillPoints} fill={hexAlpha(color, 0.08)} stroke="none" />
|
|
52
|
+
<polyline points={points} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round" strokeOpacity="0.7" />
|
|
55
53
|
</svg>
|
|
56
54
|
);
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
function MetricRow({ icon: Icon, label, value, unit, tooltip }) {
|
|
60
58
|
const content = (
|
|
61
|
-
<div className="flex items-center justify-between py-
|
|
59
|
+
<div className="flex items-center justify-between py-2">
|
|
62
60
|
<div className="flex items-center gap-2">
|
|
63
|
-
<Icon size={
|
|
64
|
-
<span className="text-
|
|
61
|
+
<Icon size={11} className="text-text-4 flex-shrink-0" />
|
|
62
|
+
<span className="text-2xs text-text-3 font-sans">{label}</span>
|
|
65
63
|
</div>
|
|
66
|
-
<span className="text-xs font-mono font-medium text-text-
|
|
67
|
-
{value != null ? value : '
|
|
64
|
+
<span className="text-xs font-mono font-medium text-text-1 tabular-nums">
|
|
65
|
+
{value != null ? value : '--'}{unit && value != null ? <span className="text-text-4 ml-0.5 text-2xs">{unit}</span> : ''}
|
|
68
66
|
</span>
|
|
69
67
|
</div>
|
|
70
68
|
);
|
|
@@ -76,30 +74,30 @@ export function MetricsPanel() {
|
|
|
76
74
|
const activeRuntime = useGrooveStore((s) => s.labActiveRuntime);
|
|
77
75
|
|
|
78
76
|
return (
|
|
79
|
-
<div className="space-y-
|
|
80
|
-
<span className="text-xs font-semibold font-sans text-text-2 uppercase tracking-wider">Metrics</span>
|
|
81
|
-
|
|
77
|
+
<div className="space-y-5">
|
|
82
78
|
{/* TTFT Gauge */}
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
<TtftGauge value={metrics.ttft} />
|
|
80
|
+
|
|
81
|
+
<div className="h-px bg-border-subtle" />
|
|
86
82
|
|
|
87
83
|
{/* Tokens/sec with sparkline */}
|
|
88
|
-
<div className="
|
|
84
|
+
<div className="space-y-2">
|
|
89
85
|
<div className="flex items-center justify-between">
|
|
90
|
-
<div className="flex items-center gap-
|
|
91
|
-
<Zap size={
|
|
92
|
-
<span className="text-
|
|
86
|
+
<div className="flex items-center gap-1.5">
|
|
87
|
+
<Zap size={11} className="text-accent" />
|
|
88
|
+
<span className="text-2xs text-text-3 font-sans">Tokens/sec</span>
|
|
93
89
|
</div>
|
|
94
|
-
<span className="text-sm font-mono font-bold text-text-0">
|
|
95
|
-
{metrics.tokensPerSec != null ? metrics.tokensPerSec.toFixed(1) : '
|
|
90
|
+
<span className="text-sm font-mono font-bold text-text-0 tabular-nums">
|
|
91
|
+
{metrics.tokensPerSec != null ? metrics.tokensPerSec.toFixed(1) : '--'}
|
|
96
92
|
</span>
|
|
97
93
|
</div>
|
|
98
|
-
<Sparkline data={metrics.tokensPerSecHistory}
|
|
94
|
+
<Sparkline data={metrics.tokensPerSecHistory} />
|
|
99
95
|
</div>
|
|
100
96
|
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
<div className="h-px bg-border-subtle" />
|
|
98
|
+
|
|
99
|
+
{/* Stats */}
|
|
100
|
+
<div className="space-y-0">
|
|
103
101
|
<MetricRow
|
|
104
102
|
icon={Cpu}
|
|
105
103
|
label="Memory"
|
|
@@ -124,11 +122,14 @@ export function MetricsPanel() {
|
|
|
124
122
|
|
|
125
123
|
{/* Attach to agent */}
|
|
126
124
|
{activeRuntime && (
|
|
127
|
-
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
<>
|
|
126
|
+
<div className="h-px bg-border-subtle" />
|
|
127
|
+
<Tooltip content="Use current preset when spawning a new agent">
|
|
128
|
+
<button className="w-full flex items-center justify-center gap-1.5 px-3 py-2 text-2xs font-sans text-text-3 hover:text-text-1 border border-border-subtle rounded-sm hover:border-border transition-colors cursor-pointer">
|
|
129
|
+
<Link size={11} /> Attach to Agent
|
|
130
|
+
</button>
|
|
131
|
+
</Tooltip>
|
|
132
|
+
</>
|
|
132
133
|
)}
|
|
133
134
|
</div>
|
|
134
135
|
);
|