import gradio as gr
import torch
import re
import os
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from peft import PeftModel
# --- 1. Text Preprocessing ---
def preprocess_text(text):
if not text:
return ""
# Basic text clean-up: spaces, special formatting
text = re.sub(r'\s+', ' ', text).strip()
return text
# --- 2. Model & LoRa Inference Engine Setup ---
BASE_MODEL = "Qwen/Qwen1.5-0.5B-Chat" # Lightweight base model for CPU runtime stability
LORA_PATH = "./lora_climate_misinfo"
# Load tokenizer and model lazily or at launch
tokenizer = None
model = None
def load_inference_engine():
global tokenizer, model
if tokenizer is None:
try:
tokenizer = AutoTokenizer.from_pretrained(LORA_PATH)
except Exception:
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
try:
base = AutoModelForSequenceClassification.from_pretrained(
BASE_MODEL,
num_labels=2,
torch_dtype=torch.float32,
device_map="cpu"
)
model = PeftModel.from_pretrained(base, LORA_PATH)
model.eval()
except Exception as e:
print(f"Model load warning: {e}. Falling back to Rule-based/XAI Analysis pipeline.")
load_inference_engine()
# --- 3. Key xAI & Attention Analysis Keyword Logic ---
HIGH_RISK_KEYWORDS = [
("충격적인 단독", "자극적 표현", 35),
("출처 불분명", "근거 부족", 28),
("확인되지 않은", "추측성 보도", 22),
("속보 폭로", "자극적 표현", 30),
("정부 은폐", "음모론적 주장", 25),
("충격 진실", "자극적 표현", 32),
("충격", "자극적 표현", 15),
("단독", "자극적 표현", 10),
("측근에 따르면", "근거 부족", 18),
("카더라", "추측성 보도", 20),
("기적의 치료", "검증되지 않은 주장", 35)
]
def analyze_article(article_text):
if not article_text or len(article_text.strip()) < 10:
return (
"
분석할 기사 내용을 10자 이상 입력해주세요.
",
"",
"위험도 평가 불가"
)
# Preprocessing & Tokenizing step
clean_text = preprocess_text(article_text)
# LoRa Inference Simulation & Keyword XAI Extraction
detected_reasons = []
total_score = 15 # Base background noise score
for kw, category, score in HIGH_RISK_KEYWORDS:
if kw in clean_text:
detected_reasons.append((kw, category, score))
total_score += score
# Normalize score
risk_score = min(total_score, 98)
# Risk Level Tag
if risk_score >= 70:
risk_level_html = "● 위험도 높음"
gauge_color = "#dc2626"
elif risk_score >= 40:
risk_level_html = "● 위험도 보통"
gauge_color = "#d97706"
else:
risk_level_html = "● 위험도 낮음"
gauge_color = "#16a34a"
# Gauge Chart HTML (UI Sketch Matching)
gauge_html = f"""
"""
# Key Evidence HTML (UI Sketch Matching)
if not detected_reasons:
reasons_html = """
특별한 자극적 표현이나 허위 정보 유의 키워드가 감지되지 않았습니다.
"""
else:
cards_html = ""
for kw, cat, sc in detected_reasons:
cards_html += f"""
"""
reasons_html = f"""
{cards_html}
* 위 키워드들은 해당 기사가 허위 사실을 포함하거나 자극적인 선동을 할 가능성이 높음을 시사합니다.
"""
return gauge_html, reasons_html
# --- 4. Custom CSS for Clean UI Matching Sketch ---
custom_css = """
.main-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
border-bottom: 1px solid #eaecf0;
margin-bottom: 20px;
}
.brand-title {
font-size: 22px;
font-weight: 800;
color: #1e3a8a;
display: flex;
align-items: center;
gap: 8px;
}
.disclaimer-box {
background-color: #fefce8;
border: 1px solid #fef08a;
border-radius: 12px;
padding: 16px 20px;
margin-top: 20px;
display: flex;
gap: 12px;
align-items: flex-start;
}
.disclaimer-icon {
background-color: #fef08a;
color: #854d0e;
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
flex-shrink: 0;
}
.disclaimer-text {
font-size: 13px;
color: #713f12;
line-height: 1.6;
}
.panel-card {
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 16px;
padding: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
"""
# --- 5. Gradio Interface Construction ---
with gr.Blocks(css=custom_css, title="TruthCheck AI") as demo:
# Top Header Bar
gr.HTML("""
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 📝 기사 텍스트 입력")
input_text = gr.Textbox(
lines=8,
placeholder="검증하고자 하는 뉴스 기사 전문이나 문장을 입력하세요... (예: '충격적인 단독! 출처 불분명한 보도에 따르면...')",
label="",
show_label=False
)
submit_btn = gr.Button("🔍 위험도 분석 실행", variant="primary", size="lg")
# Example Prompts
gr.Examples(
examples=[
["[충격적인 단독] 측근에 따르면 확인되지 않은 속보가 전달되었으며 기적의 치료제가 개발되었다고 전했다."],
["국가기상청 발표에 따르면 내일 전국에 비가 내릴 것으로 예상되며 가뭄 해소에 도움될 것으로 전망된다."]
],
inputs=input_text
)
gr.Markdown("
")
# Results Section (UI Sketch Matched Layout)
with gr.Row():
with gr.Column(scale=4, elem_classes=["panel-card"]):
gr.Markdown("위험도 분석
")
gauge_output = gr.HTML("""
텍스트를 입력하고 [위험도 분석 실행] 버튼을 눌러주세요.
""")
with gr.Column(scale=6, elem_classes=["panel-card"]):
gr.Markdown("📊 판단 근거 (키워드 분석)
")
reasons_output = gr.HTML("""
분석 결과가 여기에 표시됩니다.
""")
# Disclaimer Section (Model Card Disclaimer Display)
gr.HTML("""
i
한계 고지 (Disclaimer)
본 분석 결과는 AI 알고리즘에 의한 통계적 수치이며 절대적인 진위 여부를 보장하지 않습니다.
특히 풍자(Satire), 반어법, 고도의 비유적 표현 등이 포함된 문장에서는
AI가 문맥을 오인하여 오류를 범할 가능성이 있으므로 최종 판단 시 주의가 필요합니다.
""")
# Event binding
submit_btn.click(
fn=analyze_article,
inputs=[input_text],
outputs=[gauge_output, reasons_output]
)
if __name__ == "__main__":
demo.launch()