diff --git a/desktop/src/features/agents/lib/formatAgentModelLabel.ts b/desktop/src/features/agents/lib/formatAgentModelLabel.ts new file mode 100644 index 000000000..6c32d5393 --- /dev/null +++ b/desktop/src/features/agents/lib/formatAgentModelLabel.ts @@ -0,0 +1,8 @@ +/** + * Returns a human-readable model label for an agent or persona, falling back to + * "Auto" when no model is set (empty or whitespace-only). + */ +export function formatAgentModelLabel(model: string | null | undefined) { + const trimmed = model?.trim(); + return trimmed && trimmed.length > 0 ? trimmed : "Auto"; +} diff --git a/desktop/src/features/agents/ui/AgentIdentityCard.tsx b/desktop/src/features/agents/ui/AgentIdentityCard.tsx new file mode 100644 index 000000000..5c27d5451 --- /dev/null +++ b/desktop/src/features/agents/ui/AgentIdentityCard.tsx @@ -0,0 +1,91 @@ +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; + errorLabel?: string | null; + modelControl?: ReactNode; + modelLabel: string; + onClick: () => void; + status?: ReactNode; +}; + +export function AgentIdentityCard({ + actions, + ariaLabel, + avatarUrl, + dataTestId, + errorLabel, + label, + modelControl, + modelLabel, + onClick, + status, +}: AgentIdentityCardProps) { + const trimmedAvatarUrl = avatarUrl?.trim() || null; + + return ( +
Linked from {symlinkTarget ?? sourceDir}
+{trimmedDescription}
+- 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 +224,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"}
@@ -316,12 +252,16 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) { ) : null} {agentsError ? ( -+
{agentsError.message}
) : null} {personasError ? ( -+
{personasError.message}
) : null} @@ -329,43 +269,157 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) { ); } +function AgentPersonaCard({ + agent, + persona, + presenceLoaded, + presenceLookup, + onOpenAgentProfile, + onOpenPersonaProfile, +}: { + agent: ManagedAgent | undefined; + persona: AgentPersona; + presenceLoaded: boolean; + presenceLookup: PresenceLookup; + onOpenAgentProfile: (pubkey: string) => void; + onOpenPersonaProfile: (persona: AgentPersona) => 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; + const friendlyError = agent + ? friendlyAgentLastError(agent.lastError)?.copy + : null; + + 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. -
-