Skip to content

Commit 5468ff9

Browse files
authored
feat(ui): redo sidebar and homepage (#1610)
1 parent 8dfd3a4 commit 5468ff9

File tree

16 files changed

+171
-119
lines changed

16 files changed

+171
-119
lines changed

apps/agentstack-ui/src/components/Sidebar/AgentsNav.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
import { useMemo } from 'react';
77

88
import { useModal } from '#contexts/Modal/index.tsx';
9+
import { useParamsFromUrl } from '#hooks/useParamsFromUrl.ts';
10+
import { useListAgents } from '#modules/agents/api/queries/useListAgents.ts';
11+
import { ListAgentsOrderBy } from '#modules/agents/api/types.ts';
912
import { ImportAgentsModal } from '#modules/agents/components/import/ImportAgentsModal.tsx';
10-
import { useRecentlyAddedAgents } from '#modules/home/hooks/useRecentlyAddedAgents.ts';
1113
import { useUser } from '#modules/users/api/queries/useUser.ts';
12-
import { isUserAdmin } from '#modules/users/utils.ts';
14+
import { isUserAdminOrDev } from '#modules/users/utils.ts';
1315
import { routes } from '#utils/router.ts';
1416

1517
import { NavGroup } from './NavGroup';
@@ -22,33 +24,35 @@ interface Props {
2224
export function AgentsNav({ className }: Props) {
2325
const { openModal } = useModal();
2426

27+
const { providerId: providerIdUrl } = useParamsFromUrl();
2528
const { data: user } = useUser();
26-
const { data: agents, isLoading } = useRecentlyAddedAgents();
29+
const { data: agents, isLoading } = useListAgents({ orderBy: ListAgentsOrderBy.Name });
2730

28-
const isAdmin = isUserAdmin(user);
31+
const isAdminOrDev = isUserAdminOrDev(user);
2932

3033
const action = useMemo(
3134
() =>
32-
isAdmin
35+
isAdminOrDev
3336
? {
3437
label: 'Add new agent',
3538
onClick: () => openModal((props) => <ImportAgentsModal {...props} />),
3639
}
3740
: undefined,
38-
[isAdmin, openModal],
41+
[isAdminOrDev, openModal],
3942
);
4043

4144
const items = useMemo(
4245
() =>
4346
agents?.map(({ name, provider: { id } }) => ({
4447
label: name,
4548
href: routes.agentRun({ providerId: id }),
49+
isActive: providerIdUrl === id,
4650
})),
47-
[agents],
51+
[agents, providerIdUrl],
4852
);
4953

5054
return (
51-
<NavGroup heading="Agents added by me" className={className} action={action}>
55+
<NavGroup heading="Agents" className={className} action={action}>
5256
<NavList items={items} isLoading={isLoading} skeletonCount={5} noItemsMessage="No agent added" />
5357
</NavGroup>
5458
);

apps/agentstack-ui/src/components/Sidebar/SidebarMainContent.module.scss

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@
1212
}
1313

1414
.agentsNav {
15-
max-block-size: calc(70% - (var(--row-gap) / 2));
15+
max-block-size: calc(50% - (var(--row-gap) / 2));
1616
}
1717

18-
.sessions,
19-
.recentlyUsed {
18+
.sessions {
2019
flex: 1 1 0;
2120
}

apps/agentstack-ui/src/components/Sidebar/SidebarMainContent.tsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,21 @@
55

66
import clsx from 'clsx';
77

8-
import { useParamsFromUrl } from '#hooks/useParamsFromUrl.ts';
98
import { SessionsNav } from '#modules/history/components/SessionsNav.tsx';
109

11-
import { AgentNav } from './AgentNav';
1210
import { AgentsNav } from './AgentsNav';
13-
import { RecentlyUsedAgentsNav } from './RecentlyUsedAgentsNav';
1411
import classes from './SidebarMainContent.module.scss';
1512

1613
interface Props {
1714
className?: string;
1815
}
1916

2017
export function SidebarMainContent({ className }: Props) {
21-
const { providerId } = useParamsFromUrl();
22-
2318
return (
2419
<div className={clsx(classes.root, className)}>
25-
{providerId ? (
26-
<>
27-
<AgentNav providerId={providerId} />
28-
29-
<SessionsNav providerId={providerId} className={classes.sessions} />
30-
</>
31-
) : (
32-
<>
33-
<AgentsNav className={classes.agentsNav} />
20+
<AgentsNav className={classes.agentsNav} />
3421

35-
<RecentlyUsedAgentsNav className={classes.recentlyUsed} />
36-
</>
37-
)}
22+
<SessionsNav className={classes.sessions} />
3823
</div>
3924
);
4025
}

apps/agentstack-ui/src/components/layouts/CommonHeader.tsx

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,31 @@ import { Button } from '@carbon/react';
99

1010
import { AppName } from '#components/AppName/AppName.tsx';
1111
import { AppHeader } from '#components/layouts/AppHeader.tsx';
12-
import { Tooltip } from '#components/Tooltip/Tooltip.tsx';
1312
import { useModal } from '#contexts/Modal/index.tsx';
1413
import { ImportAgentsModal } from '#modules/agents/components/import/ImportAgentsModal.tsx';
1514
import { useUser } from '#modules/users/api/queries/useUser.ts';
16-
import { isUserAdmin } from '#modules/users/utils.ts';
15+
import { isUserAdminOrDev } from '#modules/users/utils.ts';
1716

1817
import classes from './CommonHeader.module.scss';
1918

2019
export function CommonHeader() {
2120
const { openModal } = useModal();
2221
const { data: user } = useUser();
2322

24-
const isAdmin = isUserAdmin(user);
25-
26-
const addNewAgentButton = (
27-
<Button
28-
renderIcon={Add}
29-
size="sm"
30-
disabled={!isAdmin}
31-
onClick={() => openModal((props) => <ImportAgentsModal {...props} />)}
32-
>
33-
Add new agent
34-
</Button>
35-
);
23+
const isAdminOrDev = isUserAdminOrDev(user);
3624

3725
return (
3826
<AppHeader>
3927
<div className={classes.root}>
4028
<AppName />
4129

42-
<div className={classes.right}>
43-
{isAdmin ? (
44-
addNewAgentButton
45-
) : (
46-
<Tooltip content="Adding agents requires elevated permissions." asChild placement="bottom-end">
47-
{addNewAgentButton}
48-
</Tooltip>
49-
)}
50-
</div>
30+
{isAdminOrDev && (
31+
<div className={classes.right}>
32+
<Button renderIcon={Add} size="sm" onClick={() => openModal((props) => <ImportAgentsModal {...props} />)}>
33+
Add new agent
34+
</Button>
35+
</div>
36+
)}
5137
</div>
5238
</AppHeader>
5339
);

apps/agentstack-ui/src/modules/history/components/SessionItem.module.scss

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515

1616
.link {
1717
@include link-mask();
18-
@include line-clamp();
1918
min-inline-size: 0;
2019
flex-grow: 1;
2120
text-decoration: none;
2221
font-size: rem(14px);
2322
line-height: math.div(18, 14);
2423
color: $text-dark;
25-
display: block;
24+
display: flex;
25+
flex-direction: column;
26+
row-gap: $spacing-02;
2627
&::before {
2728
border-radius: $border-radius;
2829
transition: background-color $duration-fast-01 motion(entrance, productive);
@@ -45,6 +46,17 @@
4546
}
4647
}
4748

49+
.heading {
50+
@include line-clamp();
51+
}
52+
53+
.subHeading {
54+
@include line-clamp();
55+
font-size: rem(12px);
56+
line-height: math.div(14, 12);
57+
color: $text-secondary;
58+
}
59+
4860
.options {
4961
flex-shrink: 0;
5062
display: none;
@@ -75,6 +87,6 @@
7587
.skeleton {
7688
&:global(.cds--btn.cds--skeleton) {
7789
inline-size: 100%;
78-
min-block-size: rem(32px);
90+
min-block-size: rem(50px);
7991
}
8092
}

apps/agentstack-ui/src/modules/history/components/SessionItem.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ export interface SessionItem {
1818
contextId: string;
1919
providerId: string;
2020
heading: string;
21+
subHeading?: string;
2122
isActive?: boolean;
2223
}
2324

24-
export function SessionItem({ contextId, providerId, heading, isActive }: SessionItem) {
25+
export function SessionItem({ contextId, providerId, heading, subHeading, isActive }: SessionItem) {
2526
const router = useRouter();
2627
const [optionsOpen, setOptionsOpen] = useState(false);
2728

@@ -41,7 +42,9 @@ export function SessionItem({ contextId, providerId, heading, isActive }: Sessio
4142
})}
4243
>
4344
<Link href={routes.agentRun({ providerId, contextId })} className={classes.link}>
44-
{heading}
45+
<span className={classes.heading}>{heading}</span>
46+
47+
{subHeading && <span className={classes.subHeading}>{subHeading}</span>}
4548
</Link>
4649

4750
<div className={classes.options}>

apps/agentstack-ui/src/modules/history/components/SessionsNav.tsx

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,52 +8,60 @@ import { useMemo } from 'react';
88
import { NavGroup } from '#components/Sidebar/NavGroup.tsx';
99
import { useFetchNextPage } from '#hooks/useFetchNextPage.ts';
1010
import { useParamsFromUrl } from '#hooks/useParamsFromUrl.ts';
11+
import { useListAgents } from '#modules/agents/api/queries/useListAgents.ts';
1112
import { LIST_CONTEXTS_DEFAULT_QUERY } from '#modules/platform-context/api/constants.ts';
1213
import { useListContexts } from '#modules/platform-context/api/queries/useListContexts.ts';
1314
import { isNotNull } from '#utils/helpers.ts';
1415

1516
import { SessionsList } from './SessionsList';
1617

1718
interface Props {
18-
providerId: string;
1919
className?: string;
2020
}
2121

22-
export function SessionsNav({ providerId, className }: Props) {
22+
export function SessionsNav({ className }: Props) {
2323
const { contextId: contextIdUrl } = useParamsFromUrl();
24+
25+
const { data: agents, isLoading: isAgentsLoading } = useListAgents();
2426
const { data, isLoading, isFetching, hasNextPage, fetchNextPage } = useListContexts({
25-
query: {
26-
...LIST_CONTEXTS_DEFAULT_QUERY,
27-
provider_id: providerId,
28-
},
27+
query: LIST_CONTEXTS_DEFAULT_QUERY,
2928
});
3029
const { ref: fetchNextPageRef } = useFetchNextPage({ isFetching, hasNextPage, fetchNextPage });
3130

32-
const items = useMemo(
33-
() =>
34-
data
35-
?.map(({ id: contextId, created_at, metadata }) => {
36-
if (!contextId) {
37-
return null;
38-
}
39-
40-
const heading = (metadata?.title || created_at) ?? contextId;
41-
const isActive = contextIdUrl === contextId;
42-
43-
return {
44-
contextId,
45-
providerId,
46-
heading,
47-
isActive,
48-
};
49-
})
50-
.filter(isNotNull),
51-
[data, contextIdUrl, providerId],
52-
);
31+
const items = useMemo(() => {
32+
if (!agents) {
33+
return undefined;
34+
}
35+
36+
const agentsMap = new Map(agents.map((agent) => [agent.provider.id, agent]));
37+
38+
return data
39+
?.map(({ id: contextId, created_at, metadata, provider_id }) => {
40+
const providerId = provider_id ?? metadata?.provider_id;
41+
const agent = providerId ? agentsMap.get(providerId) : undefined;
42+
43+
if (!providerId || !contextId || !agent) {
44+
return null;
45+
}
46+
47+
const heading = (metadata?.title || created_at) ?? contextId;
48+
const subHeading = agent.name;
49+
const isActive = contextIdUrl === contextId;
50+
51+
return {
52+
contextId,
53+
providerId,
54+
heading,
55+
subHeading,
56+
isActive,
57+
};
58+
})
59+
.filter(isNotNull);
60+
}, [data, agents, contextIdUrl]);
5361

5462
return (
5563
<NavGroup heading="Sessions" className={className}>
56-
<SessionsList items={items} isLoading={isLoading} />
64+
<SessionsList items={items} isLoading={isLoading || isAgentsLoading} />
5765

5866
{hasNextPage && <div ref={fetchNextPageRef} />}
5967
</NavGroup>

apps/agentstack-ui/src/modules/home/components/DiscoverAgentsList.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,13 @@ import { ListAgentsOrderBy } from '#modules/agents/api/types.ts';
1010
import { AgentCardsList } from '#modules/agents/components/cards/AgentCardsList.tsx';
1111
import type { ListProvidersResponse } from '#modules/providers/api/types.ts';
1212

13-
import { USER_NOT_OWNED_AGENTS_LIST_PARAMS } from '../constants';
14-
1513
interface Props {
1614
initialData?: ListProvidersResponse;
1715
}
1816

1917
export function DiscoverAgentsList({ initialData }: Props) {
2018
const { data: agents, isLoading } = useListAgents({
21-
...USER_NOT_OWNED_AGENTS_LIST_PARAMS,
22-
orderBy: ListAgentsOrderBy.CreatedAt,
19+
orderBy: ListAgentsOrderBy.Name,
2320
initialData,
2421
});
2522

apps/agentstack-ui/src/modules/home/components/HomeHeading.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,5 @@ export function HomeHeading() {
1414
config: { appName },
1515
} = useApp();
1616

17-
return (
18-
<h1 className={classes.root}>
19-
Welcome to {appName}.<br />
20-
No&nbsp;agents yet —&nbsp;discover what’s possible.
21-
</h1>
22-
);
17+
return <h1 className={classes.root}>Welcome to {appName}.</h1>;
2318
}

apps/agentstack-ui/src/modules/home/components/HomeView.tsx

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,19 @@ import { Container } from '#components/layouts/Container.tsx';
77
import { MainContent } from '#components/layouts/MainContent.tsx';
88
import { fetchProviders } from '#modules/providers/api/index.ts';
99

10-
import { USER_NOT_OWNED_AGENTS_LIST_PARAMS, USER_OWNED_AGENTS_LIST_PARAMS } from '../constants';
1110
import { DiscoverAgentsList } from './DiscoverAgentsList';
1211
import { HomeHeading } from './HomeHeading';
1312
import classes from './HomeView.module.scss';
14-
import { RecentlyAddedAgentsList } from './RecentlyAddedAgentsList';
15-
import { RecentlyUsedAgentsList } from './RecentlyUsedAgentsList';
1613

1714
export async function HomeView() {
18-
const [userOwned, userNotOwned] = await Promise.all([
19-
fetchProviders(USER_OWNED_AGENTS_LIST_PARAMS),
20-
fetchProviders(USER_NOT_OWNED_AGENTS_LIST_PARAMS),
21-
]);
22-
23-
const hasUserOwnedAgents = userOwned && userOwned.total_count > 0;
15+
const initialData = await fetchProviders();
2416

2517
return (
2618
<MainContent spacing="sm">
2719
<Container className={classes.root}>
28-
{!hasUserOwnedAgents && <HomeHeading />}
29-
30-
<RecentlyAddedAgentsList initialData={userOwned} />
31-
32-
<RecentlyUsedAgentsList initialData={userNotOwned} />
20+
<HomeHeading />
3321

34-
<DiscoverAgentsList initialData={userNotOwned} />
22+
<DiscoverAgentsList initialData={initialData} />
3523
</Container>
3624
</MainContent>
3725
);

0 commit comments

Comments
 (0)