groove-dev 0.27.134 → 0.27.136

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 (85) hide show
  1. package/moe-training/client/domain-tagger.js +1 -1
  2. package/moe-training/scripts/retag-delegate-yield.js +303 -0
  3. package/moe-training/test/shared/envelope-schema.test.js +3 -3
  4. package/node_modules/@groove-dev/cli/package.json +1 -1
  5. package/node_modules/@groove-dev/daemon/package.json +1 -1
  6. package/node_modules/@groove-dev/daemon/src/adaptive.js +77 -0
  7. package/node_modules/@groove-dev/daemon/src/api.js +35 -5
  8. package/node_modules/@groove-dev/daemon/src/journalist.js +28 -12
  9. package/node_modules/@groove-dev/daemon/src/model-lab.js +53 -76
  10. package/node_modules/@groove-dev/daemon/src/process.js +91 -2
  11. package/node_modules/@groove-dev/daemon/src/rotator.js +45 -3
  12. package/node_modules/@groove-dev/gui/dist/assets/{index-Dozp69tK.js → index-BrZHF7pK.js} +1770 -1766
  13. package/node_modules/@groove-dev/gui/dist/assets/index-DIfiwdKl.css +1 -0
  14. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  15. package/node_modules/@groove-dev/gui/package.json +1 -1
  16. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +60 -18
  17. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +42 -20
  18. package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +1 -1
  19. package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +1 -1
  20. package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +2 -22
  21. package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +9 -9
  22. package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +1 -1
  23. package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +7 -0
  24. package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +59 -51
  25. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +48 -48
  26. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +39 -38
  27. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +4 -5
  28. package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +11 -11
  29. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +66 -62
  30. package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +13 -13
  31. package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
  32. package/node_modules/@groove-dev/gui/src/components/preview/preview-workspace.jsx +62 -22
  33. package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +16 -17
  34. package/node_modules/@groove-dev/gui/src/components/ui/table-tree.jsx +38 -0
  35. package/node_modules/@groove-dev/gui/src/stores/groove.js +23 -9
  36. package/node_modules/@groove-dev/gui/src/views/editor.jsx +1 -1
  37. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +101 -87
  38. package/node_modules/moe-training/client/domain-tagger.js +1 -1
  39. package/node_modules/moe-training/scripts/retag-delegate-yield.js +303 -0
  40. package/node_modules/moe-training/test/shared/envelope-schema.test.js +3 -3
  41. package/package.json +1 -1
  42. package/packages/cli/package.json +1 -1
  43. package/packages/daemon/package.json +1 -1
  44. package/packages/daemon/src/adaptive.js +77 -0
  45. package/packages/daemon/src/api.js +35 -5
  46. package/packages/daemon/src/journalist.js +28 -12
  47. package/packages/daemon/src/model-lab.js +53 -76
  48. package/packages/daemon/src/process.js +91 -2
  49. package/packages/daemon/src/rotator.js +45 -3
  50. package/packages/gui/dist/assets/{index-Dozp69tK.js → index-BrZHF7pK.js} +1770 -1766
  51. package/packages/gui/dist/assets/index-DIfiwdKl.css +1 -0
  52. package/packages/gui/dist/index.html +2 -2
  53. package/packages/gui/package.json +1 -1
  54. package/packages/gui/src/components/agents/agent-chat.jsx +60 -18
  55. package/packages/gui/src/components/agents/agent-feed.jsx +42 -20
  56. package/packages/gui/src/components/agents/agent-file-tree.jsx +1 -1
  57. package/packages/gui/src/components/agents/workspace-mode.jsx +1 -1
  58. package/packages/gui/src/components/chat/chat-messages.jsx +2 -22
  59. package/packages/gui/src/components/editor/code-editor.jsx +9 -9
  60. package/packages/gui/src/components/editor/file-tree.jsx +1 -1
  61. package/packages/gui/src/components/editor/terminal.jsx +7 -0
  62. package/packages/gui/src/components/lab/chat-playground.jsx +59 -51
  63. package/packages/gui/src/components/lab/lab-assistant.jsx +48 -48
  64. package/packages/gui/src/components/lab/metrics-panel.jsx +39 -38
  65. package/packages/gui/src/components/lab/parameter-panel.jsx +4 -5
  66. package/packages/gui/src/components/lab/preset-manager.jsx +11 -11
  67. package/packages/gui/src/components/lab/runtime-config.jsx +66 -62
  68. package/packages/gui/src/components/lab/system-prompt-editor.jsx +13 -13
  69. package/packages/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
  70. package/packages/gui/src/components/preview/preview-workspace.jsx +62 -22
  71. package/packages/gui/src/components/ui/slider.jsx +16 -17
  72. package/packages/gui/src/components/ui/table-tree.jsx +38 -0
  73. package/packages/gui/src/stores/groove.js +23 -9
  74. package/packages/gui/src/views/editor.jsx +1 -1
  75. package/packages/gui/src/views/model-lab.jsx +101 -87
  76. package/plan_files/DELEGATE_YIELD_TRAINING_TAGS.md +135 -0
  77. package/plan_files/session-quality-rotation-fixes.md +218 -0
  78. package/test.py +571 -0
  79. package/node_modules/@groove-dev/gui/dist/assets/index-BgQL4bNl.css +0 -1
  80. package/packages/gui/dist/assets/index-BgQL4bNl.css +0 -1
  81. /package/{AGENT_ORCHESTRATION.md → plan_files/AGENT_ORCHESTRATION.md} +0 -0
  82. /package/{DYNAMIC_LEAF_ARCH.md → plan_files/DYNAMIC_LEAF_ARCH.md} +0 -0
  83. /package/{EMBEDDING_DIAGNOSTIC.md → plan_files/EMBEDDING_DIAGNOSTIC.md} +0 -0
  84. /package/{EMBEDDING_SERVICE_BUILD_PLAN.md → plan_files/EMBEDDING_SERVICE_BUILD_PLAN.md} +0 -0
  85. /package/{MOE_TRAINING_PIPELINE.md → plan_files/MOE_TRAINING_PIPELINE.md} +0 -0
@@ -1,7 +1,6 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { useGrooveStore } from '../../stores/groove';
3
3
  import { TuningSlider } from '../ui/slider';
4
- import { Button } from '../ui/button';
5
4
  import { Tooltip } from '../ui/tooltip';
6
5
  import { RotateCcw } from 'lucide-react';
7
6
 
@@ -32,14 +31,14 @@ export function ParameterPanel() {
32
31
 
33
32
  return (
34
33
  <div className="space-y-1">
35
- <div className="flex items-center justify-between mb-2">
36
- <span className="text-xs font-semibold font-sans text-text-2 uppercase tracking-wider">Parameters</span>
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>
37
36
  <Tooltip content="Reset to defaults">
38
37
  <button
39
38
  onClick={handleReset}
40
- className="p-1 rounded text-text-3 hover:text-accent hover:bg-accent/10 transition-colors cursor-pointer"
39
+ className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
41
40
  >
42
- <RotateCcw size={12} />
41
+ <RotateCcw size={11} />
43
42
  </button>
44
43
  </Tooltip>
45
44
  </div>
@@ -7,7 +7,7 @@ import { Dialog, DialogContent } from '../ui/dialog';
7
7
  import { Tooltip } from '../ui/tooltip';
8
8
  import { ScrollArea } from '../ui/scroll-area';
9
9
  import { cn } from '../../lib/cn';
10
- import { Save, Trash2, Download, BookmarkCheck } from 'lucide-react';
10
+ import { Save, Trash2, BookmarkCheck } from 'lucide-react';
11
11
 
12
12
  function SavePresetDialog({ open, onOpenChange }) {
13
13
  const savePreset = useGrooveStore((s) => s.saveLabPreset);
@@ -56,31 +56,31 @@ export function PresetManager() {
56
56
  return (
57
57
  <div className="space-y-2">
58
58
  <div className="flex items-center justify-between">
59
- <span className="text-xs font-semibold font-sans text-text-2 uppercase tracking-wider">Presets</span>
59
+ <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Presets</span>
60
60
  <Tooltip content="Save current settings as preset">
61
61
  <button
62
62
  onClick={() => setSaveOpen(true)}
63
- className="p-1 rounded text-text-3 hover:text-accent hover:bg-accent/10 transition-colors cursor-pointer"
63
+ className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
64
64
  >
65
- <Save size={14} />
65
+ <Save size={13} />
66
66
  </button>
67
67
  </Tooltip>
68
68
  </div>
69
69
 
70
70
  {presets.length === 0 ? (
71
- <div className="px-3 py-3 text-center">
72
- <BookmarkCheck size={16} className="mx-auto text-text-4 mb-1" />
73
- <p className="text-2xs text-text-3 font-sans">No presets saved</p>
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
74
  </div>
75
75
  ) : (
76
76
  <ScrollArea className="max-h-32">
77
- <div className="space-y-0.5">
77
+ <div className="space-y-px">
78
78
  {presets.map((preset) => (
79
79
  <div
80
80
  key={preset.id}
81
81
  className={cn(
82
- 'flex items-center gap-2 px-3 py-1.5 rounded-md transition-colors',
83
- activePreset === preset.id ? 'bg-accent/10' : 'hover:bg-surface-5/50',
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',
84
84
  )}
85
85
  >
86
86
  <button
@@ -97,7 +97,7 @@ export function PresetManager() {
97
97
  <Tooltip content="Delete preset">
98
98
  <button
99
99
  onClick={() => deletePreset(preset.id)}
100
- className="p-0.5 rounded text-text-4 hover:text-danger hover:bg-danger/10 transition-colors cursor-pointer flex-shrink-0"
100
+ className="p-0.5 text-text-4 hover:text-danger transition-colors cursor-pointer flex-shrink-0"
101
101
  >
102
102
  <Trash2 size={10} />
103
103
  </button>
@@ -8,7 +8,7 @@ import { Dialog, DialogContent } from '../ui/dialog';
8
8
  import { Select, SelectTrigger, SelectContent, SelectItem } from '../ui/select';
9
9
  import { Tooltip } from '../ui/tooltip';
10
10
  import { ScrollArea } from '../ui/scroll-area';
11
- import { Plus, Trash2, Loader2, WifiOff, RotateCcw, HardDrive, Play, CheckCircle, AlertTriangle, Info, ChevronRight, Wrench } from 'lucide-react';
11
+ import { Plus, Trash2, Loader2, WifiOff, RotateCcw, HardDrive, Play, CheckCircle, AlertTriangle, ChevronRight, Wrench } from 'lucide-react';
12
12
  import { cn } from '../../lib/cn';
13
13
 
14
14
  const RUNTIME_TYPES = [
@@ -85,18 +85,16 @@ function AddRuntimeDialog({ open, onOpenChange }) {
85
85
  }
86
86
 
87
87
  function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, testing }) {
88
- const statusColor = runtime.status === 'connected' ? 'success' : runtime.status === 'error' ? 'danger' : 'default';
89
-
90
88
  return (
91
89
  <button
92
90
  onClick={() => onSelect(runtime.id)}
93
91
  className={cn(
94
- 'w-full flex items-center gap-2.5 px-3 py-2 rounded-md text-left transition-colors cursor-pointer',
95
- active ? 'bg-accent/10 text-text-0' : 'text-text-2 hover:bg-surface-5/50 hover:text-text-0',
92
+ 'w-full flex items-center gap-2.5 px-2.5 py-2 text-left transition-colors cursor-pointer rounded-sm',
93
+ active ? 'bg-accent/8 text-text-0' : 'text-text-2 hover:bg-surface-3 hover:text-text-0',
96
94
  )}
97
95
  >
98
96
  <span className={cn(
99
- 'w-2 h-2 rounded-full flex-shrink-0',
97
+ 'w-1.5 h-1.5 rounded-full flex-shrink-0',
100
98
  runtime.status === 'connected' ? 'bg-success' : runtime.status === 'error' ? 'bg-danger' : 'bg-text-4',
101
99
  )} />
102
100
  <div className="flex-1 min-w-0">
@@ -107,24 +105,24 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, testing }) {
107
105
  {runtime.status === 'error' && <span className="text-danger">Unreachable</span>}
108
106
  </div>
109
107
  </div>
110
- <div className="flex items-center gap-1 flex-shrink-0">
108
+ <div className="flex items-center gap-0.5 flex-shrink-0">
111
109
  {runtime.latency != null && (
112
- <span className="text-2xs font-mono text-text-3">{Math.round(runtime.latency)}ms</span>
110
+ <span className="text-2xs font-mono text-text-4 mr-1">{Math.round(runtime.latency)}ms</span>
113
111
  )}
114
112
  <Tooltip content="Test connection">
115
113
  <button
116
114
  onClick={(e) => { e.stopPropagation(); onTest(runtime.id); }}
117
- className="p-1 rounded text-text-4 hover:text-accent hover:bg-accent/10 transition-colors cursor-pointer"
115
+ className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
118
116
  >
119
- {testing === runtime.id ? <Loader2 size={12} className="animate-spin" /> : <RotateCcw size={12} />}
117
+ {testing === runtime.id ? <Loader2 size={11} className="animate-spin" /> : <RotateCcw size={11} />}
120
118
  </button>
121
119
  </Tooltip>
122
120
  <Tooltip content="Remove">
123
121
  <button
124
122
  onClick={(e) => { e.stopPropagation(); onRemove(runtime.id); }}
125
- className="p-1 rounded text-text-4 hover:text-danger hover:bg-danger/10 transition-colors cursor-pointer"
123
+ className="p-1 text-text-4 hover:text-danger transition-colors cursor-pointer"
126
124
  >
127
- <Trash2 size={12} />
125
+ <Trash2 size={11} />
128
126
  </button>
129
127
  </Tooltip>
130
128
  </div>
@@ -149,15 +147,15 @@ function LaunchStatus({ phase, error }) {
149
147
  if (!phase) return null;
150
148
  return (
151
149
  <div className={cn(
152
- 'flex items-center gap-2 px-3 py-2 rounded-md text-xs font-sans',
153
- phase === 'ready' && 'bg-success/10 text-success',
154
- phase === 'error' && 'bg-danger/10 text-danger',
155
- (phase === 'starting' || phase === 'checking') && 'bg-accent/10 text-accent',
150
+ 'flex items-center gap-2 px-2.5 py-1.5 text-2xs font-sans rounded-sm',
151
+ phase === 'ready' && 'bg-success/8 text-success',
152
+ phase === 'error' && 'bg-danger/8 text-danger',
153
+ (phase === 'starting' || phase === 'checking') && 'bg-accent/8 text-accent',
156
154
  )}>
157
- {phase === 'starting' && <><Loader2 size={12} className="animate-spin" /> Starting server...</>}
158
- {phase === 'checking' && <><Loader2 size={12} className="animate-spin" /> Checking...</>}
159
- {phase === 'ready' && <><CheckCircle size={12} /> Server ready</>}
160
- {phase === 'error' && <><AlertTriangle size={12} /> {error || 'Launch failed'}</>}
155
+ {phase === 'starting' && <><Loader2 size={11} className="animate-spin" /> Starting server...</>}
156
+ {phase === 'checking' && <><Loader2 size={11} className="animate-spin" /> Checking...</>}
157
+ {phase === 'ready' && <><CheckCircle size={11} /> Server ready</>}
158
+ {phase === 'error' && <><AlertTriangle size={11} /> {error || 'Launch failed'}</>}
161
159
  </div>
162
160
  );
163
161
  }
@@ -199,29 +197,28 @@ export function LaunchModel() {
199
197
 
200
198
  return (
201
199
  <div className="space-y-3">
202
- <span className="text-xs font-semibold font-sans text-text-2 uppercase tracking-wider">Launch Model</span>
200
+ <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Launch Model</span>
203
201
 
204
202
  {localModels.length === 0 ? (
205
- <div className="px-3 py-4 text-center">
206
- <HardDrive size={20} className="mx-auto text-text-4 mb-1.5" />
203
+ <div className="py-5 text-center">
204
+ <HardDrive size={18} className="mx-auto text-text-4 mb-1.5" />
207
205
  <p className="text-xs text-text-3 font-sans">No downloaded models</p>
208
- <p className="text-2xs text-text-4 font-sans mt-1">Download GGUFs from the Models tab</p>
206
+ <p className="text-2xs text-text-4 font-sans mt-0.5">Download GGUFs from the Models tab</p>
209
207
  </div>
210
208
  ) : (
211
209
  <>
212
- {/* Model list */}
213
210
  <ScrollArea className="max-h-36">
214
- <div className="space-y-0.5">
211
+ <div className="space-y-px">
215
212
  {localModels.map((m) => (
216
213
  <button
217
214
  key={m.id}
218
215
  onClick={() => setSelectedModel(m.id)}
219
216
  className={cn(
220
- 'w-full flex items-center gap-2 px-3 py-2 rounded-md text-left transition-colors cursor-pointer',
221
- selectedModel === m.id ? 'bg-accent/10 text-text-0' : 'text-text-2 hover:bg-surface-5/50 hover:text-text-0',
217
+ 'w-full flex items-center gap-2 px-2.5 py-2 text-left transition-colors cursor-pointer rounded-sm',
218
+ selectedModel === m.id ? 'bg-accent/8 text-text-0' : 'text-text-2 hover:bg-surface-3 hover:text-text-0',
222
219
  )}
223
220
  >
224
- <HardDrive size={12} className={cn('flex-shrink-0', selectedModel === m.id ? 'text-accent' : 'text-text-4')} />
221
+ <HardDrive size={11} className={cn('flex-shrink-0', selectedModel === m.id ? 'text-accent' : 'text-text-4')} />
225
222
  <div className="flex-1 min-w-0">
226
223
  <div className="text-xs font-sans font-medium truncate">
227
224
  {m.filename?.replace(/\.gguf$/i, '') || m.id}
@@ -232,28 +229,27 @@ export function LaunchModel() {
232
229
  {m.sizeBytes && <span>{formatSize(m.sizeBytes)}</span>}
233
230
  </div>
234
231
  </div>
235
- {selectedModel === m.id && <ChevronRight size={12} className="text-accent flex-shrink-0" />}
232
+ {selectedModel === m.id && <ChevronRight size={11} className="text-accent flex-shrink-0" />}
236
233
  </button>
237
234
  ))}
238
235
  </div>
239
236
  </ScrollArea>
240
237
 
241
- {/* Backend picker — show when model selected */}
242
238
  {selectedModel && (
243
239
  <div className="space-y-2">
244
- <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Backend</span>
245
- <div className="space-y-1">
240
+ <span className="text-2xs font-semibold font-sans text-text-4 uppercase tracking-wider">Backend</span>
241
+ <div className="space-y-px">
246
242
  {BACKENDS.map((b) => (
247
243
  <button
248
244
  key={b.id}
249
245
  onClick={() => setSelectedBackend(b.id)}
250
246
  className={cn(
251
- 'w-full flex items-center gap-2.5 px-3 py-2 rounded-md text-left transition-colors cursor-pointer',
252
- selectedBackend === b.id ? 'bg-accent/10' : 'hover:bg-surface-5/50',
247
+ 'w-full flex items-center gap-2.5 px-2.5 py-2 text-left transition-colors cursor-pointer rounded-sm',
248
+ selectedBackend === b.id ? 'bg-accent/8' : 'hover:bg-surface-3',
253
249
  )}
254
250
  >
255
251
  <span className={cn(
256
- 'w-2 h-2 rounded-full border-2 flex-shrink-0',
252
+ 'w-2 h-2 rounded-full border-[1.5px] flex-shrink-0',
257
253
  selectedBackend === b.id ? 'border-accent bg-accent' : 'border-text-4',
258
254
  )} />
259
255
  <div className="flex-1 min-w-0">
@@ -269,9 +265,8 @@ export function LaunchModel() {
269
265
  ))}
270
266
  </div>
271
267
 
272
- {/* Binary status for llama.cpp */}
273
268
  {selectedBackend === 'llama-cpp' && (
274
- <div className="px-3">
269
+ <div className="px-2.5">
275
270
  {llamaInstalled === null && (
276
271
  <div className="flex items-center gap-2 text-2xs text-text-3 font-sans">
277
272
  <Loader2 size={10} className="animate-spin" /> Checking llama-server...
@@ -287,7 +282,7 @@ export function LaunchModel() {
287
282
  <div className="flex items-center gap-2 text-2xs text-danger font-sans">
288
283
  <AlertTriangle size={10} /> llama-server not found
289
284
  </div>
290
- <code className="block text-2xs font-mono text-text-3 bg-surface-1 px-2 py-1 rounded">brew install llama.cpp</code>
285
+ <code className="block text-2xs font-mono text-text-3 bg-surface-2 px-2 py-1 rounded-sm">brew install llama.cpp</code>
291
286
  <button
292
287
  onClick={checkLlama}
293
288
  className="flex items-center gap-1.5 text-2xs font-sans text-accent hover:text-accent/80 transition-colors cursor-pointer"
@@ -299,37 +294,43 @@ export function LaunchModel() {
299
294
  </div>
300
295
  )}
301
296
 
302
- {/* Setup assistant for vLLM/TGI */}
303
297
  {!currentBackend?.autoLaunch && (
304
298
  <div className="space-y-2">
305
- <Button variant="primary" size="sm" className="w-full" onClick={handleLaunchAssistant} disabled={assistantLaunching}>
299
+ <button
300
+ onClick={handleLaunchAssistant}
301
+ disabled={assistantLaunching}
302
+ className={cn(
303
+ 'w-full flex items-center justify-center gap-1.5 px-3 py-2 text-xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
304
+ assistantLaunching ? 'bg-accent/20 text-accent' : 'bg-accent text-surface-0 hover:bg-accent/90',
305
+ )}
306
+ >
306
307
  {assistantLaunching
307
- ? <><Loader2 size={12} className="animate-spin mr-1.5" /> Starting Assistant...</>
308
- : <><Wrench size={12} className="mr-1.5" /> Setup {currentBackend?.label} with Assistant</>
308
+ ? <><Loader2 size={12} className="animate-spin" /> Starting Assistant...</>
309
+ : <><Wrench size={12} /> Setup {currentBackend?.label} with Assistant</>
309
310
  }
310
- </Button>
311
- <p className="text-2xs text-text-4 font-sans px-1">
311
+ </button>
312
+ <p className="text-2xs text-text-4 font-sans">
312
313
  An AI assistant will check your system and handle the installation, or start your server manually and add it as a Runtime below.
313
314
  </p>
314
315
  </div>
315
316
  )}
316
317
 
317
- {/* Launch button + status */}
318
318
  {currentBackend?.autoLaunch && (
319
319
  <div className="space-y-2">
320
- <Button
321
- variant="primary"
322
- size="sm"
323
- className="w-full"
320
+ <button
324
321
  disabled={!canLaunch}
325
322
  onClick={handleLaunch}
323
+ className={cn(
324
+ 'w-full flex items-center justify-center gap-1.5 px-3 py-2 text-xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
325
+ canLaunch ? 'bg-accent text-surface-0 hover:bg-accent/90' : 'bg-surface-3 text-text-4 cursor-not-allowed',
326
+ )}
326
327
  >
327
328
  {launching ? (
328
- <><Loader2 size={12} className="animate-spin mr-1.5" /> Starting...</>
329
+ <><Loader2 size={12} className="animate-spin" /> Starting...</>
329
330
  ) : (
330
- <><Play size={12} className="mr-1.5" /> Launch</>
331
+ <><Play size={12} /> Launch</>
331
332
  )}
332
- </Button>
333
+ </button>
333
334
  <LaunchStatus phase={launchPhase} error={launchError} />
334
335
  </div>
335
336
  )}
@@ -360,28 +361,31 @@ export function RuntimeConfig() {
360
361
  return (
361
362
  <div className="space-y-2">
362
363
  <div className="flex items-center justify-between">
363
- <span className="text-xs font-semibold font-sans text-text-2 uppercase tracking-wider">Runtimes</span>
364
+ <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Runtimes</span>
364
365
  <Tooltip content="Add runtime">
365
366
  <button
366
367
  onClick={() => setDialogOpen(true)}
367
- className="p-1 rounded text-text-3 hover:text-accent hover:bg-accent/10 transition-colors cursor-pointer"
368
+ className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
368
369
  >
369
- <Plus size={14} />
370
+ <Plus size={13} />
370
371
  </button>
371
372
  </Tooltip>
372
373
  </div>
373
374
 
374
375
  {runtimes.length === 0 ? (
375
- <div className="px-3 py-4 text-center">
376
- <WifiOff size={20} className="mx-auto text-text-4 mb-1.5" />
376
+ <div className="py-5 text-center">
377
+ <WifiOff size={18} className="mx-auto text-text-4 mb-1.5" />
377
378
  <p className="text-xs text-text-3 font-sans">No runtimes configured</p>
378
- <Button variant="ghost" size="sm" className="mt-2" onClick={() => setDialogOpen(true)}>
379
- <Plus size={12} className="mr-1" /> Add Runtime
380
- </Button>
379
+ <button
380
+ onClick={() => setDialogOpen(true)}
381
+ className="mt-2 flex items-center gap-1 mx-auto px-3 py-1.5 text-2xs font-sans text-text-3 hover:text-text-1 transition-colors cursor-pointer"
382
+ >
383
+ <Plus size={11} /> Add Runtime
384
+ </button>
381
385
  </div>
382
386
  ) : (
383
387
  <ScrollArea className="max-h-48">
384
- <div className="space-y-0.5">
388
+ <div className="space-y-px">
385
389
  {runtimes.map((rt) => (
386
390
  <RuntimeItem
387
391
  key={rt.id}
@@ -11,26 +11,26 @@ import { tags as t } from '@lezer/highlight';
11
11
  import { markdown } from '@codemirror/lang-markdown';
12
12
 
13
13
  const editorTheme = EditorView.theme({
14
- '&': { backgroundColor: '#13161b', color: '#d4d8e0', fontFamily: 'var(--font-mono)', fontSize: '12px', height: '100%', lineHeight: '1.6' },
14
+ '&': { backgroundColor: 'var(--color-surface-1)', color: '#d4d8e0', fontFamily: 'var(--font-mono)', fontSize: '11px', height: '100%', lineHeight: '1.6' },
15
15
  '.cm-scroller': { overflow: 'auto', padding: '4px 0' },
16
16
  '.cm-content': { caretColor: '#33afbc', padding: '0 8px' },
17
17
  '.cm-cursor': { borderLeftColor: '#33afbc', borderLeftWidth: '1.5px' },
18
- '.cm-gutters': { backgroundColor: '#13161b', borderRight: '1px solid #1e2229', color: '#404852', minWidth: '32px' },
19
- '.cm-activeLine': { backgroundColor: 'rgba(255, 255, 255, 0.03)' },
18
+ '.cm-gutters': { backgroundColor: 'var(--color-surface-1)', borderRight: 'none', color: '#404852', minWidth: '28px' },
19
+ '.cm-activeLine': { backgroundColor: 'rgba(255, 255, 255, 0.02)' },
20
20
  '&.cm-focused .cm-selectionBackground, .cm-selectionBackground': { backgroundColor: 'rgba(51, 175, 188, 0.15)' },
21
21
  });
22
22
 
23
23
  const promptHighlightStyle = HighlightStyle.define([
24
24
  { tag: t.keyword, color: '#b07fd5' },
25
25
  { tag: [t.name, t.deleted, t.character, t.macroName], color: '#d4d8e0' },
26
- { tag: [t.function(t.variableName), t.labelName], color: '#7ab0df' },
26
+ { tag: [t.function(t.variableName), t.labelName], color: '#dcc9a0' },
27
27
  { tag: [t.meta, t.comment], color: '#6e7681', fontStyle: 'italic' },
28
28
  { tag: t.strong, fontWeight: 'bold' },
29
29
  { tag: t.emphasis, fontStyle: 'italic' },
30
30
  { tag: t.link, color: '#7ab0df', textDecoration: 'underline' },
31
31
  { tag: t.heading, fontWeight: '400', color: '#bcc2cd' },
32
- { tag: [t.processingInstruction, t.string, t.inserted], color: '#73c991' },
33
- { tag: [t.atom, t.bool], color: '#c4956a' },
32
+ { tag: [t.processingInstruction, t.string, t.inserted], color: '#95b2b8' },
33
+ { tag: [t.atom, t.bool], color: '#d4a07a' },
34
34
  { tag: t.invalid, color: '#d4736e' },
35
35
  ]);
36
36
 
@@ -90,29 +90,29 @@ export function SystemPromptEditor() {
90
90
  }, [systemPrompt]);
91
91
 
92
92
  return (
93
- <div className="space-y-1">
93
+ <div className="space-y-1.5">
94
94
  <button
95
95
  onClick={() => setCollapsed(!collapsed)}
96
96
  className="flex items-center gap-1.5 w-full cursor-pointer group"
97
97
  >
98
98
  {collapsed ? (
99
- <ChevronRight size={12} className="text-text-3 group-hover:text-text-1 transition-colors" />
99
+ <ChevronRight size={11} className="text-text-4 group-hover:text-text-2 transition-colors" />
100
100
  ) : (
101
- <ChevronDown size={12} className="text-text-3 group-hover:text-text-1 transition-colors" />
101
+ <ChevronDown size={11} className="text-text-4 group-hover:text-text-2 transition-colors" />
102
102
  )}
103
- <span className="text-xs font-semibold font-sans text-text-2 uppercase tracking-wider group-hover:text-text-1 transition-colors">
103
+ <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider group-hover:text-text-2 transition-colors">
104
104
  System Prompt
105
105
  </span>
106
- <span className="text-2xs font-mono text-text-4 ml-auto">{charCount > 0 ? `${charCount} chars` : ''}</span>
106
+ <span className="text-2xs font-mono text-text-4 ml-auto">{charCount > 0 ? `${charCount}` : ''}</span>
107
107
  </button>
108
108
 
109
109
  <div className={cn(
110
110
  'overflow-hidden transition-all duration-200',
111
- collapsed ? 'h-0' : 'h-40',
111
+ collapsed ? 'h-0' : 'h-36',
112
112
  )}>
113
113
  <div
114
114
  ref={containerRef}
115
- className="h-full rounded-md border border-border-subtle overflow-hidden"
115
+ className="h-full border border-border-subtle rounded-sm overflow-hidden"
116
116
  />
117
117
  </div>
118
118
  </div>
@@ -153,7 +153,7 @@ export function BreadcrumbBar({
153
153
  return (
154
154
  <header
155
155
  className={cn(
156
- 'h-11 flex-shrink-0 flex items-center gap-3 px-4 pt-0.5 bg-surface-3 border-b border-border relative',
156
+ 'h-12 flex-shrink-0 flex items-center gap-3 px-4 bg-surface-3 border-b border-border relative',
157
157
  darwinDrag && 'pl-24 electron-drag electron-no-drag-children',
158
158
  )}
159
159
  >
@@ -6,34 +6,74 @@ import { cn } from '../../lib/cn';
6
6
  import { timeAgo } from '../../lib/format';
7
7
  import { PreviewToolbar } from './preview-toolbar';
8
8
  import { ScreenshotOverlay } from './screenshot-overlay';
9
+ import { TableTree } from '../ui/table-tree';
10
+
11
+ function parsePreviewSegments(text) {
12
+ const lines = text.split('\n');
13
+ const segments = [];
14
+ let i = 0;
15
+ let textLines = [];
16
+ while (i < lines.length) {
17
+ const line = lines[i];
18
+ if (line.includes('|') && i + 1 < lines.length && /^\|?\s*[-:]+/.test(lines[i + 1])) {
19
+ if (textLines.length > 0) {
20
+ segments.push({ type: 'text', content: textLines.join('\n') });
21
+ textLines = [];
22
+ }
23
+ const headers = line.split('|').map((c) => c.trim()).filter(Boolean);
24
+ i += 2;
25
+ const rows = [];
26
+ while (i < lines.length && lines[i].includes('|')) {
27
+ rows.push(lines[i].split('|').map((c) => c.trim()).filter(Boolean));
28
+ i++;
29
+ }
30
+ segments.push({ type: 'table', headers, rows });
31
+ } else {
32
+ textLines.push(line);
33
+ i++;
34
+ }
35
+ }
36
+ if (textLines.length > 0) segments.push({ type: 'text', content: textLines.join('\n') });
37
+ return segments;
38
+ }
9
39
 
10
40
  function RenderedMarkdown({ text }) {
11
41
  if (!text) return null;
12
- const parts = text.split(/(```[\s\S]*?```|`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*)/g);
42
+ const segments = parsePreviewSegments(text);
13
43
  return (
14
44
  <>
15
- {parts.map((part, i) => {
16
- if (!part) return null;
17
- if (part.startsWith('```') && part.endsWith('```')) {
18
- const inner = part.slice(3, -3);
19
- const nl = inner.indexOf('\n');
20
- const code = nl >= 0 ? inner.slice(nl + 1) : inner;
21
- return (
22
- <pre key={i} className="my-2 px-3 py-2 rounded-lg bg-surface-0 border border-border-subtle overflow-x-auto">
23
- <code className="text-xs font-mono text-text-1 whitespace-pre">{code}</code>
24
- </pre>
25
- );
26
- }
27
- if (part.startsWith('`') && part.endsWith('`')) {
28
- return <code key={i} className="px-1.5 py-0.5 rounded bg-surface-0 text-xs font-mono text-accent">{part.slice(1, -1)}</code>;
29
- }
30
- if (part.startsWith('**') && part.endsWith('**')) {
31
- return <strong key={i} className="font-semibold text-text-0">{part.slice(2, -2)}</strong>;
32
- }
33
- if (part.startsWith('*') && part.endsWith('*')) {
34
- return <em key={i} className="italic">{part.slice(1, -1)}</em>;
45
+ {segments.map((seg, idx) => {
46
+ if (seg.type === 'table') {
47
+ return <TableTree key={idx} headers={seg.headers} rows={seg.rows} />;
35
48
  }
36
- return <span key={i}>{part}</span>;
49
+ const parts = seg.content.split(/(```[\s\S]*?```|`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*)/g);
50
+ return (
51
+ <span key={idx}>
52
+ {parts.map((part, i) => {
53
+ if (!part) return null;
54
+ if (part.startsWith('```') && part.endsWith('```')) {
55
+ const inner = part.slice(3, -3);
56
+ const nl = inner.indexOf('\n');
57
+ const code = nl >= 0 ? inner.slice(nl + 1) : inner;
58
+ return (
59
+ <pre key={i} className="my-2 px-3 py-2 rounded-lg bg-surface-0 border border-border-subtle overflow-x-auto">
60
+ <code className="text-xs font-mono text-text-1 whitespace-pre">{code}</code>
61
+ </pre>
62
+ );
63
+ }
64
+ if (part.startsWith('`') && part.endsWith('`')) {
65
+ return <code key={i} className="px-1.5 py-0.5 rounded bg-surface-0 text-xs font-mono text-accent">{part.slice(1, -1)}</code>;
66
+ }
67
+ if (part.startsWith('**') && part.endsWith('**')) {
68
+ return <strong key={i} className="font-semibold text-text-0">{part.slice(2, -2)}</strong>;
69
+ }
70
+ if (part.startsWith('*') && part.endsWith('*')) {
71
+ return <em key={i} className="italic">{part.slice(1, -1)}</em>;
72
+ }
73
+ return <span key={i}>{part}</span>;
74
+ })}
75
+ </span>
76
+ );
37
77
  })}
38
78
  </>
39
79
  );
@@ -10,14 +10,13 @@ export function TuningSlider({
10
10
  const display = typeof fmt === 'function' ? fmt(value) : (typeof fmt === 'string' ? fmt : value);
11
11
 
12
12
  return (
13
- <div className={cn('flex items-center gap-3 h-10', disabled && 'opacity-40 pointer-events-none', className)}>
14
- <span className="text-xs text-text-2 font-sans w-28 shrink-0">{label}</span>
15
- <span className="text-2xs text-text-4 font-mono w-6 text-right shrink-0">{min}</span>
16
- <div className="relative flex-1 flex items-center group">
13
+ <div className={cn('group flex items-center gap-2 py-1.5', disabled && 'opacity-40 pointer-events-none', className)}>
14
+ <span className="text-2xs text-text-2 font-sans w-20 shrink-0 truncate">{label}</span>
15
+ <div className="relative flex-1 flex items-center h-5">
17
16
  <div className="absolute inset-y-0 flex items-center w-full pointer-events-none">
18
- <div className="w-full h-1.5 rounded-full bg-surface-5 overflow-hidden">
17
+ <div className="w-full h-1 rounded-full bg-surface-5">
19
18
  <div
20
- className="h-full rounded-full bg-accent transition-all"
19
+ className="h-full rounded-full bg-accent/70 group-hover:bg-accent transition-colors"
21
20
  style={{ width: `${pct}%` }}
22
21
  />
23
22
  </div>
@@ -30,21 +29,21 @@ export function TuningSlider({
30
29
  value={value}
31
30
  disabled={disabled}
32
31
  onChange={(e) => onChange(Number(e.target.value))}
33
- className="relative w-full h-4 appearance-none bg-transparent cursor-pointer
34
- [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4
35
- [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:border-2
36
- [&::-webkit-slider-thumb]:border-surface-1 [&::-webkit-slider-thumb]:shadow-[0_0_6px_rgba(51,175,188,0.4)]
37
- [&::-webkit-slider-thumb]:hover:shadow-[0_0_10px_rgba(51,175,188,0.6)]
38
- [&::-webkit-slider-thumb]:transition-shadow
39
- [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:rounded-full
40
- [&::-moz-range-thumb]:bg-accent [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-surface-1
41
- [&::-moz-range-thumb]:shadow-[0_0_6px_rgba(51,175,188,0.4)]
32
+ className="relative w-full h-5 appearance-none bg-transparent cursor-pointer
33
+ [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
34
+ [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent
35
+ [&::-webkit-slider-thumb]:shadow-[0_0_0_2px_var(--color-surface-1)]
36
+ [&::-webkit-slider-thumb]:hover:bg-accent [&::-webkit-slider-thumb]:hover:shadow-[0_0_0_2px_var(--color-surface-1),0_0_8px_rgba(51,175,188,0.4)]
37
+ [&::-webkit-slider-thumb]:active:scale-110
38
+ [&::-webkit-slider-thumb]:transition-all
39
+ [&::-moz-range-thumb]:w-3 [&::-moz-range-thumb]:h-3 [&::-moz-range-thumb]:rounded-full
40
+ [&::-moz-range-thumb]:bg-accent [&::-moz-range-thumb]:border-none
41
+ [&::-moz-range-thumb]:shadow-[0_0_0_2px_var(--color-surface-1)]
42
42
  [&::-moz-range-track]:bg-transparent
43
43
  disabled:cursor-not-allowed"
44
44
  />
45
45
  </div>
46
- <span className="text-2xs text-text-4 font-mono w-6 shrink-0">{max}</span>
47
- <span className="text-xs text-accent font-mono font-semibold w-10 text-right shrink-0">{display}</span>
46
+ <span className="text-2xs text-accent font-mono font-medium w-10 text-right shrink-0 tabular-nums">{display}</span>
48
47
  </div>
49
48
  );
50
49
  }
@@ -0,0 +1,38 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ export function TableTree({ headers, rows }) {
4
+ if (!headers?.length || !rows?.length) return null;
5
+ return (
6
+ <div className="space-y-3 my-2 font-sans">
7
+ {rows.map((row, ri) => {
8
+ const restItems = headers.slice(1).map((h, j) => ({
9
+ label: h,
10
+ value: row[j + 1] ?? '',
11
+ }));
12
+ return (
13
+ <div key={ri}>
14
+ <div className="text-xs font-semibold text-text-0">
15
+ {headers[0]}: {row[0] ?? ''}
16
+ </div>
17
+ <div>
18
+ {restItems.map(({ label, value }, j) => {
19
+ const isLast = j === restItems.length - 1;
20
+ return (
21
+ <div key={j} className="flex items-baseline gap-1 text-xs leading-5">
22
+ <span className="text-text-3 font-mono w-3 flex-shrink-0">
23
+ {isLast ? '└' : '├'}
24
+ </span>
25
+ <span className="text-text-1">{label}:</span>
26
+ <code className="px-1.5 py-0.5 rounded bg-surface-0 text-xs font-mono text-accent">
27
+ {value}
28
+ </code>
29
+ </div>
30
+ );
31
+ })}
32
+ </div>
33
+ </div>
34
+ );
35
+ })}
36
+ </div>
37
+ );
38
+ }