from __future__ import annotations import os from threading import Lock import gradio as gr from megumin_agent.chat import ChatServices from megumin_agent.chat import create_chat_services from megumin_agent.chat import stream_chat INITIAL_GREETING = "내 이름은 메구밍! 홍마족 제일의 마법사이자, 폭렬 마법을 펼치는 자!" INITIAL_HISTORY = [{"role": "assistant", "content": INITIAL_GREETING}] SERVICES: ChatServices | None = None SERVICES_LOCK = Lock() MEGUMIN_IMAGE_URL = os.getenv( "MEGUMIN_IMAGE_URL", "https://huggingface.co/datasets/Junhoee/megumin-chat/resolve/main/%EB%A9%94%EA%B5%AC%EB%B0%8D%EC%82%AC%EC%A7%84.png", ) MEGUMIN_SVG = """ """.strip() CUSTOM_CSS = """ :root { --megumin-panel: rgba(255, 244, 232, 0.12); --megumin-panel-strong: rgba(255, 244, 232, 0.18); --megumin-line: rgba(255, 216, 169, 0.22); --megumin-text: #fff8ef; --megumin-shadow: 0 24px 80px rgba(7, 1, 4, 0.42); } body, .gradio-container { background: radial-gradient(circle at 14% 18%, rgba(255, 145, 71, 0.16), transparent 26%), radial-gradient(circle at 82% 14%, rgba(222, 49, 81, 0.18), transparent 24%), linear-gradient(135deg, #1d0710 0%, #12050b 48%, #26141f 100%); color: var(--megumin-text); } .gradio-container { max-width: 1900px !important; padding: 24px 28px 28px !important; } .megumin-stage { position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; } .megumin-stage::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, rgba(18, 5, 11, 0.82) 0%, rgba(18, 5, 11, 0.35) 42%, rgba(18, 5, 11, 0.72) 100%); } .stage-portrait { position: absolute; right: 3%; bottom: -1.5rem; width: min(40vw, 560px); opacity: 0.22; filter: drop-shadow(0 30px 80px rgba(0, 0, 0, 0.35)); } .stage-burst { position: absolute; right: 19%; top: 10%; width: 26rem; height: 26rem; border-radius: 999px; background: radial-gradient(circle, rgba(255, 175, 69, 0.35), rgba(255, 119, 53, 0.14), transparent 68%); filter: blur(14px); } .shell { position: relative; z-index: 1; } .main-layout { display: grid; grid-template-columns: 1fr 1.6fr 1fr; gap: 20px; align-items: stretch; } .left-panel { grid-column: 1; } .chat-panel-col { grid-column: 2; } .right-panel { grid-column: 3; } .glass-panel { background: linear-gradient(180deg, var(--megumin-panel-strong) 0%, var(--megumin-panel) 100%); border: 1px solid var(--megumin-line); box-shadow: var(--megumin-shadow); backdrop-filter: blur(14px); border-radius: 28px; overflow: hidden; } .profile-panel, .visual-panel, .chat-panel { min-height: 620px; } .panel-head { padding: 24px 24px 10px; } .eyebrow { font-size: 0.82rem; letter-spacing: 0.14em; text-transform: uppercase; color: rgba(255, 217, 174, 0.82); margin-bottom: 10px; } .panel-title { font-size: 1.95rem; line-height: 1.1; font-weight: 800; margin: 0; color: #ffffff; } .profile-panel .eyebrow, .visual-panel .eyebrow, .profile-panel .panel-title, .visual-panel .panel-title { color: #ffffff !important; } .panel-copy, .box-title, .example-box li, .visual-note p, .quote, .quote small { color: #ffffff !important; } .panel-copy { margin: 12px 0 0; font-size: 0.98rem; line-height: 1.75; } .example-box, .visual-note { margin: 18px 24px 0; padding: 16px 18px 14px; border-radius: 22px; background: rgba(255, 248, 239, 0.08); border: 1px solid rgba(255, 222, 189, 0.12); } .box-title { margin: 0 0 10px; font-size: 0.95rem; font-weight: 700; } .example-box ul { margin: 0; padding-left: 1.1rem; line-height: 1.7; } .example-box li { margin: 0 0 0.45rem; } .chat-hero { display: none; } .chat-header { padding: 26px 28px 10px; display: flex; align-items: end; justify-content: space-between; gap: 16px; } .chat-header h1 { margin: 0; font-size: 2rem; font-weight: 800; color: #ffffff; } .chat-header .eyebrow, .chat-header p, .status-pill { color: #111111 !important; } .status-pill { display: inline-flex; align-items: center; gap: 8px; padding: 10px 14px; border-radius: 999px; background: rgba(255, 209, 143, 0.12); font-size: 0.85rem; border: 1px solid rgba(255, 213, 157, 0.18); } .status-dot { width: 9px; height: 9px; border-radius: 999px; background: #ffb04a; box-shadow: 0 0 18px rgba(255, 176, 74, 0.9); } .mood-badge-slot { margin: 0 18px 10px; } .mood-badge { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 999px; font-size: 0.8rem; font-weight: 700; background: rgba(255, 248, 239, 0.12); border: 1px solid rgba(255, 226, 186, 0.16); color: #fff7eb; } .mood-badge::before { content: ""; width: 8px; height: 8px; border-radius: 999px; background: currentColor; box-shadow: 0 0 10px currentColor; } .mood-calm { color: #ffe1b2; } .mood-angry { color: #ff8b6e; } .mood-explosion { color: #ffbe45; } .mood-proud { color: #ffd86a; } .mood-meal { color: #ffd2a8; } .runtime-status { margin: 0 18px 12px; padding: 10px 14px; border-radius: 16px; background: rgba(255, 234, 201, 0.08); border: 1px solid rgba(255, 213, 157, 0.12); color: #fff4dc; font-size: 0.92rem; } .chatbot-wrap { padding: 0 18px 12px; } .chatbot-wrap .message, .chatbot-wrap .bubble { backdrop-filter: blur(10px); } .chatbot-wrap .message.user, .chatbot-wrap .message.user .bubble, .chatbot-wrap .message-row.user .bubble { background: rgba(255, 239, 219, 0.92) !important; color: #3c1a16 !important; } .chatbot-wrap .message.bot, .chatbot-wrap .message.bot .bubble, .chatbot-wrap .message-row.bot .bubble, .chatbot-wrap .message-row.assistant .bubble { background: var(--color-background-secondary, #f4f4f5) !important; color: var(--body-text-color, #1f2937) !important; border: 1px solid var(--border-color-primary, rgba(0, 0, 0, 0.08)) !important; } .chat-history { height: 470px; } .input-zone { padding: 0 18px 18px; } .input-row { display: flex; gap: 12px; align-items: stretch; } .input-message { flex: 8 1 0; min-width: 0; } .input-actions { flex: 2 1 0; min-width: 110px; display: flex; flex-direction: column; gap: 12px; } .input-zone textarea, .input-zone input { color: #111111 !important; } .input-zone .gr-textbox, .input-zone .gr-button, .input-zone .gr-form { background: rgba(255, 248, 239, 0.08) !important; border-color: rgba(255, 226, 186, 0.16) !important; } .input-actions .gr-button { width: 100%; min-height: 48px; } .input-zone .gr-button-primary { background: linear-gradient(135deg, #f19f35 0%, #d84f34 100%) !important; color: #fff8ef !important; border: none !important; } .input-zone .gr-button-secondary { color: #fff8ef !important; } .visual-frame { padding: 24px 22px 16px; min-height: 100%; display: flex; flex-direction: column; justify-content: space-between; } .archive-image-wrap { width: 100%; aspect-ratio: 16 / 9; overflow: hidden; border-radius: 20px; } .portrait-image, .portrait-fallback { width: 100%; height: 100%; display: block; } .portrait-image { object-fit: cover; box-shadow: 0 22px 40px rgba(0, 0, 0, 0.24); } .portrait-fallback { display: none; } .mobile-hero-image { margin-top: 14px; width: 100%; aspect-ratio: 16 / 9; border-radius: 18px; overflow: hidden; } .mobile-hero-image img, .mobile-hero-image .portrait-fallback { width: 100%; height: 100%; display: block; object-fit: cover; } @media (max-width: 768px) { .gradio-container { padding: 14px 12px 18px !important; } .main-layout { grid-template-columns: 1fr; gap: 14px; } .left-panel, .right-panel { display: none !important; visibility: hidden !important; width: 0 !important; min-width: 0 !important; max-width: 0 !important; padding: 0 !important; margin: 0 !important; overflow: hidden !important; } .left-panel .glass-panel, .right-panel .glass-panel { display: none !important; } .chat-panel-col { grid-column: 1; } .chat-panel { min-height: auto; border-radius: 22px; } .chat-hero { display: block; margin-bottom: 14px; padding: 18px 18px 14px; } .chat-hero .eyebrow { color: #ffffff !important; font-size: 0.72rem; } .chat-hero h1 { margin: 0; font-size: 1.42rem; line-height: 1.15; color: #ffffff; } .chat-hero p { margin: 10px 0 0; color: #ffffff; line-height: 1.5; font-size: 0.88rem; } .chat-header { padding: 14px 14px 6px; } .chat-header .eyebrow, .chat-header p, .status-pill { color: #ffffff !important; } .chat-header h1 { font-size: 1.28rem; } .status-pill { font-size: 0.72rem; padding: 7px 10px; } .mood-badge-slot { margin: 0 12px 8px; } .mood-badge { font-size: 0.72rem; padding: 5px 8px; } .chat-history { height: 34vh !important; min-height: 220px; } .runtime-status { margin: 0 12px 10px; font-size: 0.84rem; } .input-zone { padding: 0 12px 12px; } .input-row { gap: 6px; } .input-actions .gr-button { min-height: 40px; } } """.strip() PROFILE_HTML = """
이 멋진 세계에 축복을!의 등장인물로, 폭렬마법만을 고집하는 홍마족 아크 위저드입니다. 자존심 강하고 허세 넘치는 말투를 즐기지만, 동료들에게는 의외로 진심을 보이는 면도 있습니다. 이 챗봇은 메구밍의 말투와 감정선을 유지하면서 원작 설정까지 함께 참고해 답합니다.
이렇게 시작해보세요
이 페이지의 분위기
어두운 저녁빛과 폭렬마법의 잔광이 감도는 무드로 구성했습니다. 중앙의 반투명 채팅 패널은 실제로 메구밍과 마주 앉아 이야기를 주고받는 느낌을 주도록 설계했습니다.
폭렬마법사 메구밍의 말투와 원작 설정을 함께 참고하는 대화형 챗봇입니다.
메구밍에게 질문하고, 설정과 감정선을 모두 반영한 답변을 받아보세요.