Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions frontend/src/components/agents/AgentsHub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ const AgentsHub = ({ currentPodId: propPodId = null }) => {
const params = new URLSearchParams(location.search);
return params.get('tab') || '';
}, [location.search]);
const queryInstallable = useMemo(() => {
const params = new URLSearchParams(location.search);
return params.get('installable') || '';
}, [location.search]);
const [selectedPodId, setSelectedPodId] = useState(propPodId || queryPodId);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Expand Down Expand Up @@ -258,6 +262,7 @@ const AgentsHub = ({ currentPodId: propPodId = null }) => {
const runtimeLogsInputRef = useRef(null);
const runtimeLogsScrollRef = useRef({ top: 0, atBottom: true });
const deepLinkHandledRef = useRef('');
const installableDeepLinkHandledRef = useRef('');
const [userTokenValue, setUserTokenValue] = useState('');
const [userTokenScopes, setUserTokenScopes] = useState([]);
const [userTokenMeta, setUserTokenMeta] = useState({
Expand Down Expand Up @@ -478,6 +483,25 @@ const AgentsHub = ({ currentPodId: propPodId = null }) => {
}
}, [queryAgentName, queryInstanceId, queryView, selectedPodId, installedAgents]);

// Deep-link from the marketplace detail page: /v2/agents/browse?installable=<id>
// opens the install dialog pre-targeted to that listing. The marketplace keys
// on installableId; our catalog (/api/registry/agents) keys on name — match on
// either (plus id/displayName) and no-op if the catalog doesn't carry it, so a
// miss just leaves the user on the browse list (no regression).
useEffect(() => {
if (!queryInstallable || !agents.length) return;
if (installableDeepLinkHandledRef.current === queryInstallable) return;
const target = String(queryInstallable).trim().toLowerCase();
const matched = agents.find((a) => (
[a.installableId, a.name, a.id, a._id, a.displayName]
.some((v) => String(v || '').trim().toLowerCase() === target)
));
if (!matched) return;
installableDeepLinkHandledRef.current = queryInstallable;
setActiveTab(TAB_DISCOVER);
openInstallDialog(matched);
}, [queryInstallable, agents]);

const fetchAgents = async () => {
setLoading(true);
setError(null);
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/components/apps/AppCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { SvgIconComponent } from '@mui/icons-material';
export interface AppCardApp {
id?: string;
_id?: string;
installableId?: string;
name?: string;
displayName?: string;
description?: string;
Expand All @@ -50,6 +51,7 @@ interface AppCardProps {
installed?: boolean;
onInstall?: (app: AppCardApp) => void;
onRemove?: (app: AppCardApp) => void;
onViewDetail?: (app: AppCardApp) => void;
loading?: boolean;
showScopes?: boolean;
}
Expand All @@ -73,6 +75,7 @@ const AppCard: React.FC<AppCardProps> = ({
installed = false,
onInstall,
onRemove,
onViewDetail,
loading = false,
showScopes = false,
}) => {
Expand Down Expand Up @@ -143,7 +146,13 @@ const AppCard: React.FC<AppCardProps> = ({
textTransform: 'capitalize',
}}
/>
<CardContent sx={{ flex: 1 }}>
<CardContent
sx={{ flex: 1, ...(onViewDetail ? { cursor: 'pointer' } : {}) }}
onClick={onViewDetail ? () => onViewDetail(app) : undefined}
role={onViewDetail ? 'button' : undefined}
tabIndex={onViewDetail ? 0 : undefined}
onKeyDown={onViewDetail ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onViewDetail(app); } } : undefined}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
<Avatar
src={logo || undefined}
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/apps/AppsMarketplacePage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-nocheck
import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import AppsMarketplacePage from './AppsMarketplacePage';

const axios = require('axios');
Expand Down Expand Up @@ -104,7 +105,7 @@ describe('AppsMarketplacePage', () => {
});

it('renders official marketplace listings', async () => {
render(<AppsMarketplacePage />);
render(<MemoryRouter><AppsMarketplacePage /></MemoryRouter>);

expect(await screen.findByText('Official Marketplace')).toBeInTheDocument();
expect(await screen.findByText('Discord')).toBeInTheDocument();
Expand All @@ -115,7 +116,7 @@ describe('AppsMarketplacePage', () => {
});

it('renders installable browse results and installs via registry', async () => {
render(<AppsMarketplacePage />);
render(<MemoryRouter><AppsMarketplacePage /></MemoryRouter>);

expect((await screen.findAllByText('Community Agent')).length).toBeGreaterThan(0);
expect(screen.getAllByText('@sam/community-agent').length).toBeGreaterThan(0);
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/components/apps/AppsMarketplacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Box,
Container,
Expand Down Expand Up @@ -161,6 +162,14 @@ const toInstalledLegacyApp = (app: any): App => ({

const AppsMarketplacePage: React.FC = () => {
const v2Embedded = useV2Embedded();
const navigate = useNavigate();

// Open the v2-native manifest detail page. Only wired in the v2 shell —
// the detail route lives at /v2/marketplace/:id and has no v1 equivalent.
const handleViewDetail = (app: { installableId?: string; id?: string }) => {
const id = String(app?.installableId || app?.id || '');
if (id) navigate(`/v2/marketplace/${encodeURIComponent(id)}`);
};
const theme = useTheme();
const [apps, setApps] = useState<App[]>([]);
const [featured, setFeatured] = useState<App[]>([]);
Expand Down Expand Up @@ -687,6 +696,7 @@ const AppsMarketplacePage: React.FC = () => {
installed={isInstalled(app.id)}
onInstall={handleInstall}
onRemove={handleRemove}
onViewDetail={v2Embedded ? handleViewDetail : undefined}
/>
</Box>
))}
Expand Down Expand Up @@ -806,6 +816,7 @@ const AppsMarketplacePage: React.FC = () => {
installed={isInstalled(app.id)}
onInstall={handleInstall}
onRemove={handleRemove}
onViewDetail={v2Embedded ? handleViewDetail : undefined}
/>
</Grid>
))}
Expand Down
Loading