Skip to content
Closed
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
18 changes: 18 additions & 0 deletions desktop/src/features/agents/lib/sortProviders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { AcpProvider } from "@/shared/api/types";

/**
* Sort ACP providers with "goose" first, then alphabetically by label.
* Used by any surface that presents a provider list to the user.
*/
export function sortProviders(
providers: readonly AcpProvider[],
): AcpProvider[] {
return [...providers].sort((left, right) => {
const leftPriority = left.id === "goose" ? 0 : 1;
const rightPriority = right.id === "goose" ? 0 : 1;
if (leftPriority !== rightPriority) {
return leftPriority - rightPriority;
}
return left.label.localeCompare(right.label);
});
}
56 changes: 31 additions & 25 deletions desktop/src/features/channels/ui/ChannelMembersBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,36 @@ import {
useManagedAgentsQuery,
useRelayAgentsQuery,
} from "@/features/agents/hooks";
import { sortProviders } from "@/features/agents/lib/sortProviders";
import { useChannelMembersQuery } from "@/features/channels/hooks";
import type { Channel } from "@/shared/api/types";
import { normalizePubkey } from "@/shared/lib/pubkey";
import { Button } from "@/shared/ui/button";
import { AddChannelBotDialog } from "./AddChannelBotDialog";
import { QuickAddAgentPopover } from "./QuickAddAgentPopover";

type ChannelMembersBarProps = {
channel: Channel;
currentPubkey?: string;
isAddBotDialogOpen?: boolean;
onAddBotDialogOpenChange?: (open: boolean) => void;
onManageChannel: () => void;
onToggleMembers: () => void;
};

export function ChannelMembersBar({
channel,
currentPubkey,
isAddBotDialogOpen,
onAddBotDialogOpenChange,
onManageChannel,
onToggleMembers,
}: ChannelMembersBarProps) {
const [isAddBotOpen, setIsAddBotOpen] = React.useState(false);
// Dialog state: controlled externally if props provided, otherwise local.
const [localIsAddBotOpen, setLocalIsAddBotOpen] = React.useState(false);
const isAddBotOpen = isAddBotDialogOpen ?? localIsAddBotOpen;
const setIsAddBotOpen = onAddBotDialogOpenChange ?? setLocalIsAddBotOpen;
const [isQuickAddOpen, setIsQuickAddOpen] = React.useState(false);
const { startHuddle, isStarting: isStartingHuddle } = useHuddle();
const queryClient = useQueryClient();
const membersQuery = useChannelMembersQuery(channel.id);
Expand All @@ -39,16 +49,7 @@ export function ChannelMembersBar({
const members = membersQuery.data ?? [];
const memberCount = membersQuery.data?.length ?? channel.memberCount;
const providers = React.useMemo(
() =>
[...(providersQuery.data ?? [])].sort((left, right) => {
const leftPriority = left.id === "goose" ? 0 : 1;
const rightPriority = right.id === "goose" ? 0 : 1;
if (leftPriority !== rightPriority) {
return leftPriority - rightPriority;
}

return left.label.localeCompare(right.label);
}),
() => sortProviders(providersQuery.data ?? []),
[providersQuery.data],
);
const normalizedCurrentPubkey = currentPubkey
Expand All @@ -73,7 +74,8 @@ export function ChannelMembersBar({

previousChannelIdRef.current = channel.id;
setIsAddBotOpen(false);
}, [channel.id]);
setIsQuickAddOpen(false);
}, [channel.id, setIsAddBotOpen]);

const dialogErrorMessage =
providersQuery.error instanceof Error
Expand All @@ -87,20 +89,24 @@ export function ChannelMembersBar({
return (
<React.Fragment>
<div className="flex items-center gap-1">
<Button
aria-label="Add agent"
className="h-7 w-7 rounded-full"
data-testid="channel-add-bot-trigger"
disabled={!canAddAgents}
onClick={() => {
setIsAddBotOpen(true);
}}
size="icon"
type="button"
variant="outline"
<QuickAddAgentPopover
channelId={channel.id}
open={isQuickAddOpen}
onOpenChange={setIsQuickAddOpen}
onMoreOptions={() => setIsAddBotOpen(true)}
>
<Plus className="h-3 w-3" />
</Button>
<Button
aria-label="Add agent"
className="h-7 w-7 rounded-full"
data-testid="channel-add-bot-trigger"
disabled={!canAddAgents}
size="icon"
type="button"
variant="outline"
>
<Plus className="h-3 w-3" />
</Button>
</QuickAddAgentPopover>

<HuddleIndicator
className="h-7 w-7"
Expand Down
4 changes: 4 additions & 0 deletions desktop/src/features/channels/ui/ChannelScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export function ChannelScreen({
string | null
>(null);
const [isMembersSidebarOpen, setIsMembersSidebarOpen] = React.useState(false);
const [isAddBotDialogOpen, setIsAddBotDialogOpen] = React.useState(false);
const [openThreadHeadId, setOpenThreadHeadId] = React.useState<string | null>(
null,
);
Expand Down Expand Up @@ -436,7 +437,9 @@ export function ChannelScreen({
activeChannelTitle={activeChannelTitle}
activeDmPresenceStatus={activeDmPresenceStatus}
currentPubkey={currentPubkey}
isAddBotDialogOpen={isAddBotDialogOpen}
isJoining={joinChannelMutation.isPending}
onAddBotDialogOpenChange={setIsAddBotDialogOpen}
onJoinChannel={joinChannelMutation.mutateAsync}
onManageChannel={openChannelManagement}
onToggleMembers={() => setIsMembersSidebarOpen((prev) => !prev)}
Expand Down Expand Up @@ -530,6 +533,7 @@ export function ChannelScreen({
currentPubkey={currentPubkey}
open={isMembersSidebarOpen}
onOpenChange={setIsMembersSidebarOpen}
onOpenAddBotDialog={() => setIsAddBotDialogOpen(true)}
onViewActivity={handleOpenAgentSession}
/>
</ProfilePanelProvider>
Expand Down
6 changes: 6 additions & 0 deletions desktop/src/features/channels/ui/ChannelScreenHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ type ChannelScreenHeaderProps = {
activeChannelTitle: string;
activeDmPresenceStatus: PresenceStatus | null;
currentPubkey?: string;
isAddBotDialogOpen?: boolean;
isJoining?: boolean;
onAddBotDialogOpenChange?: (open: boolean) => void;
onJoinChannel?: () => Promise<void>;
onManageChannel: () => void;
onToggleMembers: () => void;
Expand All @@ -26,7 +28,9 @@ export function ChannelScreenHeader({
activeChannelTitle,
activeDmPresenceStatus,
currentPubkey,
isAddBotDialogOpen,
isJoining = false,
onAddBotDialogOpenChange,
onJoinChannel,
onManageChannel,
onToggleMembers,
Expand Down Expand Up @@ -56,6 +60,8 @@ export function ChannelScreenHeader({
<ChannelMembersBar
channel={activeChannel}
currentPubkey={currentPubkey}
isAddBotDialogOpen={isAddBotDialogOpen}
onAddBotDialogOpenChange={onAddBotDialogOpenChange}
onManageChannel={onManageChannel}
onToggleMembers={onToggleMembers}
/>
Expand Down
35 changes: 35 additions & 0 deletions desktop/src/features/channels/ui/MembersSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Plus } from "lucide-react";
import * as React from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import {
Expand Down Expand Up @@ -41,12 +42,14 @@ import { MembersSidebarAgentControls } from "./MembersSidebarAgentControls";
import { ChannelMemberInviteCard } from "./ChannelMemberInviteCard";
import { MembersSidebarMemberCard } from "./MembersSidebarMemberCard";
import { useMembersSidebarActions } from "./useMembersSidebarActions";
import { QuickAddAgentPopover } from "./QuickAddAgentPopover";

type MembersSidebarProps = {
channel: Channel | null;
currentPubkey?: string;
open: boolean;
onOpenChange: (open: boolean) => void;
onOpenAddBotDialog?: () => void;
onViewActivity?: (pubkey: string) => void;
};

Expand All @@ -55,6 +58,7 @@ export function MembersSidebar({
currentPubkey,
open,
onOpenChange,
onOpenAddBotDialog,
onViewActivity,
}: MembersSidebarProps) {
const channelId = channel?.id ?? null;
Expand Down Expand Up @@ -98,6 +102,14 @@ export function MembersSidebar({
selfMember?.role === "owner" || selfMember?.role === "admin";
const isArchived =
channel?.archivedAt !== null && channel?.archivedAt !== undefined;
const canAddAgents =
channel?.channelType !== "dm" &&
!isArchived &&
(channel?.visibility === "open" || canManageMembers);

const [isSidebarQuickAddOpen, setIsSidebarQuickAddOpen] =
React.useState(false);

const managedAgentByPubkey = React.useMemo(
() =>
new Map(
Expand Down Expand Up @@ -298,6 +310,29 @@ export function MembersSidebar({
</p>
)}
</div>
{canAddAgents ? (
<QuickAddAgentPopover
channelId={channelId}
open={isSidebarQuickAddOpen}
onOpenChange={setIsSidebarQuickAddOpen}
onMoreOptions={() => {
setIsSidebarQuickAddOpen(false);
onOpenAddBotDialog?.();
}}
>
<Button
aria-label="Add agent to channel"
className="mt-2 h-8 w-full justify-start gap-2 text-xs text-muted-foreground"
data-testid="sidebar-add-agent-trigger"
size="sm"
type="button"
variant="ghost"
>
<Plus className="h-3.5 w-3.5" />
Add agent
</Button>
</QuickAddAgentPopover>
) : null}
</section>

{changeRoleError ? (
Expand Down
Loading
Loading