# ui/components.py
"""PROBIN UI 컴포넌트"""
import streamlit as st
from typing import List, Dict
def render_header():
"""헤더 렌더링 (PROBIN 버전)"""
st.markdown(
"""
🧠 PROBIN
Intelligent Document Analysis System
정확도 우선 RAG 시스템
""",
unsafe_allow_html=True
)
def render_sources_with_relevance(sources: List[Dict], message_idx: int, move_to_page_callback):
"""
출처 렌더링 (가장 관련도 높은 것 우선)
Args:
sources: 출처 리스트 (이미 관련도 순으로 정렬됨)
message_idx: 메시지 인덱스 (키 중복 방지용)
move_to_page_callback: 페이지 이동 콜백 함수
구조:
🎯 핵심 근거 (1개) - sources[0]: 가장 관련도 높은 출처
📚 추가 참고 문서 (나머지) - Expander 내부
"""
if not sources:
return
# 첫 번째 출처 = 가장 관련도 높음
st.markdown("---")
st.markdown("**🎯 핵심 근거:**")
primary_source = sources[0]
if st.button(
f"📍 페이지 {primary_source['page_num']} 확인",
key=f"primary_{message_idx}",
type="primary",
use_container_width=True
):
move_to_page_callback(primary_source['page_num'], primary_source['text'])
# 미리보기
st.caption(f"💬 \"{primary_source['text'][:100]}...\"")
# 추가 참고 문서 (나머지)
if len(sources) > 1:
additional_sources = sources[1:]
with st.expander(f"📚 추가 참고 문서 ({min(2, len(additional_sources))}개)"):
cols = st.columns(2)
for i, src in enumerate(additional_sources[:2]): # 최대 2개만
with cols[i]:
if st.button(
f"p.{src['page_num']}",
key=f"additional_{message_idx}_{i}",
use_container_width=True
):
move_to_page_callback(src['page_num'], src['text'])
st.caption(f"\"{src['text'][:60]}...\"")
def render_answer(answer: str, sources: List[Dict]):
"""답변 및 출처 렌더링 (Legacy - 사용 안 함)"""
# 답변
st.markdown("### 💡 답변")
st.markdown(
f"""
{answer}
""",
unsafe_allow_html=True
)
# 출처
if sources:
st.markdown("### 📄 출처")
for i, source in enumerate(sources, 1):
with st.expander(f"출처 {i} - 페이지 {source['page_num']}"):
st.text(source['text'])
def render_file_uploader():
"""파일 업로더 렌더링"""
st.markdown("### 📤 PDF 업로드")
uploaded_file = st.file_uploader(
"RFP PDF 파일을 업로드하세요",
type=["pdf"],
help="PDF 형식의 RFP 문서를 업로드하세요"
)
return uploaded_file
def render_query_input():
"""질문 입력 렌더링 (Deprecated: st.chat_input 사용 권장)"""
st.markdown("### 💬 질문하기")
query = st.text_input(
"질문을 입력하세요",
placeholder="예: 이 프로젝트의 예산은 얼마인가요?",
help="RFP 문서에 대해 질문하세요"
)
return query
def render_chat_history(messages: list):
"""채팅 히스토리 렌더링 (현대적 방식)"""
for message in messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# 출처 표시
if message["role"] == "assistant" and "sources" in message:
with st.expander("📚 출처 보기"):
for i, src in enumerate(message["sources"], 1):
st.markdown(f"**출처 {i} - [페이지 {src['page_num']}]**")
st.caption(src["text"])
def render_sidebar():
"""사이드바 렌더링"""
with st.sidebar:
st.markdown("## ⚙️ 설정")
# 검색 설정
st.markdown("### 🔍 검색 설정")
top_k = st.slider("검색할 청크 수", 5, 20, 10)
# 청킹 설정
st.markdown("### ✂️ 청킹 설정")
chunk_size = st.number_input("청크 크기", 400, 1200, 800, step=100)
chunk_overlap = st.number_input("오버랩 크기", 50, 300, 150, step=50)
st.markdown("---")
# 정보
st.markdown("### ℹ️ 정보")
st.info(
"""
**PROBIN v2.0**
- PDF 업로드 ✅
- 질문-답변 ✅
- 출처 표시 ✅
- 벡터 검색 ✅
- 하이라이트 ✅
"""
)
# 초기화 버튼
if st.button("🗑️ 데이터베이스 초기화", type="secondary"):
return True, top_k, chunk_size, chunk_overlap
return False, top_k, chunk_size, chunk_overlap
def render_processing_status(status: str):
"""처리 상태 렌더링"""
status_icons = {
"uploading": "📤",
"extracting": "📄",
"chunking": "✂️",
"embedding": "🔢",
"storing": "💾",
"complete": "✅"
}
icon = status_icons.get(status, "⏳")
st.info(f"{icon} {status}")
def render_welcome_message():
"""웰컴 메시지 (사용 안내)"""
st.markdown("""
🧠 PROBIN에 오신 것을 환영합니다!
Intelligent Document Analysis System
📖 이용 방법
- PDF 업로드: 왼쪽 사이드바에서 문서를 업로드하세요
- 문서 처리: 30초~1분 정도 기다립니다
- 질문 입력: 채팅창에 질문을 입력하세요
- 근거 확인: 노란색 하이라이트로 근거를 확인하세요
📺 Split View
🖍️ Highlighting
🎯 High Accuracy
""", unsafe_allow_html=True)