From 27a9e26ec612e973be92f153d5a3bf3134b6fb7f Mon Sep 17 00:00:00 2001 From: CocoRoF Date: Wed, 12 Nov 2025 14:40:05 +0900 Subject: [PATCH] feat: Enhance Markdown processing by normalizing non-standard pipe characters --- .../components/chatParser/ChatParser.tsx | 10 ++++--- .../chatParser/ChatParserMarkdown.tsx | 30 +++++++++++++------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/app/_common/components/chatParser/ChatParser.tsx b/src/app/_common/components/chatParser/ChatParser.tsx index 7a3ba4fb..2726b03c 100644 --- a/src/app/_common/components/chatParser/ChatParser.tsx +++ b/src/app/_common/components/chatParser/ChatParser.tsx @@ -25,7 +25,8 @@ import { findTodoDetailsBlocks } from '@/app/_common/components/chatParser/ChatParserTodoDetails'; import { - parseSimpleMarkdown + parseSimpleMarkdown, + normalizeTableSeparators } from '@/app/_common/components/chatParser/ChatParserMarkdown'; import { parseCitation } from '@/app/_common/components/chatParser/ChatParserCite'; import { convertToString, needsConversion } from '@/app/_common/components/chatParser/ChatParserNonStr'; @@ -50,7 +51,7 @@ interface MessageRendererProps { className?: string; onViewSource?: (sourceInfo: SourceInfo) => void; onHeightChange?: () => void; - mode?: "existing" | "new-workflow" | "new-default" | "deploy"; + mode?: "existing" | "new-workflow" | "new-default" | "deploy" | "embed"; messageId?: string; // 메시지 고유 식별자 추가 timestamp?: number; // 메시지 생성 시간 추가 showEditButton?: boolean; // 편집 버튼 표시 여부 @@ -140,13 +141,14 @@ export const MessageRenderer: React.FC = ({ /** * 컨텐츠를 React 엘리먼트로 파싱 */ -const parseContentToReactElements = (content: string, onViewSource?: (sourceInfo: SourceInfo) => void, onHeightChange?: () => void, mode?: "existing" | "new-workflow" | "new-default" | "deploy"): React.ReactNode[] => { +const parseContentToReactElements = (content: string, onViewSource?: (sourceInfo: SourceInfo) => void, onHeightChange?: () => void, mode?: "existing" | "new-workflow" | "new-default" | "deploy" | "embed"): React.ReactNode[] => { // 이 시점에서 content는 이미 MessageRenderer에서 문자열로 변환되었음 if (!content) { return []; } - let processed = content; + // 비표준 파이프 문자를 먼저 정규화 + let processed = normalizeTableSeparators(content); // 스트림 모드 감지를 위한 헬퍼 함수 const detectStreaming = (text: string, textStartIndex: number, totalLength: number): boolean => { diff --git a/src/app/_common/components/chatParser/ChatParserMarkdown.tsx b/src/app/_common/components/chatParser/ChatParserMarkdown.tsx index ff6b19be..07458f72 100644 --- a/src/app/_common/components/chatParser/ChatParserMarkdown.tsx +++ b/src/app/_common/components/chatParser/ChatParserMarkdown.tsx @@ -11,9 +11,18 @@ import { processInlineMarkdownWithCitations } from '@/app/_common/components/cha */ export const normalizeTableSeparators = (text: string): string => { return text + .replace(/∣/g, '|') // ∣ (U+2223 DIVIDES) → | .replace(/\u2223/g, '|') // ∣ (DIVIDES) → | .replace(/\u2502/g, '|') // │ (BOX DRAWINGS LIGHT VERTICAL) → | - .replace(/\uFF5C/g, '|'); // | (FULLWIDTH VERTICAL LINE) → | + .replace(/\uFF5C/g, '|') // | (FULLWIDTH VERTICAL LINE) → | + .replace(/|/g, '|') // | (FULLWIDTH VERTICAL LINE) → | + .replace(/│/g, '|') // │ (BOX DRAWINGS) → | + .replace(/\u007C/g, '|') // | (VERTICAL LINE, 정규화) → | + .replace(/\u01C0/g, '|') // ǀ (LATIN LETTER DENTAL CLICK) → | + .replace(/\u05C0/g, '|') // ׀ (HEBREW PUNCTUATION PASEQ) → | + .replace(/\u2758/g, '|') // ❘ (LIGHT VERTICAL BAR) → | + .replace(/\u2759/g, '|') // ❙ (MEDIUM VERTICAL BAR) → | + .replace(/\u275A/g, '|'); // ❚ (HEAVY VERTICAL BAR) → | }; /** @@ -319,13 +328,16 @@ export const parseSimpleMarkdown = ( ): React.ReactNode[] => { if (!text.trim()) return []; + // 비표준 파이프 문자를 먼저 정규화 + const normalizedText = normalizeTableSeparators(text); + // LaTeX가 포함된 경우 전체 텍스트를 LaTeX 처리기로 넘김 (라인 분할하지 않음) - if (hasLatex(text)) { - return processLatexInText(text, `${startKey}-latex`, isStreaming, onHeightChange); + if (hasLatex(normalizedText)) { + return processLatexInText(normalizedText, `${startKey}-latex`, isStreaming, onHeightChange); } const elements: React.ReactNode[] = []; - const lines = text.split('\n'); + const lines = normalizedText.split('\n'); // 연속된 빈 줄을 하나로 축소하여 처리 const processedLines: string[] = []; @@ -344,14 +356,14 @@ export const parseSimpleMarkdown = ( } for (let i = 0; i < processedLines.length; i++) { - const line = normalizeTableSeparators(processedLines[i]); + const line = processedLines[i]; const key = `${startKey}-block-${i}`; // --- 테이블 파싱 로직 (추가된 부분) --- const isTableLine = (str: string) => str.trim().includes('|'); const isTableSeparator = (str: string) => /^\s*\|?(\s*:?-+:?\s*\|)+(\s*:?-+:?\s*\|?)\s*$/.test(str.trim()); - const nextLine = processedLines[i + 1] ? normalizeTableSeparators(processedLines[i + 1]) : undefined; + const nextLine = processedLines[i + 1] ? processedLines[i + 1] : undefined; if (isTableLine(line) && nextLine && isTableSeparator(nextLine)) { const headerLine = line; const separatorLine = nextLine; @@ -359,9 +371,9 @@ export const parseSimpleMarkdown = ( let tableEndIndex = i + 2; while (tableEndIndex < processedLines.length) { - const normalizedLine = normalizeTableSeparators(processedLines[tableEndIndex]); - if (isTableLine(normalizedLine) && !isTableSeparator(normalizedLine)) { - bodyLines.push(normalizedLine); + const currentLine = processedLines[tableEndIndex]; + if (isTableLine(currentLine) && !isTableSeparator(currentLine)) { + bodyLines.push(currentLine); tableEndIndex++; } else { break;