groove-dev 0.27.142 → 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 (187) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  4. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  5. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  6. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  7. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  8. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  9. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  10. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  11. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  12. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  13. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  14. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  21. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  22. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  23. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  24. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  25. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  26. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  28. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  29. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  30. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  32. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  33. package/node_modules/@groove-dev/gui/package.json +1 -1
  34. package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
  35. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  36. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  39. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  40. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  41. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  42. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  43. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  45. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  54. package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
  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 +410 -509
  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/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/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/editor/selection-menu.jsx +2 -0
  141. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  142. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  143. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  144. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  145. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  146. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  147. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  148. package/packages/gui/src/components/network/network-health.jsx +2 -2
  149. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  150. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  151. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  152. package/packages/gui/src/lib/cron.js +64 -0
  153. package/packages/gui/src/lib/status.js +24 -24
  154. package/packages/gui/src/lib/theme-hex.js +1 -0
  155. package/packages/gui/src/stores/groove.js +34 -3144
  156. package/packages/gui/src/stores/helpers.js +10 -0
  157. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  158. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  159. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  160. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  161. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  162. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  163. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  164. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  165. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  166. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  167. package/packages/gui/src/views/agents.jsx +5 -5
  168. package/packages/gui/src/views/dashboard.jsx +12 -13
  169. package/packages/gui/src/views/marketplace.jsx +191 -3
  170. package/packages/gui/src/views/model-lab.jsx +17 -6
  171. package/packages/gui/src/views/models.jsx +410 -509
  172. package/packages/gui/src/views/network.jsx +3 -3
  173. package/packages/gui/src/views/settings.jsx +81 -94
  174. package/packages/gui/src/views/teams.jsx +40 -483
  175. package/SECURITY_SWEEP.md +0 -228
  176. package/TRAINING_DATA_v4.md +0 -6
  177. package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
  178. package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
  179. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
  180. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  181. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  182. package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
  183. package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
  184. package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
  185. package/packages/gui/src/views/preview.jsx +0 -6
  186. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  187. 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,151 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { useState } from 'react';
2
3
  import { useGrooveStore } from '../../stores/groove';
3
4
  import { TuningSlider } from '../ui/slider';
4
5
  import { Tooltip } from '../ui/tooltip';
5
- import { RotateCcw } from 'lucide-react';
6
+ import { RotateCcw, Brain, Braces, ChevronRight, Dices, X, Plus } from 'lucide-react';
7
+ import { cn } from '../../lib/cn';
6
8
 
7
9
  const DEFAULTS = {
8
- temperature: 0.7, topP: 0.9, topK: 40, repeatPenalty: 1.1,
10
+ temperature: 0.7, topP: 0.9, topK: 40, minP: 0, repeatPenalty: 1.1,
9
11
  maxTokens: 2048, frequencyPenalty: 0, presencePenalty: 0,
12
+ thinking: false, seed: null, stopSequences: [], jsonMode: false,
10
13
  };
11
14
 
12
- const SLIDERS = [
15
+ const SAMPLING_SLIDERS = [
13
16
  { key: 'temperature', label: 'Temperature', min: 0, max: 2, step: 0.01 },
14
17
  { key: 'topP', label: 'Top P', min: 0, max: 1, step: 0.01 },
18
+ { key: 'minP', label: 'Min P', min: 0, max: 1, step: 0.01 },
15
19
  { key: 'topK', label: 'Top K', min: 0, max: 200, step: 1 },
20
+ ];
21
+
22
+ const PENALTY_SLIDERS = [
16
23
  { 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
24
  { key: 'frequencyPenalty', label: 'Freq Penalty', min: 0, max: 2, step: 0.01 },
19
25
  { key: 'presencePenalty', label: 'Pres Penalty', min: 0, max: 2, step: 0.01 },
20
26
  ];
21
27
 
28
+ function ParamGroup({ title, defaultOpen = true, children }) {
29
+ const [open, setOpen] = useState(defaultOpen);
30
+ return (
31
+ <div>
32
+ <button
33
+ onClick={() => setOpen(!open)}
34
+ className="w-full flex items-center gap-1.5 py-1.5 text-left cursor-pointer group"
35
+ >
36
+ <ChevronRight
37
+ size={10}
38
+ className={cn('text-text-4 transition-transform duration-150 flex-shrink-0', open && 'rotate-90')}
39
+ />
40
+ <span className="text-2xs font-semibold text-text-4 font-sans uppercase tracking-wider">{title}</span>
41
+ </button>
42
+ {open && <div className="space-y-0.5 pb-1">{children}</div>}
43
+ </div>
44
+ );
45
+ }
46
+
47
+ function ToggleRow({ icon: Icon, label, active, onClick, description }) {
48
+ return (
49
+ <Tooltip content={description} side="right">
50
+ <button
51
+ onClick={onClick}
52
+ className={cn(
53
+ 'w-full flex items-center gap-2 px-2 py-1.5 rounded-sm transition-colors cursor-pointer text-left',
54
+ active ? 'bg-accent/8' : 'hover:bg-surface-3',
55
+ )}
56
+ >
57
+ <Icon size={12} className={cn(active ? 'text-accent' : 'text-text-4')} />
58
+ <span className="flex-1 text-2xs font-sans text-text-2">{label}</span>
59
+ <span className={cn('text-2xs font-mono', active ? 'text-accent' : 'text-text-4')}>
60
+ {active ? 'ON' : 'OFF'}
61
+ </span>
62
+ </button>
63
+ </Tooltip>
64
+ );
65
+ }
66
+
67
+ function SeedInput({ value, onChange }) {
68
+ function handleRandomize() {
69
+ onChange(Math.floor(Math.random() * 2147483647));
70
+ }
71
+ return (
72
+ <div className="flex items-center gap-1.5 px-2 py-1">
73
+ <span className="text-2xs text-text-3 font-sans flex-shrink-0 w-12">Seed</span>
74
+ <input
75
+ type="number"
76
+ min={0}
77
+ value={value ?? ''}
78
+ onChange={(e) => onChange(e.target.value === '' ? null : parseInt(e.target.value, 10))}
79
+ placeholder="Random"
80
+ className="flex-1 min-w-0 h-6 px-1.5 text-2xs font-mono bg-surface-1 border border-border rounded-sm text-text-1 placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent tabular-nums"
81
+ />
82
+ <Tooltip content="Randomize seed">
83
+ <button onClick={handleRandomize} className="p-0.5 text-text-4 hover:text-accent transition-colors cursor-pointer">
84
+ <Dices size={12} />
85
+ </button>
86
+ </Tooltip>
87
+ {value != null && (
88
+ <button onClick={() => onChange(null)} className="p-0.5 text-text-4 hover:text-danger transition-colors cursor-pointer">
89
+ <X size={10} />
90
+ </button>
91
+ )}
92
+ </div>
93
+ );
94
+ }
95
+
96
+ function StopSequenceInput({ sequences, onChange }) {
97
+ const [input, setInput] = useState('');
98
+
99
+ function handleAdd() {
100
+ const val = input.trim();
101
+ if (!val || sequences.length >= 10 || sequences.includes(val)) return;
102
+ onChange([...sequences, val]);
103
+ setInput('');
104
+ }
105
+
106
+ function handleRemove(idx) {
107
+ onChange(sequences.filter((_, i) => i !== idx));
108
+ }
109
+
110
+ function handleKeyDown(e) {
111
+ if (e.key === 'Enter') { e.preventDefault(); handleAdd(); }
112
+ if (e.key === 'Backspace' && !input && sequences.length) handleRemove(sequences.length - 1);
113
+ }
114
+
115
+ return (
116
+ <div className="px-2 py-1 space-y-1.5">
117
+ <span className="text-2xs text-text-3 font-sans">Stop Sequences</span>
118
+ {sequences.length > 0 && (
119
+ <div className="flex flex-wrap gap-1">
120
+ {sequences.map((s, i) => (
121
+ <span key={i} className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-surface-3 rounded text-2xs font-mono text-text-2">
122
+ {s.length > 12 ? s.slice(0, 12) + '...' : s}
123
+ <button onClick={() => handleRemove(i)} className="text-text-4 hover:text-danger cursor-pointer"><X size={8} /></button>
124
+ </span>
125
+ ))}
126
+ </div>
127
+ )}
128
+ <div className="flex gap-1">
129
+ <input
130
+ value={input}
131
+ onChange={(e) => setInput(e.target.value)}
132
+ onKeyDown={handleKeyDown}
133
+ placeholder="Add sequence..."
134
+ maxLength={100}
135
+ className="flex-1 min-w-0 h-6 px-1.5 text-2xs font-mono bg-surface-1 border border-border rounded-sm text-text-1 placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
136
+ />
137
+ <button
138
+ onClick={handleAdd}
139
+ disabled={!input.trim() || sequences.length >= 10}
140
+ className="p-1 text-text-4 hover:text-accent disabled:opacity-30 transition-colors cursor-pointer"
141
+ >
142
+ <Plus size={12} />
143
+ </button>
144
+ </div>
145
+ </div>
146
+ );
147
+ }
148
+
22
149
  export function ParameterPanel() {
23
150
  const parameters = useGrooveStore((s) => s.labParameters);
24
151
  const setParameter = useGrooveStore((s) => s.setLabParameter);
@@ -42,18 +169,72 @@ export function ParameterPanel() {
42
169
  </button>
43
170
  </Tooltip>
44
171
  </div>
45
- {SLIDERS.map((s) => (
172
+
173
+ {/* Mode toggles */}
174
+ <div className="space-y-px pb-1">
175
+ <ToggleRow
176
+ icon={Brain}
177
+ label="Thinking"
178
+ active={parameters.thinking}
179
+ onClick={() => setParameter('thinking', !parameters.thinking)}
180
+ description="Chain-of-thought for supported models (Qwen3, etc.)"
181
+ />
182
+ <ToggleRow
183
+ icon={Braces}
184
+ label="JSON Mode"
185
+ active={parameters.jsonMode}
186
+ onClick={() => setParameter('jsonMode', !parameters.jsonMode)}
187
+ description="Constrain output to valid JSON"
188
+ />
189
+ </div>
190
+
191
+ {/* Sampling group */}
192
+ <ParamGroup title="Sampling">
193
+ {SAMPLING_SLIDERS.map((s) => (
194
+ <TuningSlider
195
+ key={s.key}
196
+ label={s.label}
197
+ value={parameters[s.key]}
198
+ onChange={(v) => setParameter(s.key, v)}
199
+ min={s.min}
200
+ max={s.max}
201
+ step={s.step}
202
+ />
203
+ ))}
204
+ </ParamGroup>
205
+
206
+ {/* Generation group */}
207
+ <ParamGroup title="Generation">
46
208
  <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}
209
+ label="Max Tokens"
210
+ value={parameters.maxTokens}
211
+ onChange={(v) => setParameter('maxTokens', v)}
212
+ min={1}
213
+ max={32768}
214
+ step={1}
215
+ formatValue={(v) => v >= 1000 ? `${(v / 1000).toFixed(1)}k` : v}
55
216
  />
56
- ))}
217
+ <SeedInput value={parameters.seed} onChange={(v) => setParameter('seed', v)} />
218
+ <StopSequenceInput
219
+ sequences={parameters.stopSequences || []}
220
+ onChange={(v) => setParameter('stopSequences', v)}
221
+ />
222
+ </ParamGroup>
223
+
224
+ {/* Penalties group — collapsed by default */}
225
+ <ParamGroup title="Penalties" defaultOpen={false}>
226
+ {PENALTY_SLIDERS.map((s) => (
227
+ <TuningSlider
228
+ key={s.key}
229
+ label={s.label}
230
+ value={parameters[s.key]}
231
+ onChange={(v) => setParameter(s.key, v)}
232
+ min={s.min}
233
+ max={s.max}
234
+ step={s.step}
235
+ />
236
+ ))}
237
+ </ParamGroup>
57
238
  </div>
58
239
  );
59
240
  }