groove-dev 0.27.14 → 0.27.17
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/README.md +37 -1
- package/developerID_application.cer +0 -0
- package/node_modules/@groove-dev/daemon/src/api.js +587 -68
- package/node_modules/@groove-dev/daemon/src/classifier.js +24 -0
- package/node_modules/@groove-dev/daemon/src/credentials.js +12 -2
- package/node_modules/@groove-dev/daemon/src/federation/ambassador.js +204 -0
- package/node_modules/@groove-dev/daemon/src/federation/connection.js +359 -0
- package/node_modules/@groove-dev/daemon/src/federation/contracts.js +112 -0
- package/node_modules/@groove-dev/daemon/src/federation/whitelist.js +190 -0
- package/node_modules/@groove-dev/daemon/src/federation.js +166 -7
- package/node_modules/@groove-dev/daemon/src/index.js +172 -19
- package/node_modules/@groove-dev/daemon/src/introducer.js +52 -7
- package/node_modules/@groove-dev/daemon/src/journalist.js +46 -1
- package/node_modules/@groove-dev/daemon/src/memory.js +36 -16
- package/node_modules/@groove-dev/daemon/src/process.js +140 -23
- package/node_modules/@groove-dev/daemon/src/providers/base.js +1 -0
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +1 -0
- package/node_modules/@groove-dev/daemon/src/providers/codex.js +124 -28
- package/node_modules/@groove-dev/daemon/src/providers/gemini.js +104 -17
- package/node_modules/@groove-dev/daemon/src/providers/index.js +17 -0
- package/node_modules/@groove-dev/daemon/src/registry.js +10 -1
- package/node_modules/@groove-dev/daemon/src/rotator.js +93 -30
- package/node_modules/@groove-dev/daemon/src/skills.js +33 -3
- package/node_modules/@groove-dev/daemon/src/terminal-pty.js +9 -1
- package/node_modules/@groove-dev/daemon/src/tool-executor.js +11 -5
- package/node_modules/@groove-dev/daemon/src/toys.js +69 -0
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +24 -5
- package/node_modules/@groove-dev/daemon/templates/toys-catalog.json +242 -0
- package/node_modules/@groove-dev/daemon/test/classifier.test.js +98 -0
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +72 -1
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +117 -0
- package/node_modules/@groove-dev/daemon/test/memory.test.js +37 -1
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +183 -4
- package/node_modules/@groove-dev/gui/dist/assets/index-BglPgjlu.js +8607 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CGcwmmJv.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +3 -2
- package/node_modules/@groove-dev/gui/index.html +1 -0
- package/node_modules/@groove-dev/gui/src/app.css +7 -0
- package/node_modules/@groove-dev/gui/src/app.jsx +37 -10
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +21 -31
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +11 -6
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +42 -1
- package/node_modules/@groove-dev/gui/src/components/editor/breadcrumbs.jsx +30 -0
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +33 -2
- package/node_modules/@groove-dev/gui/src/components/editor/editor-status-bar.jsx +26 -0
- package/node_modules/@groove-dev/gui/src/components/editor/editor-tabs.jsx +113 -34
- package/node_modules/@groove-dev/gui/src/components/editor/goto-line.jsx +35 -0
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +12 -6
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +13 -3
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +0 -1
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +165 -47
- package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +6 -2
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +10 -9
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +9 -1
- package/node_modules/@groove-dev/gui/src/components/onboarding/provider-card.jsx +134 -0
- package/node_modules/@groove-dev/gui/src/components/onboarding/setup-wizard.jsx +819 -0
- package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +12 -5
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +15 -8
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-modal.jsx +151 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-activity.jsx +98 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-panel.jsx +290 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-peers.jsx +126 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-wizard.jsx +293 -0
- package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +110 -67
- package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/settings/server-detail.jsx +310 -0
- package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +4 -1
- package/node_modules/@groove-dev/gui/src/components/settings/server-list.jsx +59 -0
- package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +549 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +78 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +144 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +187 -0
- package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/lib/electron.js +15 -0
- package/node_modules/@groove-dev/gui/src/lib/format.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +373 -58
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +148 -42
- package/node_modules/@groove-dev/gui/src/views/editor.jsx +92 -2
- package/node_modules/@groove-dev/gui/src/views/federation.jsx +37 -0
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +2 -42
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +32 -132
- package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +327 -0
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/views/toys.jsx +162 -0
- package/package.json +1 -1
- package/packages/daemon/src/api.js +587 -68
- package/packages/daemon/src/classifier.js +24 -0
- package/packages/daemon/src/credentials.js +12 -2
- package/packages/daemon/src/federation/ambassador.js +204 -0
- package/packages/daemon/src/federation/connection.js +359 -0
- package/packages/daemon/src/federation/contracts.js +112 -0
- package/packages/daemon/src/federation/whitelist.js +190 -0
- package/packages/daemon/src/federation.js +166 -7
- package/packages/daemon/src/index.js +172 -19
- package/packages/daemon/src/introducer.js +52 -7
- package/packages/daemon/src/journalist.js +46 -1
- package/packages/daemon/src/memory.js +36 -16
- package/packages/daemon/src/process.js +140 -23
- package/packages/daemon/src/providers/base.js +1 -0
- package/packages/daemon/src/providers/claude-code.js +1 -0
- package/packages/daemon/src/providers/codex.js +124 -28
- package/packages/daemon/src/providers/gemini.js +104 -17
- package/packages/daemon/src/providers/index.js +17 -0
- package/packages/daemon/src/registry.js +10 -1
- package/packages/daemon/src/rotator.js +93 -30
- package/packages/daemon/src/skills.js +33 -3
- package/packages/daemon/src/terminal-pty.js +9 -1
- package/packages/daemon/src/tool-executor.js +11 -5
- package/packages/daemon/src/toys.js +69 -0
- package/packages/daemon/src/tunnel-manager.js +24 -5
- package/packages/daemon/templates/toys-catalog.json +242 -0
- package/packages/gui/dist/assets/index-BglPgjlu.js +8607 -0
- package/packages/gui/dist/assets/index-CGcwmmJv.css +1 -0
- package/packages/gui/dist/index.html +3 -2
- package/packages/gui/index.html +1 -0
- package/packages/gui/src/app.css +7 -0
- package/packages/gui/src/app.jsx +37 -10
- package/packages/gui/src/components/agents/agent-chat.jsx +21 -31
- package/packages/gui/src/components/agents/agent-config.jsx +11 -6
- package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
- package/packages/gui/src/components/agents/spawn-wizard.jsx +42 -1
- package/packages/gui/src/components/editor/breadcrumbs.jsx +30 -0
- package/packages/gui/src/components/editor/code-editor.jsx +33 -2
- package/packages/gui/src/components/editor/editor-status-bar.jsx +26 -0
- package/packages/gui/src/components/editor/editor-tabs.jsx +113 -34
- package/packages/gui/src/components/editor/goto-line.jsx +35 -0
- package/packages/gui/src/components/editor/terminal.jsx +12 -6
- package/packages/gui/src/components/layout/activity-bar.jsx +13 -3
- package/packages/gui/src/components/layout/app-shell.jsx +0 -1
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +165 -47
- package/packages/gui/src/components/layout/command-palette.jsx +6 -2
- package/packages/gui/src/components/layout/terminal-panel.jsx +10 -9
- package/packages/gui/src/components/marketplace/repo-import.jsx +9 -1
- package/packages/gui/src/components/onboarding/provider-card.jsx +134 -0
- package/packages/gui/src/components/onboarding/setup-wizard.jsx +819 -0
- package/packages/gui/src/components/pro/pro-gate.jsx +12 -5
- package/packages/gui/src/components/pro/upgrade-card.jsx +15 -8
- package/packages/gui/src/components/pro/upgrade-modal.jsx +151 -0
- package/packages/gui/src/components/settings/federation-activity.jsx +98 -0
- package/packages/gui/src/components/settings/federation-panel.jsx +290 -0
- package/packages/gui/src/components/settings/federation-peers.jsx +126 -0
- package/packages/gui/src/components/settings/federation-wizard.jsx +293 -0
- package/packages/gui/src/components/settings/quick-connect.jsx +110 -67
- package/packages/gui/src/components/settings/remote-server-card.jsx +3 -3
- package/packages/gui/src/components/settings/server-detail.jsx +310 -0
- package/packages/gui/src/components/settings/server-dialog.jsx +4 -1
- package/packages/gui/src/components/settings/server-list.jsx +59 -0
- package/packages/gui/src/components/settings/ssh-wizard.jsx +549 -0
- package/packages/gui/src/components/toys/toy-card.jsx +78 -0
- package/packages/gui/src/components/toys/toy-creator.jsx +144 -0
- package/packages/gui/src/components/toys/toy-launcher.jsx +187 -0
- package/packages/gui/src/components/ui/toast.jsx +2 -2
- package/packages/gui/src/lib/electron.js +15 -0
- package/packages/gui/src/lib/format.js +1 -0
- package/packages/gui/src/stores/groove.js +373 -58
- package/packages/gui/src/views/agents.jsx +148 -42
- package/packages/gui/src/views/editor.jsx +92 -2
- package/packages/gui/src/views/federation.jsx +37 -0
- package/packages/gui/src/views/marketplace.jsx +2 -42
- package/packages/gui/src/views/settings.jsx +32 -132
- package/packages/gui/src/views/subscription-panel.jsx +327 -0
- package/packages/gui/src/views/teams.jsx +3 -3
- package/packages/gui/src/views/toys.jsx +162 -0
- package/plans/chat-persistence-refactor.md +154 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +0 -677
- package/packages/gui/dist/assets/index-BE6lYcd7.css +0 -1
- package/packages/gui/dist/assets/index-zdzOLAZM.js +0 -677
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
}
|
|
@@ -52,8 +52,8 @@ function ToastItem({ toast }) {
|
|
|
52
52
|
exit={{ opacity: 0, x: 80, scale: 0.95 }}
|
|
53
53
|
transition={{ duration: 0.2 }}
|
|
54
54
|
className={cn(
|
|
55
|
-
'w-80
|
|
56
|
-
'border-l-
|
|
55
|
+
'w-80 border border-border bg-surface-1 shadow-xl',
|
|
56
|
+
'border-l-2 flex items-center gap-3 px-4 py-3',
|
|
57
57
|
BORDER_COLORS[toast.type],
|
|
58
58
|
)}
|
|
59
59
|
>
|
|
@@ -16,6 +16,21 @@ export function openExternal(url) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export async function selectFolder(options = {}) {
|
|
20
|
+
if (window.groove?.folders?.select) {
|
|
21
|
+
return window.groove.folders.select(options);
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function setProjectDir(dir) {
|
|
27
|
+
if (window.groove?.folders?.setProjectDir) {
|
|
28
|
+
return window.groove.folders.setProjectDir(dir);
|
|
29
|
+
}
|
|
30
|
+
const { api } = await import('./api.js');
|
|
31
|
+
return api.post('/project-dir', { dir });
|
|
32
|
+
}
|
|
33
|
+
|
|
19
34
|
export const electronAuth = {
|
|
20
35
|
login: () => window.groove?.auth?.login(),
|
|
21
36
|
logout: () => window.groove?.auth?.logout(),
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
export function fmtNum(n) {
|
|
5
5
|
if (n == null || isNaN(n)) return '0';
|
|
6
|
+
if (n >= 1_000_000_000) return (n / 1_000_000_000).toFixed(1).replace(/\.0$/, '') + 'B';
|
|
6
7
|
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1).replace(/\.0$/, '') + 'M';
|
|
7
8
|
if (n >= 1_000) return (n / 1_000).toFixed(1).replace(/\.0$/, '') + 'k';
|
|
8
9
|
return Math.round(n).toLocaleString();
|