immunochat2 / gradio_app.py
lss9566's picture
Upload gradio_app.py
f9b6151 verified
import gradio as gr
from enhanced_rag_system import EnhancedImmunoRAGSystem
import json
# RAG ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™”
rag_system = None
def initialize_rag_system():
global rag_system
try:
rag_system = EnhancedImmunoRAGSystem("merged_qa_dataset.json")
return "โœ… ์‹œ์Šคํ…œ ์ค€๋น„ ์™„๋ฃŒ"
except Exception as e:
return f"โŒ ๋กœ๋”ฉ ์‹คํŒจ: {str(e)}"
def answer_question(question, history):
if rag_system is None:
return history + [[question, "โš ๏ธ ์‹œ์Šคํ…œ์„ ๋จผ์ € ์ดˆ๊ธฐํ™”ํ•ด์ฃผ์„ธ์š”."]]
if not question.strip():
return history + [[question, "๐Ÿ’ญ ์งˆ๋ฌธ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."]]
try:
result = rag_system.answer_question(question)
# ๊น”๋”ํ•œ ์‘๋‹ต ํฌ๋งท
confidence = result['confidence']
confidence_bar = "โ–ˆ" * int(confidence * 10) + "โ–‘" * (10 - int(confidence * 10))
confidence_color = "๐ŸŸข" if confidence > 0.7 else "๐ŸŸก" if confidence > 0.4 else "๐Ÿ”ด"
response = f"{result['answer']}\n\n"
response += f"{confidence_color} **์‹ ๋ขฐ๋„** `{confidence:.0%}` {confidence_bar}\n\n"
if result['matched_questions'] and confidence > 0.3:
top_match = result['matched_questions'][0]
response += f"๐Ÿ’ก **๊ด€๋ จ ์งˆ๋ฌธ:** {top_match['question']}"
return history + [[question, response]]
except Exception as e:
return history + [[question, f"โŒ **์˜ค๋ฅ˜**\n```\n{str(e)}\n```"]]
def clear_chat():
return []
# ์„ธ๋ จ๋œ CSS ์Šคํƒ€์ผ
custom_css = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
:root {
--primary: #6366f1;
--primary-light: #818cf8;
--primary-dark: #4f46e5;
--secondary: #64748b;
--accent: #06b6d4;
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
--background: #fafbfc;
--surface: #ffffff;
--surface-2: #f8fafc;
--border: #e1e5e9;
--border-light: #f1f3f4;
--text: #1e293b;
--text-light: #64748b;
--text-muted: #94a3b8;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--radius: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
}
/* ์ „์—ญ ๋ฆฌ์…‹ ๋ฐ ๊ธฐ๋ณธ ์Šคํƒ€์ผ */
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
box-sizing: border-box;
}
/* ์ „์—ญ ํ…์ŠคํŠธ ์ƒ‰์ƒ */
*:not(.app-header):not(.app-header *):not(.user-message):not(.user-message *) {
color: var(--text) !important;
}
/* ๋ฉ”์ธ ์ปจํ…Œ์ด๋„ˆ */
.gradio-container {
max-width: 1000px !important;
margin: 0 auto !important;
padding: 24px !important;
background: var(--background) !important;
min-height: 100vh;
}
/* ํ—ค๋” - ๋ฏธ๋‹ˆ๋ฉ€ ๋””์ž์ธ */
.app-header {
text-align: center;
padding: 48px 32px;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
border-radius: var(--radius-xl);
margin-bottom: 32px;
box-shadow: var(--shadow-lg);
position: relative;
overflow: hidden;
}
.app-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
pointer-events: none;
}
.app-header * {
color: white !important;
position: relative;
z-index: 1;
}
.app-header h1 {
font-size: 2.75rem;
font-weight: 700;
margin: 0 0 12px 0;
letter-spacing: -0.02em;
line-height: 1.1;
}
.app-header p {
font-size: 1.125rem;
opacity: 0.9;
margin: 0;
font-weight: 400;
line-height: 1.5;
}
/* ์ฑ„ํŒ… ์ปจํ…Œ์ด๋„ˆ - ๊น”๋”ํ•œ ์นด๋“œ ๋””์ž์ธ */
.chat-container {
background: var(--surface);
border-radius: var(--radius-xl);
padding: 0;
margin-bottom: 24px;
border: 1px solid var(--border-light);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
/* ์ฑ„ํŒ…๋ฐ•์Šค */
.chatbot {
border: none !important;
border-radius: 0 !important;
background: transparent !important;
box-shadow: none !important;
min-height: 500px !important;
padding: 24px !important;
}
.chatbot .message {
border-radius: var(--radius) !important;
padding: 20px !important;
margin: 16px 0 !important;
box-shadow: none !important;
border: none !important;
max-width: 85% !important;
}
/* ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ */
.chatbot .message.user,
.user-message {
background: var(--primary) !important;
margin-left: auto !important;
margin-right: 0 !important;
border-radius: var(--radius) var(--radius) 4px var(--radius) !important;
}
.chatbot .message.user *,
.user-message * {
color: white !important;
}
/* ๋ด‡ ๋ฉ”์‹œ์ง€ */
.chatbot .message.bot {
background: var(--surface-2) !important;
margin-left: 0 !important;
margin-right: auto !important;
border: 1px solid var(--border-light) !important;
border-radius: var(--radius) var(--radius) var(--radius) 4px !important;
}
.chatbot .message.bot * {
color: var(--text) !important;
}
/* ์ž…๋ ฅ ์˜์—ญ - ํ”Œ๋กœํŒ… ๋””์ž์ธ */
.input-container {
background: var(--surface);
border-radius: var(--radius-lg);
padding: 20px;
border: 1px solid var(--border-light);
box-shadow: var(--shadow-sm);
margin-top: 24px;
}
.input-row {
background: transparent;
border-radius: 0;
padding: 0;
border: none;
box-shadow: none;
margin: 0;
}
.input-row .textbox,
.input-row input,
.input-row textarea {
border: 1px solid var(--border) !important;
background: var(--surface) !important;
border-radius: var(--radius) !important;
padding: 16px 20px !important;
font-size: 16px !important;
line-height: 1.5 !important;
transition: all 0.2s ease !important;
color: var(--text) !important;
box-shadow: var(--shadow-sm) !important;
}
.input-row .textbox::placeholder,
.input-row input::placeholder,
.input-row textarea::placeholder {
color: var(--text-muted) !important;
opacity: 1 !important;
}
.input-row .textbox:focus,
.input-row input:focus,
.input-row textarea:focus {
border-color: var(--primary) !important;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1) !important;
outline: none !important;
}
/* ๋ฒ„ํŠผ - ๋ชจ๋˜ ๋””์ž์ธ */
.btn-primary,
button[variant="primary"] {
background: var(--primary) !important;
border: none !important;
border-radius: var(--radius) !important;
padding: 16px 24px !important;
font-weight: 600 !important;
font-size: 15px !important;
line-height: 1 !important;
transition: all 0.2s ease !important;
box-shadow: var(--shadow-sm) !important;
color: white !important;
cursor: pointer !important;
}
.btn-primary:hover,
button[variant="primary"]:hover {
background: var(--primary-dark) !important;
transform: translateY(-1px) !important;
box-shadow: var(--shadow-md) !important;
}
.btn-secondary {
background: var(--surface) !important;
border: 1px solid var(--border) !important;
border-radius: var(--radius) !important;
padding: 16px 24px !important;
font-weight: 500 !important;
font-size: 15px !important;
color: var(--text-light) !important;
transition: all 0.2s ease !important;
cursor: pointer !important;
}
.btn-secondary:hover {
border-color: var(--primary) !important;
color: var(--primary) !important;
transform: translateY(-1px) !important;
box-shadow: var(--shadow-sm) !important;
}
/* ์˜ˆ์‹œ ์งˆ๋ฌธ - ์นด๋“œ ๊ทธ๋ฆฌ๋“œ */
.examples-section {
margin: 32px 0;
}
.examples-title {
font-size: 1.125rem;
font-weight: 600;
color: var(--text);
margin-bottom: 16px;
text-align: center;
}
.example-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
margin-top: 20px;
}
.example-btn {
background: var(--surface) !important;
border: 1px solid var(--border-light) !important;
border-radius: var(--radius) !important;
padding: 20px !important;
text-align: left !important;
font-size: 14px !important;
font-weight: 500 !important;
color: var(--text) !important;
transition: all 0.2s ease !important;
box-shadow: var(--shadow-sm) !important;
cursor: pointer !important;
line-height: 1.4 !important;
}
.example-btn:hover {
border-color: var(--primary) !important;
background: var(--surface) !important;
transform: translateY(-2px) !important;
box-shadow: var(--shadow-md) !important;
color: var(--primary) !important;
}
/* ํƒญ - ๋ฏธ๋‹ˆ๋ฉ€ ๋””์ž์ธ */
.tab-nav {
background: var(--surface) !important;
border-radius: var(--radius-lg) !important;
border: 1px solid var(--border-light) !important;
overflow: hidden !important;
box-shadow: var(--shadow-sm) !important;
margin-bottom: 24px !important;
}
.tab-nav .tab-item {
padding: 20px 32px !important;
font-weight: 500 !important;
font-size: 15px !important;
transition: all 0.2s ease !important;
color: var(--text-light) !important;
border: none !important;
}
.tab-nav .tab-item.selected {
background: var(--primary) !important;
color: white !important;
}
.tab-nav .tab-item:hover:not(.selected) {
background: var(--surface-2) !important;
color: var(--text) !important;
}
/* ์‹œ์Šคํ…œ ์ •๋ณด ์„น์…˜ */
.system-info {
background: var(--surface);
border-radius: var(--radius-lg);
padding: 32px;
border: 1px solid var(--border-light);
box-shadow: var(--shadow-sm);
}
/* ๋งˆํฌ๋‹ค์šด ์Šคํƒ€์ผ๋ง */
.markdown,
.gr-markdown {
color: var(--text) !important;
line-height: 1.6;
}
.markdown h1, .markdown h2, .markdown h3,
.gr-markdown h1, .gr-markdown h2, .gr-markdown h3 {
color: var(--text) !important;
font-weight: 600;
margin-top: 24px;
margin-bottom: 12px;
}
.markdown h3, .gr-markdown h3 {
font-size: 1.125rem;
color: var(--text) !important;
}
.markdown p, .gr-markdown p {
color: var(--text) !important;
margin-bottom: 16px;
}
.markdown table, .gr-markdown table {
border-collapse: collapse;
width: 100%;
margin: 20px 0;
border-radius: var(--radius);
overflow: hidden;
box-shadow: var(--shadow-sm);
}
.markdown th, .gr-markdown th {
background: var(--surface-2) !important;
color: var(--text) !important;
font-weight: 600;
padding: 16px;
text-align: left;
border-bottom: 1px solid var(--border);
}
.markdown td, .gr-markdown td {
color: var(--text) !important;
padding: 16px;
border-bottom: 1px solid var(--border-light);
}
.markdown strong, .gr-markdown strong {
color: var(--text) !important;
font-weight: 600;
}
.markdown code, .gr-markdown code {
background: var(--surface-2);
color: var(--text) !important;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.875em;
}
/* ์ƒํƒœ ํ‘œ์‹œ */
.status-output {
background: var(--surface-2);
border: 1px solid var(--border-light);
border-radius: var(--radius);
padding: 16px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 14px;
color: var(--text) !important;
}
/* ๋ชจ๋ฐ”์ผ ์ตœ์ ํ™” */
@media (max-width: 768px) {
.gradio-container {
padding: 16px !important;
}
.app-header {
padding: 32px 24px !important;
margin-bottom: 24px !important;
}
.app-header h1 {
font-size: 2.25rem !important;
}
.chatbot {
padding: 16px !important;
min-height: 400px !important;
}
.chatbot .message {
max-width: 90% !important;
padding: 16px !important;
}
.input-container {
padding: 16px !important;
}
.example-grid {
grid-template-columns: 1fr !important;
gap: 12px !important;
}
.example-btn {
padding: 16px !important;
}
}
/* ๋‹คํฌ๋ชจ๋“œ ์ง€์› */
@media (prefers-color-scheme: dark) {
:root {
--background: #0f172a;
--surface: #1e293b;
--surface-2: #334155;
--border: #475569;
--border-light: #374151;
--text: #f1f5f9;
--text-light: #cbd5e0;
--text-muted: #94a3b8;
}
}
/* ๋ถ€๋“œ๋Ÿฌ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜ */
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: slideUp 0.4s ease-out;
}
/* ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ๋ง */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--surface-2);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
"""
# ์˜ˆ์‹œ ์งˆ๋ฌธ๋“ค (๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ)
example_questions = [
"HBs Ag ์ฐธ๊ณ ์น˜๋Š”?",
"CEA ์ •์ƒ๋ฒ”์œ„๋Š”?",
"PIVKA ๊ฒ€์‚ฌ ์›๋ฆฌ๋Š”?",
"CA19-9 ์–ธ์ œ ๋‚˜์™€์š”?",
"AFP ๊ธฐ์ค€์น˜๋Š”?",
"PSA ์ •์ƒ๊ฐ’์€?"
]
# Gradio ์ธํ„ฐํŽ˜์ด์Šค
with gr.Blocks(
title="๋ฉด์—ญ๊ฒ€์‚ฌ AI ์ฑ—๋ด‡",
theme=gr.themes.Default(
primary_hue="blue",
secondary_hue="slate",
neutral_hue="slate",
font=gr.themes.GoogleFont("Inter")
),
css=custom_css
) as demo:
# ํ—ค๋”
gr.HTML("""
<div class="app-header fade-in">
<h1>๐Ÿงฌ ๋ฉด์—ญ๊ฒ€์‚ฌ AI</h1>
<p>์ •ํ™•ํ•˜๊ณ  ๋น ๋ฅด์€ ๋ฉด์—ญ๊ฒ€์‚ฌ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค</p>
</div>
""")
with gr.Tabs(elem_classes="tab-nav") as tabs:
# ์ฑ„ํŒ… ํƒญ
with gr.Tab("๐Ÿ’ฌ ์ฑ„ํŒ…"):
with gr.Group(elem_classes="chat-container"):
gr.Markdown("๐Ÿ’ฌ ์•„๋ž˜ ์ฑ„ํŒ…์ฐฝ์— ์งˆ๋ฌธ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. ์ฒ˜์Œ ๋กœ๋”ฉ ์‹œ ๋ชจ๋ธ ์ค€๋น„์— ์‹œ๊ฐ„์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")
chatbot = gr.Chatbot(
height=500,
)
with gr.Row():
msg = gr.Textbox(
placeholder="์˜ˆ: HBs Ag ์ฐธ๊ณ ์น˜๋Š” ์–ผ๋งˆ์ธ๊ฐ€์š”?",
container=False,
scale=4,
show_label=False,
elem_classes="textbox"
)
with gr.Column(scale=1, min_width=120):
submit_btn = gr.Button(
"์ „์†ก",
variant="primary",
elem_classes="btn-primary"
)
clear_btn = gr.Button(
"์ดˆ๊ธฐํ™”",
elem_classes="btn-secondary"
)
# ์˜ˆ์‹œ ์งˆ๋ฌธ๋“ค
gr.Markdown("### ๐Ÿ’ก ๋น ๋ฅธ ์งˆ๋ฌธ")
gr.HTML('<div class="example-grid">')
with gr.Row():
for i, question in enumerate(example_questions):
if i % 3 == 0 and i > 0:
gr.HTML('</div><div class="example-grid">')
example_btn = gr.Button(
f"๐Ÿ’ญ {question}",
elem_classes="example-btn"
)
example_btn.click(
lambda x=question: x,
outputs=msg
)
gr.HTML('</div>')
# ์‹œ์Šคํ…œ ์ •๋ณด ํƒญ
with gr.Tab("โš™๏ธ ์‹œ์Šคํ…œ"):
with gr.Row():
init_btn = gr.Button(
"๐Ÿ”„ ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™”",
variant="primary",
elem_classes="btn-primary"
)
init_output = gr.Textbox(
label="์ƒํƒœ",
interactive=False,
lines=1
)
# ์‹œ์Šคํ…œ ์ •๋ณด
gr.Markdown("""
### ๐Ÿ“‹ ์ง€์›ํ•˜๋Š” ๊ฒ€์‚ฌ ํ•ญ๋ชฉ
| ๋ถ„๋ฅ˜ | ๊ฒ€์‚ฌ ํ•ญ๋ชฉ |
|------|----------|
| **๊ฐ„์—ผ ๊ฒ€์‚ฌ** | HBs Ag, HBs Ab, HBc Ab, HCV Ab |
| **์ข…์–‘ ํ‘œ์ง€์ž** | CEA, AFP, CA19-9, CA125, PSA, PIVKA-II |
| **ํ˜ธ๋ฅด๋ชฌ ๊ฒ€์‚ฌ** | TSH, T3, T4, ์ธ์А๋ฆฐ |
| **๊ฐ์—ผ ๊ฒ€์‚ฌ** | HIV, ๋งค๋…, ๊ฒฐํ•ต |
### ๐ŸŽฏ ์‚ฌ์šฉ๋ฒ•
1. ์ž์—ฐ์–ด๋กœ ์งˆ๋ฌธํ•˜์„ธ์š” (์˜ˆ: "HBs Ag ์ •์ƒ์น˜๋Š”?")
2. ๋™์˜์–ด๋„ ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: "๋น„ํ˜•๊ฐ„์—ผํ•ญ์›", "Bํ˜•๊ฐ„์—ผํ‘œ๋ฉดํ•ญ์›")
3. ์‹ ๋ขฐ๋„๊ฐ€ ๋†’์„์ˆ˜๋ก ์ •ํ™•ํ•œ ๋‹ต๋ณ€์ž…๋‹ˆ๋‹ค
""")
# ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
msg.submit(
answer_question,
[msg, chatbot],
[chatbot]
).then(
lambda: "",
outputs=msg
)
submit_btn.click(
answer_question,
[msg, chatbot],
[chatbot]
).then(
lambda: "",
outputs=msg
)
clear_btn.click(clear_chat, outputs=chatbot)
init_btn.click(initialize_rag_system, outputs=init_output)
# ์ž๋™ ์ดˆ๊ธฐํ™”
demo.load(initialize_rag_system, outputs=init_output)
if __name__ == "__main__":
import os
# Hugging Face Spaces/์ผ๋ฐ˜ ํ™˜๊ฒฝ ๋ชจ๋‘ ํ˜ธํ™˜
port = int(os.environ.get("PORT", "7860"))
is_spaces = bool(os.environ.get("SPACE_ID"))
if is_spaces:
# Hugging Face Spaces: ๊ฐ„๋‹จํ•œ ์‹คํ–‰
demo.launch()
else:
# ๋กœ์ปฌ/๊ธฐํƒ€ PaaS: ํ•„์š” ์‹œ share ํ—ˆ์šฉ
demo.launch(
server_name="0.0.0.0",
server_port=port,
share=True,
show_error=True
)
# Hugging Face Spaces์—์„œ ์—ฌ์ „ํžˆ ๋‘ ๊ฐ€์ง€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค:
# 1. **JSON ์Šคํ‚ค๋งˆ ์˜ค๋ฅ˜**: Gradio ๋‚ด๋ถ€ ์˜ค๋ฅ˜๋กœ ๋ณด์ž…๋‹ˆ๋‹ค
# 2. **localhost ์ ‘๊ทผ ์˜ค๋ฅ˜**: ์—ฌ์ „ํžˆ ๋„คํŠธ์›Œํฌ ์„ค์ • ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค
#
# ์ตœ์ข… ํ•ด๊ฒฐ์ฑ…: gradio_app.py ์ˆ˜์ •
#
# Hugging Face Spaces์—์„œ๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์„ค์ •๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:
import os
# Hugging Face Spaces ๊ฐ์ง€
is_spaces = "SPACE_ID" in os.environ
try:
if is_spaces:
# Hugging Face Spaces: ๊ธฐ๋ณธ ์„ค์ •๋งŒ ์‚ฌ์šฉ
demo.launch(enable_queue=True)
else:
# ๋กœ์ปฌ ํ™˜๊ฒฝ
port = int(os.environ.get("PORT", "7860"))
demo.launch(
server_name="0.0.0.0",
server_port=port,
share=True
)
except Exception as e:
print(f"Launch error: {e}")
# ์ตœํ›„์˜ ์ˆ˜๋‹จ: ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์‹คํ–‰
demo.launch()