mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-22 01:24:52 +02:00
[eric] marketplace: plain words everywhere; Install, Open, Installed, Bundles, Everything, Browse, Type, and a review sheet that says Install
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9zwUaHucUgrdxvK8FvjYT
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
e2fd38d17a
commit
3c1b28cffd
@@ -44,7 +44,7 @@ const ImportModal: React.FC<Props> = ({ preflight, open, committing, onConfirm,
|
||||
<>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', px: 3, pt: 2.5, pb: 1 }}>
|
||||
<Typography sx={{ fontSize: '1rem', fontWeight: 700, color: c.text.primary }}>
|
||||
Add {preflight.summary.root.name}?
|
||||
Install {preflight.summary.root.name}?
|
||||
</Typography>
|
||||
<IconButton size="small" onClick={onClose} sx={{ color: c.text.tertiary }}>
|
||||
<CloseIcon sx={{ fontSize: 18 }} />
|
||||
@@ -55,7 +55,7 @@ const ImportModal: React.FC<Props> = ({ preflight, open, committing, onConfirm,
|
||||
{preflight.review && preflight.review.findings.length > 0 && <ReviewFindings review={preflight.review} />}
|
||||
{preflight.conflicts.length > 0 && (
|
||||
<Typography sx={{ fontSize: '0.75rem', color: c.text.muted, mt: 1.5 }}>
|
||||
Some items already exist and will be added as copies.
|
||||
Some of this already exists; copies will be added.
|
||||
</Typography>
|
||||
)}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1, mt: 2 }}>
|
||||
@@ -80,7 +80,7 @@ const ImportModal: React.FC<Props> = ({ preflight, open, committing, onConfirm,
|
||||
boxShadow: 'none',
|
||||
}}
|
||||
>
|
||||
Add to OpenSwarm
|
||||
Install
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -110,7 +110,7 @@ const DirectoryPackagesTab: React.FC<{ onOpenSkill?: (skillId: string) => void }
|
||||
setInstalls(await recordInstall(recordFor(listingId, rootType, rootId, version)));
|
||||
} catch (e: unknown) {
|
||||
setInstalls((prev) => ({ ...prev, [listingId]: { ...recordFor(listingId, rootType, rootId, version), installed_at: Date.now() / 1000 } }));
|
||||
setToast({ message: e instanceof Error ? e.message : "Installed, but the store couldn't remember it.", severity: 'error' });
|
||||
setToast({ message: e instanceof Error ? e.message : 'Installed, but it may show Install again after a restart.', severity: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -121,7 +121,7 @@ const DirectoryPackagesTab: React.FC<{ onOpenSkill?: (skillId: string) => void }
|
||||
await finish(listingId, res.root_type, res.root_id);
|
||||
setConfirm(null);
|
||||
} catch (e: unknown) {
|
||||
setToast({ message: e instanceof Error ? e.message : "We couldn't finish the install.", severity: 'error' });
|
||||
setToast({ message: e instanceof Error ? e.message : "Couldn't finish installing.", severity: 'error' });
|
||||
} finally {
|
||||
setCommitting(false);
|
||||
}
|
||||
@@ -134,7 +134,7 @@ const DirectoryPackagesTab: React.FC<{ onOpenSkill?: (skillId: string) => void }
|
||||
if (marketplaceNeedsConfirm(preflight)) setConfirm({ preflight, listingId });
|
||||
else await commit(preflight, listingId);
|
||||
} catch (e: unknown) {
|
||||
setToast({ message: e instanceof Error ? e.message : "We couldn't download this package.", severity: 'error' });
|
||||
setToast({ message: e instanceof Error ? e.message : "Couldn't download it. Try again.", severity: 'error' });
|
||||
} finally {
|
||||
setInstallingId(null);
|
||||
}
|
||||
@@ -143,7 +143,7 @@ const DirectoryPackagesTab: React.FC<{ onOpenSkill?: (skillId: string) => void }
|
||||
const installBundle = async (bundle: Listing) => {
|
||||
const members = resolveBundleMembers(bundle, listings).filter((m) => m.download_url);
|
||||
if (members.length === 0) {
|
||||
setToast({ message: 'This bundle has no installable packages yet.', severity: 'error' });
|
||||
setToast({ message: 'Nothing in this bundle can be installed yet.', severity: 'error' });
|
||||
return;
|
||||
}
|
||||
// One at a time: each member gets its own review, and a bundle must not be a way to skip one.
|
||||
@@ -177,7 +177,7 @@ const DirectoryPackagesTab: React.FC<{ onOpenSkill?: (skillId: string) => void }
|
||||
if (visible.length === 0 && visibleBundles.length === 0) {
|
||||
return (
|
||||
<Typography sx={{ fontSize: '0.9375rem', color: c.text.tertiary, pt: 6, textAlign: 'center' }}>
|
||||
{error ? error : 'No packages match that search yet.'}
|
||||
{error ? error : 'Nothing matches that search.'}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
@@ -185,7 +185,7 @@ const DirectoryPackagesTab: React.FC<{ onOpenSkill?: (skillId: string) => void }
|
||||
<>
|
||||
{visibleBundles.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
{sectionLabel('Collections')}
|
||||
{sectionLabel('Bundles')}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
{visibleBundles.map((bundle) => (
|
||||
<PackageBundleCard
|
||||
@@ -198,7 +198,7 @@ const DirectoryPackagesTab: React.FC<{ onOpenSkill?: (skillId: string) => void }
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{visible.length > 0 && sectionLabel(visibleBundles.length > 0 ? 'All packages' : '', visible.length)}
|
||||
{visible.length > 0 && sectionLabel(visibleBundles.length > 0 ? 'Everything' : '', visible.length)}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
@@ -227,16 +227,16 @@ const DirectoryPackagesTab: React.FC<{ onOpenSkill?: (skillId: string) => void }
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, gap: 1.75 }}>
|
||||
<DirectoryFilterBar
|
||||
searchPlaceholder="Search packages, tags, authors"
|
||||
searchPlaceholder="Search"
|
||||
query={query}
|
||||
onQuery={setQuery}
|
||||
filterSections={[{ label: 'Kind', options: kindOptions }]}
|
||||
filterSections={[{ label: 'Type', options: kindOptions }]}
|
||||
filterSelected={kinds}
|
||||
onToggleFilter={(value) => setKinds((prev) => (prev.includes(value) ? prev.filter((k) => k !== value) : [...prev, value]))}
|
||||
sortOptions={[
|
||||
{ value: 'newest', label: 'Newest' },
|
||||
{ value: 'name', label: 'Name' },
|
||||
{ value: 'kind', label: 'Kind' },
|
||||
{ value: 'kind', label: 'Type' },
|
||||
]}
|
||||
sortValue={sort}
|
||||
onSort={setSort}
|
||||
@@ -250,7 +250,7 @@ const DirectoryPackagesTab: React.FC<{ onOpenSkill?: (skillId: string) => void }
|
||||
/>
|
||||
{source === 'cache' && (
|
||||
<Typography sx={{ fontSize: '0.8125rem', color: c.text.muted }}>
|
||||
Showing the last catalog we loaded; the marketplace could not be reached just now.
|
||||
Showing the last list we loaded. The store can't be reached right now.
|
||||
</Typography>
|
||||
)}
|
||||
<Box sx={{ flex: 1, minHeight: 0, overflowY: 'auto', pr: 0.5 }}>{body()}</Box>
|
||||
|
||||
@@ -80,7 +80,7 @@ const MarketplaceBody: React.FC = () => {
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, bgcolor: c.bg.surface }}>
|
||||
<Box sx={{ display: 'flex', flex: 1, minHeight: 0, pt: 1.5 }}>
|
||||
<Box sx={{ width: 210, minWidth: 210, px: 2, pt: 0.5, display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
{railRow('packages', 'Packages', <Inventory2OutlinedIcon sx={{ fontSize: 19, color: c.text.secondary }} />)}
|
||||
{railRow('packages', 'Browse', <Inventory2OutlinedIcon sx={{ fontSize: 19, color: c.text.secondary }} />)}
|
||||
{railRow('connectors', 'Connectors', <GridViewOutlinedIcon sx={{ fontSize: 19, color: c.text.secondary }} />)}
|
||||
<Box sx={{ height: '1px', bgcolor: c.border.subtle, mx: 1.5, my: 1 }} />
|
||||
{railRow('my-skills', 'My skills', <FolderSpecialOutlinedIcon sx={{ fontSize: 19, color: c.text.secondary }} />)}
|
||||
|
||||
@@ -13,7 +13,7 @@ interface Props {
|
||||
size?: 'sm' | 'md';
|
||||
}
|
||||
|
||||
// The one action a package has, the way the App Store draws it: Get, a spinner while it lands, then Open. It swallows the click so a card underneath never opens its sheet.
|
||||
// The one action a package has, the way the App Store draws it: Install, a spinner while it lands, then Open. It swallows the click so a card underneath never opens its sheet.
|
||||
export default function InstallPill({ state, disabled, onGet, onOpen, size = 'md' }: Props) {
|
||||
const c = useClaudeTokens();
|
||||
const sm = size === 'sm';
|
||||
@@ -51,7 +51,7 @@ export default function InstallPill({ state, disabled, onGet, onOpen, size = 'md
|
||||
disableElevation
|
||||
sx={{ ...base, bgcolor: c.accent.primary, color: c.text.inverse, '&:hover': { bgcolor: c.accent.hover }, '&.Mui-disabled': { bgcolor: c.accent.primary, color: c.text.inverse, opacity: state === 'installing' ? 1 : 0.5 } }}
|
||||
>
|
||||
{state === 'installing' ? <CircularProgress size={14} thickness={5} sx={{ color: 'inherit' }} /> : 'Get'}
|
||||
{state === 'installing' ? <CircularProgress size={14} thickness={5} sx={{ color: 'inherit' }} /> : 'Install'}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function PackageBundleCard({ bundle, members, onOpen }: Props) {
|
||||
{bundle.title}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '0.75rem', color: c.text.muted, flexShrink: 0 }}>
|
||||
{members.length} {members.length === 1 ? 'package' : 'packages'}
|
||||
{members.length} {members.length === 1 ? 'item' : 'items'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography
|
||||
|
||||
@@ -95,7 +95,7 @@ export default function PackageCard({ listing, state, onOpen, onGet, onOpenInsta
|
||||
minHeight: '2.6em',
|
||||
}}
|
||||
>
|
||||
{listing.description || 'No description provided.'}
|
||||
{listing.description || 'No description yet.'}
|
||||
</Typography>
|
||||
|
||||
<PackageTagRow tags={tags} onTag={onTag} sx={{ mt: 'auto' }} />
|
||||
|
||||
@@ -52,7 +52,7 @@ test('Packages is the default marketplace tab and the old skills store is gone',
|
||||
const dir = path.join(process.cwd(), 'src/app/pages/Directory');
|
||||
const body = fs.readFileSync(path.join(dir, 'MarketplaceBody.tsx'), 'utf8');
|
||||
assert.match(body, /useState<DirectoryTab>\('packages'\)/);
|
||||
assert.match(body, /railRow\('packages', 'Packages'/);
|
||||
assert.match(body, /railRow\('packages', 'Browse'/);
|
||||
assert.doesNotMatch(body, /DirectorySkillsTab/);
|
||||
assert.equal(fs.existsSync(path.join(dir, 'DirectorySkillsTab.tsx')), false);
|
||||
const open = fs.readFileSync(path.join(dir, 'openMarketplace.ts'), 'utf8');
|
||||
|
||||
@@ -67,7 +67,7 @@ export default function PackageBundleDialog({ bundle, members, stateOf, onOpenIn
|
||||
{bundle.title}
|
||||
</Typography>
|
||||
<Typography sx={{ mt: 0.4, fontSize: '0.8125rem', color: c.text.muted }}>
|
||||
{members.length} {members.length === 1 ? 'package' : 'packages'}{bundle.author ? ` · ${bundle.author}` : ''}
|
||||
{members.length} {members.length === 1 ? 'item' : 'items'}{bundle.author ? ` · ${bundle.author}` : ''}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
@@ -77,7 +77,7 @@ export default function PackageBundleDialog({ bundle, members, stateOf, onOpenIn
|
||||
disableElevation
|
||||
sx={{ ...pill, bgcolor: c.accent.primary, color: c.text.inverse, '&:hover': { bgcolor: c.accent.hover }, '&.Mui-disabled': { bgcolor: c.bg.secondary, color: c.text.muted } }}
|
||||
>
|
||||
{installing ? <CircularProgress size={14} thickness={5} sx={{ color: 'inherit' }} /> : allInstalled ? 'Installed' : 'Get all'}
|
||||
{installing ? <CircularProgress size={14} thickness={5} sx={{ color: 'inherit' }} /> : allInstalled ? 'Installed' : 'Install all'}
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
@@ -92,7 +92,7 @@ export default function PackageBundleDialog({ bundle, members, stateOf, onOpenIn
|
||||
Included
|
||||
</Typography>
|
||||
{members.length === 0 ? (
|
||||
<Typography sx={{ color: c.text.tertiary, fontSize: '0.875rem' }}>The packages in this bundle are no longer available.</Typography>
|
||||
<Typography sx={{ color: c.text.tertiary, fontSize: '0.875rem' }}>The items in this bundle are no longer available.</Typography>
|
||||
) : (
|
||||
<Stack spacing={0.75}>
|
||||
{members.map((m) => {
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function stagePackageInstall(listingId: string): Promise<ImportPref
|
||||
body: JSON.stringify({ id: listingId }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
let detail = "We couldn't download this package.";
|
||||
let detail = "Couldn't download it. Try again.";
|
||||
try {
|
||||
const body = (await res.json()) as { detail?: string };
|
||||
if (body.detail) detail = body.detail;
|
||||
|
||||
@@ -54,7 +54,7 @@ export async function recordInstall(rec: Omit<InstallRecord, 'installed_at'>): P
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...rec, installed_at: 0 }),
|
||||
});
|
||||
if (!res.ok) throw new Error("Installed, but the store couldn't remember it.");
|
||||
if (!res.ok) throw new Error('Installed, but it may show Install again after a restart.');
|
||||
const body = (await res.json()) as { installs: Record<string, InstallRecord> };
|
||||
return body.installs || {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user