Skip to content
This repository was archived by the owner on Mar 18, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/app/_common/components/chatParser/ChatParser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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; // 편집 버튼 표시 여부
Expand Down Expand Up @@ -140,13 +141,14 @@ export const MessageRenderer: React.FC<MessageRendererProps> = ({
/**
* 컨텐츠를 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 => {
Expand Down
30 changes: 21 additions & 9 deletions src/app/_common/components/chatParser/ChatParserMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) → |
};

/**
Expand Down Expand Up @@ -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[] = [];
Expand All @@ -344,24 +356,24 @@ 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;
const bodyLines = [];

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;
Expand Down