Spaces:
Sleeping
Sleeping
| import sys | |
| # 1. μ΅μλ¨ λͺ½ν€ ν¨μΉ - λͺ¨λ μν¬νΈλ³΄λ€ μ΅μ°μ | |
| import huggingface_hub | |
| if not hasattr(huggingface_hub, "HfFolder"): | |
| class MockHfFolder: | |
| def get_token(): return None | |
| def save_token(token): pass | |
| 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} | {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;">📊 μ μ© μ§ν μ λ ₯</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) | |