groove-dev 0.27.143 → 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.
Files changed (251) 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/conversations.js +18 -48
  6. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  7. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  8. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  9. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  10. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  11. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  12. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  13. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  14. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  15. package/node_modules/@groove-dev/daemon/src/routes/agents.js +812 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  21. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  22. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  23. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  24. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  25. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  26. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  27. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  28. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  29. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  30. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  31. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  32. package/node_modules/@groove-dev/gui/dist/assets/index-Bxc0gU06.js +1006 -0
  33. package/node_modules/@groove-dev/gui/dist/assets/index-C0pztKBn.css +1 -0
  34. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  35. package/node_modules/@groove-dev/gui/package.json +1 -1
  36. package/node_modules/@groove-dev/gui/src/{app.jsx → App.jsx} +0 -2
  37. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  39. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +210 -112
  40. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  41. package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +2 -70
  42. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  43. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  44. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  45. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  46. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  47. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  48. package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +2 -0
  49. package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +68 -66
  50. package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +4 -8
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  54. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  55. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  56. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  57. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  58. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  59. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  60. package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +39 -31
  61. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
  62. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
  63. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +200 -18
  64. package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +17 -14
  65. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +335 -152
  66. package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +10 -8
  67. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
  68. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
  69. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
  70. package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
  71. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
  72. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
  73. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
  74. package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +8 -8
  75. package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
  76. package/node_modules/@groove-dev/gui/src/lib/status.js +25 -24
  77. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
  78. package/node_modules/@groove-dev/gui/src/stores/groove.js +51 -3144
  79. package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
  80. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +459 -0
  81. package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
  82. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +226 -0
  83. package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
  84. package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
  85. package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
  86. package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
  87. package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
  88. package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
  89. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
  90. package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
  91. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
  92. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
  93. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +54 -12
  94. package/node_modules/@groove-dev/gui/src/views/models.jsx +419 -496
  95. package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
  96. package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
  97. package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
  98. package/node_modules/axios/CHANGELOG.md +260 -0
  99. package/node_modules/axios/README.md +595 -223
  100. package/node_modules/axios/dist/axios.js +1460 -1090
  101. package/node_modules/axios/dist/axios.js.map +1 -1
  102. package/node_modules/axios/dist/axios.min.js +3 -3
  103. package/node_modules/axios/dist/axios.min.js.map +1 -1
  104. package/node_modules/axios/dist/browser/axios.cjs +1560 -1132
  105. package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
  106. package/node_modules/axios/dist/esm/axios.js +1557 -1128
  107. package/node_modules/axios/dist/esm/axios.js.map +1 -1
  108. package/node_modules/axios/dist/esm/axios.min.js +2 -2
  109. package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
  110. package/node_modules/axios/dist/node/axios.cjs +1594 -1057
  111. package/node_modules/axios/dist/node/axios.cjs.map +1 -1
  112. package/node_modules/axios/index.d.cts +40 -41
  113. package/node_modules/axios/index.d.ts +151 -227
  114. package/node_modules/axios/index.js +2 -0
  115. package/node_modules/axios/lib/adapters/adapters.js +4 -2
  116. package/node_modules/axios/lib/adapters/fetch.js +147 -16
  117. package/node_modules/axios/lib/adapters/http.js +306 -58
  118. package/node_modules/axios/lib/adapters/xhr.js +6 -2
  119. package/node_modules/axios/lib/core/Axios.js +7 -3
  120. package/node_modules/axios/lib/core/AxiosError.js +120 -34
  121. package/node_modules/axios/lib/core/AxiosHeaders.js +27 -25
  122. package/node_modules/axios/lib/core/buildFullPath.js +1 -1
  123. package/node_modules/axios/lib/core/dispatchRequest.js +19 -7
  124. package/node_modules/axios/lib/core/mergeConfig.js +21 -4
  125. package/node_modules/axios/lib/core/settle.js +7 -11
  126. package/node_modules/axios/lib/defaults/index.js +14 -9
  127. package/node_modules/axios/lib/env/data.js +1 -1
  128. package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +1 -2
  129. package/node_modules/axios/lib/helpers/buildURL.js +1 -1
  130. package/node_modules/axios/lib/helpers/cookies.js +14 -2
  131. package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +28 -1
  132. package/node_modules/axios/lib/helpers/formDataToJSON.js +3 -1
  133. package/node_modules/axios/lib/helpers/formDataToStream.js +3 -2
  134. package/node_modules/axios/lib/helpers/parseProtocol.js +1 -1
  135. package/node_modules/axios/lib/helpers/progressEventReducer.js +5 -5
  136. package/node_modules/axios/lib/helpers/resolveConfig.js +54 -18
  137. package/node_modules/axios/lib/helpers/shouldBypassProxy.js +74 -2
  138. package/node_modules/axios/lib/helpers/toFormData.js +10 -2
  139. package/node_modules/axios/lib/helpers/validator.js +3 -1
  140. package/node_modules/axios/lib/utils.js +33 -21
  141. package/node_modules/axios/package.json +17 -24
  142. package/node_modules/follow-redirects/README.md +7 -5
  143. package/node_modules/follow-redirects/index.js +24 -1
  144. package/node_modules/follow-redirects/package.json +1 -1
  145. package/package.json +1 -1
  146. package/packages/cli/package.json +1 -1
  147. package/packages/daemon/package.json +1 -1
  148. package/packages/daemon/src/api.js +1086 -6532
  149. package/packages/daemon/src/conversations.js +18 -48
  150. package/packages/daemon/src/gateways/manager.js +35 -1
  151. package/packages/daemon/src/index.js +3 -0
  152. package/packages/daemon/src/journalist.js +23 -13
  153. package/packages/daemon/src/mlx-server.js +365 -0
  154. package/packages/daemon/src/model-lab.js +308 -12
  155. package/packages/daemon/src/pm.js +1 -1
  156. package/packages/daemon/src/process.js +2 -2
  157. package/packages/daemon/src/providers/local.js +36 -8
  158. package/packages/daemon/src/registry.js +21 -5
  159. package/packages/daemon/src/routes/agents.js +812 -0
  160. package/packages/daemon/src/routes/coordination.js +318 -0
  161. package/packages/daemon/src/routes/files.js +751 -0
  162. package/packages/daemon/src/routes/integrations.js +485 -0
  163. package/packages/daemon/src/routes/network.js +1784 -0
  164. package/packages/daemon/src/routes/providers.js +755 -0
  165. package/packages/daemon/src/routes/schedules.js +110 -0
  166. package/packages/daemon/src/routes/teams.js +650 -0
  167. package/packages/daemon/src/scheduler.js +456 -24
  168. package/packages/daemon/src/teams.js +1 -1
  169. package/packages/daemon/src/validate.js +38 -1
  170. package/packages/daemon/templates/mlx-setup.json +12 -0
  171. package/packages/daemon/templates/tgi-setup.json +1 -1
  172. package/packages/daemon/templates/vllm-setup.json +1 -1
  173. package/packages/gui/dist/assets/index-Bxc0gU06.js +1006 -0
  174. package/packages/gui/dist/assets/index-C0pztKBn.css +1 -0
  175. package/packages/gui/dist/index.html +2 -2
  176. package/packages/gui/package.json +1 -1
  177. package/packages/gui/src/{app.jsx → App.jsx} +0 -2
  178. package/packages/gui/src/app.css +35 -0
  179. package/packages/gui/src/components/agents/agent-config.jsx +1 -128
  180. package/packages/gui/src/components/agents/agent-feed.jsx +210 -112
  181. package/packages/gui/src/components/agents/agent-node.jsx +8 -13
  182. package/packages/gui/src/components/agents/agent-panel.jsx +2 -70
  183. package/packages/gui/src/components/agents/code-review.jsx +159 -122
  184. package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
  185. package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
  186. package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
  187. package/packages/gui/src/components/automations/automation-card.jsx +274 -0
  188. package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
  189. package/packages/gui/src/components/chat/chat-header.jsx +2 -0
  190. package/packages/gui/src/components/chat/chat-input.jsx +68 -66
  191. package/packages/gui/src/components/chat/chat-view.jsx +4 -8
  192. package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
  193. package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
  194. package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
  195. package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  196. package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
  197. package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
  198. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  199. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  200. package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
  201. package/packages/gui/src/components/lab/chat-playground.jsx +39 -31
  202. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  203. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  204. package/packages/gui/src/components/lab/parameter-panel.jsx +200 -18
  205. package/packages/gui/src/components/lab/preset-manager.jsx +17 -14
  206. package/packages/gui/src/components/lab/runtime-config.jsx +335 -152
  207. package/packages/gui/src/components/lab/system-prompt-editor.jsx +10 -8
  208. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  209. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  210. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  211. package/packages/gui/src/components/network/network-health.jsx +2 -2
  212. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  213. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  214. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  215. package/packages/gui/src/components/ui/slider.jsx +8 -8
  216. package/packages/gui/src/lib/cron.js +64 -0
  217. package/packages/gui/src/lib/status.js +25 -24
  218. package/packages/gui/src/lib/theme-hex.js +1 -0
  219. package/packages/gui/src/stores/groove.js +51 -3144
  220. package/packages/gui/src/stores/helpers.js +10 -0
  221. package/packages/gui/src/stores/slices/agents-slice.js +459 -0
  222. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  223. package/packages/gui/src/stores/slices/chat-slice.js +226 -0
  224. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  225. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  226. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  227. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  228. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  229. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  230. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  231. package/packages/gui/src/views/agents.jsx +5 -5
  232. package/packages/gui/src/views/dashboard.jsx +12 -13
  233. package/packages/gui/src/views/marketplace.jsx +191 -3
  234. package/packages/gui/src/views/model-lab.jsx +54 -12
  235. package/packages/gui/src/views/models.jsx +419 -496
  236. package/packages/gui/src/views/network.jsx +3 -3
  237. package/packages/gui/src/views/settings.jsx +81 -94
  238. package/packages/gui/src/views/teams.jsx +40 -483
  239. package/SECURITY_SWEEP.md +0 -228
  240. package/TRAINING_DATA_v4.md +0 -6
  241. package/node_modules/@groove-dev/gui/dist/assets/index-CCVvAoQn.css +0 -1
  242. package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +0 -984
  243. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -379
  244. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  245. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  246. package/packages/gui/dist/assets/index-CCVvAoQn.css +0 -1
  247. package/packages/gui/dist/assets/index-DGIv_TRm.js +0 -984
  248. package/packages/gui/src/components/agents/agent-chat.jsx +0 -379
  249. package/packages/gui/src/views/preview.jsx +0 -6
  250. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  251. package/test.py +0 -571
@@ -4,41 +4,237 @@ import { useGrooveStore } from '../../stores/groove';
4
4
  import { ScrollArea } from '../ui/scroll-area';
5
5
  import { Badge } from '../ui/badge';
6
6
  import { cn } from '../../lib/cn';
7
- import { Send, X, Bot, ArrowRight } from 'lucide-react';
7
+ import { Send, X, ArrowRight, ChevronDown, AlertCircle, FlaskConical } from 'lucide-react';
8
8
 
9
+ // ── Inline formatting (bold, code) ──────────────────────────
10
+ function InlineFormat({ text }) {
11
+ if (!text) return null;
12
+ return text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).map((part, i) => {
13
+ if (part.startsWith('**') && part.endsWith('**'))
14
+ return <strong key={i} className="font-semibold text-text-0">{part.slice(2, -2)}</strong>;
15
+ if (part.startsWith('`') && part.endsWith('`'))
16
+ return <code key={i} className="px-1 py-px rounded bg-accent/8 text-[11px] font-mono text-accent">{part.slice(1, -1)}</code>;
17
+ return <span key={i}>{part}</span>;
18
+ });
19
+ }
20
+
21
+ // ── Structured message renderer ─────────────────────────────
22
+ function StructuredMessage({ text }) {
23
+ if (!text) return null;
24
+
25
+ const blocks = [];
26
+ const lines = text.split('\n');
27
+ let i = 0;
28
+
29
+ while (i < lines.length) {
30
+ const line = lines[i];
31
+
32
+ if (line.trimStart().startsWith('```')) {
33
+ const codeLines = [];
34
+ i++;
35
+ while (i < lines.length && !lines[i].trimStart().startsWith('```')) {
36
+ codeLines.push(lines[i]);
37
+ i++;
38
+ }
39
+ i++;
40
+ blocks.push({ type: 'code', content: codeLines.join('\n') });
41
+ continue;
42
+ }
43
+
44
+ if (/^#{1,3}\s/.test(line) || /^\*\*[^*]+:\*\*\s*$/.test(line.trim())) {
45
+ const heading = line.replace(/^#+\s*/, '').replace(/^\*\*/, '').replace(/:\*\*\s*$/, ':').trim();
46
+ blocks.push({ type: 'heading', content: heading });
47
+ i++;
48
+ continue;
49
+ }
50
+
51
+ if (/^\s*[-*]\s/.test(line)) {
52
+ const items = [];
53
+ while (i < lines.length && /^\s*[-*]\s/.test(lines[i])) {
54
+ items.push(lines[i].replace(/^\s*[-*]\s+/, '').trim());
55
+ i++;
56
+ }
57
+ blocks.push({ type: 'list', items });
58
+ continue;
59
+ }
60
+
61
+ if (/^\s*\d+[\.)]\s/.test(line)) {
62
+ const items = [];
63
+ while (i < lines.length && /^\s*\d+[\.)]\s/.test(lines[i])) {
64
+ items.push(lines[i].replace(/^\s*\d+[\.)]\s+/, '').trim());
65
+ i++;
66
+ }
67
+ blocks.push({ type: 'numbered', items });
68
+ continue;
69
+ }
70
+
71
+ if (!line.trim()) { i++; continue; }
72
+
73
+ if (/^(Note|Warning|Important|IMPORTANT|TODO):/i.test(line.trim())) {
74
+ blocks.push({ type: 'note', content: line.trim() });
75
+ i++;
76
+ continue;
77
+ }
78
+
79
+ const paraLines = [];
80
+ 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('```')) {
81
+ paraLines.push(lines[i].trim());
82
+ i++;
83
+ }
84
+ if (paraLines.length > 0) {
85
+ blocks.push({ type: 'para', content: paraLines.join(' ') });
86
+ }
87
+ }
88
+
89
+ return (
90
+ <div className="space-y-2">
91
+ {blocks.map((block, idx) => {
92
+ switch (block.type) {
93
+ case 'heading':
94
+ return (
95
+ <div key={idx} className="flex items-center gap-1.5 pt-1.5 first:pt-0">
96
+ <div className="w-1 h-3.5 rounded-full bg-accent/40 flex-shrink-0" />
97
+ <span className="text-[13px] font-semibold text-text-0 font-sans"><InlineFormat text={block.content} /></span>
98
+ </div>
99
+ );
100
+ case 'list':
101
+ return (
102
+ <div key={idx} className="space-y-1 pl-2">
103
+ {block.items.map((item, j) => (
104
+ <div key={j} className="flex gap-2 text-[13px] text-text-1 font-sans leading-relaxed">
105
+ <span className="text-accent/50 mt-0.5 flex-shrink-0">-</span>
106
+ <span className="min-w-0"><InlineFormat text={item} /></span>
107
+ </div>
108
+ ))}
109
+ </div>
110
+ );
111
+ case 'numbered':
112
+ return (
113
+ <div key={idx} className="space-y-1 pl-2">
114
+ {block.items.map((item, j) => (
115
+ <div key={j} className="flex gap-2 text-[13px] text-text-1 font-sans leading-relaxed">
116
+ <span className="text-text-4 font-mono w-4 text-right flex-shrink-0">{j + 1}.</span>
117
+ <span className="min-w-0"><InlineFormat text={item} /></span>
118
+ </div>
119
+ ))}
120
+ </div>
121
+ );
122
+ case 'code':
123
+ return (
124
+ <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">
125
+ {block.content}
126
+ </pre>
127
+ );
128
+ case 'note':
129
+ return (
130
+ <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">
131
+ <AlertCircle size={10} className="text-warning mt-0.5 flex-shrink-0" />
132
+ <span className="text-[12px] text-warning/80 font-sans"><InlineFormat text={block.content} /></span>
133
+ </div>
134
+ );
135
+ case 'para':
136
+ default:
137
+ return <p key={idx} className="text-[13px] text-text-1 font-sans leading-relaxed"><InlineFormat text={block.content} /></p>;
138
+ }
139
+ })}
140
+ </div>
141
+ );
142
+ }
143
+
144
+ // ── Thinking indicator ──────────────────────────────────────
145
+ const THINKING_MESSAGES = [
146
+ 'Checking your system...',
147
+ 'Running setup commands...',
148
+ 'Working through installation...',
149
+ 'Configuring the server...',
150
+ 'Making progress...',
151
+ 'Almost there...',
152
+ ];
153
+
154
+ function LabThinkingIndicator() {
155
+ const [idx, setIdx] = useState(0);
156
+ const [fade, setFade] = useState(true);
157
+
158
+ useEffect(() => {
159
+ const t = setInterval(() => {
160
+ setFade(false);
161
+ setTimeout(() => {
162
+ setIdx((i) => (i + 1) % THINKING_MESSAGES.length);
163
+ setFade(true);
164
+ }, 250);
165
+ }, 2800);
166
+ return () => clearInterval(t);
167
+ }, []);
168
+
169
+ return (
170
+ <div>
171
+ <div className="flex items-center gap-2 mb-1">
172
+ <span className="text-2xs font-semibold text-text-1 font-sans">Lab Assistant</span>
173
+ <span className="text-2xs text-accent font-mono">working</span>
174
+ </div>
175
+ <div className="border-l border-accent/40 pl-3.5 py-1 flex items-center gap-2.5">
176
+ <div className="relative w-3.5 h-3.5 flex-shrink-0">
177
+ <span className="absolute inset-0 rounded-full border border-transparent border-t-accent animate-spin" style={{ animationDuration: '0.9s' }} />
178
+ </div>
179
+ <span
180
+ className="text-[13px] font-sans text-text-3 transition-opacity duration-[250ms]"
181
+ style={{ opacity: fade ? 1 : 0 }}
182
+ >
183
+ {THINKING_MESSAGES[idx]}
184
+ </span>
185
+ </div>
186
+ </div>
187
+ );
188
+ }
189
+
190
+ // ── Message components ──────────────────────────────────────
9
191
  function AssistantMessage({ msg }) {
192
+ const [collapsed, setCollapsed] = useState(msg.text?.length > 800);
193
+ const isLong = msg.text?.length > 800;
194
+
10
195
  return (
11
196
  <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>
197
+ <div className="flex items-center gap-2 mb-1">
198
+ <span className="text-2xs font-semibold text-text-1 font-sans">Lab Assistant</span>
17
199
  </div>
18
- <div className="ml-5">
19
- <div className={cn(
20
- 'text-xs font-sans whitespace-pre-wrap break-words leading-relaxed',
21
- msg.error ? 'text-danger' : 'text-text-1',
22
- )}>
23
- {msg.text}
24
- </div>
200
+ <div className="border-l border-accent pl-3.5 py-1">
201
+ <StructuredMessage text={collapsed ? msg.text.slice(0, 800) + '...' : msg.text} />
25
202
  </div>
203
+ {collapsed && (
204
+ <button
205
+ onClick={() => setCollapsed(false)}
206
+ className="ml-3.5 mt-1.5 flex items-center gap-1.5 text-[11px] text-accent/70 hover:text-accent font-sans font-medium cursor-pointer transition-colors"
207
+ >
208
+ <ChevronDown size={11} />
209
+ Show full response
210
+ </button>
211
+ )}
212
+ {isLong && !collapsed && (
213
+ <button
214
+ onClick={() => setCollapsed(true)}
215
+ className="ml-3.5 mt-1.5 flex items-center gap-1.5 text-[11px] text-accent/70 hover:text-accent font-sans font-medium cursor-pointer transition-colors"
216
+ >
217
+ <ChevronDown size={11} className="rotate-180" />
218
+ Collapse
219
+ </button>
220
+ )}
26
221
  </div>
27
222
  );
28
223
  }
29
224
 
30
225
  function UserMessage({ msg }) {
31
226
  return (
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">
35
- <p className="text-xs text-text-0 font-sans whitespace-pre-wrap break-words leading-relaxed">{msg.text}</p>
227
+ <div className="flex justify-end pl-8 animate-chat-fade-in">
228
+ <div className="max-w-[90%]">
229
+ <div className="px-3.5 py-2.5 rounded-lg border bg-info/10 border-info/25">
230
+ <p className="text-[13px] text-text-0 font-sans whitespace-pre-wrap break-words leading-relaxed">{msg.text}</p>
36
231
  </div>
37
232
  </div>
38
233
  </div>
39
234
  );
40
235
  }
41
236
 
237
+ // ── Main component ──────────────────────────────────────────
42
238
  export function LabAssistant() {
43
239
  const agentId = useGrooveStore((s) => s.labAssistantAgentId);
44
240
  const backend = useGrooveStore((s) => s.labAssistantBackend);
@@ -48,10 +244,15 @@ export function LabAssistant() {
48
244
  const instructAgent = useGrooveStore((s) => s.instructAgent);
49
245
  const dismissLabAssistant = useGrooveStore((s) => s.dismissLabAssistant);
50
246
  const setLabAssistantMode = useGrooveStore((s) => s.setLabAssistantMode);
247
+ const onLabAssistantComplete = useGrooveStore((s) => s.onLabAssistantComplete);
248
+ const fetchLabRuntimes = useGrooveStore((s) => s.fetchLabRuntimes);
249
+ const activeRuntime = useGrooveStore((s) => s.labActiveRuntime);
250
+ const activeModel = useGrooveStore((s) => s.labActiveModel);
51
251
 
52
252
  const [input, setInput] = useState('');
53
253
  const scrollRef = useRef(null);
54
254
  const inputRef = useRef(null);
255
+ const completionHandled = useRef(false);
55
256
 
56
257
  const agent = agents.find((a) => a.id === agentId);
57
258
  const messages = chatHistory[agentId] || [];
@@ -59,19 +260,33 @@ export function LabAssistant() {
59
260
  const isRunning = agent?.status === 'running';
60
261
  const isComplete = agent && agent.status !== 'running';
61
262
 
263
+ useEffect(() => {
264
+ if (isComplete && !completionHandled.current) {
265
+ completionHandled.current = true;
266
+ onLabAssistantComplete();
267
+ }
268
+ if (isRunning) completionHandled.current = false;
269
+ }, [isComplete, isRunning, onLabAssistantComplete]);
270
+
271
+ useEffect(() => {
272
+ if (!isRunning || activeRuntime) return;
273
+ const interval = setInterval(fetchLabRuntimes, 5000);
274
+ return () => clearInterval(interval);
275
+ }, [isRunning, activeRuntime, fetchLabRuntimes]);
276
+
62
277
  useEffect(() => {
63
278
  if (scrollRef.current) {
64
279
  const el = scrollRef.current.querySelector('[data-radix-scroll-area-viewport]');
65
280
  if (el) el.scrollTop = el.scrollHeight;
66
281
  }
67
- }, [messages.length, messages[messages.length - 1]?.text]);
282
+ }, [messages.length, messages[messages.length - 1]?.text, isThinking]);
68
283
 
69
284
  const handleSend = useCallback(() => {
70
285
  const text = input.trim();
71
- if (!text || !agentId || !isRunning) return;
286
+ if (!text || !agentId) return;
72
287
  setInput('');
73
288
  instructAgent(agentId, text);
74
- }, [input, agentId, isRunning, instructAgent]);
289
+ }, [input, agentId, instructAgent]);
75
290
 
76
291
  function handleKeyDown(e) {
77
292
  if (e.key === 'Enter' && !e.shiftKey) {
@@ -84,39 +299,31 @@ export function LabAssistant() {
84
299
 
85
300
  return (
86
301
  <div className="h-full flex flex-col">
87
- {/* Header */}
88
- <div className="flex-shrink-0 flex items-center justify-between px-4 pb-2">
89
- <div className="flex items-center gap-2">
90
- {backend && (
91
- <span className="text-2xs font-mono text-text-3">{backend}</span>
92
- )}
93
- {agent && (
94
- <Badge
95
- variant={isRunning ? 'success' : isComplete ? 'default' : 'warning'}
96
- className="text-2xs"
97
- >
98
- {agent.status}
99
- </Badge>
100
- )}
302
+ {/* Streaming status bar */}
303
+ {isRunning && (
304
+ <div className="flex-shrink-0 flex items-center gap-3 px-4 h-8 border-b border-border-subtle bg-surface-1/80">
305
+ <div className="relative flex items-center justify-center w-4 h-4">
306
+ <span className="absolute inset-0 rounded-full bg-accent/15 animate-ping [animation-duration:2s]" />
307
+ <span className="relative w-1.5 h-1.5 rounded-full bg-accent" />
308
+ </div>
309
+ <span className="text-2xs font-sans text-text-2">
310
+ Lab Assistant is setting up <span className="font-medium text-text-1">{backend?.toUpperCase()}</span>
311
+ </span>
312
+ <div className="flex-1" />
313
+ <Badge variant="success" className="text-2xs">running</Badge>
101
314
  </div>
102
- <button
103
- onClick={dismissLabAssistant}
104
- className="p-1 text-text-4 hover:text-text-1 transition-colors cursor-pointer"
105
- >
106
- <X size={14} />
107
- </button>
108
- </div>
315
+ )}
109
316
 
110
317
  {/* Messages */}
111
318
  <ScrollArea ref={scrollRef} className="flex-1 min-h-0">
112
- <div className="px-4 py-3 space-y-5">
319
+ <div className="px-5 py-4 space-y-6">
113
320
  {messages.length === 0 && !isThinking ? (
114
321
  <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" />
322
+ <div className="w-12 h-12 rounded-lg bg-accent/10 flex items-center justify-center mb-3">
323
+ <FlaskConical size={22} className="text-accent" />
117
324
  </div>
118
- <p className="text-sm text-text-2 font-sans font-medium">Setting up {backend}</p>
119
- <p className="text-xs text-text-4 font-sans mt-1">The assistant is starting up...</p>
325
+ <p className="text-sm text-text-1 font-sans font-medium">Setting up {backend?.toUpperCase()}</p>
326
+ <p className="text-[13px] text-text-3 font-sans mt-1.5">The assistant is starting up and will begin configuring your runtime...</p>
120
327
  </div>
121
328
  ) : (
122
329
  messages.map((msg, i) =>
@@ -128,69 +335,96 @@ export function LabAssistant() {
128
335
  )
129
336
  )}
130
337
 
131
- {isThinking && (
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" />
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
- </div>
144
- </div>
145
- )}
338
+ {isThinking && <LabThinkingIndicator />}
146
339
  </div>
147
340
  </ScrollArea>
148
341
 
149
342
  {/* Completion banner */}
150
343
  {isComplete && messages.length > 0 && (
151
- <div className="flex-shrink-0 px-4 py-2 bg-success/5 border-t border-success/10">
152
- <div className="flex items-center justify-between">
153
- <span className="text-xs font-sans text-success font-medium">Setup complete</span>
154
- <button
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"
157
- >
158
- <ArrowRight size={12} /> Playground
159
- </button>
160
- </div>
344
+ <div className={cn(
345
+ 'flex-shrink-0 px-4 py-3 border-t',
346
+ activeRuntime && activeModel
347
+ ? 'bg-success/10 border-success/20'
348
+ : activeRuntime
349
+ ? 'bg-warning/10 border-warning/20'
350
+ : 'bg-surface-2 border-border',
351
+ )}>
352
+ {activeRuntime && activeModel ? (
353
+ <div className="flex items-center justify-between">
354
+ <div>
355
+ <span className="text-[13px] font-sans text-success font-semibold">Runtime ready</span>
356
+ <p className="text-xs text-text-2 font-sans mt-0.5">Your model is loaded and ready to chat</p>
357
+ </div>
358
+ <button
359
+ onClick={() => setLabAssistantMode(false)}
360
+ className="flex items-center gap-1.5 px-4 py-2 text-[13px] font-sans font-medium text-surface-0 bg-accent hover:bg-accent/90 rounded-md transition-colors cursor-pointer"
361
+ >
362
+ <ArrowRight size={14} /> Open Playground
363
+ </button>
364
+ </div>
365
+ ) : activeRuntime ? (
366
+ <div className="flex items-center justify-between">
367
+ <div>
368
+ <span className="text-[13px] font-sans text-warning font-semibold">Runtime registered</span>
369
+ <p className="text-xs text-text-2 font-sans mt-0.5">Model may still be loading — test the runtime in the sidebar</p>
370
+ </div>
371
+ <button
372
+ onClick={() => setLabAssistantMode(false)}
373
+ className="flex items-center gap-1.5 px-4 py-2 text-[13px] font-sans font-medium text-text-0 bg-surface-3 hover:bg-surface-4 rounded-md transition-colors cursor-pointer"
374
+ >
375
+ <ArrowRight size={14} /> View Playground
376
+ </button>
377
+ </div>
378
+ ) : (
379
+ <div className="flex items-center justify-between">
380
+ <div>
381
+ <span className="text-[13px] font-sans text-text-1 font-semibold">Setup finished</span>
382
+ <p className="text-xs text-text-2 font-sans mt-0.5">
383
+ If the model is still downloading, the runtime will appear once the server starts.
384
+ </p>
385
+ </div>
386
+ <button
387
+ onClick={fetchLabRuntimes}
388
+ className="flex items-center gap-1.5 px-4 py-2 text-[13px] font-sans font-medium text-text-0 bg-surface-3 hover:bg-surface-4 rounded-md transition-colors cursor-pointer"
389
+ >
390
+ Check Runtimes
391
+ </button>
392
+ </div>
393
+ )}
161
394
  </div>
162
395
  )}
163
396
 
164
397
  {/* Input */}
165
398
  <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">
399
+ <div className="flex items-end gap-2 border rounded-xl bg-surface-0 p-1.5 transition-colors border-border-subtle focus-within:border-accent/30">
167
400
  <textarea
168
401
  ref={inputRef}
169
402
  value={input}
170
403
  onChange={(e) => setInput(e.target.value)}
171
404
  onKeyDown={handleKeyDown}
172
- placeholder={isRunning ? 'Type a message...' : 'Assistant is not running'}
173
- disabled={!isRunning}
405
+ placeholder={isRunning ? 'Ask the assistant anything...' : 'Send a message to continue...'}
174
406
  rows={1}
175
407
  className={cn(
176
- 'flex-1 resize-none bg-transparent px-2 py-1.5',
177
- 'text-xs text-text-0 font-sans placeholder:text-text-4',
408
+ 'flex-1 resize-none bg-transparent px-2.5 py-1.5',
409
+ 'text-[13px] text-text-0 font-sans placeholder:text-text-4',
178
410
  'focus:outline-none',
179
- 'disabled:opacity-40 disabled:cursor-not-allowed',
180
- 'min-h-[28px] max-h-32',
411
+ 'min-h-[32px] max-h-32',
181
412
  )}
182
413
  style={{ height: 'auto', overflowY: input.split('\n').length > 4 ? 'auto' : 'hidden' }}
183
414
  onInput={(e) => { e.target.style.height = 'auto'; e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`; }}
184
415
  />
185
416
  <button
186
- disabled={!input.trim() || !isRunning}
417
+ disabled={!input.trim()}
187
418
  onClick={handleSend}
188
419
  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',
420
+ 'flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-lg transition-all cursor-pointer',
421
+ 'disabled:opacity-15 disabled:cursor-not-allowed',
422
+ input.trim()
423
+ ? 'bg-accent/15 text-accent hover:bg-accent/25 border border-accent/25'
424
+ : 'bg-transparent text-text-4',
191
425
  )}
192
426
  >
193
- <Send size={12} />
427
+ <Send size={13} />
194
428
  </button>
195
429
  </div>
196
430
  </div>