import { AgentAvatar, type AvatarAgent } from "../AgentAvatar"; import { useState } from "react"; import { Pencil } from "lucide-react"; import { cn } from "@/lib/utils"; import { FoldCurtain } from "@/components/FoldCurtain"; import { InlineEditor } from "@/components/InlineEditor"; import { MarkdownBody, type MarkdownExternalReferenceMap } from "@/components/MarkdownBody"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import type { MentionOption } from "@/components/MarkdownEditor"; import { formatTaskChatTimestamp } from "./task-chat-adapter"; /** * Host binding for the description-as-first-bubble (PAP-375): the LIVE issue * description plus everything the edit surface needs. Carried on the shared * thread props (`issueBrief`); only the redesigned TaskChatThread consumes it. */ export interface TaskChatIssueBrief { /** LIVE issue.description (never a frozen copy) — empty string when unset. */ description: string; /** Originating actor: human-created → blue user bubble, agent-created → agent-side bubble. */ author: "human" | "agent"; /** Creating agent's name, shown in the agent-side avatar header. */ authorName?: string; /** Creating agent's assigned icon (AgentIconName) for the avatar header. */ agentIcon?: string | null; agent?: AvatarAgent; /** issue.createdAt — rendered like the other bubbles' footer timestamps. */ createdAt?: string | Date; onSave: (description: string) => void | Promise; mentions?: MentionOption[]; externalReferences?: MarkdownExternalReferenceMap; imageUploadHandler?: (file: File) => Promise; onDropFile?: (file: File) => Promise; } interface TaskChatDescriptionBubbleProps { brief: TaskChatIssueBrief; } /** ~12 text-sm lines (20px line-height) — the accepted fold default (PAP-375). */ const FOLD_COLLAPSED_HEIGHT_PX = 240; function initialsForName(name: string) { const parts = name.trim().split(/\s+/); if (parts.length >= 2) { return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); } return name.slice(0, 2).toUpperCase(); } /** * The task description as the requester's first chat bubble (PAP-375): * human-created tasks get the blue user bubble, agent-created tasks the * agent-side card with the avatar header — same chrome as TaskChatBubble. The * body is the live issue.description. Human-authored blue bubbles are * presentation-only; agent-authored descriptions retain the hover edit * affordance. Long descriptions fold at ~12 lines behind a Show more curtain; * an empty description renders a muted ghost row that opens the editor — never * an empty bubble. */ export function TaskChatDescriptionBubble({ brief }: TaskChatDescriptionBubbleProps) { const [editing, setEditing] = useState(false); const isHuman = brief.author === "human"; const hasDescription = brief.description.trim().length > 0; if (editing) { return (
{ if (!next) setEditing(false); }} mentions={brief.mentions} externalReferences={brief.externalReferences} imageUploadHandler={brief.imageUploadHandler} onDropFile={brief.onDropFile} />
); } if (!hasDescription) { // Accepted default: no fabricated empty bubble — a muted ghost affordance. return ( ); } const timestamp = formatTaskChatTimestamp(brief.createdAt); return (
{!isHuman && brief.authorName ? ( {brief.authorName} ) : null}
{brief.description}
{!isHuman ? ( ) : null}
{timestamp ? ( {timestamp} ) : null}
); }