diff --git a/desktop/src-tauri/src/managed_agents/persona_card_tests.rs b/desktop/src-tauri/src/managed_agents/persona_card_tests.rs index 1ea2f5742..a685b802a 100644 --- a/desktop/src-tauri/src/managed_agents/persona_card_tests.rs +++ b/desktop/src-tauri/src/managed_agents/persona_card_tests.rs @@ -1,4 +1,3 @@ - use super::*; use png::{BitDepth, ColorType, Encoder}; use std::io::Write; diff --git a/desktop/src/features/agents/ui/AgentsView.tsx b/desktop/src/features/agents/ui/AgentsView.tsx index 08e90a90f..79f61366a 100644 --- a/desktop/src/features/agents/ui/AgentsView.tsx +++ b/desktop/src/features/agents/ui/AgentsView.tsx @@ -111,6 +111,9 @@ export function AgentsView() { onExport={teamActions.handleExportTeam} onSync={teamActions.handleSyncTeam} onRevealInFinder={teamActions.handleRevealInFinder} + onImportTeamFile={(fileBytes, fileName) => { + void teamActions.handleImportFile(fileBytes, fileName); + }} onAddToChannel={teamActions.setTeamToAddToChannel} personas={personas.libraryPersonas} teams={teamActions.teams} @@ -169,10 +172,7 @@ export function AgentsView() { isImportPending={ personas.personaImportActions.isApplyingPersonaImportUpdate } - isPending={ - personas.createPersonaMutation.isPending || - personas.updatePersonaMutation.isPending - } + isPending={personas.isPending} runtimes={personas.acpRuntimesQuery.data ?? []} runtimesLoading={personas.acpRuntimesQuery.isLoading} onImportUpdateFile={ diff --git a/desktop/src/features/agents/ui/TeamsSection.tsx b/desktop/src/features/agents/ui/TeamsSection.tsx index e3dc709ee..cb4871e35 100644 --- a/desktop/src/features/agents/ui/TeamsSection.tsx +++ b/desktop/src/features/agents/ui/TeamsSection.tsx @@ -11,6 +11,7 @@ import { import { resolveTeamPersonas } from "@/features/agents/lib/teamPersonas"; import type { AgentPersona, AgentTeam } from "@/shared/api/types"; +import { useFileImportZone } from "@/shared/hooks/useFileImportZone"; import { DropdownMenu, DropdownMenuContent, @@ -40,6 +41,7 @@ type TeamsSectionProps = { onAddToChannel: (team: AgentTeam) => void; onSync: (team: AgentTeam) => void; onRevealInFinder: (team: AgentTeam) => void; + onImportTeamFile: (fileBytes: number[], fileName: string) => void; }; export function TeamsSection({ @@ -56,9 +58,37 @@ export function TeamsSection({ onAddToChannel, onSync, onRevealInFinder, + onImportTeamFile, }: TeamsSectionProps) { + const { + fileInputRef, + isDragOver, + dropHandlers, + handleFileChange, + openFilePicker, + } = useFileImportZone({ onImportFile: onImportTeamFile }); + return ( -
+
+ {isDragOver ? ( +
+

+ Drop .team.json or .zip to import +

+
+ ) : null} + +
@@ -196,11 +226,10 @@ export function TeamsSection({ ); })} -
) : null} @@ -215,3 +244,36 @@ export function TeamsSection({
); } + +function NewTeamCard({ + isPending, + onCreate, + onImport, +}: { + isPending: boolean; + onCreate: () => void; + onImport: () => void; +}) { + return ( + + + + + event.preventDefault()} + > + + Create team + + + Import team file + + + + ); +} diff --git a/desktop/src/features/agents/ui/useManagedAgentActions.ts b/desktop/src/features/agents/ui/useManagedAgentActions.ts index 448bd168a..cc19f0179 100644 --- a/desktop/src/features/agents/ui/useManagedAgentActions.ts +++ b/desktop/src/features/agents/ui/useManagedAgentActions.ts @@ -156,10 +156,7 @@ export function useManagedAgentActions() { try { const agent = managedAgents.find((a) => a.pubkey === pubkey); if (!agent) return; - const result = await stopAndRemoveLocalAgent(agent); - if (agent.backend.type === "local" && logAgentPubkey === pubkey) { - setLogAgentPubkey(null); - } + const result = await stopManagedAgent(agent); if (result.noticeMessage) { setActionNoticeMessage(result.noticeMessage); } @@ -283,7 +280,7 @@ export function useManagedAgentActions() { targets, "Stop", "stop", - stopAndRemoveLocalAgent, + stopManagedAgent, ); if ( executed && @@ -327,18 +324,13 @@ export function useManagedAgentActions() { }); } - async function stopAndRemoveLocalAgent(agent: ManagedAgent) { - const result = await stopManagedAgentWithRules({ + async function stopManagedAgent(agent: ManagedAgent) { + return stopManagedAgentWithRules({ agent, channels: channelsQuery.data ?? [], relayAgents: relayAgentsQuery.data ?? [], stopManagedAgent: stopMutation.mutateAsync, }); - if (agent.backend.type === "local") { - await deleteManagedAgent(agent); - await removeAgentFromAllChannels(agent.pubkey); - } - return result; } const isPending = diff --git a/desktop/src/features/profile/ui/UserProfilePanel.tsx b/desktop/src/features/profile/ui/UserProfilePanel.tsx index 453693c01..ff0f3f58b 100644 --- a/desktop/src/features/profile/ui/UserProfilePanel.tsx +++ b/desktop/src/features/profile/ui/UserProfilePanel.tsx @@ -325,18 +325,15 @@ export function UserProfilePanel({ setEditAgentOpen(true); }, [resolvedPersona]); - const { - deleteManagedAgentRecord, - deleteManagedAgentsForPersona, - removeAgentFromAllChannels, - } = useProfileAgentDeletion({ - channels: channelsQuery.data, - deleteManagedAgent: deleteAgentMutation.mutateAsync, - managedAgent, - managedAgents: managedAgentsQuery.data, - presenceLookup: presenceQuery.data, - relayAgents: relayAgentsQuery.data, - }); + const { deleteManagedAgentRecord, deleteManagedAgentsForPersona } = + useProfileAgentDeletion({ + channels: channelsQuery.data, + deleteManagedAgent: deleteAgentMutation.mutateAsync, + managedAgent, + managedAgents: managedAgentsQuery.data, + presenceLookup: presenceQuery.data, + relayAgents: relayAgentsQuery.data, + }); const handleAgentPrimaryAction = React.useCallback(async () => { if (!managedAgent) return; @@ -349,15 +346,6 @@ export function UserProfilePanel({ relayAgents: relayAgentsQuery.data ?? [], stopManagedAgent: stopAgentMutation.mutateAsync, }); - if (managedAgent.backend.type === "local") { - await deleteAgentMutation.mutateAsync({ - pubkey: managedAgent.pubkey, - }); - await removeAgentFromAllChannels(managedAgent.pubkey); - void managedAgentsQuery.refetch(); - void relayAgentsQuery.refetch(); - return; - } toast.success(result.noticeMessage ?? `Stopped ${managedAgent.name}.`); return; } @@ -378,12 +366,8 @@ export function UserProfilePanel({ } }, [ channelsQuery.data, - deleteAgentMutation.mutateAsync, managedAgent, - managedAgentsQuery.refetch, relayAgentsQuery.data, - relayAgentsQuery.refetch, - removeAgentFromAllChannels, startAgentMutation.mutateAsync, stopAgentMutation.mutateAsync, ]); diff --git a/desktop/tests/e2e/mentions.spec.ts b/desktop/tests/e2e/mentions.spec.ts index 8c851931a..d6d424a6e 100644 --- a/desktop/tests/e2e/mentions.spec.ts +++ b/desktop/tests/e2e/mentions.spec.ts @@ -1074,7 +1074,10 @@ test("clicking author name opens user profile panel", async ({ page }) => { // Click now opens the full profile panel instead of the popover const panel = page.getByTestId("user-profile-panel"); await expect(panel).toBeVisible(); - await expect(panel).toContainText("deadbeef"); + await expect(panel).toContainText("npub1mock..."); + await expect( + panel.getByTestId("user-profile-agent-info-ingress"), + ).toBeVisible(); }); test("hovering avatar opens popover, clicking opens profile panel", async ({