gardio_test / app.py
dev-yuje's picture
fix: quantization_config 역직렬화 였λ₯˜λ₯Ό config.json 패치둜 μ™„μ „ ν•΄κ²° (둜컬 톡합 ν…ŒμŠ€νŠΈ 톡과)
415ebef
import sys
# 1. μ΅œμƒλ‹¨ λͺ½ν‚€ 패치 - λͺ¨λ“  μž„ν¬νŠΈλ³΄λ‹€ μ΅œμš°μ„ 
import huggingface_hub
if not hasattr(huggingface_hub, "HfFolder"):
class MockHfFolder:
@staticmethod
def get_token(): return None
@staticmethod
def save_token(token): pass
@staticmethod
def delete_token(): pass
huggingface_hub.HfFolder = MockHfFolder
import gradio as gr
import os
import re
import zipfile
import tempfile
import numpy as np
import pandas as pd
import tensorflow as tf
import joblib
import traceback
from huggingface_hub import hf_hub_download
# TF μ΅œμ ν™” κ²½κ³  λ°©μ§€ 및 μ•ˆμ •μ„±
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
# 무거운 λͺ¨λΈ μ§€μ—° λ‘œλ”©
_models = {"predictor": None, "consultant": None}
REPO_ID = "dev-yuje/gardio_test"
def load_keras_model_compat(model_path):
"""quantization_config 역직렬화 였λ₯˜λ₯Ό config.json 패치둜 우회"""
with tempfile.TemporaryDirectory() as tmpdir:
with zipfile.ZipFile(model_path, 'r') as z:
z.extractall(tmpdir)
config_path = os.path.join(tmpdir, 'config.json')
with open(config_path, 'r', encoding='utf-8') as f:
config_str = f.read()
config_str = re.sub(r',\s*"quantization_config":\s*null', '', config_str)
config_str = re.sub(r'"quantization_config":\s*null,?\s*', '', config_str)
with open(config_path, 'w', encoding='utf-8') as f:
f.write(config_str)
fixed_path = model_path + '.tmp_fixed.keras'
with zipfile.ZipFile(fixed_path, 'w', zipfile.ZIP_DEFLATED) as z:
for root, dirs, files in os.walk(tmpdir):
for file in files:
fp = os.path.join(root, file)
arcname = os.path.relpath(fp, tmpdir)
z.write(fp, arcname)
model = tf.keras.models.load_model(fixed_path, compile=False)
os.remove(fixed_path)
return model
def load_all_models():
if _models["predictor"] is None:
try:
# λ‚΄λΆ€ 예츑 둜직 (Self-contained)
class RobustCreditPredictor:
def __init__(self):
self.preprocessor_path = "models/preprocessor.pkl"
self.model_path = "models/telecom_cb_model.keras"
self.preprocessor = None
self.model = None
self.load_error = "μ΄ˆκΈ°ν™”λ¨"
self.load_resources()
def load_resources(self):
try:
# [κ°œμ„ ] 둜컬 파일 μš°μ„  λ‘œλ“œ β†’ μ—†μœΌλ©΄ HuggingFace λ‹€μš΄λ‘œλ“œ
# --- μ „μ²˜λ¦¬κΈ° λ‘œλ“œ ---
try:
if os.path.exists(self.preprocessor_path):
self.preprocessor = joblib.load(self.preprocessor_path)
print(f"βœ… μ „μ²˜λ¦¬κΈ° 둜컬 λ‘œλ“œ 성곡: {self.preprocessor_path}")
else:
cached_preprocessor = hf_hub_download(repo_id=REPO_ID, filename=self.preprocessor_path, repo_type="space")
self.preprocessor = joblib.load(cached_preprocessor)
print(f"βœ… μ „μ²˜λ¦¬κΈ° HuggingFace λ‹€μš΄λ‘œλ“œ 성곡")
except Exception as prep_e:
self.load_error = f"μ „μ²˜λ¦¬κΈ° λ‘œλ“œ μ‹€νŒ¨: {prep_e}"
return
# --- λͺ¨λΈ λ‘œλ“œ ---
try:
if os.path.exists(self.model_path):
target_path = self.model_path
print(f"βœ… λͺ¨λΈ 둜컬 경둜 μ‚¬μš©: {target_path}")
else:
target_path = hf_hub_download(repo_id=REPO_ID, filename=self.model_path, repo_type="space")
print(f"βœ… λͺ¨λΈ HuggingFace λ‹€μš΄λ‘œλ“œ μ™„λ£Œ")
fsize = os.path.getsize(target_path)
if fsize < 1000:
self.load_error = f"파일이 λ„ˆλ¬΄ μž‘μŒ({fsize}B). LFS 포인터일 κ°€λŠ₯μ„± 있음."
return
self.model = load_keras_model_compat(target_path)
self.load_error = "성곡"
print(f"βœ… λͺ¨λΈ λ‘œλ“œ 성곡 (파일 크기: {fsize:,}B)")
except Exception as model_e:
self.load_error = f"λͺ¨λΈ λ‘œλ“œ μ‹€νŒ¨: {str(model_e)[:300]}"
except Exception as e:
self.load_error = f"λ¦¬μ†ŒμŠ€ λ‘œλ“œ 톡합 μ—λŸ¬: {e}"
def predict(self, features_dict):
try:
if self.model is None or self.preprocessor is None:
err_short = self.load_error[:500] if self.load_error else "원인 뢈λͺ…"
return f"Error: λ‘œλ“œ μƒνƒœ 확인 μš”λ§. 원본 μ—λŸ¬: {err_short}"
ALL_FEATURES = [
'C1Z001386', 'C1M210000', 'C18210000', 'C1L120001', 'C1L120004',
'L10210000', 'L90210100', 'L90210200', 'L10210B00', 'L10216000',
'L10217000', 'D10110000', 'D10133000', 'PERF1'
]
input_values = [float(features_dict.get(col, 0.0)) for col in ALL_FEATURES]
df = pd.DataFrame([input_values], columns=ALL_FEATURES)
log_cols = ['C1Z001386', 'C1L120004', 'D10110000', 'D10133000', 'L90210200',
'L10216000', 'L10210B00', 'L10217000', 'L90210100', 'L10210000']
df[log_cols] = np.log1p(df[log_cols].astype(float).clip(lower=0))
scaled_data = self.preprocessor.transform(df)
prediction = self.model.predict(scaled_data, verbose=0)
return float(prediction[0][0])
except Exception as e:
return f"Error: 예츑 μ—°μ‚° μ—λŸ¬: {str(e)}"
_models["predictor"] = RobustCreditPredictor()
# 상담사 둜직
from langchain_google_genai import ChatGoogleGenerativeAI
class Consultant:
def __init__(self):
api_key = os.getenv("GOOGLE_API_KEY", "")
from config import LLM_MODEL
# [μˆ˜μ •] λͺ¨λΈ λͺ…μΉ­ 및 API 버전 ν˜Έν™˜μ„± κ³ λ € (config 연동)
self.llm = ChatGoogleGenerativeAI(
model=LLM_MODEL,
google_api_key=api_key,
temperature=0.7,
convert_system_message_to_human=True
)
self.embedding_model = None
self.retriever = None
def lazy_load_search(self):
if self.embedding_model is None:
try:
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from config import EMBEDDING_MODEL, FAISS_PATH, RETRIEVER_K
self.embedding_model = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)
if os.path.exists(FAISS_PATH):
self.vectorstore = FAISS.load_local(FAISS_PATH, self.embedding_model, allow_dangerous_deserialization=True)
self.retriever = self.vectorstore.as_retriever(search_kwargs={"k": RETRIEVER_K})
except: pass
_models["consultant"] = Consultant()
except Exception as e:
print(f"Grand Load Error: {e}")
FEATURES_DETAIL = {
'C1Z001386': ('1λ…„ λ‚΄ μΉ΄λ“œ 총 μ΄μš©κΈˆμ•‘', 'λ§Œμ› λ‹¨μœ„', '0'),
'C1M210000': ('보유 μ‹ μš©μΉ΄λ“œ 수', '개수', '0'),
'C18210000': ('보유 μ²΄ν¬μΉ΄λ“œ 수', '개수', '0'),
'C1L120001': ('μΉ΄λ“œ 총 ν•œλ„κΈˆμ•‘', 'λ§Œμ› λ‹¨μœ„', '0'),
'C1L120004': ('μ‹ μš©μΉ΄λ“œ κ°œμ„€ ν›„ 경과일수', '일 λ‹¨μœ„', '0'),
'L90210100': ('은행업쒅 λŒ€μΆœ 건수', '개수', '0'),
'L90210200': ('μΉ΄λ“œμ—…μ’… λŒ€μΆœ 건수', '개수', '0'),
'L10210B00': ('λ³΄ν—˜μ—…μ’… λŒ€μΆœ 건수', '개수', '0'),
'L10216000': ('μ‹ μš©λŒ€μΆœ 건수', '개수', '0'),
'L10217000': ('λ‹΄λ³΄λŒ€μΆœ 건수', '개수', '0'),
'D10110000': ('κ³Όκ±° 연체 건수', '개수', '0'),
'D10133000': ('총 연체 μƒν™˜ κΈˆμ•‘', 'λ§Œμ› λ‹¨μœ„', '0'),
'PERF1': ('1λ…„ λ‚΄ 90일 이상 연체 κ²½ν—˜', '체크 μ‹œ 연체 κ²½ν—˜ 있음', None),
}
ALL_KEYS = list(FEATURES_DETAIL.keys())
def get_grade_info(score_str):
"""점수 β†’ (λ“±κΈ‰, λ“±κΈ‰ ꡬ뢄, 의미/νŠΉμ§•, 색상) λ°˜ν™˜"""
try:
score = int(score_str)
except:
return None
if score >= 942: grade, label, desc, color = "1λ“±κΈ‰", "μ΅œμš°λŸ‰λ“±κΈ‰", "였랜 μ‹ μš©κ±°λž˜ κ²½λ ₯κ³Ό λ‹€μ–‘ν•˜κ³  μš°λŸ‰ν•œ μ‹ μš©κ±°λž˜ 싀적을 λ³΄μœ ν•˜κ³  μžˆμ–΄ λΆ€μ‹€ν™” κ°€λŠ₯성이 맀우 λ‚™μŒ", "#A8D8EA"
elif score >= 891: grade, label, desc, color = "2λ“±κΈ‰", "μ΅œμš°λŸ‰λ“±κΈ‰", "였랜 μ‹ μš©κ±°λž˜ κ²½λ ₯κ³Ό λ‹€μ–‘ν•˜κ³  μš°λŸ‰ν•œ μ‹ μš©κ±°λž˜ 싀적을 λ³΄μœ ν•˜κ³  μžˆμ–΄ λΆ€μ‹€ν™” κ°€λŠ₯성이 맀우 λ‚™μŒ", "#A8D8EA"
elif score >= 832: grade, label, desc, color = "3λ“±κΈ‰", "μš°λŸ‰λ“±κΈ‰", "ν™œλ°œν•œ μ‹ μš©κ±°λž˜ 싀적은 μ—†μœΌλ‚˜, κΌΈμ€€νžˆ μš°λŸ‰ 거래λ₯Ό μ§€μ†ν•œλ‹€λ©΄ μƒμœ„λ“±κΈ‰ μ§„μž… κ°€λŠ₯ν•˜λ©° λΆ€μ‹€ν™” κ°€λŠ₯성은 낙은 μˆ˜μ€€μž„", "#B8F0C8"
elif score >= 768: grade, label, desc, color = "4λ“±κΈ‰", "μš°λŸ‰λ“±κΈ‰", "ν™œλ°œν•œ μ‹ μš©κ±°λž˜ 싀적은 μ—†μœΌλ‚˜, κΌΈμ€€νžˆ μš°λŸ‰ 거래λ₯Ό μ§€μ†ν•œλ‹€λ©΄ μƒμœ„λ“±κΈ‰ μ§„μž… κ°€λŠ₯ν•˜λ©° λΆ€μ‹€ν™” κ°€λŠ₯성은 낙은 μˆ˜μ€€μž„", "#B8F0C8"
elif score >= 698: grade, label, desc, color = "5λ“±κΈ‰", "μΌλ°˜λ“±κΈ‰", "비ꡐ적 κΈˆλ¦¬κ°€ 높은 κΈˆμœ΅μ—…κΆŒκ³Όμ˜ κ±°λž˜κ°€ μžˆλŠ” 고객으둜, 단기연체 κ²½ν—˜μ΄ 있으며 λΆ€μ‹€ν™” κ°€λŠ₯성은 일반적인 μˆ˜μ€€μž„", "#FEE8A0"
elif score >= 620: grade, label, desc, color = "6λ“±κΈ‰", "μΌλ°˜λ“±κΈ‰", "비ꡐ적 κΈˆλ¦¬κ°€ 높은 κΈˆμœ΅μ—…κΆŒκ³Όμ˜ κ±°λž˜κ°€ μžˆλŠ” 고객으둜, 단기연체 κ²½ν—˜μ΄ 있으며 λΆ€μ‹€ν™” κ°€λŠ₯성은 일반적인 μˆ˜μ€€μž„", "#FEE8A0"
elif score >= 530: grade, label, desc, color = "7λ“±κΈ‰", "μ£Όμ˜λ“±κΈ‰", "비ꡐ적 κΈˆλ¦¬κ°€ 높은 κΈˆμœ΅μ—…κΆŒκ³Όμ˜ κ±°λž˜κ°€ λ§Žμ€ 고객으둜, 단기연체 κ²½ν—˜μ„ 비ꡐ적 많이 λ³΄μœ ν•˜κ³  μžˆμ–΄ λΆ€μ‹€ν™” κ°€λŠ₯성이 λ†’μŒ", "#FFCB9A"
elif score >= 454: grade, label, desc, color = "8λ“±κΈ‰", "μ£Όμ˜λ“±κΈ‰", "비ꡐ적 κΈˆλ¦¬κ°€ 높은 κΈˆμœ΅μ—…κΆŒκ³Όμ˜ κ±°λž˜κ°€ λ§Žμ€ 고객으둜, 단기연체 κ²½ν—˜μ„ 비ꡐ적 많이 λ³΄μœ ν•˜κ³  μžˆμ–΄ λΆ€μ‹€ν™” κ°€λŠ₯성이 λ†’μŒ", "#FFCB9A"
elif score >= 335: grade, label, desc, color = "9λ“±κΈ‰", "μœ„ν—˜λ“±κΈ‰", "ν˜„μž¬ 연체 μ€‘μ΄κ±°λ‚˜ 맀우 μ‹¬κ°ν•œ 연체 κ²½ν—˜μ„ λ³΄μœ ν•˜κ³  μžˆμ–΄ λΆ€μ‹€ν™” κ°€λŠ₯성이 맀우 λ†’μŒ", "#FFB3B3"
else: grade, label, desc, color = "10λ“±κΈ‰", "μœ„ν—˜λ“±κΈ‰", "ν˜„μž¬ 연체 μ€‘μ΄κ±°λ‚˜ 맀우 μ‹¬κ°ν•œ 연체 κ²½ν—˜μ„ λ³΄μœ ν•˜κ³  μžˆμ–΄ λΆ€μ‹€ν™” κ°€λŠ₯성이 맀우 λ†’μŒ", "#FFB3B3"
return grade, label, desc, color
SCORE_PLACEHOLDER = '''
<div style="border:2px dashed rgba(128,128,128,0.3); border-radius:16px; padding:24px; text-align:center; min-height:130px; display:flex; flex-direction:column; align-items:center; justify-content:center;">
<div style="font-size:36px; opacity:0.25;">πŸ“‹</div>
<div style="font-size:14px; opacity:0.45; margin-top:10px;">πŸ“Œ 정보λ₯Ό μž…λ ₯ν•˜κ³  점수 λΆ„μ„ν•˜κΈ° λ²„νŠΌμ„ λˆŒλŸ¬μ£Όμ„Έμš”</div>
</div>
'''
def make_score_html(score_str):
if not score_str or score_str.startswith("❌") or score_str.startswith("⚠️"):
return f'''
<div style="border:2px solid #e74c3c33; border-radius:16px; padding:24px; text-align:center; min-height:120px; display:flex; align-items:center; justify-content:center;">
<div style="font-size:15px; color:#e74c3c;">{score_str}</div>
</div>'''
info = get_grade_info(score_str)
if info is None:
return f'<div style="padding:20px; text-align:center;">{score_str}</div>'
grade, label, desc, color = info
# 닀크λͺ¨λ“œ ν˜Έν™˜: λ°°κ²½ 반투λͺ…, ν…μŠ€νŠΈλŠ” CSS inherit λŒ€μ‹  λͺ…μ‹œμ  닀크λͺ¨λ“œ 곡유 μƒˆλ¬Έ (prefers-color-scheme)
return f'''
<style>
.score-card-{grade[0]} {{ background:{color}22; border:2px solid {color}; border-radius:16px; padding:24px; text-align:center; font-family:sans-serif; }}
.score-num-{grade[0]} {{ font-size:64px; font-weight:900; color:{color}; letter-spacing:-2px; line-height:1; }}
.score-grade-{grade[0]} {{ font-size:22px; font-weight:700; margin-top:8px; color:{color}; }}
.score-desc-{grade[0]} {{ font-size:13px; line-height:1.6; opacity:0.75; }}
@media (prefers-color-scheme: dark) {{
.score-desc-{grade[0]} {{ color: #ccc; }}
}}
@media (prefers-color-scheme: light) {{
.score-desc-{grade[0]} {{ color: #555; }}
}}
</style>
<div class="score-card-{grade[0]}">
<div class="score-num-{grade[0]}">{score_str}점</div>
<div class="score-grade-{grade[0]}">{grade} &nbsp;|&nbsp; {label}</div>
<hr style="border:none; border-top:1px solid {color}; margin:14px 0;">
<div class="score-desc-{grade[0]}">{desc}</div>
</div>
'''
def handle_predict(*args):
try:
load_all_models()
features_dict = {}
for i, key in enumerate(ALL_KEYS):
val_raw = str(args[i]).strip().replace(",", "")
if key == 'PERF1':
features_dict[key] = 1.0 if (val_raw.lower() == 'true' or val_raw == '1' or args[i] is True) else 0.0
else:
try:
features_dict[key] = float(val_raw or 0)
except:
return f"❌ 였λ₯˜: '{FEATURES_DETAIL[key][0]}' 숫자 μ•„λ‹˜", "❌"
features_dict['L10210000'] = features_dict['L10216000'] + features_dict['L10217000']
res = _models["predictor"].predict(features_dict)
if isinstance(res, str) and "Error" in res:
return f"❌ 뢄석 μ‹€νŒ¨: {res}", make_score_html(f"❌ λ‘œλ“œ μ‹€νŒ¨: {res[:60]}")
score_val = str(int(round(float(res))))
return {"features": features_dict, "score": score_val}, make_score_html(score_val)
except Exception as e:
return f"❌ μ‹œμŠ€ν…œ 였λ₯˜: {str(e)}", make_score_html(f"❌ μ‹œμŠ€ν…œ μ—λŸ¬")
def generate_response(chatbot, user_message, analysis_report):
if not user_message: yield chatbot, ""; return
# [μ΅œμ’… μˆ˜μ •] Gradio 5의 'Data incompatible' μ—λŸ¬ 해결을 μœ„ν•΄ λͺ…μ‹œμ μΈ λ”•μ…”λ„ˆλ¦¬ 포맷 μ‚¬μš©
chatbot.append({"role": "user", "content": user_message})
chatbot.append({"role": "assistant", "content": "πŸ” [Retrieval] κ΄€λ ¨ κ·œμ • 및 지침을 κ²€μƒ‰ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€..."})
yield chatbot, ""
try:
load_all_models()
cons = _models["consultant"]
cons.lazy_load_search()
context = ""
if cons.retriever:
try:
docs = cons.retriever.invoke(user_message)
context = "\n\n".join([d.page_content for d in docs])
except: pass
chatbot[-1] = {"role": "assistant", "content": "🧠 [Augmentation] κ²€μƒ‰λœ 지식을 λ°”νƒ•μœΌλ‘œ ν”„λ‘¬ν”„νŠΈλ₯Ό κ΅¬μ„±ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€..."}
yield chatbot, ""
from llm.prompt import QA_PROMPT
if isinstance(analysis_report, dict) and "features" in analysis_report:
score_val = analysis_report.get("score", "λ―ΈμΈ‘μ •")
# L10210000은 νŒŒμƒ 컨럼으둜 FEATURES_DETAIL에 μ—†μŒ - ν‚€κ°€ μžˆλŠ” κ²ƒλ§Œ ν‘œμ‹œ
features_text = "\n".join([f"- {FEATURES_DETAIL[k][0]}: {v}" for k, v in analysis_report["features"].items() if k in FEATURES_DETAIL])
query_text = f"β–  고객 μ‹ μš© 점수: {score_val}점\nβ–  고객의 ν˜„μž¬ μƒνƒœ(μž…λ ₯된 정보):\n{features_text}\n\nβ–  고객 질문: {user_message}"
else:
query_text = f"β–  고객 μ‹ μš© 점수: 정보 μ—†μŒ(일반 질문 μƒνƒœ)\nβ–  μƒνƒœ: 뢄석을 μ§„ν–‰ν•˜μ§€ μ•ŠμŒ\nβ–  질문: {user_message}"
full_prompt = QA_PROMPT.format(context=context, query=query_text)
# [μ€‘μš”] λ”•μ…”λ„ˆλ¦¬ 리슀트둜 전달 (Gradio 5 UI λŒ€μ‘)
from langchain_core.messages import HumanMessage
messages = [HumanMessage(content=full_prompt)]
chatbot[-1] = {"role": "assistant", "content": "πŸ’‘ [Generation] 닡변을 생성 μ€‘μž…λ‹ˆλ‹€...\n\n"}
yield chatbot, ""
answer_buffer = ""
for chunk in cons.llm.stream(messages):
answer_buffer += chunk.content
# λ§ˆμ§€λ§‰ λ©”μ‹œμ§€μ˜ λ‚΄μš©μ„ λ”•μ…”λ„ˆλ¦¬ 포맷으둜 μ—…λ°μ΄νŠΈ
chatbot[-1] = {"role": "assistant", "content": answer_buffer}
yield chatbot, ""
except Exception as e:
chatbot[-1] = {"role": "assistant", "content": f"⚠️ 상담 μ—λŸ¬: {str(e)}"}
yield chatbot, ""
with gr.Blocks(title="KCB AI Consultant") as demo:
analysis_report = gr.State(None)
gr.Markdown("# πŸ›‘οΈ KCB AI μ‹ μš© 상담 μ‹œμŠ€ν…œ")
with gr.Row(equal_height=False):
# ==== μ™Όμͺ½: μž…λ ₯ νŒ¨λ„ (2μ—΄ κ·Έλ¦¬λ“œ) ====
with gr.Column(scale=1, min_width=340):
gr.HTML('<h2 style="margin:4px 0 12px 0; font-size:20px; font-weight:700;">&#128202; μ‹ μš© μ§€ν‘œ μž…λ ₯</h2>')
input_list = []
keys_no_perf = [k for k in ALL_KEYS if k != 'PERF1']
# 2μ—΄λ‘œ μž…λ ₯ ν•„λ“œ 배치
for i in range(0, len(keys_no_perf), 2):
with gr.Row():
for key in keys_no_perf[i:i+2]:
field_label, unit, _ = FEATURES_DETAIL[key]
unit_short = unit.replace(" λ‹¨μœ„", "").replace("개수", "개")
# label에 λ‹¨μœ„λ₯Ό 괴호둜 λΆ™μ΄λ˜, info μ—†μ•  λ ˆμ΄μ•„μ›ƒ 정리
tb = gr.Textbox(
label=f"{field_label} ({unit_short})",
placeholder="0",
min_width=120
)
input_list.append(tb)
# PERF1 μΌ¬ μ½”λ„ˆλ‘œ 배치
with gr.Row():
perf_cb = gr.Checkbox(label=FEATURES_DETAIL['PERF1'][0], info=FEATURES_DETAIL['PERF1'][1], value=False)
input_list.append(perf_cb)
predict_btn = gr.Button("πŸ“ˆ 점수 λΆ„μ„ν•˜κΈ°", variant="primary", size="lg")
# ==== 였λ₯Έμͺ½: κ²°κ³Ό νŒ¨λ„ ====
with gr.Column(scale=2):
gr.HTML('<h2 style="margin:0 0 8px 0; font-size:22px; font-weight:700;">🎯 AI 예츑 μ‹ μš© 점수</h2>')
result_display = gr.HTML(value=SCORE_PLACEHOLDER)
chatbot = gr.Chatbot(label="AI 상담사", height=430)
with gr.Row():
msg = gr.Textbox(placeholder="μ§ˆλ¬Έμ„ μž…λ ₯ν•˜μ„Έμš” (μ‹ μš©μ μˆ˜ 뢄석 ν›„μ—λŠ” κ°œμΈν™” 상담이 κ°€λŠ₯ν•©λ‹ˆλ‹€)", show_label=False, scale=8)
submit_btn = gr.Button("전솑", variant="primary", scale=1)
predict_btn.click(handle_predict, inputs=input_list, outputs=[analysis_report, result_display])
msg.submit(generate_response, inputs=[chatbot, msg, analysis_report], outputs=[chatbot, msg])
submit_btn.click(generate_response, inputs=[chatbot, msg, analysis_report], outputs=[chatbot, msg])
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)