groove-dev 0.27.143 → 0.27.144

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.
Files changed (186) hide show
  1. package/CLAUDE.md +0 -7
  2. package/node_modules/@groove-dev/cli/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  5. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  6. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  7. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  8. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  9. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  10. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  11. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  12. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  13. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  14. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  21. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  22. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  23. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  24. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  25. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  26. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  28. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  29. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  30. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  32. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  33. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  34. package/node_modules/@groove-dev/gui/package.json +1 -1
  35. package/node_modules/@groove-dev/gui/src/{app.jsx → App.jsx} +0 -2
  36. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  39. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  40. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  41. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  42. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  43. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  45. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  54. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  55. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
  56. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
  57. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
  58. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
  59. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
  60. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
  61. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
  62. package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
  63. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
  64. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
  65. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
  66. package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
  67. package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
  68. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
  69. package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
  70. package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
  71. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
  72. package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
  73. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
  74. package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
  75. package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
  76. package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
  77. package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
  78. package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
  79. package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
  80. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
  81. package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
  82. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
  83. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
  84. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
  85. package/node_modules/@groove-dev/gui/src/views/models.jsx +409 -507
  86. package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
  87. package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
  88. package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
  89. package/package.json +1 -1
  90. package/packages/cli/package.json +1 -1
  91. package/packages/daemon/package.json +1 -1
  92. package/packages/daemon/src/api.js +1086 -6532
  93. package/packages/daemon/src/gateways/manager.js +35 -1
  94. package/packages/daemon/src/index.js +3 -0
  95. package/packages/daemon/src/journalist.js +23 -13
  96. package/packages/daemon/src/mlx-server.js +365 -0
  97. package/packages/daemon/src/model-lab.js +308 -12
  98. package/packages/daemon/src/pm.js +1 -1
  99. package/packages/daemon/src/process.js +2 -2
  100. package/packages/daemon/src/providers/local.js +36 -8
  101. package/packages/daemon/src/registry.js +21 -5
  102. package/packages/daemon/src/routes/agents.js +889 -0
  103. package/packages/daemon/src/routes/coordination.js +318 -0
  104. package/packages/daemon/src/routes/files.js +751 -0
  105. package/packages/daemon/src/routes/integrations.js +485 -0
  106. package/packages/daemon/src/routes/network.js +1784 -0
  107. package/packages/daemon/src/routes/providers.js +755 -0
  108. package/packages/daemon/src/routes/schedules.js +110 -0
  109. package/packages/daemon/src/routes/teams.js +650 -0
  110. package/packages/daemon/src/scheduler.js +456 -24
  111. package/packages/daemon/src/teams.js +1 -1
  112. package/packages/daemon/src/validate.js +38 -1
  113. package/packages/daemon/templates/mlx-setup.json +12 -0
  114. package/packages/daemon/templates/tgi-setup.json +1 -1
  115. package/packages/daemon/templates/vllm-setup.json +1 -1
  116. package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  117. package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  118. package/packages/gui/dist/index.html +2 -2
  119. package/packages/gui/package.json +1 -1
  120. package/packages/gui/src/{app.jsx → App.jsx} +0 -2
  121. package/packages/gui/src/app.css +35 -0
  122. package/packages/gui/src/components/agents/agent-config.jsx +1 -128
  123. package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
  124. package/packages/gui/src/components/agents/agent-node.jsx +8 -13
  125. package/packages/gui/src/components/agents/code-review.jsx +159 -122
  126. package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
  127. package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
  128. package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
  129. package/packages/gui/src/components/automations/automation-card.jsx +274 -0
  130. package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
  131. package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
  132. package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
  133. package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
  134. package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  135. package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
  136. package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
  137. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  138. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  139. package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
  140. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  141. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  142. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  143. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  144. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  145. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  146. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  147. package/packages/gui/src/components/network/network-health.jsx +2 -2
  148. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  149. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  150. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  151. package/packages/gui/src/lib/cron.js +64 -0
  152. package/packages/gui/src/lib/status.js +24 -24
  153. package/packages/gui/src/lib/theme-hex.js +1 -0
  154. package/packages/gui/src/stores/groove.js +34 -3144
  155. package/packages/gui/src/stores/helpers.js +10 -0
  156. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  157. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  158. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  159. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  160. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  161. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  162. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  163. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  164. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  165. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  166. package/packages/gui/src/views/agents.jsx +5 -5
  167. package/packages/gui/src/views/dashboard.jsx +12 -13
  168. package/packages/gui/src/views/marketplace.jsx +191 -3
  169. package/packages/gui/src/views/model-lab.jsx +17 -6
  170. package/packages/gui/src/views/models.jsx +409 -507
  171. package/packages/gui/src/views/network.jsx +3 -3
  172. package/packages/gui/src/views/settings.jsx +81 -94
  173. package/packages/gui/src/views/teams.jsx +40 -483
  174. package/SECURITY_SWEEP.md +0 -228
  175. package/TRAINING_DATA_v4.md +0 -6
  176. package/node_modules/@groove-dev/gui/dist/assets/index-CCVvAoQn.css +0 -1
  177. package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +0 -984
  178. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -379
  179. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  180. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  181. package/packages/gui/dist/assets/index-CCVvAoQn.css +0 -1
  182. package/packages/gui/dist/assets/index-DGIv_TRm.js +0 -984
  183. package/packages/gui/src/components/agents/agent-chat.jsx +0 -379
  184. package/packages/gui/src/views/preview.jsx +0 -6
  185. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  186. package/test.py +0 -571
@@ -1,379 +0,0 @@
1
- // FSL-1.1-Apache-2.0 — see LICENSE
2
- import { useState, useRef, useEffect } from 'react';
3
- import { Send, Loader2, MessageSquare, HelpCircle, ArrowRight, Paperclip, Square, FileCode, Terminal as TerminalIcon, X } from 'lucide-react';
4
- import { useGrooveStore } from '../../stores/groove';
5
- import { cn } from '../../lib/cn';
6
- import { ThinkingIndicator } from '../ui/thinking-indicator';
7
- import { TableTree } from '../ui/table-tree';
8
- import { timeAgo } from '../../lib/format';
9
-
10
- const EMPTY = [];
11
-
12
- function parseSegments(text) {
13
- const lines = text.split('\n');
14
- const segments = [];
15
- let i = 0;
16
- let textLines = [];
17
- while (i < lines.length) {
18
- const line = lines[i];
19
- if (line.includes('|') && i + 1 < lines.length && /^\|?\s*[-:]+/.test(lines[i + 1])) {
20
- if (textLines.length > 0) {
21
- segments.push({ type: 'text', content: textLines.join('\n') });
22
- textLines = [];
23
- }
24
- const headers = line.split('|').map((c) => c.trim()).filter(Boolean);
25
- i += 2;
26
- const rows = [];
27
- while (i < lines.length && lines[i].includes('|')) {
28
- rows.push(lines[i].split('|').map((c) => c.trim()).filter(Boolean));
29
- i++;
30
- }
31
- segments.push({ type: 'table', headers, rows });
32
- } else {
33
- textLines.push(line);
34
- i++;
35
- }
36
- }
37
- if (textLines.length > 0) segments.push({ type: 'text', content: textLines.join('\n') });
38
- return segments;
39
- }
40
-
41
- function highlightKeeper(text) {
42
- const parts = text.split(/(\[(?:save|append|update|delete|view|doc|link|read|instruct)\]|#[\w/.-]+)/gi);
43
- return parts.map((part, i) => {
44
- if (/^\[(?:save|append|update|delete|view|doc|link|read|instruct)\]$/i.test(part)) {
45
- return <span key={i} className="px-1 py-0.5 rounded bg-accent/15 text-accent font-semibold font-mono text-2xs">{part}</span>;
46
- }
47
- if (/^#[\w/.-]+$/.test(part)) {
48
- return <span key={i} className="text-accent font-medium">{part}</span>;
49
- }
50
- return part;
51
- });
52
- }
53
-
54
- function FormattedText({ text }) {
55
- if (!text) return null;
56
- const segments = parseSegments(text);
57
- return (
58
- <>
59
- {segments.map((seg, idx) => {
60
- if (seg.type === 'table') {
61
- return <TableTree key={idx} headers={seg.headers} rows={seg.rows} />;
62
- }
63
- const parts = seg.content.split(/(```[\s\S]*?```|`[^`]+`)/g);
64
- return (
65
- <span key={idx}>
66
- {parts.map((part, i) => {
67
- if (part.startsWith('```') && part.endsWith('```')) {
68
- const code = part.slice(3, -3).replace(/^\w+\n/, '');
69
- return <pre key={i} className="my-2 p-3 rounded-md bg-surface-0 text-xs font-mono text-text-1 overflow-x-auto whitespace-pre-wrap">{code}</pre>;
70
- }
71
- if (part.startsWith('`') && part.endsWith('`')) {
72
- return <code key={i} className="px-1.5 py-0.5 rounded bg-surface-0 text-xs font-mono text-accent">{part.slice(1, -1)}</code>;
73
- }
74
- return <span key={i}>{part.split(/(\*\*[^*]+\*\*)/g).map((s, j) =>
75
- s.startsWith('**') && s.endsWith('**')
76
- ? <strong key={j} className="font-semibold text-text-0">{s.slice(2, -2)}</strong>
77
- : <span key={j}>{highlightKeeper(s)}</span>
78
- )}</span>;
79
- })}
80
- </span>
81
- );
82
- })}
83
- </>
84
- );
85
- }
86
-
87
- function UserMessage({ msg }) {
88
- return (
89
- <div className="flex justify-end">
90
- <div className="max-w-[85%]">
91
- {msg.isQuery && (
92
- <div className="flex items-center gap-1 justify-end mb-1">
93
- <HelpCircle size={10} className="text-info" />
94
- <span className="text-2xs text-info font-sans">Query</span>
95
- </div>
96
- )}
97
- <div className={cn(
98
- 'px-3 py-2 rounded-xl rounded-br-sm',
99
- msg.isQuery ? 'bg-info/10 border border-info/15' : 'bg-accent/10 border border-accent/15',
100
- )}>
101
- <p className="text-xs text-text-0 font-sans whitespace-pre-wrap break-words leading-relaxed">
102
- <FormattedText text={msg.text} />
103
- </p>
104
- </div>
105
- <div className="text-2xs text-text-4 font-sans mt-0.5 text-right">{timeAgo(msg.timestamp)}</div>
106
- </div>
107
- </div>
108
- );
109
- }
110
-
111
- function AgentMessage({ msg, agent }) {
112
- return (
113
- <div>
114
- <div className="text-2xs text-text-3 font-sans mb-0.5 font-medium">{agent?.name}</div>
115
- <div className="border-l-2 border-accent/40 pl-3 py-0.5">
116
- <div className="text-xs text-text-1 font-sans whitespace-pre-wrap break-words leading-relaxed">
117
- <FormattedText text={msg.text} />
118
- </div>
119
- </div>
120
- <div className="text-2xs text-text-4 font-sans mt-0.5">{timeAgo(msg.timestamp)}</div>
121
- </div>
122
- );
123
- }
124
-
125
- function SystemMessage({ msg }) {
126
- return (
127
- <div className="flex justify-center py-1">
128
- <div className="flex items-center gap-1.5 px-3 py-1 rounded-full bg-surface-4/50">
129
- <ArrowRight size={10} className="text-text-4" />
130
- <span className="text-2xs text-text-3 font-sans">{msg.text}</span>
131
- </div>
132
- </div>
133
- );
134
- }
135
-
136
- function TypingIndicator() {
137
- return (
138
- <div className="border-l-2 border-accent/40 pl-3 py-2">
139
- <div className="flex items-center gap-1">
140
- <span className="w-1.5 h-1.5 rounded-full bg-text-3 animate-pulse" style={{ animationDelay: '0ms' }} />
141
- <span className="w-1.5 h-1.5 rounded-full bg-text-3 animate-pulse" style={{ animationDelay: '150ms' }} />
142
- <span className="w-1.5 h-1.5 rounded-full bg-text-3 animate-pulse" style={{ animationDelay: '300ms' }} />
143
- </div>
144
- </div>
145
- );
146
- }
147
-
148
- function SnippetTag({ snippet, onRemove }) {
149
- const isCode = snippet.type === 'code';
150
- const Icon = isCode ? FileCode : TerminalIcon;
151
- const lines = snippet.code.split('\n').length;
152
- let label;
153
- if (isCode && snippet.filePath) {
154
- const fileName = snippet.filePath.split('/').pop();
155
- label = `${fileName}:${snippet.lineStart}-${snippet.lineEnd}`;
156
- } else {
157
- label = `${isCode ? '' : 'Terminal · '}${lines} line${lines !== 1 ? 's' : ''}`;
158
- }
159
- return (
160
- <div className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-accent/10 border border-accent/20 text-accent">
161
- <Icon size={11} className="flex-shrink-0" />
162
- <span className="text-2xs font-sans font-medium truncate max-w-[160px]">{label}</span>
163
- {snippet.instruction && (
164
- <span className="text-2xs text-accent/60 truncate max-w-[100px]">· {snippet.instruction}</span>
165
- )}
166
- <button onClick={onRemove} className="p-0.5 rounded hover:bg-accent/20 cursor-pointer flex-shrink-0">
167
- <X size={9} />
168
- </button>
169
- </div>
170
- );
171
- }
172
-
173
- export function AgentChat({ agent }) {
174
- const chatHistory = useGrooveStore((s) => s.chatHistory[agent.id]) || EMPTY;
175
- const activityLog = useGrooveStore((s) => s.activityLog[agent.id]) || EMPTY;
176
- const instructAgent = useGrooveStore((s) => s.instructAgent);
177
- const isThinking = useGrooveStore((s) => s.thinkingAgents?.has(agent.id));
178
-
179
- const pendingSnippet = useGrooveStore((s) => s.editorPendingSnippet);
180
- const clearSnippet = useGrooveStore((s) => s.clearSnippet);
181
-
182
- const storeInput = useGrooveStore((s) => s.chatInputs[agent.id] || '');
183
- const setStoreInput = (val) => useGrooveStore.setState((s) => ({ chatInputs: { ...s.chatInputs, [agent.id]: val } }));
184
- const input = storeInput;
185
- const setInput = setStoreInput;
186
- const [sending, setSending] = useState(false);
187
- const [attachedFiles, setAttachedFiles] = useState([]);
188
- const scrollRef = useRef(null);
189
- const inputRef = useRef(null);
190
- const fileInputRef = useRef(null);
191
- const isAtBottomRef = useRef(true);
192
-
193
- useEffect(() => {
194
- if (pendingSnippet) inputRef.current?.focus();
195
- }, [pendingSnippet]);
196
-
197
- useEffect(() => {
198
- const el = scrollRef.current;
199
- if (!el) return;
200
- function handleScroll() {
201
- isAtBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 60;
202
- }
203
- el.addEventListener('scroll', handleScroll);
204
- return () => el.removeEventListener('scroll', handleScroll);
205
- }, []);
206
-
207
- useEffect(() => {
208
- if (isAtBottomRef.current && scrollRef.current) {
209
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
210
- }
211
- }, [chatHistory.length, activityLog.length, sending, isThinking]);
212
-
213
- function handleFileSelect(e) {
214
- const files = Array.from(e.target.files || []);
215
- if (files.length === 0) return;
216
- // Use webkitRelativePath or name — files from input have path info
217
- const paths = files.map((f) => f.name);
218
- setAttachedFiles((prev) => [...prev, ...paths]);
219
- // Also add file paths to the prompt so the agent knows about them
220
- const pathList = files.map((f) => f.name).join(', ');
221
- setInput((prev) => prev + (prev ? '\n' : '') + `[Attached files: ${pathList}]`);
222
- e.target.value = '';
223
- }
224
-
225
- async function handleSend() {
226
- const text = input.trim();
227
- if ((!text && !pendingSnippet) || sending) return;
228
- const parts = [];
229
- if (text) parts.push(text);
230
- if (pendingSnippet) {
231
- const s = pendingSnippet;
232
- if (s.type === 'code' && s.filePath) {
233
- if (s.instruction && !text) parts.push(s.instruction);
234
- parts.push(`File: ${s.filePath} (lines ${s.lineStart}-${s.lineEnd})`);
235
- parts.push('```\n' + s.code + '\n```');
236
- } else if (s.code) {
237
- parts.push('```\n' + s.code + '\n```');
238
- }
239
- }
240
- const message = parts.join('\n\n');
241
- setInput('');
242
- setAttachedFiles([]);
243
- clearSnippet();
244
- setSending(true);
245
- isAtBottomRef.current = true;
246
- requestAnimationFrame(() => {
247
- if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
248
- });
249
- try {
250
- await instructAgent(agent.id, message);
251
- } catch { /* toast handles */ }
252
- setSending(false);
253
- inputRef.current?.focus();
254
- }
255
-
256
- function onKeyDown(e) {
257
- if (e.key === 'Enter' && !e.shiftKey) {
258
- e.preventDefault();
259
- handleSend();
260
- }
261
- }
262
-
263
- const isAlive = agent.status === 'running' || agent.status === 'starting';
264
-
265
- // Build merged timeline
266
- const recentActivity = activityLog.slice(-8).map((a, i) => ({
267
- from: 'agent', text: a.text, timestamp: a.timestamp, key: `act-${i}`,
268
- }));
269
- const messages = chatHistory.length > 0 ? chatHistory : recentActivity;
270
-
271
- return (
272
- <div className="flex flex-col h-full">
273
- {/* Messages */}
274
- <div ref={scrollRef} className="flex-1 overflow-y-auto px-3 py-3 space-y-3">
275
- {messages.length === 0 && (
276
- <div className="flex flex-col items-center justify-center h-full text-center py-12">
277
- <div className="w-12 h-12 rounded-full bg-accent/8 flex items-center justify-center mb-4">
278
- <MessageSquare size={20} className="text-accent" />
279
- </div>
280
- <p className="text-sm font-medium text-text-1 font-sans">
281
- {isAlive ? 'Agent is running' : 'Agent finished'}
282
- </p>
283
- <p className="text-xs text-text-3 font-sans mt-1 max-w-[240px]">
284
- {isAlive
285
- ? 'Send a message to guide this agent. Use the stop button to interrupt.'
286
- : 'Reply to continue the conversation — a new session starts with full context.'}
287
- </p>
288
- </div>
289
- )}
290
- {messages.map((msg, i) => {
291
- if (msg.from === 'user') return <UserMessage key={msg.key || i} msg={msg} />;
292
- if (msg.from === 'system') return <SystemMessage key={msg.key || i} msg={msg} />;
293
- return <AgentMessage key={msg.key || i} msg={msg} agent={agent} />;
294
- })}
295
- {(sending || isThinking) && <ThinkingIndicator className="ml-8 py-1" />}
296
- </div>
297
-
298
- {/* ── Input area ──────────────────────────────────── */}
299
- <div className="border-t border-border-subtle px-3 py-2 bg-surface-1">
300
- {pendingSnippet && (
301
- <div className="mb-1.5">
302
- <SnippetTag snippet={pendingSnippet} onRemove={clearSnippet} />
303
- </div>
304
- )}
305
- {input && /\[(?:save|append|update|delete|view|doc|link|read|instruct)\]/i.test(input) && (() => {
306
- const cmdMatch = input.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
307
- const tags = (input.match(/#[\w/.-]+/g) || []);
308
- return (
309
- <div className="flex items-center gap-1.5 px-3 py-1 mb-1.5 rounded-md bg-accent/5 border border-accent/10">
310
- <span className="px-1.5 py-0.5 rounded bg-accent/15 text-accent font-semibold font-mono text-2xs">{cmdMatch[0]}</span>
311
- {tags.map((tag, i) => <span key={i} className="text-accent font-medium text-2xs">{tag}</span>)}
312
- <span className="text-2xs text-text-4 ml-auto">memory command</span>
313
- </div>
314
- );
315
- })()}
316
- <div className="flex items-end gap-1.5">
317
- <input
318
- ref={fileInputRef}
319
- type="file"
320
- multiple
321
- accept=".pdf,.png,.jpg,.jpeg,.gif,.svg,.csv,.txt,.md,.json,.yaml,.yml,.docx,.pptx,.xlsx"
322
- onChange={handleFileSelect}
323
- className="hidden"
324
- />
325
- <button
326
- onClick={() => fileInputRef.current?.click()}
327
- className="w-8 h-8 flex items-center justify-center rounded-lg text-text-4 hover:text-text-1 hover:bg-surface-3 transition-colors cursor-pointer flex-shrink-0"
328
- title="Attach file"
329
- >
330
- <Paperclip size={14} />
331
- </button>
332
- <div className="flex-1">
333
- <textarea
334
- ref={inputRef}
335
- value={input}
336
- onChange={(e) => setInput(e.target.value)}
337
- onKeyDown={onKeyDown}
338
- placeholder={pendingSnippet ? 'Add a message (optional)...' : isAlive ? 'Instruct this agent...' : 'Continue conversation...'}
339
- rows={1}
340
- className={cn(
341
- 'w-full resize-none rounded-lg px-3 py-1.5 text-xs',
342
- 'bg-surface-0 border font-sans',
343
- 'placeholder:text-text-4',
344
- 'focus:outline-none focus:ring-1',
345
- 'min-h-[32px] max-h-[120px]',
346
- 'border-border focus:ring-accent/40',
347
- input && /\[(?:save|append|update|delete|view|doc|link|read|instruct)\]/i.test(input)
348
- ? 'text-accent'
349
- : 'text-text-0',
350
- )}
351
- />
352
- </div>
353
- {isAlive && (
354
- <button
355
- onClick={() => useGrooveStore.getState().stopAgent(agent.id)}
356
- title="Stop agent"
357
- className="w-8 h-8 flex items-center justify-center rounded-lg transition-all cursor-pointer bg-danger/80 text-white hover:bg-danger flex-shrink-0"
358
- >
359
- <Square size={12} fill="currentColor" />
360
- </button>
361
- )}
362
- <button
363
- onClick={handleSend}
364
- disabled={(!input.trim() && !pendingSnippet) || sending}
365
- className={cn(
366
- 'w-8 h-8 flex items-center justify-center rounded-lg transition-all cursor-pointer flex-shrink-0',
367
- 'disabled:opacity-20 disabled:cursor-not-allowed',
368
- (input.trim() || pendingSnippet)
369
- ? 'bg-accent/15 text-accent hover:bg-accent/25 border border-accent/25'
370
- : 'bg-surface-4 text-text-4',
371
- )}
372
- >
373
- {sending ? <Loader2 size={14} className="animate-spin" /> : <Send size={14} />}
374
- </button>
375
- </div>
376
- </div>
377
- </div>
378
- );
379
- }
@@ -1,6 +0,0 @@
1
- // FSL-1.1-Apache-2.0 — see LICENSE
2
- import { PreviewWorkspace } from '../components/preview/preview-workspace';
3
-
4
- export default function PreviewView() {
5
- return <PreviewWorkspace />;
6
- }