diff --git a/frontend/src/components/workspace/messages/message-list-item.tsx b/frontend/src/components/workspace/messages/message-list-item.tsx
index 52c4fa0d..7cc04ea2 100644
--- a/frontend/src/components/workspace/messages/message-list-item.tsx
+++ b/frontend/src/components/workspace/messages/message-list-item.tsx
@@ -28,6 +28,7 @@ import { resolveArtifactURL } from "@/core/artifacts/utils";
import { useI18n } from "@/core/i18n/hooks";
import {
extractContentFromMessage,
+ normalizeHumanMessageDisplayText,
extractReasoningContentFromMessage,
parseUploadedFiles,
stripPriorityHintSuffix,
@@ -167,9 +168,11 @@ function MessageContent_({
const contentToDisplay = useMemo(() => {
if (isHuman) {
- return rawContent
- ? stripPriorityHintSuffix(stripUploadedFilesTag(rawContent))
- : "";
+ if (!rawContent) {
+ return "";
+ }
+ const cleaned = stripPriorityHintSuffix(stripUploadedFilesTag(rawContent));
+ return normalizeHumanMessageDisplayText(cleaned);
}
return rawContent ?? "";
}, [rawContent, isHuman]);
diff --git a/frontend/src/core/messages/utils.ts b/frontend/src/core/messages/utils.ts
index fe174166..37c89480 100644
--- a/frontend/src/core/messages/utils.ts
+++ b/frontend/src/core/messages/utils.ts
@@ -364,6 +364,20 @@ export function stripPriorityHintSuffix(content: string): string {
.trim();
}
+/**
+ * Normalize human-authored message text for markdown rendering.
+ * - Decode literal "\n" into real line breaks.
+ * - Split Chinese-numbered items (e.g. "1)...") into separate paragraphs.
+ */
+export function normalizeHumanMessageDisplayText(content: string): string {
+ return content
+ .replace(/\\n/g, "\n")
+ .replace(/\r\n?/g, "\n")
+ .replace(/\n(?=\d+[))]\s*)/g, "\n\n")
+ .replace(/\n{3,}/g, "\n\n")
+ .trim();
+}
+
export function parseUploadedFiles(content: string): FileInMessage[] {
// Match ... tag
const uploadedFilesRegex = /([\s\S]*?)<\/uploaded_files>/;