diff --git a/ui/src/components/ChatMessage.test.tsx b/ui/src/components/ChatMessage.test.tsx index 2251af72..df015897 100644 --- a/ui/src/components/ChatMessage.test.tsx +++ b/ui/src/components/ChatMessage.test.tsx @@ -1,12 +1,20 @@ -import { describe, it } from "vitest"; +// @vitest-environment jsdom +import { describe, it, expect } from "vitest"; describe("ChatMessage", () => { + it("exports ChatMessage component", async () => { + const mod = await import("./ChatMessage"); + expect(mod.ChatMessage).toBeDefined(); + }); + it.todo("renders user message as right-aligned bubble with plain text"); it.todo("renders assistant message with ChatMarkdownMessage"); it.todo("renders ChatMessageIdentityBar for assistant messages when agentName is provided"); it.todo("shows edit pencil on hover for user messages"); it.todo("shows retry button on hover for assistant messages"); - it.todo("hides retry button when isStreaming is true"); + it.todo("hides retry button when isAnyStreaming is true"); it.todo("switches to inline edit textarea on pencil click"); it.todo("renders ChatStreamingCursor when isStreaming is true"); + it.todo("Save edit button disabled when edit textarea is empty"); + it.todo("Discard edit reverts to read-only bubble"); }); diff --git a/ui/src/components/ChatMessage.tsx b/ui/src/components/ChatMessage.tsx index ac5d02e2..8e21654d 100644 --- a/ui/src/components/ChatMessage.tsx +++ b/ui/src/components/ChatMessage.tsx @@ -1,10 +1,14 @@ +import { useState } from "react"; import { ChatMarkdownMessage } from "./ChatMarkdownMessage"; import { ChatMessageIdentityBar } from "./ChatMessageIdentityBar"; import { ChatStreamingCursor } from "./ChatStreamingCursor"; +import { ChatMessageActions } from "./ChatMessageActions"; +import { Button } from "@/components/ui/button"; import { cn } from "../lib/utils"; import type { AgentRole } from "@paperclipai/shared"; interface ChatMessageProps { + id?: string; role: "user" | "assistant" | "system"; content: string; agentName?: string | null; @@ -12,9 +16,13 @@ interface ChatMessageProps { agentRole?: AgentRole | null; timestamp?: string; isStreaming?: boolean; + isAnyStreaming?: boolean; + onEdit?: (messageId: string, newContent: string) => void; + onRetry?: (messageId: string) => void; } export function ChatMessage({ + id, role, content, agentName, @@ -22,20 +30,73 @@ export function ChatMessage({ agentRole, timestamp, isStreaming, + isAnyStreaming, + onEdit, + onRetry, }: ChatMessageProps) { + const [isEditing, setIsEditing] = useState(false); + const [editValue, setEditValue] = useState(content); + if (role === "user") { + if (isEditing) { + return ( +