Address agents panel review feedback

This commit is contained in:
klopez4212
2026-06-22 15:43:27 +01:00
parent facace3277
commit 74fd4a50e3
6 changed files with 89 additions and 49 deletions
@@ -1,4 +1,3 @@
use super::*;
use png::{BitDepth, ColorType, Encoder};
use std::io::Write;
@@ -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={
@@ -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 (
<section className="space-y-4" data-testid="agents-library-teams">
<section
className="relative space-y-4"
data-testid="agents-library-teams"
{...dropHandlers}
>
{isDragOver ? (
<div className="pointer-events-none absolute -inset-1 z-10 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary/50 bg-background/80 backdrop-blur-sm">
<p className="text-sm font-medium text-primary">
Drop .team.json or .zip to import
</p>
</div>
) : null}
<input
accept=".json,.zip"
className="hidden"
onChange={handleFileChange}
ref={fileInputRef}
type="file"
/>
<div
className={`${TEAM_CARD_COLUMN_CLASS} flex items-center justify-between gap-3`}
>
@@ -196,11 +226,10 @@ export function TeamsSection({
</TeamIdentityCard>
);
})}
<CreateIdentityCard
ariaLabel="Create team"
dataTestId="new-team-card"
label="New team"
onClick={onCreate}
<NewTeamCard
isPending={isPending}
onCreate={onCreate}
onImport={openFilePicker}
/>
</div>
) : null}
@@ -215,3 +244,36 @@ export function TeamsSection({
</section>
);
}
function NewTeamCard({
isPending,
onCreate,
onImport,
}: {
isPending: boolean;
onCreate: () => void;
onImport: () => void;
}) {
return (
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<CreateIdentityCard
ariaLabel="New team"
dataTestId="new-team-card"
label="New team"
/>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
onCloseAutoFocus={(event) => event.preventDefault()}
>
<DropdownMenuItem disabled={isPending} onClick={onCreate}>
Create team
</DropdownMenuItem>
<DropdownMenuItem disabled={isPending} onClick={onImport}>
Import team file
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
@@ -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 =
@@ -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,
]);
+4 -1
View File
@@ -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 ({