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,29 +4,29 @@ import { Tooltip } from '../ui/tooltip';
4
4
  import { cn } from '../../lib/cn';
5
5
  import { fmtNum } from '../../lib/format';
6
6
  import { HEX, hexAlpha } from '../../lib/theme-hex';
7
- import { Zap, Clock, Cpu, Hash, Timer, Link } from 'lucide-react';
7
+ import { Zap, Clock, Cpu, Hash, Timer, Link, MessageSquare, TrendingUp, ArrowUpRight, ArrowDownRight, Layers } from 'lucide-react';
8
8
 
9
9
  function TtftGauge({ value }) {
10
10
  if (value == null) {
11
11
  return (
12
- <div className="text-center py-4">
12
+ <div className="text-center py-3">
13
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>
14
+ <div className="text-2xs text-text-4 font-sans mt-0.5">TTFT</div>
15
15
  </div>
16
16
  );
17
17
  }
18
18
  const color = value < 200 ? 'text-success' : value < 500 ? 'text-warning' : 'text-danger';
19
19
  return (
20
- <div className="text-center py-4">
20
+ <div className="text-center py-3">
21
21
  <div className={cn('text-2xl font-mono font-bold tabular-nums', color)}>
22
22
  {Math.round(value)}
23
23
  </div>
24
- <div className="text-2xs text-text-3 font-sans mt-1">ms TTFT</div>
24
+ <div className="text-2xs text-text-3 font-sans mt-0.5">ms TTFT</div>
25
25
  </div>
26
26
  );
27
27
  }
28
28
 
29
- function Sparkline({ data, width = 140, height = 32, color = HEX.accent }) {
29
+ function Sparkline({ data, width = 140, height = 28, color = HEX.accent }) {
30
30
  if (!data || data.length < 2) {
31
31
  return <div className="flex-shrink-0" style={{ width, height }} />;
32
32
  }
@@ -54,11 +54,11 @@ function Sparkline({ data, width = 140, height = 32, color = HEX.accent }) {
54
54
  );
55
55
  }
56
56
 
57
- function MetricRow({ icon: Icon, label, value, unit, tooltip }) {
57
+ function MetricRow({ icon: Icon, label, value, unit, tooltip, accent }) {
58
58
  const content = (
59
- <div className="flex items-center justify-between py-2">
59
+ <div className="flex items-center justify-between py-1.5">
60
60
  <div className="flex items-center gap-2">
61
- <Icon size={11} className="text-text-4 flex-shrink-0" />
61
+ <Icon size={11} className={cn('flex-shrink-0', accent ? 'text-accent' : 'text-text-4')} />
62
62
  <span className="text-2xs text-text-3 font-sans">{label}</span>
63
63
  </div>
64
64
  <span className="text-xs font-mono font-medium text-text-1 tabular-nums">
@@ -69,48 +69,203 @@ function MetricRow({ icon: Icon, label, value, unit, tooltip }) {
69
69
  return tooltip ? <Tooltip content={tooltip}>{content}</Tooltip> : content;
70
70
  }
71
71
 
72
+ function SectionLabel({ children }) {
73
+ return (
74
+ <span className="text-2xs font-semibold font-sans text-text-4 uppercase tracking-wider">{children}</span>
75
+ );
76
+ }
77
+
78
+ function CapacityBar({ used, total, label }) {
79
+ if (!total) return null;
80
+ const pct = Math.min((used / total) * 100, 100);
81
+ const color = pct < 50 ? 'bg-success' : pct < 80 ? 'bg-warning' : 'bg-danger';
82
+ return (
83
+ <div className="space-y-1">
84
+ <div className="flex items-center justify-between">
85
+ <span className="text-2xs text-text-3 font-sans">{label}</span>
86
+ <span className="text-2xs font-mono text-text-2 tabular-nums">{Math.round(pct)}%</span>
87
+ </div>
88
+ <div className="h-1.5 bg-surface-3 rounded-full overflow-hidden">
89
+ <div className={cn('h-full rounded-full transition-all duration-300', color)} style={{ width: `${pct}%` }} />
90
+ </div>
91
+ <div className="text-2xs font-mono text-text-4 tabular-nums">
92
+ {fmtNum(used)} / {fmtNum(total)}
93
+ </div>
94
+ </div>
95
+ );
96
+ }
97
+
98
+ function SparklineSection({ icon: Icon, label, value, unit, data, color = HEX.accent }) {
99
+ return (
100
+ <div className="space-y-1.5">
101
+ <div className="flex items-center justify-between">
102
+ <div className="flex items-center gap-1.5">
103
+ <Icon size={11} className="text-accent" />
104
+ <span className="text-2xs text-text-3 font-sans">{label}</span>
105
+ </div>
106
+ <span className="text-sm font-mono font-bold text-text-0 tabular-nums">
107
+ {value != null ? value : '--'}{unit && value != null ? <span className="text-text-4 ml-0.5 text-xs">{unit}</span> : ''}
108
+ </span>
109
+ </div>
110
+ <Sparkline data={data} color={color} />
111
+ </div>
112
+ );
113
+ }
114
+
72
115
  export function MetricsPanel() {
73
116
  const metrics = useGrooveStore((s) => s.labMetrics);
74
117
  const activeRuntime = useGrooveStore((s) => s.labActiveRuntime);
118
+ const activeSession = useGrooveStore((s) => s.labActiveSession);
119
+ const sessions = useGrooveStore((s) => s.labSessions);
120
+
121
+ const session = sessions.find((s) => s.id === activeSession);
122
+ const messageCount = session?.messages?.length || 0;
123
+ const generationCount = metrics.generationCount || 0;
124
+ const avgTps = metrics.tokensPerSecHistory.length > 0
125
+ ? (metrics.tokensPerSecHistory.reduce((a, b) => a + (b || 0), 0) / metrics.tokensPerSecHistory.length)
126
+ : null;
127
+ const avgTtft = metrics.ttftHistory?.length > 0
128
+ ? (metrics.ttftHistory.reduce((a, b) => a + (b || 0), 0) / metrics.ttftHistory.length)
129
+ : null;
130
+ const peakTps = metrics.tokensPerSecHistory.length > 0
131
+ ? Math.max(...metrics.tokensPerSecHistory.filter((v) => v != null))
132
+ : null;
133
+
134
+ const sessionDuration = metrics.sessionStartTime
135
+ ? Math.round((Date.now() - metrics.sessionStartTime) / 1000)
136
+ : null;
137
+
138
+ function fmtDuration(secs) {
139
+ if (secs == null) return null;
140
+ if (secs < 60) return `${secs}s`;
141
+ if (secs < 3600) return `${Math.floor(secs / 60)}m ${secs % 60}s`;
142
+ return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
143
+ }
75
144
 
76
145
  return (
77
- <div className="space-y-5">
78
- {/* TTFT Gauge */}
146
+ <div className="space-y-4">
147
+ {/* Performance */}
79
148
  <TtftGauge value={metrics.ttft} />
80
149
 
150
+ <SparklineSection
151
+ icon={Zap}
152
+ label="Tokens/sec"
153
+ value={metrics.tokensPerSec != null ? metrics.tokensPerSec.toFixed(1) : null}
154
+ data={metrics.tokensPerSecHistory}
155
+ />
156
+
157
+ {metrics.ttftHistory?.length > 1 && (
158
+ <SparklineSection
159
+ icon={Clock}
160
+ label="TTFT Trend"
161
+ value={metrics.ttft != null ? `${Math.round(metrics.ttft)}` : null}
162
+ unit="ms"
163
+ data={metrics.ttftHistory}
164
+ color={HEX.warning || HEX.accent}
165
+ />
166
+ )}
167
+
81
168
  <div className="h-px bg-border-subtle" />
82
169
 
83
- {/* Tokens/sec with sparkline */}
84
- <div className="space-y-2">
85
- <div className="flex items-center justify-between">
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>
89
- </div>
90
- <span className="text-sm font-mono font-bold text-text-0 tabular-nums">
91
- {metrics.tokensPerSec != null ? metrics.tokensPerSec.toFixed(1) : '--'}
92
- </span>
93
- </div>
94
- <Sparkline data={metrics.tokensPerSecHistory} />
170
+ {/* Token Usage */}
171
+ <div className="space-y-1">
172
+ <SectionLabel>Tokens</SectionLabel>
173
+ <MetricRow
174
+ icon={Hash}
175
+ label="Total"
176
+ value={metrics.totalTokens > 0 ? fmtNum(metrics.totalTokens) : null}
177
+ tooltip="Total tokens generated this session"
178
+ />
179
+ {metrics.promptTokens > 0 && (
180
+ <MetricRow
181
+ icon={ArrowUpRight}
182
+ label="Prompt"
183
+ value={fmtNum(metrics.promptTokens)}
184
+ tooltip="Input/prompt tokens"
185
+ />
186
+ )}
187
+ {metrics.completionTokens > 0 && (
188
+ <MetricRow
189
+ icon={ArrowDownRight}
190
+ label="Completion"
191
+ value={fmtNum(metrics.completionTokens)}
192
+ tooltip="Output/completion tokens"
193
+ />
194
+ )}
95
195
  </div>
96
196
 
97
197
  <div className="h-px bg-border-subtle" />
98
198
 
99
- {/* Stats */}
100
- <div className="space-y-0">
199
+ {/* Session Stats */}
200
+ <div className="space-y-1">
201
+ <SectionLabel>Session</SectionLabel>
202
+ <MetricRow
203
+ icon={MessageSquare}
204
+ label="Messages"
205
+ value={messageCount > 0 ? messageCount : null}
206
+ tooltip="Messages in current session"
207
+ />
208
+ <MetricRow
209
+ icon={Layers}
210
+ label="Generations"
211
+ value={generationCount > 0 ? generationCount : null}
212
+ tooltip="Number of model generations"
213
+ />
214
+ {avgTps != null && (
215
+ <MetricRow
216
+ icon={TrendingUp}
217
+ label="Avg TPS"
218
+ value={avgTps.toFixed(1)}
219
+ tooltip="Average tokens/sec across session"
220
+ />
221
+ )}
222
+ {avgTtft != null && (
223
+ <MetricRow
224
+ icon={Clock}
225
+ label="Avg TTFT"
226
+ value={`${Math.round(avgTtft)}`}
227
+ unit="ms"
228
+ tooltip="Average time to first token"
229
+ />
230
+ )}
231
+ {peakTps != null && (
232
+ <MetricRow
233
+ icon={Zap}
234
+ label="Peak TPS"
235
+ value={peakTps.toFixed(1)}
236
+ accent
237
+ tooltip="Highest tokens/sec this session"
238
+ />
239
+ )}
240
+ <MetricRow
241
+ icon={Timer}
242
+ label="Duration"
243
+ value={fmtDuration(sessionDuration)}
244
+ tooltip="Time since first generation"
245
+ />
246
+ </div>
247
+
248
+ <div className="h-px bg-border-subtle" />
249
+
250
+ {/* Resources */}
251
+ <div className="space-y-1">
252
+ <SectionLabel>Resources</SectionLabel>
101
253
  <MetricRow
102
254
  icon={Cpu}
103
255
  label="Memory"
104
256
  value={metrics.memory != null ? `${(metrics.memory / 1024 / 1024).toFixed(0)}` : null}
105
257
  unit="MB"
106
- tooltip="GPU/CPU memory usage"
107
- />
108
- <MetricRow
109
- icon={Hash}
110
- label="Total Tokens"
111
- value={metrics.totalTokens > 0 ? fmtNum(metrics.totalTokens) : null}
112
- tooltip="Total tokens generated this session"
258
+ tooltip="Current GPU/CPU memory usage"
113
259
  />
260
+ {metrics.peakMemory != null && metrics.peakMemory > 0 && (
261
+ <MetricRow
262
+ icon={Cpu}
263
+ label="Peak Memory"
264
+ value={`${(metrics.peakMemory / 1024 / 1024).toFixed(0)}`}
265
+ unit="MB"
266
+ tooltip="Peak memory usage this session"
267
+ />
268
+ )}
114
269
  <MetricRow
115
270
  icon={Timer}
116
271
  label="Gen Time"
@@ -1,24 +1,156 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { useState } from 'react';
2
3
  import { useGrooveStore } from '../../stores/groove';
4
+ import { SidebarSection } from '../../views/model-lab';
3
5
  import { TuningSlider } from '../ui/slider';
4
6
  import { Tooltip } from '../ui/tooltip';
5
- import { RotateCcw } from 'lucide-react';
7
+ import { RotateCcw, ChevronRight, Dices, X, Plus } from 'lucide-react';
8
+ import { cn } from '../../lib/cn';
6
9
 
7
10
  const DEFAULTS = {
8
- temperature: 0.7, topP: 0.9, topK: 40, repeatPenalty: 1.1,
11
+ temperature: 0.7, topP: 0.9, topK: 40, minP: 0, repeatPenalty: 1.1,
9
12
  maxTokens: 2048, frequencyPenalty: 0, presencePenalty: 0,
13
+ thinking: false, seed: null, stopSequences: [], jsonMode: false,
10
14
  };
11
15
 
12
- const SLIDERS = [
16
+ const SAMPLING_SLIDERS = [
13
17
  { key: 'temperature', label: 'Temperature', min: 0, max: 2, step: 0.01 },
14
18
  { key: 'topP', label: 'Top P', min: 0, max: 1, step: 0.01 },
19
+ { key: 'minP', label: 'Min P', min: 0, max: 1, step: 0.01 },
15
20
  { key: 'topK', label: 'Top K', min: 0, max: 200, step: 1 },
21
+ ];
22
+
23
+ const PENALTY_SLIDERS = [
16
24
  { key: 'repeatPenalty', label: 'Repeat Penalty', min: 0, max: 2, step: 0.01 },
17
- { key: 'maxTokens', label: 'Max Tokens', min: 1, max: 32768, step: 1, format: (v) => v >= 1000 ? `${(v / 1000).toFixed(1)}k` : v },
18
25
  { key: 'frequencyPenalty', label: 'Freq Penalty', min: 0, max: 2, step: 0.01 },
19
26
  { key: 'presencePenalty', label: 'Pres Penalty', min: 0, max: 2, step: 0.01 },
20
27
  ];
21
28
 
29
+ function ParamGroup({ title, defaultOpen = false, children }) {
30
+ const [open, setOpen] = useState(defaultOpen);
31
+ return (
32
+ <div>
33
+ <button
34
+ onClick={() => setOpen(!open)}
35
+ className="w-full flex items-center gap-1.5 h-7 text-left cursor-pointer group"
36
+ >
37
+ <ChevronRight
38
+ size={10}
39
+ className={cn('text-text-4 transition-transform duration-150 flex-shrink-0', open && 'rotate-90')}
40
+ />
41
+ <span className="text-[10px] font-semibold text-text-4 font-sans uppercase tracking-widest">{title}</span>
42
+ </button>
43
+ {open && <div className="space-y-1 pl-0.5 pt-1">{children}</div>}
44
+ </div>
45
+ );
46
+ }
47
+
48
+ function ToggleSwitch({ label, active, onClick, description }) {
49
+ return (
50
+ <Tooltip content={description} side="right">
51
+ <button
52
+ onClick={onClick}
53
+ className="w-full flex items-center gap-2.5 h-9 text-left cursor-pointer group"
54
+ >
55
+ <span className="text-[11px] font-sans text-text-2 flex-1">{label}</span>
56
+ <div className={cn(
57
+ 'w-7 h-[16px] rounded-full transition-colors relative flex-shrink-0',
58
+ active ? 'bg-accent' : 'bg-surface-5',
59
+ )}>
60
+ <div className={cn(
61
+ 'absolute top-[2px] w-3 h-3 rounded-full bg-white shadow-sm transition-all',
62
+ active ? 'left-[14px]' : 'left-[2px]',
63
+ )} />
64
+ </div>
65
+ </button>
66
+ </Tooltip>
67
+ );
68
+ }
69
+
70
+ function SeedInput({ value, onChange }) {
71
+ function handleRandomize() {
72
+ onChange(Math.floor(Math.random() * 2147483647));
73
+ }
74
+ return (
75
+ <div className="space-y-2 pt-1.5">
76
+ <span className="text-[11px] text-text-2 font-sans">Seed</span>
77
+ <div className="flex items-center gap-1.5">
78
+ <input
79
+ type="number"
80
+ min={0}
81
+ value={value ?? ''}
82
+ onChange={(e) => onChange(e.target.value === '' ? null : parseInt(e.target.value, 10))}
83
+ placeholder="Random"
84
+ className="flex-1 min-w-0 h-7 px-2.5 text-[11px] font-mono bg-surface-1 border border-border rounded text-text-1 placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent/50 focus:border-accent/50 tabular-nums"
85
+ />
86
+ <Tooltip content="Randomize seed">
87
+ <button onClick={handleRandomize} className="p-0.5 text-text-4 hover:text-accent transition-colors cursor-pointer">
88
+ <Dices size={12} />
89
+ </button>
90
+ </Tooltip>
91
+ {value != null && (
92
+ <button onClick={() => onChange(null)} className="p-0.5 text-text-4 hover:text-danger transition-colors cursor-pointer">
93
+ <X size={10} />
94
+ </button>
95
+ )}
96
+ </div>
97
+ </div>
98
+ );
99
+ }
100
+
101
+ function StopSequenceInput({ sequences, onChange }) {
102
+ const [input, setInput] = useState('');
103
+
104
+ function handleAdd() {
105
+ const val = input.trim();
106
+ if (!val || sequences.length >= 10 || sequences.includes(val)) return;
107
+ onChange([...sequences, val]);
108
+ setInput('');
109
+ }
110
+
111
+ function handleRemove(idx) {
112
+ onChange(sequences.filter((_, i) => i !== idx));
113
+ }
114
+
115
+ function handleKeyDown(e) {
116
+ if (e.key === 'Enter') { e.preventDefault(); handleAdd(); }
117
+ if (e.key === 'Backspace' && !input && sequences.length) handleRemove(sequences.length - 1);
118
+ }
119
+
120
+ return (
121
+ <div className="space-y-2 pt-1.5">
122
+ <span className="text-[11px] text-text-2 font-sans">Stop Sequences</span>
123
+ <div className="flex items-center gap-1.5">
124
+ <input
125
+ value={input}
126
+ onChange={(e) => setInput(e.target.value)}
127
+ onKeyDown={handleKeyDown}
128
+ placeholder="Add sequence..."
129
+ maxLength={100}
130
+ className="flex-1 min-w-0 h-7 px-2.5 text-[11px] font-mono bg-surface-1 border border-border rounded text-text-1 placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent/50 focus:border-accent/50"
131
+ />
132
+ <button
133
+ onClick={handleAdd}
134
+ disabled={!input.trim() || sequences.length >= 10}
135
+ className="p-0.5 text-text-4 hover:text-accent disabled:opacity-30 transition-colors cursor-pointer"
136
+ >
137
+ <Plus size={12} />
138
+ </button>
139
+ </div>
140
+ {sequences.length > 0 && (
141
+ <div className="flex flex-wrap gap-1">
142
+ {sequences.map((s, i) => (
143
+ <span key={i} className="inline-flex items-center gap-1 px-1.5 py-0.5 bg-surface-2 border border-border-subtle rounded text-[10px] font-mono text-text-2">
144
+ {s.length > 12 ? s.slice(0, 12) + '...' : s}
145
+ <button onClick={() => handleRemove(i)} className="text-text-4 hover:text-danger cursor-pointer"><X size={8} /></button>
146
+ </span>
147
+ ))}
148
+ </div>
149
+ )}
150
+ </div>
151
+ );
152
+ }
153
+
22
154
  export function ParameterPanel() {
23
155
  const parameters = useGrooveStore((s) => s.labParameters);
24
156
  const setParameter = useGrooveStore((s) => s.setLabParameter);
@@ -30,9 +162,11 @@ export function ParameterPanel() {
30
162
  }
31
163
 
32
164
  return (
33
- <div className="space-y-1">
34
- <div className="flex items-center justify-between mb-1">
35
- <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Parameters</span>
165
+ <SidebarSection
166
+ label="Parameters"
167
+ collapsible
168
+ defaultOpen={false}
169
+ action={
36
170
  <Tooltip content="Reset to defaults">
37
171
  <button
38
172
  onClick={handleReset}
@@ -41,19 +175,67 @@ export function ParameterPanel() {
41
175
  <RotateCcw size={11} />
42
176
  </button>
43
177
  </Tooltip>
178
+ }
179
+ >
180
+ <div className="space-y-0.5 rounded-md bg-surface-1/50 border border-border-subtle px-3 py-2">
181
+ <ToggleSwitch
182
+ label="Thinking"
183
+ active={parameters.thinking}
184
+ onClick={() => setParameter('thinking', !parameters.thinking)}
185
+ description="Chain-of-thought for supported models (Qwen3, etc.)"
186
+ />
187
+ <ToggleSwitch
188
+ label="JSON Mode"
189
+ active={parameters.jsonMode}
190
+ onClick={() => setParameter('jsonMode', !parameters.jsonMode)}
191
+ description="Constrain output to valid JSON"
192
+ />
44
193
  </div>
45
- {SLIDERS.map((s) => (
194
+
195
+ <ParamGroup title="Sampling">
196
+ {SAMPLING_SLIDERS.map((s) => (
197
+ <TuningSlider
198
+ key={s.key}
199
+ label={s.label}
200
+ value={parameters[s.key]}
201
+ onChange={(v) => setParameter(s.key, v)}
202
+ min={s.min}
203
+ max={s.max}
204
+ step={s.step}
205
+ />
206
+ ))}
207
+ </ParamGroup>
208
+
209
+ <ParamGroup title="Generation">
46
210
  <TuningSlider
47
- key={s.key}
48
- label={s.label}
49
- value={parameters[s.key]}
50
- onChange={(v) => setParameter(s.key, v)}
51
- min={s.min}
52
- max={s.max}
53
- step={s.step}
54
- formatValue={s.format}
211
+ label="Max Tokens"
212
+ value={parameters.maxTokens}
213
+ onChange={(v) => setParameter('maxTokens', v)}
214
+ min={1}
215
+ max={32768}
216
+ step={1}
217
+ formatValue={(v) => v >= 1000 ? `${(v / 1000).toFixed(1)}k` : v}
55
218
  />
56
- ))}
57
- </div>
219
+ <SeedInput value={parameters.seed} onChange={(v) => setParameter('seed', v)} />
220
+ <StopSequenceInput
221
+ sequences={parameters.stopSequences || []}
222
+ onChange={(v) => setParameter('stopSequences', v)}
223
+ />
224
+ </ParamGroup>
225
+
226
+ <ParamGroup title="Penalties" defaultOpen={false}>
227
+ {PENALTY_SLIDERS.map((s) => (
228
+ <TuningSlider
229
+ key={s.key}
230
+ label={s.label}
231
+ value={parameters[s.key]}
232
+ onChange={(v) => setParameter(s.key, v)}
233
+ min={s.min}
234
+ max={s.max}
235
+ step={s.step}
236
+ />
237
+ ))}
238
+ </ParamGroup>
239
+ </SidebarSection>
58
240
  );
59
241
  }
@@ -1,6 +1,7 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { useState } from 'react';
3
3
  import { useGrooveStore } from '../../stores/groove';
4
+ import { SidebarSection } from '../../views/model-lab';
4
5
  import { Button } from '../ui/button';
5
6
  import { Input } from '../ui/input';
6
7
  import { Dialog, DialogContent } from '../ui/dialog';
@@ -54,33 +55,35 @@ export function PresetManager() {
54
55
  const [saveOpen, setSaveOpen] = useState(false);
55
56
 
56
57
  return (
57
- <div className="space-y-2">
58
- <div className="flex items-center justify-between">
59
- <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Presets</span>
58
+ <SidebarSection
59
+ label="Presets"
60
+ collapsible
61
+ defaultOpen={false}
62
+ action={
60
63
  <Tooltip content="Save current settings as preset">
61
64
  <button
62
65
  onClick={() => setSaveOpen(true)}
63
66
  className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
64
67
  >
65
- <Save size={13} />
68
+ <Save size={12} />
66
69
  </button>
67
70
  </Tooltip>
68
- </div>
69
-
71
+ }
72
+ >
70
73
  {presets.length === 0 ? (
71
- <div className="py-3 text-center">
72
- <BookmarkCheck size={14} className="mx-auto text-text-4 mb-1" />
73
- <p className="text-2xs text-text-4 font-sans">No presets saved</p>
74
+ <div className="py-5 text-center rounded-md bg-surface-1/50 border border-border-subtle">
75
+ <BookmarkCheck size={14} className="mx-auto text-text-4 mb-1.5" />
76
+ <p className="text-[10px] text-text-4 font-sans">No presets saved</p>
74
77
  </div>
75
78
  ) : (
76
79
  <ScrollArea className="max-h-32">
77
- <div className="space-y-px">
80
+ <div className="space-y-1 rounded-md bg-surface-1/50 border border-border-subtle p-2">
78
81
  {presets.map((preset) => (
79
82
  <div
80
83
  key={preset.id}
81
84
  className={cn(
82
- 'flex items-center gap-2 px-2.5 py-1.5 rounded-sm transition-colors',
83
- activePreset === preset.id ? 'bg-accent/8' : 'hover:bg-surface-3',
85
+ 'flex items-center gap-2.5 px-2.5 py-2 rounded transition-colors',
86
+ activePreset === preset.id ? 'bg-accent/10' : 'hover:bg-surface-3',
84
87
  )}
85
88
  >
86
89
  <button
@@ -88,7 +91,7 @@ export function PresetManager() {
88
91
  className="flex-1 text-left min-w-0 cursor-pointer"
89
92
  >
90
93
  <div className={cn(
91
- 'text-xs font-sans truncate',
94
+ 'text-[11px] font-sans truncate',
92
95
  activePreset === preset.id ? 'text-accent font-medium' : 'text-text-2',
93
96
  )}>
94
97
  {preset.name}
@@ -109,6 +112,6 @@ export function PresetManager() {
109
112
  )}
110
113
 
111
114
  <SavePresetDialog open={saveOpen} onOpenChange={setSaveOpen} />
112
- </div>
115
+ </SidebarSection>
113
116
  );
114
117
  }