groove-dev 0.22.4 → 0.22.6

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.
@@ -5,12 +5,12 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/png" href="/favicon.png" />
7
7
  <title>Groove GUI</title>
8
- <script type="module" crossorigin src="/assets/index-BHDZqhzW.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DZxQV2hQ.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
13
- <link rel="stylesheet" crossorigin href="/assets/index-BDyGhxDd.css">
13
+ <link rel="stylesheet" crossorigin href="/assets/index-DHaSTu1c.css">
14
14
  </head>
15
15
  <body>
16
16
  <div id="root"></div>
@@ -38,29 +38,162 @@ function activityMeta(text) {
38
38
  return { icon: Code2, color: 'text-text-3', label: 'Activity' };
39
39
  }
40
40
 
41
- // ── Markdown-lite rendering ──────────────────────────────────
41
+ // ── Inline formatting (bold, code) ───────────────────────────
42
+ function InlineFormat({ text }) {
43
+ if (!text) return null;
44
+ return text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).map((part, i) => {
45
+ if (part.startsWith('**') && part.endsWith('**'))
46
+ return <strong key={i} className="font-semibold text-text-0">{part.slice(2, -2)}</strong>;
47
+ if (part.startsWith('`') && part.endsWith('`'))
48
+ return <code key={i} className="px-1 py-px rounded bg-accent/8 text-[11px] font-mono text-accent">{part.slice(1, -1)}</code>;
49
+ return <span key={i}>{part}</span>;
50
+ });
51
+ }
52
+
53
+ // ── Structured message renderer ──────────────────────────────
54
+ // Parses agent output into sections: headers, bullets, code blocks, paragraphs
55
+ function StructuredMessage({ text }) {
56
+ if (!text) return null;
57
+
58
+ const blocks = [];
59
+ const lines = text.split('\n');
60
+ let i = 0;
61
+
62
+ while (i < lines.length) {
63
+ const line = lines[i];
64
+
65
+ // Code block
66
+ if (line.trimStart().startsWith('```')) {
67
+ const codeLines = [];
68
+ const lang = line.trim().slice(3);
69
+ i++;
70
+ while (i < lines.length && !lines[i].trimStart().startsWith('```')) {
71
+ codeLines.push(lines[i]);
72
+ i++;
73
+ }
74
+ i++; // skip closing ```
75
+ blocks.push({ type: 'code', content: codeLines.join('\n'), lang });
76
+ continue;
77
+ }
78
+
79
+ // Header (## or **Header:** at start of line)
80
+ if (/^#{1,3}\s/.test(line) || /^\*\*[^*]+:\*\*\s*$/.test(line.trim())) {
81
+ const heading = line.replace(/^#+\s*/, '').replace(/^\*\*/, '').replace(/:\*\*\s*$/, ':').trim();
82
+ blocks.push({ type: 'heading', content: heading });
83
+ i++;
84
+ continue;
85
+ }
86
+
87
+ // Bullet / dash list item
88
+ if (/^\s*[-*]\s/.test(line)) {
89
+ const items = [];
90
+ while (i < lines.length && /^\s*[-*]\s/.test(lines[i])) {
91
+ items.push(lines[i].replace(/^\s*[-*]\s+/, '').trim());
92
+ i++;
93
+ }
94
+ blocks.push({ type: 'list', items });
95
+ continue;
96
+ }
97
+
98
+ // Numbered list
99
+ if (/^\s*\d+[\.)]\s/.test(line)) {
100
+ const items = [];
101
+ while (i < lines.length && /^\s*\d+[\.)]\s/.test(lines[i])) {
102
+ items.push(lines[i].replace(/^\s*\d+[\.)]\s+/, '').trim());
103
+ i++;
104
+ }
105
+ blocks.push({ type: 'numbered', items });
106
+ continue;
107
+ }
108
+
109
+ // Empty line — skip
110
+ if (!line.trim()) { i++; continue; }
111
+
112
+ // Note/warning line
113
+ if (/^(Note|Warning|Important|IMPORTANT|TODO):/i.test(line.trim())) {
114
+ blocks.push({ type: 'note', content: line.trim() });
115
+ i++;
116
+ continue;
117
+ }
118
+
119
+ // Regular paragraph — collect consecutive non-empty lines
120
+ const paraLines = [];
121
+ while (i < lines.length && lines[i].trim() && !/^#{1,3}\s/.test(lines[i]) && !/^\s*[-*]\s/.test(lines[i]) && !/^\s*\d+[\.)]\s/.test(lines[i]) && !lines[i].trimStart().startsWith('```')) {
122
+ paraLines.push(lines[i].trim());
123
+ i++;
124
+ }
125
+ if (paraLines.length > 0) {
126
+ blocks.push({ type: 'para', content: paraLines.join(' ') });
127
+ }
128
+ }
129
+
130
+ return (
131
+ <div className="space-y-2">
132
+ {blocks.map((block, idx) => {
133
+ switch (block.type) {
134
+ case 'heading':
135
+ return (
136
+ <div key={idx} className="flex items-center gap-1.5 pt-1.5 first:pt-0">
137
+ <div className="w-1 h-3.5 rounded-full bg-accent/40 flex-shrink-0" />
138
+ <span className="text-[12px] font-semibold text-text-0 font-sans"><InlineFormat text={block.content} /></span>
139
+ </div>
140
+ );
141
+ case 'list':
142
+ return (
143
+ <div key={idx} className="space-y-1 pl-2">
144
+ {block.items.map((item, j) => (
145
+ <div key={j} className="flex gap-2 text-[12px] text-text-2 font-sans leading-relaxed">
146
+ <span className="text-accent/50 mt-0.5 flex-shrink-0">-</span>
147
+ <span className="min-w-0"><InlineFormat text={item} /></span>
148
+ </div>
149
+ ))}
150
+ </div>
151
+ );
152
+ case 'numbered':
153
+ return (
154
+ <div key={idx} className="space-y-1 pl-2">
155
+ {block.items.map((item, j) => (
156
+ <div key={j} className="flex gap-2 text-[12px] text-text-2 font-sans leading-relaxed">
157
+ <span className="text-text-4 font-mono w-4 text-right flex-shrink-0">{j + 1}.</span>
158
+ <span className="min-w-0"><InlineFormat text={item} /></span>
159
+ </div>
160
+ ))}
161
+ </div>
162
+ );
163
+ case 'code':
164
+ return (
165
+ <pre key={idx} className="p-2.5 rounded-md bg-[#0d1117] text-[11px] font-mono text-[#c9d1d9] overflow-x-auto whitespace-pre-wrap border border-white/[0.06] leading-relaxed">
166
+ {block.content}
167
+ </pre>
168
+ );
169
+ case 'note':
170
+ return (
171
+ <div key={idx} className="flex items-start gap-1.5 px-2.5 py-1.5 rounded-md bg-warning/6 border border-warning/12">
172
+ <AlertCircle size={10} className="text-warning mt-0.5 flex-shrink-0" />
173
+ <span className="text-[11px] text-warning/80 font-sans"><InlineFormat text={block.content} /></span>
174
+ </div>
175
+ );
176
+ case 'para':
177
+ default:
178
+ return <p key={idx} className="text-[12px] text-text-2 font-sans leading-relaxed"><InlineFormat text={block.content} /></p>;
179
+ }
180
+ })}
181
+ </div>
182
+ );
183
+ }
184
+
185
+ // Simple inline formatting for user messages
42
186
  function FormattedText({ text }) {
43
187
  if (!text) return null;
44
- const parts = text.split(/(```[\s\S]*?```|`[^`]+`)/g);
188
+ const parts = text.split(/(```[\s\S]*?```)/g);
45
189
  return (
46
190
  <span>
47
191
  {parts.map((part, i) => {
48
192
  if (part.startsWith('```') && part.endsWith('```')) {
49
193
  const code = part.slice(3, -3).replace(/^\w+\n/, '');
50
- return (
51
- <pre key={i} className="my-2.5 p-3.5 rounded-lg bg-[#0d1117] text-[12px] font-mono text-[#c9d1d9] overflow-x-auto whitespace-pre-wrap border border-white/[0.06] leading-relaxed">
52
- {code}
53
- </pre>
54
- );
194
+ return <pre key={i} className="my-1.5 p-2 rounded-md bg-[#0d1117] text-[11px] font-mono text-[#c9d1d9] overflow-x-auto whitespace-pre-wrap border border-white/[0.06]">{code}</pre>;
55
195
  }
56
- if (part.startsWith('`') && part.endsWith('`')) {
57
- return <code key={i} className="px-1.5 py-0.5 rounded bg-accent/8 text-[12px] font-mono text-accent border border-accent/10">{part.slice(1, -1)}</code>;
58
- }
59
- return <span key={i}>{part.split(/(\*\*[^*]+\*\*)/g).map((s, j) =>
60
- s.startsWith('**') && s.endsWith('**')
61
- ? <strong key={j} className="font-semibold text-text-0">{s.slice(2, -2)}</strong>
62
- : s
63
- )}</span>;
196
+ return <span key={i}><InlineFormat text={part} /></span>;
64
197
  })}
65
198
  </span>
66
199
  );
@@ -71,8 +204,8 @@ function FormattedText({ text }) {
71
204
  function UserMessage({ msg }) {
72
205
  const isQuery = msg.isQuery;
73
206
  return (
74
- <div className="flex justify-end pl-12">
75
- <div className="max-w-[85%]">
207
+ <div className="flex justify-end pl-8">
208
+ <div className="max-w-[90%]">
76
209
  {isQuery && (
77
210
  <div className="flex items-center justify-end gap-1 mb-1">
78
211
  <HelpCircle size={9} className="text-info" />
@@ -80,24 +213,27 @@ function UserMessage({ msg }) {
80
213
  </div>
81
214
  )}
82
215
  <div className={cn(
83
- 'px-4 py-3 rounded-2xl rounded-br-md',
216
+ 'px-3.5 py-2.5 rounded-2xl rounded-br-md',
84
217
  isQuery
85
218
  ? 'bg-info/10 border border-info/15'
86
219
  : 'bg-accent/10 border border-accent/15',
87
220
  )}>
88
- <div className="text-[13px] font-sans whitespace-pre-wrap break-words leading-relaxed text-text-0">
221
+ <div className="text-[12px] font-sans whitespace-pre-wrap break-words leading-relaxed text-text-0">
89
222
  <FormattedText text={msg.text} />
90
223
  </div>
91
224
  </div>
92
- <div className="text-[10px] text-text-4 font-sans mt-1.5 text-right">{timeAgo(msg.timestamp)}</div>
225
+ <div className="text-[10px] text-text-4 font-sans mt-1 text-right">{timeAgo(msg.timestamp)}</div>
93
226
  </div>
94
227
  </div>
95
228
  );
96
229
  }
97
230
 
98
231
  function AgentMessage({ msg, agent }) {
232
+ const [collapsed, setCollapsed] = useState(msg.text?.length > 600);
233
+ const isLong = msg.text?.length > 600;
234
+
99
235
  return (
100
- <div className="pr-6">
236
+ <div className="pr-2">
101
237
  <div className="flex items-center gap-2 mb-1.5">
102
238
  <div className="w-5 h-5 rounded-md bg-accent/12 flex items-center justify-center flex-shrink-0">
103
239
  <Code2 size={10} className="text-accent" />
@@ -105,12 +241,32 @@ function AgentMessage({ msg, agent }) {
105
241
  <span className="text-2xs font-semibold text-text-1 font-sans">{agent?.name || 'Agent'}</span>
106
242
  <span className="text-2xs text-text-4 font-sans">{agent?.role}</span>
107
243
  </div>
108
- <div className="ml-7 px-4 py-3 rounded-2xl rounded-tl-md bg-surface-2/60 border border-border-subtle">
109
- <div className="text-[13px] text-text-1 font-sans whitespace-pre-wrap break-words leading-relaxed">
110
- <FormattedText text={msg.text} />
111
- </div>
244
+ <div className={cn(
245
+ 'ml-7 px-3.5 py-3 rounded-xl bg-surface-2/40 border border-border-subtle/50 overflow-hidden',
246
+ collapsed && 'max-h-[200px] relative',
247
+ )}>
248
+ <StructuredMessage text={collapsed ? msg.text.slice(0, 600) : msg.text} />
249
+ {collapsed && (
250
+ <div className="absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-surface-2/90 to-transparent flex items-end justify-center pb-2">
251
+ <button
252
+ onClick={() => setCollapsed(false)}
253
+ className="flex items-center gap-1 px-3 py-1 rounded-full bg-surface-3 border border-border-subtle text-2xs text-text-2 font-sans hover:text-text-0 cursor-pointer transition-colors"
254
+ >
255
+ <ChevronDown size={10} />
256
+ Show full response
257
+ </button>
258
+ </div>
259
+ )}
112
260
  </div>
113
- <div className="text-[10px] text-text-4 font-sans mt-1.5 ml-7">{timeAgo(msg.timestamp)}</div>
261
+ {isLong && !collapsed && (
262
+ <button
263
+ onClick={() => setCollapsed(true)}
264
+ className="ml-7 mt-1 text-[10px] text-text-4 hover:text-text-2 font-sans cursor-pointer"
265
+ >
266
+ Collapse
267
+ </button>
268
+ )}
269
+ <div className="text-[10px] text-text-4 font-sans mt-1 ml-7">{timeAgo(msg.timestamp)}</div>
114
270
  </div>
115
271
  );
116
272
  }
@@ -26,7 +26,8 @@ const LANGS = {
26
26
 
27
27
  // Custom theme overrides to match our design tokens
28
28
  const grooveTheme = EditorView.theme({
29
- '&': { backgroundColor: '#24282f', color: '#bcc2cd', fontFamily: 'var(--font-mono)', fontSize: '13px' },
29
+ '&': { backgroundColor: '#24282f', color: '#bcc2cd', fontFamily: 'var(--font-mono)', fontSize: '13px', height: '100%' },
30
+ '.cm-scroller': { overflow: 'auto' },
30
31
  '.cm-content': { caretColor: '#33afbc' },
31
32
  '.cm-cursor': { borderLeftColor: '#33afbc' },
32
33
  '.cm-gutters': { backgroundColor: '#24282f', borderRight: '1px solid #2c313a', color: '#505862' },
@@ -103,5 +104,5 @@ export function CodeEditor({ content, language, onChange, onSave }) {
103
104
  view.dispatch({ effects: langCompartment.current.reconfigure(langExt()) });
104
105
  }, [language]);
105
106
 
106
- return <div ref={containerRef} className="w-full h-full" />;
107
+ return <div ref={containerRef} className="w-full h-full overflow-hidden" />;
107
108
  }