Skip to content
Open
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.
`COMPUTER_MEMORY_HIGH_PERCENT=0` and `COMPUTER_TAB_IDLE_MS=0` (Helm: `computers.memoryHighPercent`
and `computers.tabIdleMs`) keep the old behaviour.

### The default look: an inset page, slate greys and quieter headings

Each page now sits in a panel inset from the window, on a darker frame shared with the sidebar,
with white cards on a light grey page; admin and settings scroll inside it like the rest of the
app. The greys are Tailwind's slate rather than neutral, and page and section headings are lighter.
Rows show an app's logo or initial instead of the same plug; Home shows Bots as compact cards; a
Bot's page leads with its own one-liner; Connections tiles no longer cut their text off; and the
Composio directory shows its rows loading. Empty states carry a faint dot pattern whose file,
colour and strength are theme tokens (`--empty-pattern*` in `theme.css`), so a deployment can
replace it or turn it off. Skins are unchanged.

### A computer closes browsers nobody is using when its memory runs high

A computer runs one browser for each Bot that is browsing, and the only limits were a count and half
Expand Down
136 changes: 136 additions & 0 deletions app/src/assets/patterns/dot-matrix.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions app/src/components/admin/admin-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import {
IconArrowLeft,
IconBuildingBank,
IconBulb,
IconChecks,
IconCode,
IconDeviceDesktop,
IconFileText,
IconKey,
IconLayoutGrid,
IconListDetails,
IconPuzzle,
IconShieldCheck,
IconSlash,
IconUsers,
} from "@tabler/icons-react";
import { Link, type LinkOptions } from "@tanstack/react-router";
Expand Down Expand Up @@ -79,12 +80,12 @@ const GROUPS: {
},
{
title: "Skills",
icon: IconFileText,
icon: IconSlash,
linkOptions: { to: "/admin/skills" },
},
{
title: "Automatic learning",
icon: IconFileText,
icon: IconBulb,
linkOptions: { to: "/admin/learning" },
},
{
Expand Down Expand Up @@ -130,7 +131,7 @@ export function AdminSidebar({
...props
}: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar {...props}>
<Sidebar variant="inset" {...props}>
{/*
* Pinned to the same height as the app sidebar's header. Left to itself this one is 60px
* against the app's 45px — `p-2` around a `size="lg"` button rather than a fixed height — so
Expand Down
22 changes: 11 additions & 11 deletions app/src/components/agents/agent-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@ export function AgentCard({
appearance = "compact",
}: {
agent: AgentProfile;
appearance?: "compact" | "tile";
appearance?: "compact" | "row";
}) {
if (appearance === "tile") {
if (appearance === "row") {
return (
<div className="flex h-[180px] w-[144px] flex-col overflow-hidden rounded-2xl border border-border bg-card p-3 transition-colors hover:border-ring dark:border-transparent">
/*
* The avatar beside the name rather than above it: a tall card left most of its height empty
* between the two, and a row reads the name and what the Bot is for in one glance.
*/
<div className="flex h-16 min-w-0 items-center gap-3 rounded-xl border border-border bg-card px-3 transition-colors hover:border-ring dark:border-transparent">
<div aria-hidden="true" className="flex shrink-0">
<AbstractAvatar name={agent.name} seed={agent.avatarSeed} size={36} />
<AbstractAvatar name={agent.name} seed={agent.avatarSeed} size={32} />
</div>
{/* Bottom-aligned, where the text sat on the earlier artwork card; the avatar holds the top. */}
<div className="mt-auto flex min-w-0 flex-col gap-2">
<span
className="line-clamp-1 break-words text-sm font-medium leading-5"
title={agent.name}
>
<div className="flex min-w-0 flex-col gap-0.5">
<span className="truncate text-sm font-medium" title={agent.name}>
{agent.name}
</span>
<span className="line-clamp-3 break-words text-xs leading-4 text-muted-foreground">
<span className="truncate text-xs text-muted-foreground">
{agent.roleDescription}
</span>
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/agents/handoff-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IconArrowsExchange, IconLock, IconRobot } from "@tabler/icons-react";
import { IconArrowsExchange, IconBolt, IconLock } from "@tabler/icons-react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { Fragment } from "react";
import { AbstractAvatar } from "@/components/agents/abstract-avatar";
Expand Down Expand Up @@ -102,7 +102,7 @@ export function HandoffPanel({ agentId }: { agentId: string }) {
) : null}

{grantable && total === 0 ? (
<PageEmpty icon={IconRobot} title="No other Bots yet">
<PageEmpty icon={IconBolt} title="No other Bots yet">
When there are more, you can let this Bot ask them here.
</PageEmpty>
) : null}
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/app-sidebar/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const navigation = navigationFor(currentUser?.role);

return (
<Sidebar {...props}>
<Sidebar variant="inset" {...props}>
<SidebarHeader className="h-12 p-2">
<SidebarMenu>
<SidebarMenuItem className="flex flex-row gap-1.5">
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/app-sidebar/channel-item-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function ChannelItemContent({
pinned?: boolean;
revealing?: boolean;
}) {
const primaryText = `text-[0.9rem] leading-5 tracking-[-1%] ${unread ? "font-semibold" : "font-medium"}`;
const primaryText = `text-sm leading-5 tracking-[-1%] ${unread ? "font-semibold" : "font-medium"}`;
const secondaryText = "text-[12px] leading-4 text-muted-foreground";

return (
Expand Down
102 changes: 74 additions & 28 deletions app/src/components/bot-profile/activity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,22 +197,31 @@ function ActivitySection({
/** How many running or scheduled items the Bot's page shows before "All activity". */
const GLANCE = 3;

/** Running work first, then what is lined up, as many as the Bot's page shows. */
function upcomingOf(activity: BotActivity | undefined) {
return activity
? [
...activity.inProgress.map((item) => ({ item, live: true })),
...activity.scheduled.map((item) => ({ item, live: false })),
].slice(0, GLANCE)
: [];
}

/**
* The Bot page's view of its activity: what is under way and what is lined up, so either is seen
* without opening anything, then a row to the full lists. Running work comes first, with its dot.
*
* Only when there is something running or lined up. With nothing to show, a whole section for one
* row to the full lists is a heading over nothing; that row joins "For you" instead, through
* `BotActivityRow`.
*
* The full lists used to sit here and ran on for screens; finished work is the long part and the
* least urgent, so it lives only on the Activity page.
*/
export function BotActivityGlance({ agentId }: { agentId: string }) {
const activity = useQuery(botActivityQueryOptions(agentId));
if (activity.isPending) return null;
const upcoming = activity.data
? [
...activity.data.inProgress.map((item) => ({ item, live: true })),
...activity.data.scheduled.map((item) => ({ item, live: false })),
].slice(0, GLANCE)
: [];
const upcoming = upcomingOf(activity.data);
if (activity.isPending || upcoming.length === 0) return null;
return (
<PageSection title="Activity">
<PageRows>
Expand All @@ -222,32 +231,69 @@ export function BotActivityGlance({ agentId }: { agentId: string }) {
<Separator />
</Fragment>
))}
<Item
render={<Link params={{ agentId }} to="/bots/$agentId/activity" />}
size="sm"
>
{upcoming.length ? null : (
<ItemMedia variant="icon">
<IconHistory />
</ItemMedia>
)}
<ItemContent>
<ItemTitle>All activity</ItemTitle>
<ItemDescription>
{activity.data
? activitySummary(activity.data)
: "Couldn't load this Bot's activity."}
</ItemDescription>
</ItemContent>
<ItemActions>
<IconChevronRight className="size-4 text-muted-foreground" />
</ItemActions>
</Item>
<AllActivityItem activity={activity.data} agentId={agentId} />
</PageRows>
</PageSection>
);
}

/**
* The row to a Bot's activity, inside "For you", for when nothing is running or lined up and
* `BotActivityGlance` draws no section of its own. Starts with a separator, because it follows the
* card's other rows.
*/
export function BotActivityRow({ agentId }: { agentId: string }) {
const activity = useQuery(botActivityQueryOptions(agentId));
if (activity.isPending || upcomingOf(activity.data).length > 0) return null;
return (
<>
<Separator />
<AllActivityItem
activity={activity.data}
agentId={agentId}
title="Activity"
withIcon
/>
</>
);
}

function AllActivityItem({
activity,
agentId,
title = "All activity",
withIcon = false,
}: {
activity: BotActivity | undefined;
agentId: string;
title?: string;
withIcon?: boolean;
}) {
return (
<Item
render={<Link params={{ agentId }} to="/bots/$agentId/activity" />}
size="sm"
>
{withIcon ? (
<ItemMedia variant="icon">
<IconHistory />
</ItemMedia>
) : null}
<ItemContent>
<ItemTitle>{title}</ItemTitle>
<ItemDescription>
{activity
? activitySummary(activity)
: "Couldn't load this Bot's activity."}
</ItemDescription>
</ItemContent>
<ItemActions>
<IconChevronRight className="size-4 text-muted-foreground" />
</ItemActions>
</Item>
);
}

/**
* The Bot page's one line about its activity: what is running and what is lined up, the two that
* might want a look. Finished work is only counted when there is nothing else to say.
Expand Down
Loading
Loading