diff --git a/backend/services/langchain_service.py b/backend/services/langchain_service.py
index 006a51b..1fd9bcd 100644
--- a/backend/services/langchain_service.py
+++ b/backend/services/langchain_service.py
@@ -107,6 +107,8 @@ class LangChainRAGService:
8. 중요한 정보나 주의사항이 있다면 강조하세요
9. 문서에 없는 내용은 추측하지 말고 "문서에 명시되지 않음"이라고 하세요
10. 마크다운 형식을 사용하여 구조화된 답변을 제공하세요
+ 11. 영어로 된 사고 과정이나 내부 대화를 절대 포함하지 마세요
+ 12. "part. but the user", "wait, the initial", "let me check" 같은 영어 표현을 절대 사용하지 마세요
**참조 문서:**
{context}
@@ -217,24 +219,27 @@ class LangChainRAGService:
korean_found = False
for line in lines:
- line = line.strip()
- if not line:
- filtered_lines.append(line)
+ original_line = line # 원본 줄 보존 (들여쓰기, 공백 등)
+ line_stripped = line.strip()
+
+ # 빈 줄은 그대로 유지 (마크다운 구조 보존)
+ if not line_stripped:
+ filtered_lines.append(original_line)
continue
# 한국어가 포함된 줄이 나오면 korean_found = True
- if re.search(r'[가-힣]', line):
+ if re.search(r'[가-힣]', line_stripped):
korean_found = True
# 한국어가 발견되기 전까지는 모든 영어 줄 무조건 제거
if not korean_found:
- if re.match(r'^[A-Za-z]', line):
+ if re.match(r'^[A-Za-z]', line_stripped):
continue
- # 한국어가 발견된 후에도 영어 사고 과정 제거
- if re.match(r'^[A-Za-z]', line):
+ # 한국어가 발견된 후에도 영어 사고 과정 제거 (극강화)
+ if re.match(r'^[A-Za-z]', line_stripped):
# 모든 영어 패턴 제거 (더 많은 패턴 추가)
- if any(phrase in line.lower() for phrase in [
+ if any(phrase in line_stripped.lower() for phrase in [
'let me', 'i need to', 'i should', 'i will', 'i can',
'the answer should', 'make sure', 'avoid any',
'structure the answer', 'break down', 'organize',
@@ -243,24 +248,90 @@ class LangChainRAGService:
'thinking process', 'internal thought',
'i need to focus', 'main content', 'tag', 'thinking',
'tag or any', 'so i need', 'focus on', 'main content of',
- 'documents', 'thinking', 'process', 'internal'
+ 'documents', 'thinking', 'process', 'internal',
+ 'part. but the user', 'wait, the initial', 'let me check',
+ 'user also wants', 'answer in markdown', 'instructions say',
+ 'use markdown for', 'check again', 'but the user also',
+ 'wants the answer in', 'markdown. wait', 'the initial instructions',
+ 'say to use markdown', 'for the answer', 'let me check again',
+ 'part. but the user also wants the answer in markdown',
+ 'wait, the initial instructions say to use markdown',
+ 'let me check again', 'but the user also wants',
+ 'wants the answer in markdown', 'markdown. wait',
+ 'the initial instructions', 'say to use markdown for the answer'
]):
continue
# 영어 문장이지만 중요한 내용이 아닌 경우 제거
- if len(line) > 10 and any(word in line.lower() for word in [
+ if len(line_stripped) > 10 and any(word in line_stripped.lower() for word in [
'thinking', 'process', 'structure', 'format', 'markdown',
'tag', 'focus', 'content', 'documents', 'internal',
- 'answer', 'should', 'make', 'sure', 'avoid'
+ 'answer', 'should', 'make', 'sure', 'avoid',
+ 'user', 'wants', 'instructions', 'check', 'again',
+ 'part', 'but', 'let', 'me', 'say', 'use', 'for',
+ 'wait', 'initial', 'also', 'the', 'in', 'to'
]):
continue
# 짧은 영어 문장도 제거 (사고 과정일 가능성)
- if len(line) < 200 and not re.search(r'[가-힣]', line):
+ if len(line_stripped) < 200 and not re.search(r'[가-힣]', line_stripped):
continue
- filtered_lines.append(line)
+ # 원본 줄 유지 (들여쓰기, 공백 등 마크다운 구조 보존)
+ filtered_lines.append(original_line)
answer = '\n'.join(filtered_lines).strip()
+ # 마크다운 구조 보존을 위한 추가 처리
+ # 연속된 빈 줄을 하나로 정리하되, 마크다운 구조는 유지
+ answer = re.sub(r'\n\s*\n\s*\n+', '\n\n', answer)
+
+ # 마크다운 문법이 있는지 확인하고, 없다면 강제로 마크다운 형식 적용
+ if not re.search(r'^#{1,6}\s|^\*\*|^[-*]\s|^\d+\.\s|^\|', answer, re.MULTILINE):
+ # 마크다운 문법이 없으면 첫 번째 줄을 제목으로 만들기
+ lines = answer.split('\n')
+ if lines and lines[0].strip():
+ # 첫 번째 줄이 제목이 될 수 있는 내용인지 확인
+ first_line = lines[0].strip()
+ if len(first_line) > 5 and not first_line.endswith('.'):
+ lines[0] = f"# {first_line}"
+ answer = '\n'.join(lines)
+
+ # 추가 영어 사고 과정 제거 (전체 텍스트 레벨)
+ english_phrases_to_remove = [
+ "part. but the user also wants the answer in markdown. wait, the initial instructions say to use markdown for the answer. let me check again.",
+ "part. but the user also wants the answer in markdown",
+ "wait, the initial instructions say to use markdown",
+ "let me check again",
+ "but the user also wants",
+ "wants the answer in markdown",
+ "markdown. wait",
+ "the initial instructions",
+ "say to use markdown for the answer",
+ "part. but the user",
+ "user also wants",
+ "answer in markdown",
+ "initial instructions",
+ "use markdown",
+ "check again"
+ ]
+
+ for phrase in english_phrases_to_remove:
+ answer = answer.replace(phrase, '').strip()
+ answer = answer.replace(phrase.lower(), '').strip()
+ answer = answer.replace(phrase.upper(), '').strip()
+ answer = answer.replace(phrase.capitalize(), '').strip()
+
+ # 정규식으로 영어 사고 과정 제거
+ import re
+ english_thinking_patterns = [
+ r'^[A-Za-z].*user.*wants.*answer.*markdown.*$',
+ r'^[A-Za-z].*part\.\s*but.*user.*also.*wants.*$',
+ r'^[A-Za-z].*wait.*initial.*instructions.*$',
+ r'^[A-Za-z].*let.*me.*check.*again.*$'
+ ]
+
+ for pattern in english_thinking_patterns:
+ answer = re.sub(pattern, '', answer, flags=re.MULTILINE | re.IGNORECASE).strip()
+
response = {
"answer": answer,
"references": references,
diff --git a/frontend/src/components/ChatInterface.tsx b/frontend/src/components/ChatInterface.tsx
index 86d716c..1d1309c 100644
--- a/frontend/src/components/ChatInterface.tsx
+++ b/frontend/src/components/ChatInterface.tsx
@@ -96,7 +96,7 @@ const ChatInterface: React.FC = () => {
};
return (
-
+
{/* 메시지 영역 */}
{messages.length === 0 ? (
@@ -140,7 +140,19 @@ const ChatInterface: React.FC = () => {
{/* 입력 영역 */}
-
+
@@ -153,6 +165,18 @@ const ChatInterface: React.FC = () => {
placeholder="질문을 입력하세요..."
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all"
disabled={isLoading}
+ style={{
+ border: '1px solid #d1d5db',
+ backgroundColor: 'white',
+ color: '#374151',
+ display: 'block',
+ visibility: 'visible',
+ opacity: 1,
+ width: '100%',
+ minHeight: '48px',
+ paddingLeft: '20px',
+ paddingRight: '16px'
+ }}
/>
{
onClick={handleSendMessage}
disabled={!inputMessage.trim() || isLoading}
className="px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 text-white rounded-xl font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-all btn-animate"
+ style={{
+ display: 'block',
+ visibility: 'visible',
+ opacity: 1,
+ backgroundColor: 'linear-gradient(to right, #2563eb, #9333ea)',
+ color: 'white'
+ }}
>
diff --git a/frontend/src/components/MessageBubble.tsx b/frontend/src/components/MessageBubble.tsx
index 908e3ee..9de90a2 100644
--- a/frontend/src/components/MessageBubble.tsx
+++ b/frontend/src/components/MessageBubble.tsx
@@ -1,11 +1,9 @@
import React, { useState } from 'react';
import { motion } from 'framer-motion';
import { User, FileText } from 'lucide-react';
-import ReactMarkdown from 'react-markdown';
-import remarkGfm from 'remark-gfm';
-import remarkBreaks from 'remark-breaks';
import { Message } from '../contexts/ChatContext';
import PDFViewer from './PDFViewer';
+import SimpleMarkdownRenderer from './SimpleMarkdownRenderer';
interface MessageBubbleProps {
message: Message;
@@ -115,45 +113,7 @@ const MessageBubble: React.FC
= ({ message }) => {
{message.content}
) : (
-
-
{children}
,
- // code 태그 처리
- code: ({ children, className }) => {
- const isInline = !className;
- return isInline ? (
- {children}
- ) : (
- {children}
- );
- },
- // 모든 마크다운 요소에 대한 커스텀 스타일링
- h1: ({ children }) => {children}
,
- h2: ({ children }) => {children}
,
- h3: ({ children }) => {children}
,
- h4: ({ children }) => {children}
,
- p: ({ children }) => {children}
,
- ul: ({ children }) => ,
- ol: ({ children }) => {children}
,
- li: ({ children }) => {children},
- strong: ({ children }) => {children},
- em: ({ children }) => {children},
- blockquote: ({ children }) => {children}
,
- table: ({ children }) => ,
- thead: ({ children }) => {children},
- tbody: ({ children }) => {children},
- tr: ({ children }) => {children}
,
- th: ({ children }) => {children} | ,
- td: ({ children }) => {children} | ,
- hr: () =>
,
- }}
- >
- {message.content}
-
-
+
)}
{/* 소스 정보 */}
@@ -214,4 +174,4 @@ const MessageBubble: React.FC
= ({ message }) => {
);
};
-export default MessageBubble;
+export default MessageBubble;
\ No newline at end of file
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 3eb7cfc..dc3618a 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -16,7 +16,6 @@ body {
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
@@ -310,18 +309,8 @@ code {
border: none !important;
}
-/* 모든 div 요소의 상단 테두리 제거 */
-div {
- border-top: none !important;
-}
-
-/* 특정 클래스의 테두리 제거 */
-.border-t {
- border-top: none !important;
-}
-
-/* 모든 가능한 선과 배경 제거 */
-* {
+/* 특정 클래스의 테두리 제거 (입력 영역 제외) */
+.border-t:not(input):not(.input-area) {
border-top: none !important;
}
@@ -437,6 +426,42 @@ div {
line-height: inherit !important;
}
+/* 마크다운 pre 래퍼 스타일 */
+.markdown-pre-wrapper {
+ white-space: normal !important;
+ word-wrap: break-word !important;
+ background: transparent !important;
+ border: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ font-family: inherit !important;
+ font-size: inherit !important;
+ line-height: inherit !important;
+ display: block !important;
+ overflow: visible !important;
+}
+
+/* react-markdown이 생성하는 모든 pre 태그에 대한 강력한 리셋 */
+.markdown-content pre,
+.markdown-content > pre,
+.markdown-content pre[class*="language-"],
+.markdown-content pre:not([class]),
+.markdown-content pre code,
+.markdown-content > pre code {
+ white-space: normal !important;
+ word-wrap: break-word !important;
+ background: transparent !important;
+ border: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ font-family: inherit !important;
+ font-size: inherit !important;
+ line-height: inherit !important;
+ display: block !important;
+ overflow: visible !important;
+ color: inherit !important;
+}
+
/* 실제 코드 블록에만 적용되는 pre 태그 스타일 */
.markdown-content pre {
background-color: #F3F4F6 !important;
@@ -491,9 +516,49 @@ div {
}
.markdown-content hr {
+ display: none !important;
+ visibility: hidden !important;
+ height: 0 !important;
+ margin: 0 !important;
+ padding: 0 !important;
border: none !important;
- border-top: 1px solid #E5E7EB !important;
- margin: 2rem 0 !important;
+ border-top: none !important;
+ border-bottom: none !important;
+ border-left: none !important;
+ border-right: none !important;
+ outline: none !important;
+ box-shadow: none !important;
+}
+
+/* 모든 hr 요소 강제 숨김 */
+hr {
+ display: none !important;
+ visibility: hidden !important;
+ height: 0 !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ border: none !important;
+ outline: none !important;
+ box-shadow: none !important;
+}
+
+/* 모든 가능한 가로줄 요소 강제 숨김 */
+hr,
+.hr,
+.horizontal-line,
+.border-t,
+.border-top,
+[class*="border-t"],
+[class*="border-top"] {
+ display: none !important;
+ visibility: hidden !important;
+ height: 0 !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ border: none !important;
+ border-top: none !important;
+ outline: none !important;
+ box-shadow: none !important;
}
/* 참조 문서 섹션의 모든 선과 배경 제거 */
@@ -505,4 +570,65 @@ div {
border-right: none !important;
background: transparent !important;
background-color: transparent !important;
+ outline: none !important;
+ box-shadow: none !important;
+}
+
+/* 모든 hr 태그 완전 제거 */
+hr {
+ display: none !important;
+ visibility: hidden !important;
+ height: 0 !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ border: none !important;
+ background: none !important;
+}
+
+/* 참조 문서 위의 모든 선 제거 */
+.mt-3.pt-3::before,
+.mt-3.pt-3::after {
+ display: none !important;
+ content: none !important;
+}
+
+/* 입력 영역 스타일 보호 */
+.input-area {
+ border-top: none !important;
+ background: white !important;
+ position: relative !important;
+ z-index: 10 !important;
+ display: block !important;
+ visibility: visible !important;
+ opacity: 1 !important;
+ height: auto !important;
+ min-height: 80px !important;
+}
+
+.input-area input {
+ border: 1px solid #d1d5db !important;
+ background: white !important;
+ color: #374151 !important;
+ display: block !important;
+ visibility: visible !important;
+ opacity: 1 !important;
+ width: 100% !important;
+ height: auto !important;
+ min-height: 48px !important;
+ padding-left: 20px !important;
+ padding-right: 16px !important;
+}
+
+.input-area input:focus {
+ border: 1px solid #3b82f6 !important;
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
+}
+
+/* 입력 영역 버튼 스타일 */
+.input-area button {
+ display: block !important;
+ visibility: visible !important;
+ opacity: 1 !important;
+ background: linear-gradient(to right, #2563eb, #9333ea) !important;
+ color: white !important;
}