From fe51918ed2dfeb8ebfb355e4d4ca0a9f807fea64 Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Wed, 1 Apr 2026 21:41:32 +0000 Subject: [PATCH] feat(23-02): add ChatTaskCreatedBadge, ChatStatusUpdateBadge, useBrainstormerDefault - ChatTaskCreatedBadge renders loading state and resolved badge with View task link - ChatStatusUpdateBadge renders CheckCircle2 + agent completion text with task link - useBrainstormerDefault returns general role agent ID with React Query deduplication - Fix ChatMessageList synthetic streaming entry to include messageType: null --- ui/src/components/ChatMessageList.tsx | 1 + ui/src/components/ChatStatusUpdateBadge.tsx | 40 +++++++++++++++++++++ ui/src/components/ChatTaskCreatedBadge.tsx | 40 +++++++++++++++++++++ ui/src/hooks/useBrainstormerDefault.ts | 21 +++++++++++ 4 files changed, 102 insertions(+) create mode 100644 ui/src/components/ChatStatusUpdateBadge.tsx create mode 100644 ui/src/components/ChatTaskCreatedBadge.tsx create mode 100644 ui/src/hooks/useBrainstormerDefault.ts diff --git a/ui/src/components/ChatMessageList.tsx b/ui/src/components/ChatMessageList.tsx index 5ada7423..086091e9 100644 --- a/ui/src/components/ChatMessageList.tsx +++ b/ui/src/components/ChatMessageList.tsx @@ -44,6 +44,7 @@ export function ChatMessageList({ role: "assistant" as const, content: streamingContent, agentId: null, + messageType: null, createdAt: new Date().toISOString(), updatedAt: null, isStreamingEntry: true, diff --git a/ui/src/components/ChatStatusUpdateBadge.tsx b/ui/src/components/ChatStatusUpdateBadge.tsx new file mode 100644 index 00000000..ea197586 --- /dev/null +++ b/ui/src/components/ChatStatusUpdateBadge.tsx @@ -0,0 +1,40 @@ +import { Link } from "@/lib/router"; +import { CheckCircle2 } from "lucide-react"; +import { cn } from "../lib/utils"; + +interface ChatStatusUpdateBadgeProps { + agentName: string; + taskId: string; + taskTitle?: string; + taskUrl?: string; +} + +export function ChatStatusUpdateBadge({ agentName, taskId, taskTitle, taskUrl }: ChatStatusUpdateBadgeProps) { + const displayTitle = + taskTitle && taskTitle.length > 40 ? taskTitle.slice(0, 40) + "..." : taskTitle; + + return ( +
+ + + {agentName} completed {taskId} + {displayTitle ? `: ${displayTitle}` : ""} + + {taskUrl && ( + + View task + + )} +
+ ); +} diff --git a/ui/src/components/ChatTaskCreatedBadge.tsx b/ui/src/components/ChatTaskCreatedBadge.tsx new file mode 100644 index 00000000..287b1ffc --- /dev/null +++ b/ui/src/components/ChatTaskCreatedBadge.tsx @@ -0,0 +1,40 @@ +import { Link } from "@/lib/router"; +import { cn } from "../lib/utils"; + +interface ChatTaskCreatedBadgeProps { + taskId?: string | null; + taskTitle?: string | null; + taskUrl?: string | null; +} + +export function ChatTaskCreatedBadge({ taskId, taskTitle, taskUrl }: ChatTaskCreatedBadgeProps) { + if (!taskId) { + return ( +
+ Creating task... +
+ ); + } + + return ( +
+ {taskId} + {taskTitle} + {taskUrl && ( + + View task + + )} +
+ ); +} diff --git a/ui/src/hooks/useBrainstormerDefault.ts b/ui/src/hooks/useBrainstormerDefault.ts new file mode 100644 index 00000000..a8807bdf --- /dev/null +++ b/ui/src/hooks/useBrainstormerDefault.ts @@ -0,0 +1,21 @@ +import { useQuery } from "@tanstack/react-query"; +import type { Agent } from "@paperclipai/shared"; +import { agentsApi } from "../api/agents"; +import { useCompany } from "../context/CompanyContext"; + +export function useBrainstormerDefault(): string | null { + const { selectedCompanyId } = useCompany(); + + const { data: agents = [] } = useQuery({ + queryKey: ["agents", selectedCompanyId], + queryFn: () => agentsApi.list(selectedCompanyId!), + enabled: !!selectedCompanyId, + }); + + // Reuses same queryKey as ChatPanel's agent list — React Query deduplicates + const generalAgent = agents + .filter((a) => a.role === "general") + .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())[0]; + + return generalAgent?.id ?? null; +}