Compare commits

...

2 Commits

Author SHA1 Message Date
openhands
e2eb28ed0f Add margin-bottom to paragraphs in chat messages for better readability 2025-05-12 01:30:23 +00:00
openhands
dbb2437b86 Fix markdown rendering of newlines in user messages 2025-05-12 01:13:26 +00:00
2 changed files with 68 additions and 2 deletions

View File

@@ -0,0 +1,55 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { ChatMessage } from "../../../../src/components/features/chat/chat-message";
import { test, expect, vi, describe } from "vitest";
describe("ChatMessage", () => {
test("renders a simple message", () => {
render(<ChatMessage type="user" message="Hello world" />);
expect(screen.getByText("Hello world")).toBeInTheDocument();
});
test("renders a message with newlines", () => {
const message = "Line 1\nLine 2";
render(<ChatMessage type="user" message={message} />);
// In markdown, a single newline is preserved in the paragraph
const element = screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'p' &&
element.textContent.includes('Line 1') &&
element.textContent.includes('Line 2');
});
expect(element).toBeInTheDocument();
expect(element.tagName).toBe("P");
});
test("renders a message with double newlines as separate paragraphs", () => {
const message = "Paragraph 1\n\nParagraph 2";
render(<ChatMessage type="user" message={message} />);
// In markdown, double newlines create separate paragraphs
const paragraph1 = screen.getByText("Paragraph 1");
const paragraph2 = screen.getByText("Paragraph 2");
expect(paragraph1).toBeInTheDocument();
expect(paragraph2).toBeInTheDocument();
// They should be in separate paragraph elements
expect(paragraph1.tagName).toBe("P");
expect(paragraph2.tagName).toBe("P");
});
test("renders a message with markdown formatting", () => {
const message = "**Bold text** and *italic text*";
render(<ChatMessage type="user" message={message} />);
const boldElement = screen.getByText("Bold text");
const italicElement = screen.getByText("italic text");
expect(boldElement).toBeInTheDocument();
expect(italicElement).toBeInTheDocument();
expect(boldElement.tagName).toBe("STRONG");
expect(italicElement.tagName).toBe("EM");
});
});

View File

@@ -39,6 +39,16 @@ export function ChatMessage({
};
}, [isCopy]);
// Ensure newlines are properly preserved for markdown rendering
// This is especially important for double newlines which should create paragraphs
const processedMessage = React.useMemo(() => {
// Ensure the message is a string
if (typeof message !== "string") return "";
// Preserve the original newlines
return message;
}, [message]);
return (
<article
data-testid={`${type}-message`}
@@ -57,17 +67,18 @@ export function ChatMessage({
onClick={handleCopyToClipboard}
mode={isCopy ? "copied" : "copy"}
/>
<div className="text-sm break-words">
<div className="text-sm break-words markdown-content">
<Markdown
components={{
code,
ul,
ol,
a: anchor,
p: ({ children }) => <p className="mb-4">{children}</p>,
}}
remarkPlugins={[remarkGfm]}
>
{message}
{processedMessage}
</Markdown>
</div>
{children}