groove-dev 0.15.0 → 0.15.1
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/daemon/src/api.js +10 -0
- package/node_modules/@groove-dev/daemon/src/skills.js +20 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-8Kqi_LVo.js +74 -0
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/src/views/SkillsMarketplace.jsx +112 -1
- package/package.json +1 -1
- package/packages/daemon/src/api.js +10 -0
- package/packages/daemon/src/skills.js +20 -0
- package/packages/gui/dist/assets/index-8Kqi_LVo.js +74 -0
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/src/views/SkillsMarketplace.jsx +112 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-CNCSwHwH.js +0 -74
- package/packages/gui/dist/assets/index-CNCSwHwH.js +0 -74
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>GROOVE</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-8Kqi_LVo.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-Gfb8Zxy9.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -89,10 +89,55 @@ function sortSkills(skills, sortBy) {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// ── Interactive Star Rating ──────────────────────────────────────────
|
|
93
|
+
function StarRating({ current, onRate, disabled }) {
|
|
94
|
+
const [hover, setHover] = useState(0);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div style={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
|
98
|
+
{[1, 2, 3, 4, 5].map((star) => {
|
|
99
|
+
const filled = hover > 0 ? star <= hover : star <= (current || 0);
|
|
100
|
+
return (
|
|
101
|
+
<span
|
|
102
|
+
key={star}
|
|
103
|
+
onClick={() => !disabled && onRate(star)}
|
|
104
|
+
onMouseEnter={() => !disabled && setHover(star)}
|
|
105
|
+
onMouseLeave={() => setHover(0)}
|
|
106
|
+
style={{
|
|
107
|
+
fontSize: 18,
|
|
108
|
+
cursor: disabled ? 'default' : 'pointer',
|
|
109
|
+
color: filled ? 'var(--amber)' : 'var(--bg-active)',
|
|
110
|
+
transition: 'color 0.1s, transform 0.1s',
|
|
111
|
+
transform: hover === star ? 'scale(1.2)' : 'none',
|
|
112
|
+
userSelect: 'none',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
{'\u2605'}
|
|
116
|
+
</span>
|
|
117
|
+
);
|
|
118
|
+
})}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
92
123
|
// ── Skill Detail Modal ──────────────────────────────────────────────
|
|
93
|
-
function SkillDetailModal({ skill, content, installing, onInstall, onUninstall, onClose }) {
|
|
124
|
+
function SkillDetailModal({ skill, content, installing, onInstall, onUninstall, onRate, onClose }) {
|
|
125
|
+
const [userRating, setUserRating] = useState(0);
|
|
126
|
+
const [ratingSubmitted, setRatingSubmitted] = useState(false);
|
|
127
|
+
const [submittingRating, setSubmittingRating] = useState(false);
|
|
128
|
+
|
|
94
129
|
if (!skill) return null;
|
|
95
130
|
|
|
131
|
+
async function handleRate(stars) {
|
|
132
|
+
setSubmittingRating(true);
|
|
133
|
+
try {
|
|
134
|
+
await onRate(skill.id, stars);
|
|
135
|
+
setUserRating(stars);
|
|
136
|
+
setRatingSubmitted(true);
|
|
137
|
+
} catch { /* ignore */ }
|
|
138
|
+
setSubmittingRating(false);
|
|
139
|
+
}
|
|
140
|
+
|
|
96
141
|
return (
|
|
97
142
|
<div style={modal.overlay} onClick={onClose}>
|
|
98
143
|
<div style={modal.container} onClick={(e) => e.stopPropagation()}>
|
|
@@ -162,6 +207,31 @@ function SkillDetailModal({ skill, content, installing, onInstall, onUninstall,
|
|
|
162
207
|
{skill.price === 0 && !skill.installed && <span style={modal.freeLabel}>Free</span>}
|
|
163
208
|
</div>
|
|
164
209
|
|
|
210
|
+
{/* Rating */}
|
|
211
|
+
<div style={modal.section}>
|
|
212
|
+
<div style={modal.sectionTitle}>Rating</div>
|
|
213
|
+
<div style={modal.ratingRow}>
|
|
214
|
+
<div style={modal.ratingLeft}>
|
|
215
|
+
<div style={modal.ratingBig}>{skill.rating || '-'}</div>
|
|
216
|
+
<div style={{ color: 'var(--amber)', fontSize: 13 }}>{renderStars(skill.rating)}</div>
|
|
217
|
+
<div style={modal.ratingCount}>{skill.ratingCount || 0} ratings</div>
|
|
218
|
+
</div>
|
|
219
|
+
<div style={modal.ratingRight}>
|
|
220
|
+
<div style={modal.rateLabel}>
|
|
221
|
+
{ratingSubmitted ? 'Thanks for rating!' : 'Rate this skill'}
|
|
222
|
+
</div>
|
|
223
|
+
<StarRating
|
|
224
|
+
current={userRating}
|
|
225
|
+
onRate={handleRate}
|
|
226
|
+
disabled={submittingRating || ratingSubmitted}
|
|
227
|
+
/>
|
|
228
|
+
{submittingRating && (
|
|
229
|
+
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 4 }}>Submitting...</div>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
165
235
|
{/* Description */}
|
|
166
236
|
<div style={modal.section}>
|
|
167
237
|
<div style={modal.sectionTitle}>About</div>
|
|
@@ -413,6 +483,23 @@ export default function SkillsMarketplace() {
|
|
|
413
483
|
setInstalling(null);
|
|
414
484
|
}
|
|
415
485
|
|
|
486
|
+
async function handleRate(id, rating) {
|
|
487
|
+
const res = await fetch(`/api/skills/${id}/rate`, {
|
|
488
|
+
method: 'POST',
|
|
489
|
+
headers: { 'Content-Type': 'application/json' },
|
|
490
|
+
body: JSON.stringify({ rating }),
|
|
491
|
+
});
|
|
492
|
+
if (!res.ok) throw new Error('Rating failed');
|
|
493
|
+
const data = await res.json();
|
|
494
|
+
// Update the skill in local state with new rating
|
|
495
|
+
setSkills((prev) => prev.map((s) =>
|
|
496
|
+
s.id === id ? { ...s, rating: data.rating, ratingCount: data.rating_count ?? data.ratingCount } : s
|
|
497
|
+
));
|
|
498
|
+
if (selectedSkill?.id === id) {
|
|
499
|
+
setSelectedSkill((prev) => prev ? { ...prev, rating: data.rating, ratingCount: data.rating_count ?? data.ratingCount } : null);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
416
503
|
async function handleSelect(skill) {
|
|
417
504
|
setSelectedSkill(skill);
|
|
418
505
|
setSkillContent(null);
|
|
@@ -561,6 +648,7 @@ export default function SkillsMarketplace() {
|
|
|
561
648
|
installing={installing}
|
|
562
649
|
onInstall={handleInstall}
|
|
563
650
|
onUninstall={handleUninstall}
|
|
651
|
+
onRate={handleRate}
|
|
564
652
|
onClose={() => { setSelectedSkill(null); setSkillContent(null); }}
|
|
565
653
|
/>
|
|
566
654
|
</div>
|
|
@@ -876,6 +964,29 @@ const modal = {
|
|
|
876
964
|
section: {
|
|
877
965
|
marginBottom: 16,
|
|
878
966
|
},
|
|
967
|
+
ratingRow: {
|
|
968
|
+
display: 'flex', gap: 20, alignItems: 'center',
|
|
969
|
+
padding: '12px 14px',
|
|
970
|
+
background: 'var(--bg-surface)', border: '1px solid var(--border)',
|
|
971
|
+
borderRadius: 6,
|
|
972
|
+
},
|
|
973
|
+
ratingLeft: {
|
|
974
|
+
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2,
|
|
975
|
+
minWidth: 70,
|
|
976
|
+
},
|
|
977
|
+
ratingBig: {
|
|
978
|
+
fontSize: 24, fontWeight: 700, color: 'var(--text-bright)',
|
|
979
|
+
lineHeight: 1,
|
|
980
|
+
},
|
|
981
|
+
ratingCount: {
|
|
982
|
+
fontSize: 9, color: 'var(--text-muted)', marginTop: 2,
|
|
983
|
+
},
|
|
984
|
+
ratingRight: {
|
|
985
|
+
flex: 1,
|
|
986
|
+
},
|
|
987
|
+
rateLabel: {
|
|
988
|
+
fontSize: 11, color: 'var(--text-dim)', marginBottom: 6,
|
|
989
|
+
},
|
|
879
990
|
sectionTitle: {
|
|
880
991
|
fontSize: 10, fontWeight: 600, color: 'var(--text-muted)',
|
|
881
992
|
textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 8,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Open-source agent orchestration layer for AI coding tools. GUI dashboard, multi-agent coordination, zero cold-start (Journalist), infinite sessions (adaptive context rotation), AI Project Manager, Quick Launch. Works with Claude Code, Codex, Gemini CLI, Ollama.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -400,6 +400,16 @@ export function createApi(app, daemon) {
|
|
|
400
400
|
}
|
|
401
401
|
});
|
|
402
402
|
|
|
403
|
+
app.post('/api/skills/:id/rate', async (req, res) => {
|
|
404
|
+
try {
|
|
405
|
+
const rating = parseInt(req.body?.rating, 10);
|
|
406
|
+
const result = await daemon.skills.rate(req.params.id, rating);
|
|
407
|
+
res.json(result);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
res.status(400).json({ error: err.message });
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
403
413
|
app.get('/api/skills/:id/content', (req, res) => {
|
|
404
414
|
const content = daemon.skills.getContent(req.params.id);
|
|
405
415
|
if (!content) return res.status(404).json({ error: 'Skill not installed' });
|
|
@@ -193,6 +193,26 @@ export class SkillStore {
|
|
|
193
193
|
return { id: skillId, installed: false };
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Rate a skill. Proxies to the skills server.
|
|
198
|
+
*/
|
|
199
|
+
async rate(skillId, rating) {
|
|
200
|
+
if (!Number.isInteger(rating) || rating < 1 || rating > 5) {
|
|
201
|
+
throw new Error('Rating must be an integer from 1 to 5');
|
|
202
|
+
}
|
|
203
|
+
const res = await fetch(`${SKILLS_API}/skills/${skillId}/rate`, {
|
|
204
|
+
method: 'POST',
|
|
205
|
+
headers: { 'Content-Type': 'application/json' },
|
|
206
|
+
body: JSON.stringify({ rating }),
|
|
207
|
+
signal: AbortSignal.timeout(10000),
|
|
208
|
+
});
|
|
209
|
+
if (!res.ok) {
|
|
210
|
+
const data = await res.json().catch(() => ({}));
|
|
211
|
+
throw new Error(data.error || 'Rating failed');
|
|
212
|
+
}
|
|
213
|
+
return res.json();
|
|
214
|
+
}
|
|
215
|
+
|
|
196
216
|
/**
|
|
197
217
|
* Get the full content of an installed skill.
|
|
198
218
|
*/
|