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
This commit is contained in:
Nexus Dev 2026-04-01 21:41:32 +00:00
parent 5671b24210
commit fe51918ed2
4 changed files with 102 additions and 0 deletions

View file

@ -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,

View file

@ -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 (
<div
className={cn(
"motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-1",
"inline-flex items-center gap-2 rounded-md border border-border bg-card px-3 py-1 text-[13px]"
)}
role="status"
>
<CheckCircle2 className="h-3.5 w-3.5 text-green-500 dark:text-green-400" />
<span className="text-foreground">
{agentName} completed {taskId}
{displayTitle ? `: ${displayTitle}` : ""}
</span>
{taskUrl && (
<Link
to={taskUrl}
className="text-primary underline-offset-2 hover:underline"
aria-label={`View task ${taskId}`}
>
View task
</Link>
)}
</div>
);
}

View file

@ -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 (
<div className="motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-1 inline-flex items-center gap-2 rounded-md border border-border bg-card px-3 py-1 text-[13px] text-muted-foreground">
Creating task...
</div>
);
}
return (
<div
className={cn(
"motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-1",
"inline-flex items-center gap-2 rounded-md border border-border bg-card px-3 py-1 text-[13px]"
)}
role="status"
>
<span className="text-[11px] font-semibold text-muted-foreground">{taskId}</span>
<span className="text-foreground">{taskTitle}</span>
{taskUrl && (
<Link
to={taskUrl}
className="text-primary underline-offset-2 hover:underline"
aria-label={`View task ${taskId}`}
>
View task
</Link>
)}
</div>
);
}

View file

@ -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<Agent[]>({
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;
}