groove-dev 0.27.141 → 0.27.142
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/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/api.js +18 -7
- package/node_modules/@groove-dev/daemon/src/introducer.js +1 -1
- package/node_modules/@groove-dev/daemon/src/journalist.js +3 -2
- package/node_modules/@groove-dev/daemon/src/keeper.js +2 -2
- package/node_modules/@groove-dev/daemon/src/memory.js +8 -5
- package/node_modules/@groove-dev/daemon/src/process.js +5 -16
- package/node_modules/@groove-dev/daemon/src/rotator.js +25 -8
- package/node_modules/@groove-dev/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
- package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +984 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +3 -3
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.jsx +0 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +6 -7
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +8 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +5 -6
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +79 -5
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +2 -53
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +111 -0
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +70 -33
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +2 -68
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +1 -2
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +0 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +3 -3
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +3 -71
- package/node_modules/@groove-dev/gui/src/views/models.jsx +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/api.js +18 -7
- package/packages/daemon/src/introducer.js +1 -1
- package/packages/daemon/src/journalist.js +3 -2
- package/packages/daemon/src/keeper.js +2 -2
- package/packages/daemon/src/memory.js +8 -5
- package/packages/daemon/src/process.js +5 -16
- package/packages/daemon/src/rotator.js +25 -8
- package/packages/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
- package/packages/gui/dist/assets/index-Bjd91ufV.js +984 -0
- package/packages/gui/dist/assets/index-BqdwIFn4.css +1 -0
- package/packages/gui/dist/index.html +3 -3
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.jsx +0 -2
- package/packages/gui/src/components/agents/agent-chat.jsx +6 -7
- package/packages/gui/src/components/agents/agent-feed.jsx +8 -2
- package/packages/gui/src/components/agents/agent-file-tree.jsx +5 -6
- package/packages/gui/src/components/agents/agent-panel.jsx +79 -5
- package/packages/gui/src/components/agents/workspace-mode.jsx +2 -53
- package/packages/gui/src/components/dashboard/context-gauges.jsx +111 -0
- package/packages/gui/src/components/dashboard/routing-chart.jsx +70 -33
- package/packages/gui/src/components/editor/code-editor.jsx +2 -68
- package/packages/gui/src/components/layout/activity-bar.jsx +1 -2
- package/packages/gui/src/components/layout/terminal-panel.jsx +0 -1
- package/packages/gui/src/stores/groove.js +3 -3
- package/packages/gui/src/views/dashboard.jsx +2 -0
- package/packages/gui/src/views/marketplace.jsx +3 -71
- package/packages/gui/src/views/models.jsx +3 -3
- package/node_modules/@groove-dev/gui/dist/assets/index-A4e1gIDh.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-P1hsM27-.js +0 -8696
- package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +0 -78
- package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +0 -144
- package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +0 -187
- package/node_modules/@groove-dev/gui/src/views/toys.jsx +0 -162
- package/packages/gui/dist/assets/index-A4e1gIDh.css +0 -1
- package/packages/gui/dist/assets/index-P1hsM27-.js +0 -8696
- package/packages/gui/src/components/toys/toy-card.jsx +0 -78
- package/packages/gui/src/components/toys/toy-creator.jsx +0 -144
- package/packages/gui/src/components/toys/toy-launcher.jsx +0 -187
- package/packages/gui/src/views/toys.jsx +0 -162
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { Card } from '../ui/card';
|
|
3
|
-
import { Badge } from '../ui/badge';
|
|
4
|
-
import * as Icons from 'lucide-react';
|
|
5
|
-
|
|
6
|
-
const DIFFICULTY_VARIANT = {
|
|
7
|
-
beginner: 'success',
|
|
8
|
-
intermediate: 'warning',
|
|
9
|
-
advanced: 'danger',
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
function resolveIcon(name) {
|
|
13
|
-
if (!name) return Icons.Box;
|
|
14
|
-
const pascal = name.replace(/(^|-)(\w)/g, (_, __, c) => c.toUpperCase());
|
|
15
|
-
return Icons[pascal] || Icons[name] || Icons.Box;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function ToyCard({ toy, onClick }) {
|
|
19
|
-
const Icon = resolveIcon(toy.icon);
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<Card
|
|
23
|
-
hover
|
|
24
|
-
className="group flex flex-col p-4 gap-3 transition-all duration-150 hover:scale-[1.02] hover:shadow-lg hover:shadow-black/20"
|
|
25
|
-
onClick={() => onClick(toy)}
|
|
26
|
-
>
|
|
27
|
-
{/* Icon + name + category */}
|
|
28
|
-
<div className="flex items-start gap-3">
|
|
29
|
-
<div className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 bg-accent/10 border border-accent/20 text-accent">
|
|
30
|
-
<Icon size={20} />
|
|
31
|
-
</div>
|
|
32
|
-
<div className="flex-1 min-w-0 pt-0.5">
|
|
33
|
-
<h3 className="text-sm font-semibold text-text-0 font-sans truncate pr-1">{toy.name}</h3>
|
|
34
|
-
</div>
|
|
35
|
-
<Badge variant="default" className="flex-shrink-0 mt-0.5">
|
|
36
|
-
{toy.category}
|
|
37
|
-
</Badge>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
{/* Description — 2 lines max */}
|
|
41
|
-
<p className="text-xs text-text-2 font-sans leading-relaxed line-clamp-2">{toy.description}</p>
|
|
42
|
-
|
|
43
|
-
{/* Bottom badges */}
|
|
44
|
-
<div className="flex items-center gap-1.5 mt-auto flex-wrap">
|
|
45
|
-
{toy.custom && (
|
|
46
|
-
<Badge variant="accent">Custom</Badge>
|
|
47
|
-
)}
|
|
48
|
-
<Badge variant={DIFFICULTY_VARIANT[toy.difficulty] || 'default'}>
|
|
49
|
-
{toy.difficulty || 'beginner'}
|
|
50
|
-
</Badge>
|
|
51
|
-
<Badge variant={toy.authType === 'none' ? 'success' : 'warning'}>
|
|
52
|
-
{toy.authType === 'none' ? 'No Key Required' : 'API Key Required'}
|
|
53
|
-
</Badge>
|
|
54
|
-
</div>
|
|
55
|
-
</Card>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function ToyCardSkeleton() {
|
|
60
|
-
return (
|
|
61
|
-
<Card className="p-4 space-y-3">
|
|
62
|
-
<div className="flex items-start gap-3">
|
|
63
|
-
<div className="w-10 h-10 rounded-lg bg-surface-4 animate-pulse" />
|
|
64
|
-
<div className="flex-1 space-y-2 pt-1">
|
|
65
|
-
<div className="h-3.5 w-24 bg-surface-4 rounded animate-pulse" />
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
<div className="space-y-1.5">
|
|
69
|
-
<div className="h-3 w-full bg-surface-4 rounded animate-pulse" />
|
|
70
|
-
<div className="h-3 w-3/4 bg-surface-4 rounded animate-pulse" />
|
|
71
|
-
</div>
|
|
72
|
-
<div className="flex gap-1.5">
|
|
73
|
-
<div className="h-5 w-16 bg-surface-4 rounded animate-pulse" />
|
|
74
|
-
<div className="h-5 w-24 bg-surface-4 rounded animate-pulse" />
|
|
75
|
-
</div>
|
|
76
|
-
</Card>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { Sheet, SheetContent } from '../ui/sheet';
|
|
4
|
-
import { Button } from '../ui/button';
|
|
5
|
-
import { ScrollArea } from '../ui/scroll-area';
|
|
6
|
-
import { api } from '../../lib/api';
|
|
7
|
-
import { useToast } from '../../lib/hooks/use-toast';
|
|
8
|
-
import { Globe, Loader2, Sparkles, Link2 } from 'lucide-react';
|
|
9
|
-
|
|
10
|
-
const STEPS = {
|
|
11
|
-
input: 'input',
|
|
12
|
-
researching: 'researching',
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export function ToyCreator({ open, onClose, onCreated }) {
|
|
16
|
-
const [url, setUrl] = useState('');
|
|
17
|
-
const [name, setName] = useState('');
|
|
18
|
-
const [step, setStep] = useState(STEPS.input);
|
|
19
|
-
const toast = useToast();
|
|
20
|
-
|
|
21
|
-
function reset() {
|
|
22
|
-
setUrl('');
|
|
23
|
-
setName('');
|
|
24
|
-
setStep(STEPS.input);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function handleClose() {
|
|
28
|
-
if (step === STEPS.researching) return;
|
|
29
|
-
reset();
|
|
30
|
-
onClose();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function handleCreate() {
|
|
34
|
-
const trimmed = url.trim();
|
|
35
|
-
if (!trimmed) {
|
|
36
|
-
toast.warning('URL required', 'Paste an API documentation or reference URL.');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
setStep(STEPS.researching);
|
|
41
|
-
try {
|
|
42
|
-
const toy = await api.post('/toys', {
|
|
43
|
-
docsUrl: trimmed,
|
|
44
|
-
name: name.trim() || undefined,
|
|
45
|
-
});
|
|
46
|
-
toast.success('Toy created', `${toy.name || 'New toy'} is ready to launch.`);
|
|
47
|
-
onCreated?.(toy);
|
|
48
|
-
reset();
|
|
49
|
-
onClose();
|
|
50
|
-
} catch (err) {
|
|
51
|
-
toast.error('Research failed', err.message);
|
|
52
|
-
setStep(STEPS.input);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<Sheet open={open} onOpenChange={(v) => !v && handleClose()}>
|
|
58
|
-
<SheetContent title="New Toy" width={440}>
|
|
59
|
-
<ScrollArea className="h-[calc(100%-65px)]">
|
|
60
|
-
<div className="px-5 py-4 space-y-5">
|
|
61
|
-
{step === STEPS.input && (
|
|
62
|
-
<>
|
|
63
|
-
<div className="flex items-start gap-3 p-3 rounded-lg bg-accent/5 border border-accent/10">
|
|
64
|
-
<Sparkles size={16} className="text-accent mt-0.5 flex-shrink-0" />
|
|
65
|
-
<p className="text-xs text-text-2 font-sans leading-relaxed">
|
|
66
|
-
Paste a docs URL and Groove's AI will research the API — endpoints, auth, data structures, rate limits — and build a reusable toy card you can launch anytime.
|
|
67
|
-
</p>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<div>
|
|
71
|
-
<h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">
|
|
72
|
-
API Docs URL
|
|
73
|
-
</h4>
|
|
74
|
-
<div className="relative">
|
|
75
|
-
<Link2 size={13} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-text-4" />
|
|
76
|
-
<input
|
|
77
|
-
type="url"
|
|
78
|
-
value={url}
|
|
79
|
-
onChange={(e) => setUrl(e.target.value)}
|
|
80
|
-
placeholder="https://api.example.com/docs"
|
|
81
|
-
className="w-full bg-surface-3 border border-border-subtle rounded pl-8 pr-3 py-2 text-xs text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:border-accent/50"
|
|
82
|
-
onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
|
|
83
|
-
autoFocus
|
|
84
|
-
/>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
<div>
|
|
89
|
-
<h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">
|
|
90
|
-
Name <span className="text-text-4 normal-case tracking-normal">(optional)</span>
|
|
91
|
-
</h4>
|
|
92
|
-
<input
|
|
93
|
-
type="text"
|
|
94
|
-
value={name}
|
|
95
|
-
onChange={(e) => setName(e.target.value)}
|
|
96
|
-
placeholder="Auto-detected from docs"
|
|
97
|
-
className="w-full bg-surface-3 border border-border-subtle rounded px-3 py-2 text-xs text-text-0 font-sans placeholder:text-text-4 focus:outline-none focus:border-accent/50"
|
|
98
|
-
onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
|
|
99
|
-
/>
|
|
100
|
-
</div>
|
|
101
|
-
</>
|
|
102
|
-
)}
|
|
103
|
-
|
|
104
|
-
{step === STEPS.researching && (
|
|
105
|
-
<div className="flex flex-col items-center justify-center py-12 gap-4">
|
|
106
|
-
<div className="w-14 h-14 rounded-xl bg-accent/10 border border-accent/20 flex items-center justify-center">
|
|
107
|
-
<Globe size={24} className="text-accent animate-pulse" />
|
|
108
|
-
</div>
|
|
109
|
-
<div className="text-center space-y-1.5">
|
|
110
|
-
<p className="text-sm font-semibold text-text-0 font-sans">Researching API...</p>
|
|
111
|
-
<p className="text-xs text-text-3 font-sans max-w-[260px]">
|
|
112
|
-
Reading documentation, mapping endpoints, and building your toy card. This takes 15–30 seconds.
|
|
113
|
-
</p>
|
|
114
|
-
</div>
|
|
115
|
-
<Loader2 size={16} className="text-accent animate-spin" />
|
|
116
|
-
</div>
|
|
117
|
-
)}
|
|
118
|
-
</div>
|
|
119
|
-
</ScrollArea>
|
|
120
|
-
|
|
121
|
-
<div className="px-5 py-3 border-t border-border-subtle bg-surface-1">
|
|
122
|
-
{step === STEPS.input && (
|
|
123
|
-
<Button
|
|
124
|
-
variant="primary"
|
|
125
|
-
size="lg"
|
|
126
|
-
className="w-full gap-2"
|
|
127
|
-
onClick={handleCreate}
|
|
128
|
-
disabled={!url.trim()}
|
|
129
|
-
>
|
|
130
|
-
<Sparkles size={14} />
|
|
131
|
-
Research & Create
|
|
132
|
-
</Button>
|
|
133
|
-
)}
|
|
134
|
-
{step === STEPS.researching && (
|
|
135
|
-
<Button variant="ghost" size="lg" className="w-full" disabled>
|
|
136
|
-
<Loader2 size={14} className="animate-spin mr-2" />
|
|
137
|
-
AI is analyzing...
|
|
138
|
-
</Button>
|
|
139
|
-
)}
|
|
140
|
-
</div>
|
|
141
|
-
</SheetContent>
|
|
142
|
-
</Sheet>
|
|
143
|
-
);
|
|
144
|
-
}
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { Sheet, SheetContent } from '../ui/sheet';
|
|
4
|
-
import { Button } from '../ui/button';
|
|
5
|
-
import { Badge } from '../ui/badge';
|
|
6
|
-
import { ScrollArea } from '../ui/scroll-area';
|
|
7
|
-
import { api } from '../../lib/api';
|
|
8
|
-
import { useToast } from '../../lib/hooks/use-toast';
|
|
9
|
-
import { useGrooveStore } from '../../stores/groove';
|
|
10
|
-
import { ExternalLink, Eye, EyeOff, Rocket, Loader2 } from 'lucide-react';
|
|
11
|
-
import * as Icons from 'lucide-react';
|
|
12
|
-
|
|
13
|
-
function resolveIcon(name) {
|
|
14
|
-
if (!name) return Icons.Box;
|
|
15
|
-
const pascal = name.replace(/(^|-)(\w)/g, (_, __, c) => c.toUpperCase());
|
|
16
|
-
return Icons[pascal] || Icons[name] || Icons.Box;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function ToyLauncher({ toy, open, onClose }) {
|
|
20
|
-
const [apiKey, setApiKey] = useState('');
|
|
21
|
-
const [showKey, setShowKey] = useState(false);
|
|
22
|
-
const [selectedPrompt, setSelectedPrompt] = useState(null);
|
|
23
|
-
const [launching, setLaunching] = useState(false);
|
|
24
|
-
const toast = useToast();
|
|
25
|
-
const setActiveView = useGrooveStore((s) => s.setActiveView);
|
|
26
|
-
|
|
27
|
-
if (!toy) return null;
|
|
28
|
-
const Icon = resolveIcon(toy.icon);
|
|
29
|
-
const needsKey = toy.authType !== 'none';
|
|
30
|
-
|
|
31
|
-
async function handleLaunch() {
|
|
32
|
-
if (needsKey && !apiKey.trim()) {
|
|
33
|
-
toast.warning('API key required', `${toy.name} needs an API key to work.`);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
setLaunching(true);
|
|
37
|
-
try {
|
|
38
|
-
await api.post(`/toys/${toy.id}/launch`, {
|
|
39
|
-
apiKey: needsKey ? apiKey.trim() : undefined,
|
|
40
|
-
starterPrompt: selectedPrompt || undefined,
|
|
41
|
-
});
|
|
42
|
-
toast.success(`${toy.name} launched`, 'Team is spinning up — switching to agents view.');
|
|
43
|
-
setActiveView('agents');
|
|
44
|
-
onClose();
|
|
45
|
-
} catch (err) {
|
|
46
|
-
toast.error('Launch failed', err.message);
|
|
47
|
-
}
|
|
48
|
-
setLaunching(false);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<Sheet open={open} onOpenChange={(v) => !v && onClose()}>
|
|
53
|
-
<SheetContent title={toy.name} width={440}>
|
|
54
|
-
<ScrollArea className="h-[calc(100%-65px)]">
|
|
55
|
-
<div className="px-5 py-4 space-y-5">
|
|
56
|
-
{/* Header */}
|
|
57
|
-
<div className="flex items-start gap-3">
|
|
58
|
-
<div className="w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0 bg-accent/10 border border-accent/20 text-accent">
|
|
59
|
-
<Icon size={24} />
|
|
60
|
-
</div>
|
|
61
|
-
<div className="flex-1 min-w-0">
|
|
62
|
-
<div className="flex items-center gap-2">
|
|
63
|
-
<Badge variant={toy.authType === 'none' ? 'success' : 'warning'}>
|
|
64
|
-
{toy.authType === 'none' ? 'Free' : 'Key Required'}
|
|
65
|
-
</Badge>
|
|
66
|
-
<Badge variant="default">{toy.category}</Badge>
|
|
67
|
-
</div>
|
|
68
|
-
<p className="text-xs text-text-2 font-sans leading-relaxed mt-2">{toy.description}</p>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
|
|
72
|
-
{/* Base URL */}
|
|
73
|
-
{toy.baseUrl && (
|
|
74
|
-
<div>
|
|
75
|
-
<h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">Base URL</h4>
|
|
76
|
-
<code className="text-xs text-accent font-mono bg-surface-3 px-2.5 py-1.5 rounded block truncate">
|
|
77
|
-
{toy.baseUrl}
|
|
78
|
-
</code>
|
|
79
|
-
</div>
|
|
80
|
-
)}
|
|
81
|
-
|
|
82
|
-
{/* Sample Endpoints */}
|
|
83
|
-
{toy.sampleEndpoints?.length > 0 && (
|
|
84
|
-
<div>
|
|
85
|
-
<h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">Endpoints</h4>
|
|
86
|
-
<div className="space-y-1">
|
|
87
|
-
{toy.sampleEndpoints.map((ep, i) => {
|
|
88
|
-
const str = typeof ep === 'string' ? ep : `${ep.method || 'GET'} ${ep.path || ''}`;
|
|
89
|
-
const spaceIdx = str.indexOf(' ');
|
|
90
|
-
const method = spaceIdx > 0 ? str.slice(0, spaceIdx) : 'GET';
|
|
91
|
-
const path = spaceIdx > 0 ? str.slice(spaceIdx + 1) : str;
|
|
92
|
-
return (
|
|
93
|
-
<div key={i} className="flex items-center gap-2 text-xs font-mono text-text-2 bg-surface-3 px-2.5 py-1.5 rounded">
|
|
94
|
-
<Badge variant="accent" className="text-[9px] px-1 py-0">{method}</Badge>
|
|
95
|
-
<span className="truncate">{path}</span>
|
|
96
|
-
</div>
|
|
97
|
-
);
|
|
98
|
-
})}
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
)}
|
|
102
|
-
|
|
103
|
-
{/* Starter Ideas */}
|
|
104
|
-
{toy.starterPrompts?.length > 0 && (
|
|
105
|
-
<div>
|
|
106
|
-
<h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">Starter Ideas</h4>
|
|
107
|
-
<div className="flex flex-wrap gap-1.5">
|
|
108
|
-
{toy.starterPrompts.map((prompt, i) => (
|
|
109
|
-
<button
|
|
110
|
-
key={i}
|
|
111
|
-
onClick={() => setSelectedPrompt(selectedPrompt === prompt ? null : prompt)}
|
|
112
|
-
className={`px-3 py-1.5 text-xs font-sans rounded-full cursor-pointer select-none transition-colors border ${
|
|
113
|
-
selectedPrompt === prompt
|
|
114
|
-
? 'bg-accent/15 text-accent border-accent/25'
|
|
115
|
-
: 'text-text-2 bg-surface-3 border-border-subtle hover:text-text-0 hover:border-border'
|
|
116
|
-
}`}
|
|
117
|
-
>
|
|
118
|
-
{prompt}
|
|
119
|
-
</button>
|
|
120
|
-
))}
|
|
121
|
-
</div>
|
|
122
|
-
</div>
|
|
123
|
-
)}
|
|
124
|
-
|
|
125
|
-
{/* API Key Input */}
|
|
126
|
-
{needsKey && (
|
|
127
|
-
<div>
|
|
128
|
-
<h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">API Key</h4>
|
|
129
|
-
<div className="relative">
|
|
130
|
-
<input
|
|
131
|
-
type={showKey ? 'text' : 'password'}
|
|
132
|
-
value={apiKey}
|
|
133
|
-
onChange={(e) => setApiKey(e.target.value)}
|
|
134
|
-
placeholder={`Enter your ${toy.name} API key`}
|
|
135
|
-
className="w-full bg-surface-3 border border-border-subtle rounded px-3 py-2 text-xs text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:border-accent/50 pr-9"
|
|
136
|
-
/>
|
|
137
|
-
<button
|
|
138
|
-
onClick={() => setShowKey(!showKey)}
|
|
139
|
-
className="absolute right-2 top-1/2 -translate-y-1/2 text-text-4 hover:text-text-1 cursor-pointer bg-transparent border-0 p-0.5"
|
|
140
|
-
>
|
|
141
|
-
{showKey ? <EyeOff size={14} /> : <Eye size={14} />}
|
|
142
|
-
</button>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
)}
|
|
146
|
-
|
|
147
|
-
{/* Docs link */}
|
|
148
|
-
{toy.docsUrl && (
|
|
149
|
-
<a
|
|
150
|
-
href={toy.docsUrl}
|
|
151
|
-
target="_blank"
|
|
152
|
-
rel="noopener noreferrer"
|
|
153
|
-
className="inline-flex items-center gap-1.5 text-xs text-accent font-sans hover:underline"
|
|
154
|
-
>
|
|
155
|
-
<ExternalLink size={12} />
|
|
156
|
-
View Documentation
|
|
157
|
-
</a>
|
|
158
|
-
)}
|
|
159
|
-
</div>
|
|
160
|
-
</ScrollArea>
|
|
161
|
-
|
|
162
|
-
{/* Footer — sticky launch button */}
|
|
163
|
-
<div className="px-5 py-3 border-t border-border-subtle bg-surface-1">
|
|
164
|
-
<Button
|
|
165
|
-
variant="primary"
|
|
166
|
-
size="lg"
|
|
167
|
-
className="w-full gap-2"
|
|
168
|
-
onClick={handleLaunch}
|
|
169
|
-
disabled={launching || (needsKey && !apiKey.trim())}
|
|
170
|
-
>
|
|
171
|
-
{launching ? (
|
|
172
|
-
<>
|
|
173
|
-
<Loader2 size={14} className="animate-spin" />
|
|
174
|
-
Launching...
|
|
175
|
-
</>
|
|
176
|
-
) : (
|
|
177
|
-
<>
|
|
178
|
-
<Rocket size={14} />
|
|
179
|
-
Launch Team
|
|
180
|
-
</>
|
|
181
|
-
)}
|
|
182
|
-
</Button>
|
|
183
|
-
</div>
|
|
184
|
-
</SheetContent>
|
|
185
|
-
</Sheet>
|
|
186
|
-
);
|
|
187
|
-
}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
3
|
-
import { ScrollArea } from '../components/ui/scroll-area';
|
|
4
|
-
import { Badge } from '../components/ui/badge';
|
|
5
|
-
import { useGrooveStore } from '../stores/groove';
|
|
6
|
-
import { api } from '../lib/api';
|
|
7
|
-
import { ToyCard, ToyCardSkeleton } from '../components/toys/toy-card';
|
|
8
|
-
import { ToyLauncher } from '../components/toys/toy-launcher';
|
|
9
|
-
import { ToyCreator } from '../components/toys/toy-creator';
|
|
10
|
-
import { Gamepad2, Search, Rocket, Plus } from 'lucide-react';
|
|
11
|
-
|
|
12
|
-
const CATEGORIES = ['All', 'Space', 'Weather', 'Finance', 'Fun', 'Maps', 'Data'];
|
|
13
|
-
|
|
14
|
-
export default function ToysView() {
|
|
15
|
-
const [toys, setToys] = useState([]);
|
|
16
|
-
const [loading, setLoading] = useState(true);
|
|
17
|
-
const [search, setSearch] = useState('');
|
|
18
|
-
const [category, setCategory] = useState('All');
|
|
19
|
-
const [selectedToy, setSelectedToy] = useState(null);
|
|
20
|
-
const [creatorOpen, setCreatorOpen] = useState(false);
|
|
21
|
-
const teams = useGrooveStore((s) => s.teams);
|
|
22
|
-
|
|
23
|
-
const fetchToys = useCallback(() => {
|
|
24
|
-
setLoading(true);
|
|
25
|
-
api.get('/toys')
|
|
26
|
-
.then((d) => setToys(d.toys || d.items || (Array.isArray(d) ? d : [])))
|
|
27
|
-
.catch(() => setToys([]))
|
|
28
|
-
.finally(() => setLoading(false));
|
|
29
|
-
}, []);
|
|
30
|
-
|
|
31
|
-
useEffect(() => { fetchToys(); }, [fetchToys]);
|
|
32
|
-
|
|
33
|
-
const searchLower = search.toLowerCase();
|
|
34
|
-
const filtered = toys.filter((t) => {
|
|
35
|
-
if (category !== 'All' && t.category?.toLowerCase() !== category.toLowerCase()) return false;
|
|
36
|
-
if (searchLower && !t.name?.toLowerCase().includes(searchLower) && !t.description?.toLowerCase().includes(searchLower)) return false;
|
|
37
|
-
return true;
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const recentToyTeams = (teams || []).filter((t) => (t.name || '').startsWith('Toy:'));
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div className="flex flex-col h-full">
|
|
44
|
-
{/* Header */}
|
|
45
|
-
<div className="flex-shrink-0 px-5 pt-5 pb-4">
|
|
46
|
-
<div className="flex items-center gap-2.5 mb-1">
|
|
47
|
-
<Gamepad2 size={18} className="text-accent" />
|
|
48
|
-
<h1 className="text-lg font-bold text-text-0 font-sans">Toys</h1>
|
|
49
|
-
<div className="flex-1" />
|
|
50
|
-
<button
|
|
51
|
-
onClick={() => setCreatorOpen(true)}
|
|
52
|
-
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold font-sans rounded-full cursor-pointer select-none transition-colors bg-accent/15 text-accent border border-accent/25 hover:bg-accent/25"
|
|
53
|
-
>
|
|
54
|
-
<Plus size={13} />
|
|
55
|
-
New
|
|
56
|
-
</button>
|
|
57
|
-
</div>
|
|
58
|
-
<p className="text-xs text-text-3 font-sans">Plug in an API and start building</p>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
{/* Search + category bar */}
|
|
62
|
-
<div className="flex-shrink-0 px-5 pb-4 space-y-3">
|
|
63
|
-
{/* Search */}
|
|
64
|
-
<div className="relative w-full max-w-sm">
|
|
65
|
-
<Search size={13} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-text-4" />
|
|
66
|
-
<input
|
|
67
|
-
type="text"
|
|
68
|
-
value={search}
|
|
69
|
-
onChange={(e) => setSearch(e.target.value)}
|
|
70
|
-
placeholder="Search toys..."
|
|
71
|
-
className="w-full bg-surface-1 border border-border-subtle rounded pl-8 pr-3 py-1.5 text-xs text-text-0 font-sans placeholder:text-text-4 focus:outline-none focus:border-accent/50"
|
|
72
|
-
/>
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
{/* Categories */}
|
|
76
|
-
<div className="flex items-center gap-1.5 flex-wrap">
|
|
77
|
-
{CATEGORIES.map((cat) => (
|
|
78
|
-
<button
|
|
79
|
-
key={cat}
|
|
80
|
-
onClick={() => setCategory(cat)}
|
|
81
|
-
className={`px-3 py-1.5 text-xs font-semibold font-sans rounded-full cursor-pointer select-none transition-colors ${
|
|
82
|
-
category === cat
|
|
83
|
-
? 'bg-accent/15 text-accent border border-accent/25'
|
|
84
|
-
: 'text-text-3 hover:text-text-1 border border-transparent hover:border-border-subtle'
|
|
85
|
-
}`}
|
|
86
|
-
>
|
|
87
|
-
{cat}
|
|
88
|
-
</button>
|
|
89
|
-
))}
|
|
90
|
-
<div className="flex-1" />
|
|
91
|
-
<span className="text-2xs text-text-4 font-mono">{filtered.length}</span>
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
|
|
95
|
-
{/* Content */}
|
|
96
|
-
<ScrollArea className="flex-1 min-h-0">
|
|
97
|
-
<div className="px-5 pt-1 pb-5">
|
|
98
|
-
{/* Grid */}
|
|
99
|
-
<div className="grid gap-3 grid-cols-[repeat(auto-fill,minmax(260px,1fr))]">
|
|
100
|
-
{loading
|
|
101
|
-
? Array.from({ length: 6 }).map((_, i) => <ToyCardSkeleton key={i} />)
|
|
102
|
-
: <>
|
|
103
|
-
{filtered.map((t) => <ToyCard key={t.id} toy={t} onClick={setSelectedToy} />)}
|
|
104
|
-
<button
|
|
105
|
-
onClick={() => setCreatorOpen(true)}
|
|
106
|
-
className="flex flex-col items-center justify-center gap-2 p-4 rounded-xl border-2 border-dashed border-border-subtle text-text-4 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-colors cursor-pointer min-h-[140px]"
|
|
107
|
-
>
|
|
108
|
-
<Plus size={20} />
|
|
109
|
-
<span className="text-xs font-semibold font-sans">Add API</span>
|
|
110
|
-
</button>
|
|
111
|
-
</>
|
|
112
|
-
}
|
|
113
|
-
</div>
|
|
114
|
-
|
|
115
|
-
{!loading && filtered.length === 0 && (
|
|
116
|
-
<div className="text-center py-16 text-text-4 font-sans text-sm">
|
|
117
|
-
No toys found. Try a different search or category.
|
|
118
|
-
</div>
|
|
119
|
-
)}
|
|
120
|
-
|
|
121
|
-
{/* Recent Toys */}
|
|
122
|
-
{recentToyTeams.length > 0 && (
|
|
123
|
-
<div className="mt-8">
|
|
124
|
-
<h3 className="text-xs font-semibold text-text-2 font-sans uppercase tracking-wider mb-3 flex items-center gap-1.5">
|
|
125
|
-
<Rocket size={12} />
|
|
126
|
-
Recent Toys ({recentToyTeams.length})
|
|
127
|
-
</h3>
|
|
128
|
-
<div className="space-y-1.5">
|
|
129
|
-
{recentToyTeams.map((team) => (
|
|
130
|
-
<div key={team.id} className="flex items-center gap-3 px-3 py-2.5 rounded-md bg-surface-1 border border-border-subtle">
|
|
131
|
-
<div className="w-8 h-8 rounded-md bg-accent/10 flex items-center justify-center flex-shrink-0">
|
|
132
|
-
<Gamepad2 size={14} className="text-accent" />
|
|
133
|
-
</div>
|
|
134
|
-
<div className="flex-1 min-w-0">
|
|
135
|
-
<div className="text-xs font-semibold text-text-0 font-sans truncate">{team.name}</div>
|
|
136
|
-
<div className="text-2xs text-text-4 font-sans">{team.agents?.length || 0} agents</div>
|
|
137
|
-
</div>
|
|
138
|
-
<Badge variant="accent">Active</Badge>
|
|
139
|
-
</div>
|
|
140
|
-
))}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
)}
|
|
144
|
-
</div>
|
|
145
|
-
</ScrollArea>
|
|
146
|
-
|
|
147
|
-
{/* Launcher sheet */}
|
|
148
|
-
<ToyLauncher
|
|
149
|
-
toy={selectedToy}
|
|
150
|
-
open={!!selectedToy}
|
|
151
|
-
onClose={() => setSelectedToy(null)}
|
|
152
|
-
/>
|
|
153
|
-
|
|
154
|
-
{/* Creator sheet */}
|
|
155
|
-
<ToyCreator
|
|
156
|
-
open={creatorOpen}
|
|
157
|
-
onClose={() => setCreatorOpen(false)}
|
|
158
|
-
onCreated={() => fetchToys()}
|
|
159
|
-
/>
|
|
160
|
-
</div>
|
|
161
|
-
);
|
|
162
|
-
}
|