diff --git a/desktop/src/features/agents/ui/AgentIdentityCard.tsx b/desktop/src/features/agents/ui/AgentIdentityCard.tsx new file mode 100644 index 000000000..a66fb9cd7 --- /dev/null +++ b/desktop/src/features/agents/ui/AgentIdentityCard.tsx @@ -0,0 +1,69 @@ +import type { ReactNode } from "react"; + +import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar"; +import { cn } from "@/shared/lib/cn"; +import { IdentityInitialsAvatar } from "./IdentityInitialsAvatar"; + +type AgentIdentityCardProps = { + actions?: ReactNode; + ariaLabel: string; + avatarUrl?: string | null; + dataTestId: string; + label: string; + modelLabel: string; + onClick: () => void; +}; + +export function AgentIdentityCard({ + actions, + ariaLabel, + avatarUrl, + dataTestId, + label, + modelLabel, + onClick, +}: AgentIdentityCardProps) { + const trimmedAvatarUrl = avatarUrl?.trim() || null; + + return ( +
Linked from {symlinkTarget ?? sourceDir}
+- Drop .team.json to import + Drop .team.json or .zip to import
+
Saved groups from My Agents that you can add to a channel together.
- {team.name} -
- {team.isSymlink ? ( -- Linked from {team.symlinkTarget ?? team.sourceDir} -
-{team.description}
-+
{missingPersonaCount} persona {missingPersonaCount === 1 ? "" : "s"} in this team{" "} {missingPersonaCount === 1 ? "is" : "are"} no longer in your @@ -299,42 +225,68 @@ export function TeamsSection({ exporting.
) : null} -+
{error.message}
) : null} ); } + +function NewTeamCard({ + isPending, + onCreate, + onImport, + onInstallFromDirectory, +}: { + isPending: boolean; + onCreate: () => void; + onImport: () => void; + onInstallFromDirectory?: () => void; +}) { + return ( +{stoppedCount} stopped {stoppedCount === 1 ? "agent" : "agents"}
@@ -338,12 +320,16 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) { ) : null} {agentsError ? ( -+
{agentsError.message}
) : null} {personasError ? ( -+
{personasError.message}
) : null} @@ -351,43 +337,417 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) { ); } +function pickProfileAgent(agents: ManagedAgent[]) { + return [...agents].sort((left, right) => { + const activeDiff = + Number(isManagedAgentActive(right)) - Number(isManagedAgentActive(left)); + if (activeDiff !== 0) return activeDiff; + return left.name.localeCompare(right.name); + })[0]; +} + +type AgentMenuProps = { + isActionPending: boolean; + onAddToChannel: (agent: ManagedAgent) => void; + onDelete: (pubkey: string) => void; + onOpenLogs: (pubkey: string) => void; + onStart: (pubkey: string) => void; + onStop: (pubkey: string) => void; + onToggleStartOnAppLaunch: (pubkey: string, startOnAppLaunch: boolean) => void; +}; + +type PersonaMenuProps = { + isActionPending: boolean; + isPersonasPending: boolean; + onDeactivatePersona: (persona: AgentPersona) => void; + onDeletePersona: (persona: AgentPersona) => void; + onDuplicatePersona: (persona: AgentPersona) => void; + onEditPersona: (persona: AgentPersona) => void; + onExportPersona: (persona: AgentPersona) => void; +}; + +function AgentPersonaCard({ + agent, + agentMenuProps, + persona, + personaMenuProps, + onOpenAgentProfile, +}: { + agent: ManagedAgent | undefined; + agentMenuProps: AgentMenuProps; + persona: AgentPersona; + personaMenuProps: PersonaMenuProps; + onOpenAgentProfile?: (pubkey: string) => void; +}) { + const title = persona.displayName; + const modelLabel = formatAgentModelLabel(agent?.model ?? persona.model); + const profileQuery = useUserProfileQuery(agent?.pubkey); + const avatarUrl = agent + ? firstAvatarUrl(profileQuery.data?.avatarUrl, persona.avatarUrl) + : persona.avatarUrl; + + return ( +- Personas and their deployed agent instances. +
+ Agents in this workspace.
No agents yet
-- Create a persona or choose one from the catalog, then deploy it to a - channel. -
-