init
This commit is contained in:
parent
94e8857f44
commit
b2eca41af3
@ -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,
|
||||
|
||||
@ -96,7 +96,7 @@ const ChatInterface: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col h-full">
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
{/* 메시지 영역 */}
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-16 bg-white">
|
||||
{messages.length === 0 ? (
|
||||
@ -140,7 +140,19 @@ const ChatInterface: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* 입력 영역 */}
|
||||
<div className="bg-white border-t border-gray-200 p-4">
|
||||
<div
|
||||
className="bg-white p-4 input-area"
|
||||
style={{
|
||||
position: 'relative',
|
||||
zIndex: 1000,
|
||||
display: 'block',
|
||||
visibility: 'visible',
|
||||
opacity: 1,
|
||||
minHeight: '80px',
|
||||
backgroundColor: 'white',
|
||||
borderTop: 'none'
|
||||
}}
|
||||
>
|
||||
<div className="w-full max-w-none mx-auto px-4" style={{ maxWidth: '80vw' }}>
|
||||
<div className="flex space-x-3">
|
||||
<div className="flex-1 relative">
|
||||
@ -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'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<motion.button
|
||||
@ -161,6 +185,13 @@ const ChatInterface: React.FC = () => {
|
||||
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'
|
||||
}}
|
||||
>
|
||||
<Send className="w-5 h-5" />
|
||||
</motion.button>
|
||||
|
||||
@ -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<MessageBubbleProps> = ({ message }) => {
|
||||
{message.content}
|
||||
</div>
|
||||
) : (
|
||||
<div className="markdown-content">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
components={{
|
||||
// pre 태그를 완전히 제거하고 일반 div로 대체
|
||||
pre: ({ children }) => <div>{children}</div>,
|
||||
// code 태그 처리
|
||||
code: ({ children, className }) => {
|
||||
const isInline = !className;
|
||||
return isInline ? (
|
||||
<code className="bg-gray-200 px-2 py-1 rounded text-sm font-mono text-gray-800">{children}</code>
|
||||
) : (
|
||||
<div className="bg-gray-100 text-gray-800 p-4 rounded-lg overflow-x-auto mb-4 font-mono text-sm">{children}</div>
|
||||
);
|
||||
},
|
||||
// 모든 마크다운 요소에 대한 커스텀 스타일링
|
||||
h1: ({ children }) => <h1 className="text-2xl font-bold mb-4 text-gray-800 border-b border-gray-200 pb-2">{children}</h1>,
|
||||
h2: ({ children }) => <h2 className="text-xl font-bold mb-3 text-gray-800 mt-6">{children}</h2>,
|
||||
h3: ({ children }) => <h3 className="text-lg font-bold mb-2 text-gray-800 mt-4">{children}</h3>,
|
||||
h4: ({ children }) => <h4 className="text-base font-bold mb-2 text-gray-800 mt-3">{children}</h4>,
|
||||
p: ({ children }) => <p className="mb-3 text-gray-700 leading-relaxed">{children}</p>,
|
||||
ul: ({ children }) => <ul className="list-disc list-inside mb-4 text-gray-700 space-y-1 ml-4">{children}</ul>,
|
||||
ol: ({ children }) => <ol className="list-decimal list-inside mb-4 text-gray-700 space-y-1 ml-4">{children}</ol>,
|
||||
li: ({ children }) => <li className="mb-1">{children}</li>,
|
||||
strong: ({ children }) => <strong className="font-bold text-gray-800">{children}</strong>,
|
||||
em: ({ children }) => <em className="italic text-gray-700">{children}</em>,
|
||||
blockquote: ({ children }) => <blockquote className="border-l-4 border-blue-300 pl-4 italic text-gray-600 bg-blue-50 py-2 rounded-r mb-4">{children}</blockquote>,
|
||||
table: ({ children }) => <div className="overflow-x-auto mb-4"><table className="min-w-full border-collapse border border-gray-300">{children}</table></div>,
|
||||
thead: ({ children }) => <thead className="bg-gray-50">{children}</thead>,
|
||||
tbody: ({ children }) => <tbody>{children}</tbody>,
|
||||
tr: ({ children }) => <tr className="border-b border-gray-200 hover:bg-gray-50">{children}</tr>,
|
||||
th: ({ children }) => <th className="border border-gray-300 px-4 py-2 text-left font-semibold text-gray-800 bg-gray-100">{children}</th>,
|
||||
td: ({ children }) => <td className="border border-gray-300 px-4 py-2 text-gray-700">{children}</td>,
|
||||
hr: () => <hr className="my-6 border-gray-300" />,
|
||||
}}
|
||||
>
|
||||
{message.content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
<SimpleMarkdownRenderer content={message.content || ''} />
|
||||
)}
|
||||
|
||||
{/* 소스 정보 */}
|
||||
@ -214,4 +174,4 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageBubble;
|
||||
export default MessageBubble;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user