mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Address agents panel review feedback
This commit is contained in:
@@ -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,
|
||||
]);
|
||||
|
||||
@@ -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 ({
|
||||
|
||||
Reference in New Issue
Block a user