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.
- package/moe-training/client/domain-tagger.js +1 -1
- package/moe-training/scripts/retag-delegate-yield.js +303 -0
- package/moe-training/test/shared/envelope-schema.test.js +3 -3
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/adaptive.js +77 -0
- package/node_modules/@groove-dev/daemon/src/api.js +35 -5
- package/node_modules/@groove-dev/daemon/src/journalist.js +28 -12
- package/node_modules/@groove-dev/daemon/src/model-lab.js +53 -76
- package/node_modules/@groove-dev/daemon/src/process.js +91 -2
- package/node_modules/@groove-dev/daemon/src/rotator.js +45 -3
- package/node_modules/@groove-dev/gui/dist/assets/{index-Dozp69tK.js → index-BrZHF7pK.js} +1770 -1766
- package/node_modules/@groove-dev/gui/dist/assets/index-DIfiwdKl.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +60 -18
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +42 -20
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +2 -22
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +9 -9
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +7 -0
- package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +59 -51
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +48 -48
- package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +39 -38
- package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +4 -5
- package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +11 -11
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +66 -62
- package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +13 -13
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/preview/preview-workspace.jsx +62 -22
- package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +16 -17
- package/node_modules/@groove-dev/gui/src/components/ui/table-tree.jsx +38 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +23 -9
- package/node_modules/@groove-dev/gui/src/views/editor.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +101 -87
- package/node_modules/moe-training/client/domain-tagger.js +1 -1
- package/node_modules/moe-training/scripts/retag-delegate-yield.js +303 -0
- package/node_modules/moe-training/test/shared/envelope-schema.test.js +3 -3
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/adaptive.js +77 -0
- package/packages/daemon/src/api.js +35 -5
- package/packages/daemon/src/journalist.js +28 -12
- package/packages/daemon/src/model-lab.js +53 -76
- package/packages/daemon/src/process.js +91 -2
- package/packages/daemon/src/rotator.js +45 -3
- package/packages/gui/dist/assets/{index-Dozp69tK.js → index-BrZHF7pK.js} +1770 -1766
- package/packages/gui/dist/assets/index-DIfiwdKl.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-chat.jsx +60 -18
- package/packages/gui/src/components/agents/agent-feed.jsx +42 -20
- package/packages/gui/src/components/agents/agent-file-tree.jsx +1 -1
- package/packages/gui/src/components/agents/workspace-mode.jsx +1 -1
- package/packages/gui/src/components/chat/chat-messages.jsx +2 -22
- package/packages/gui/src/components/editor/code-editor.jsx +9 -9
- package/packages/gui/src/components/editor/file-tree.jsx +1 -1
- package/packages/gui/src/components/editor/terminal.jsx +7 -0
- package/packages/gui/src/components/lab/chat-playground.jsx +59 -51
- package/packages/gui/src/components/lab/lab-assistant.jsx +48 -48
- package/packages/gui/src/components/lab/metrics-panel.jsx +39 -38
- package/packages/gui/src/components/lab/parameter-panel.jsx +4 -5
- package/packages/gui/src/components/lab/preset-manager.jsx +11 -11
- package/packages/gui/src/components/lab/runtime-config.jsx +66 -62
- package/packages/gui/src/components/lab/system-prompt-editor.jsx +13 -13
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
- package/packages/gui/src/components/preview/preview-workspace.jsx +62 -22
- package/packages/gui/src/components/ui/slider.jsx +16 -17
- package/packages/gui/src/components/ui/table-tree.jsx +38 -0
- package/packages/gui/src/stores/groove.js +23 -9
- package/packages/gui/src/views/editor.jsx +1 -1
- package/packages/gui/src/views/model-lab.jsx +101 -87
- package/plan_files/DELEGATE_YIELD_TRAINING_TAGS.md +135 -0
- package/plan_files/session-quality-rotation-fixes.md +218 -0
- package/test.py +571 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BgQL4bNl.css +0 -1
- package/packages/gui/dist/assets/index-BgQL4bNl.css +0 -1
- /package/{AGENT_ORCHESTRATION.md → plan_files/AGENT_ORCHESTRATION.md} +0 -0
- /package/{DYNAMIC_LEAF_ARCH.md → plan_files/DYNAMIC_LEAF_ARCH.md} +0 -0
- /package/{EMBEDDING_DIAGNOSTIC.md → plan_files/EMBEDDING_DIAGNOSTIC.md} +0 -0
- /package/{EMBEDDING_SERVICE_BUILD_PLAN.md → plan_files/EMBEDDING_SERVICE_BUILD_PLAN.md} +0 -0
- /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-
|
|
36
|
-
<span className="text-
|
|
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
|
|
39
|
+
className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
|
|
41
40
|
>
|
|
42
|
-
<RotateCcw size={
|
|
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,
|
|
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-
|
|
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
|
|
63
|
+
className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
|
|
64
64
|
>
|
|
65
|
-
<Save size={
|
|
65
|
+
<Save size={13} />
|
|
66
66
|
</button>
|
|
67
67
|
</Tooltip>
|
|
68
68
|
</div>
|
|
69
69
|
|
|
70
70
|
{presets.length === 0 ? (
|
|
71
|
-
<div className="
|
|
72
|
-
<BookmarkCheck size={
|
|
73
|
-
<p className="text-2xs text-text-
|
|
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-
|
|
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-
|
|
83
|
-
activePreset === preset.id ? 'bg-accent/
|
|
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
|
|
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,
|
|
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-
|
|
95
|
-
active ? 'bg-accent/
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
115
|
+
className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
|
|
118
116
|
>
|
|
119
|
-
{testing === runtime.id ? <Loader2 size={
|
|
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
|
|
123
|
+
className="p-1 text-text-4 hover:text-danger transition-colors cursor-pointer"
|
|
126
124
|
>
|
|
127
|
-
<Trash2 size={
|
|
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-
|
|
153
|
-
phase === 'ready' && 'bg-success/
|
|
154
|
-
phase === 'error' && 'bg-danger/
|
|
155
|
-
(phase === 'starting' || phase === 'checking') && 'bg-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={
|
|
158
|
-
{phase === 'checking' && <><Loader2 size={
|
|
159
|
-
{phase === 'ready' && <><CheckCircle size={
|
|
160
|
-
{phase === 'error' && <><AlertTriangle size={
|
|
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-
|
|
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="
|
|
206
|
-
<HardDrive size={
|
|
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-
|
|
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-
|
|
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-
|
|
221
|
-
selectedModel === m.id ? 'bg-accent/
|
|
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={
|
|
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={
|
|
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-
|
|
245
|
-
<div className="space-y-
|
|
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-
|
|
252
|
-
selectedBackend === b.id ? 'bg-accent/
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
<
|
|
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
|
|
308
|
-
: <><Wrench size={12}
|
|
308
|
+
? <><Loader2 size={12} className="animate-spin" /> Starting Assistant...</>
|
|
309
|
+
: <><Wrench size={12} /> Setup {currentBackend?.label} with Assistant</>
|
|
309
310
|
}
|
|
310
|
-
</
|
|
311
|
-
<p className="text-2xs text-text-4 font-sans
|
|
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
|
-
<
|
|
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
|
|
329
|
+
<><Loader2 size={12} className="animate-spin" /> Starting...</>
|
|
329
330
|
) : (
|
|
330
|
-
<><Play size={12}
|
|
331
|
+
<><Play size={12} /> Launch</>
|
|
331
332
|
)}
|
|
332
|
-
</
|
|
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-
|
|
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
|
|
368
|
+
className="p-1 text-text-4 hover:text-accent transition-colors cursor-pointer"
|
|
368
369
|
>
|
|
369
|
-
<Plus size={
|
|
370
|
+
<Plus size={13} />
|
|
370
371
|
</button>
|
|
371
372
|
</Tooltip>
|
|
372
373
|
</div>
|
|
373
374
|
|
|
374
375
|
{runtimes.length === 0 ? (
|
|
375
|
-
<div className="
|
|
376
|
-
<WifiOff size={
|
|
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
|
-
<
|
|
379
|
-
|
|
380
|
-
|
|
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-
|
|
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: '
|
|
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: '
|
|
19
|
-
'.cm-activeLine': { backgroundColor: 'rgba(255, 255, 255, 0.
|
|
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: '#
|
|
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: '#
|
|
33
|
-
{ tag: [t.atom, t.bool], color: '#
|
|
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={
|
|
99
|
+
<ChevronRight size={11} className="text-text-4 group-hover:text-text-2 transition-colors" />
|
|
100
100
|
) : (
|
|
101
|
-
<ChevronDown size={
|
|
101
|
+
<ChevronDown size={11} className="text-text-4 group-hover:text-text-2 transition-colors" />
|
|
102
102
|
)}
|
|
103
|
-
<span className="text-
|
|
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}
|
|
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-
|
|
111
|
+
collapsed ? 'h-0' : 'h-36',
|
|
112
112
|
)}>
|
|
113
113
|
<div
|
|
114
114
|
ref={containerRef}
|
|
115
|
-
className="h-full
|
|
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-
|
|
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
|
|
42
|
+
const segments = parsePreviewSegments(text);
|
|
13
43
|
return (
|
|
14
44
|
<>
|
|
15
|
-
{
|
|
16
|
-
if (
|
|
17
|
-
|
|
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
|
-
|
|
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-
|
|
14
|
-
<span className="text-
|
|
15
|
-
<
|
|
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
|
|
17
|
+
<div className="w-full h-1 rounded-full bg-surface-5">
|
|
19
18
|
<div
|
|
20
|
-
className="h-full rounded-full bg-accent transition-
|
|
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-
|
|
34
|
-
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-
|
|
35
|
-
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent
|
|
36
|
-
[&::-webkit-slider-thumb]:
|
|
37
|
-
[&::-webkit-slider-thumb]:hover:shadow-[
|
|
38
|
-
[&::-webkit-slider-thumb]:
|
|
39
|
-
[&::-
|
|
40
|
-
[&::-moz-range-thumb]:
|
|
41
|
-
[&::-moz-range-thumb]:
|
|
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-
|
|
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
|
+
}
|