qnabot / app.py
LLDDWW's picture
add swmirae4
262c670
# app.py
# -----------------------------
# HF Router Inference API (OpenAI-compatible)
# Toss-like UI + "SW ๋ฏธ๋ž˜ ์˜์žฌํ•™์›" theme
# Tabs: ์˜ˆ์•ฝยท์ถ”์ฒœ / ์งˆ๋ฌธ(์ฑ„ํŒ…) / ํ•™์› ์†Œ๊ฐœ / ์ƒํƒœ ์ ๊ฒ€
# -----------------------------
import os, json, time, requests, gradio as gr
from typing import List, Tuple, Dict, Any
from datetime import datetime, timedelta
# =========================
# Config
# =========================
API_BASE = os.getenv("HF_ENDPOINT_URL") or "https://router.huggingface.co/v1" # OpenAI-compatible Router
MODEL_QA = os.getenv("HF_MODEL_ID") or "Qwen/Qwen2.5-7B-Instruct" # Provider-attached model
HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACEHUB_API_TOKEN")
TIMEOUT_S = 60
# ์˜ˆ์•ฝ ์„ค์ •
OPEN_DAYS = [0, 1, 2, 3, 4, 5] # Mon(0) ~ Sat(5), Sun(6) off
OPEN_TIMES = ["15:00", "17:00", "19:00"] # 3 slots/day
SLOT_DAYS_AHEAD = 14 # next 14 days
RESV_FILE = "reservations.json" # local JSON store
KWDAYS = ["์›”", "ํ™”", "์ˆ˜", "๋ชฉ", "๊ธˆ", "ํ† ", "์ผ"]
# =========================
# HF Router caller
# =========================
def hf_chat(model: str, messages: list, temperature: float = 0.2, max_tokens: int = 256,
timeout: int = TIMEOUT_S, retries: int = 3) -> str:
if not HF_TOKEN:
return "โš ๏ธ HF_TOKEN์ด ์—†์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ๋ณ€์ˆ˜์— HF_TOKEN์„ ์„ค์ •ํ•˜์„ธ์š”."
headers = {"Authorization": f"Bearer {HF_TOKEN}", "Content-Type": "application/json"}
url = f"{API_BASE}/chat/completions"
payload = {
"model": model,
"messages": messages,
"temperature": float(temperature),
"max_tokens": int(max_tokens),
}
backoff = 1.5
for attempt in range(retries):
try:
r = requests.post(url, headers=headers, data=json.dumps(payload), timeout=timeout)
if r.status_code in (429, 500, 502, 503, 504, 529):
time.sleep(backoff ** (attempt + 1))
continue
if not r.ok:
return f"โŒ HF Router ์˜ค๋ฅ˜({r.status_code}): {r.text[:600]}"
data = r.json()
return (data["choices"][0]["message"]["content"] or "").strip()
except Exception:
time.sleep(backoff ** (attempt + 1))
return "โŒ ์š”์ฒญ ์‹คํŒจ(๋„คํŠธ์›Œํฌ/ํƒ€์ž„์•„์›ƒ). ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”."
# =========================
# Reservations: storage helpers
# =========================
def load_reservations() -> List[Dict[str, Any]]:
if not os.path.exists(RESV_FILE):
return []
try:
with open(RESV_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return []
def save_reservations(resv: List[Dict[str, Any]]) -> None:
tmp = RESV_FILE + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(resv, f, ensure_ascii=False, indent=2)
os.replace(tmp, RESV_FILE)
def make_slots(start: datetime | None = None, days: int = SLOT_DAYS_AHEAD) -> List[str]:
now = datetime.now()
base = start or now
out = []
for i in range(days + 1):
d = base + timedelta(days=i)
if d.weekday() in OPEN_DAYS:
for t in OPEN_TIMES:
label = f"{d.strftime('%Y-%m-%d')} {t} ({KWDAYS[d.weekday()]})"
out.append(label)
return out
def booked_slots() -> set:
return {item["slot"] for item in load_reservations()}
def available_slots() -> List[str]:
bset = booked_slots()
return [s for s in make_slots() if s not in bset]
def reservation_rows(resv: List[Dict[str, Any]]) -> List[List[str]]:
rows = []
for r in sorted(resv, key=lambda x: x.get("slot", "")):
rows.append([
r.get("slot", ""),
r.get("name", ""),
r.get("grade", ""),
r.get("focus", ""),
r.get("level", ""),
r.get("contact", ""),
r.get("notes", ""),
r.get("created_at", ""),
])
return rows
# =========================
# Recommendation
# =========================
def recommend_plan(grade: str, focuses: List[str], level: str, goals: str,
weeks: int, hours_per_week: int, constraints: str,
temperature: float, max_tokens: int) -> str:
focus_str = ", ".join(focuses) if focuses else "์ผ๋ฐ˜"
user_profile = (
f"- ํ•™๋…„: {grade}\n- ๊ด€์‹ฌ ๋ถ„์•ผ: {focus_str}\n- ์ˆ˜์ค€: {level}\n"
f"- ๋ชฉํ‘œ: {goals or '๋ฏธ์ •'}\n- ๊ธฐ๊ฐ„: {weeks}์ฃผ\n- ์ฃผ๋‹น ์‹œ๊ฐ„: {hours_per_week}์‹œ๊ฐ„\n"
f"- ์ œ์•ฝ: {constraints or '์—†์Œ'}"
)
system = (
"๋‹น์‹ ์€ ํ•™์› ์ƒ๋‹ด/์ปค๋ฆฌํ˜๋Ÿผ ์„ค๊ณ„ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. "
"ํ•™์ƒ๊ณผ ํ•™๋ถ€๋ชจ๊ฐ€ ๋ฐ”๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ๊ณ„ํš์„ ์ œ์‹œํ•˜์„ธ์š”. "
"ํ•ญ์ƒ ์ฃผ์ฐจ๋ณ„(Week 1~N) ๋กœ๋“œ๋งต, ๊ณผ์ œ/์„ฑ๊ณผ๋ฌผ, ์ถ”์ฒœ ๊ต์žฌ/๋„๊ตฌ, ํ‰๊ฐ€ ํฌ์ธํŠธ๋ฅผ ์งง๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ."
)
user_prompt = (
"์•„๋ž˜ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ตœ์ ์˜ ํ•™์Šต ์ถ”์ฒœ์•ˆ์„ ๋งŒ๋“ค์–ด ์ฃผ์„ธ์š”.\n\n"
+ user_profile +
"\n\nํ˜•์‹:\n"
"1) ํ•œ ์ค„ ์š”์•ฝ\n"
"2) ์ฃผ์ฐจ๋ณ„ ๊ณ„ํš(์ฃผ์ฐจ/ํ•™์Šต๋ชฉํ‘œ/ํ™œ๋™/๊ณผ์ œ)\n"
"3) ์ถ”์ฒœ ๊ต์žฌ/ํˆด(๊ฐ„๋‹จ ๋งํฌ๋ช…๋งŒ, ์‹ค์ œ URL ์ƒ๋žต)\n"
"4) ์˜ˆ์ƒ ๊ฒฐ๊ณผ๋ฌผ(ํฌํŠธํด๋ฆฌ์˜ค/๋Œ€ํšŒ/์ž๊ฒฉ์ฆ)\n"
"5) ๋‹ค์Œ ์˜ˆ์•ฝ ์ œ์•ˆ(์ฒดํ—˜ ์ˆ˜์—… 1ํšŒ + ์ •๊ทœ ๊ณผ์ •)\n"
"๋ฌธ์žฅ ์งง๊ฒŒ. ๋ถˆํ•„์š”ํ•œ ์ˆ˜์‹์–ด ๊ธˆ์ง€."
)
msgs = [
{"role": "system", "content": system},
{"role": "user", "content": user_prompt},
]
out = hf_chat(MODEL_QA, msgs, temperature=temperature, max_tokens=max_tokens)
if out.startswith("โŒ") or out.startswith("โš ๏ธ"):
rec = f"""[๊ฐ„๋‹จ ์ถ”์ฒœ์•ˆ โ€” Fallback]
ํ•œ ์ค„ ์š”์•ฝ: {grade} ๋Œ€์ƒ {focus_str} {level} ๊ณผ์ •, {weeks}์ฃผ, ์ฃผ {hours_per_week}์‹œ๊ฐ„.
์ฃผ์ฐจ๋ณ„:
- Week 1: ํ™˜๊ฒฝ ์„ค์ •/๊ธฐ์ดˆ ๋ฌธ๋ฒ• โ€” ๊ณผ์ œ: ์„ค์น˜/HelloWorld/๊ฐ„๋‹จ ๋ฌธ์ œ 3๊ฐœ
- Week 2: ํ•ต์‹ฌ ๊ฐœ๋… ์ตํžˆ๊ธฐ โ€” ๊ณผ์ œ: {('์„ผ์„œ ์ œ์–ด ์‹ค์Šต' if '์•„๋‘์ด๋…ธ' in focus_str else '๋ฏธ๋‹ˆ ํ”„๋กœ์ ํŠธ 1')}
- Week 3: ์ฃผ์ œ ์‹ฌํ™” โ€” ๊ณผ์ œ: ๋ฏธ๋‹ˆ ํ”„๋กœ์ ํŠธ 2
- Week 4: ์ข…ํ•ฉ ํ”„๋กœ์ ํŠธ โ€” ๊ณผ์ œ: ๋ฐœํ‘œ ์ž๋ฃŒ/ํฌํŠธํด๋ฆฌ์˜ค ์ •๋ฆฌ
์ถ”์ฒœ ๋„๊ตฌ: VSCode, GitHub(์ฝ”๋“œ/๋ณด๊ณ ์„œ), {('Arduino IDE' if '์•„๋‘์ด๋…ธ' in focus_str else 'Notion')}
์˜ˆ์ƒ ๊ฒฐ๊ณผ๋ฌผ: ํ”„๋กœ์ ํŠธ 1~2๊ฐœ, ํฌํŠธํด๋ฆฌ์˜ค ํŽ˜์ด์ง€
๋‹ค์Œ ์˜ˆ์•ฝ ์ œ์•ˆ: ์ฒดํ—˜ 1ํšŒ(60๋ถ„) ํ›„ ์ฃผ {hours_per_week}์‹œ๊ฐ„ ์ •๊ทœ๋ฐ˜ ๋“ฑ๋ก
"""
return rec.strip()
return out
# =========================
# Chat helpers
# =========================
def history_to_messages(history: List[Tuple[str, str]], user_text: str) -> list:
msgs = [
{"role": "system",
"content": (
"๋‹น์‹ ์€ ๊ฐ„๊ฒฐํ•˜๊ณ  ์ •ํ™•ํ•œ ํ•œ๊ตญ์–ด ์กฐ์ˆ˜์ž…๋‹ˆ๋‹ค. ๋ถˆํ•„์š”ํ•œ ์ˆ˜์‹์–ด ์—†์ด ํ•ต์‹ฌ๋งŒ ๋‹ตํ•˜์„ธ์š”. "
"ํ•™์ƒ(์ดˆ/์ค‘/๊ณ )๊ณผ ํ•™๋ถ€๋ชจ๊ฐ€ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋‹จ๊ณ„๋ณ„๋กœ ์„ค๋ช…ํ•˜๊ณ , ํ•„์š”์‹œ ์งง์€ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด์ฃผ์„ธ์š”."
)},
]
for u, a in history:
if u:
msgs.append({"role": "user", "content": u})
if a:
msgs.append({"role": "assistant", "content": a})
if user_text:
msgs.append({"role": "user", "content": user_text})
return msgs
def chat_qa(user_input: str, history: List[Tuple[str, str]], temperature: float, max_new_tokens: int):
user_input = (user_input or "").strip()
if not user_input:
return history, "", history
msgs = history_to_messages(history, user_input)
answer = hf_chat(MODEL_QA, msgs, temperature=temperature, max_tokens=max_new_tokens)
history = history + [(user_input, answer)]
return history, "", history
def reset_chat():
return [], []
# =========================
# Booking handlers
# =========================
def refresh_slots_update():
return gr.update(choices=available_slots(), value=None)
def submit_booking(name: str, grade: str, focuses: List[str], level: str,
contact: str, slot: str, notes: str):
name = (name or "").strip()
contact = (contact or "").strip()
slot = (slot or "").strip()
focus_str = ", ".join(focuses) if focuses else "์ผ๋ฐ˜"
if not name:
return "โš ๏ธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”.", reservation_rows(load_reservations()), refresh_slots_update()
if not contact:
return "โš ๏ธ ์—ฐ๋ฝ์ฒ˜(์ „ํ™”/์นด์นด์˜ค/์ด๋ฉ”์ผ) ์ž…๋ ฅํ•˜์„ธ์š”.", reservation_rows(load_reservations()), refresh_slots_update()
if not slot:
return "โš ๏ธ ์˜ˆ์•ฝ ์Šฌ๋กฏ์„ ์„ ํƒํ•˜์„ธ์š”.", reservation_rows(load_reservations()), refresh_slots_update()
if slot in booked_slots():
return "โŒ ์ด๋ฏธ ์˜ˆ์•ฝ๋œ ์Šฌ๋กฏ์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์‹œ๊ฐ„์„ ์„ ํƒํ•˜์„ธ์š”.", reservation_rows(load_reservations()), refresh_slots_update()
resv = load_reservations()
item = {
"name": name,
"grade": grade,
"focus": focus_str,
"level": level,
"contact": contact,
"slot": slot,
"notes": (notes or "").strip(),
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
}
resv.append(item)
save_reservations(resv)
return (f"โœ… ์˜ˆ์•ฝ ์™„๋ฃŒ: {slot} ยท {name} ({grade}, {focus_str}/{level}) โ€” ๋‹ด๋‹น์ž๊ฐ€ ๊ณง ์—ฐ๋ฝ๋“œ๋ฆฝ๋‹ˆ๋‹ค.",
reservation_rows(resv),
refresh_slots_update())
# =========================
# Health check
# =========================
def diagnose() -> str:
if not HF_TOKEN:
return "HF_TOKEN ์—†์Œ"
try:
r = requests.get(f"{API_BASE}/models", headers={"Authorization": f"Bearer {HF_TOKEN}"}, timeout=20)
head = f"GET /models -> {r.status_code}"
body = r.text[:1000]
return f"{head}\n{body}"
except Exception as e:
return f"์š”์ฒญ ์‹คํŒจ: {e}"
# =========================
# UI (Toss-like)
# =========================
CSS = """
:root {
--primary: #0064ff;
--bg: #ffffff;
--card: #ffffff;
--text: #0b1020;
--muted: #6b7280;
--stroke: #e6ecf5;
--radius: 16px;
}
.gradio-container { font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", sans-serif; background: var(--bg); }
.toss-header {
padding: 28px 24px; border-radius: 24px;
background: linear-gradient(135deg, #eaf2ff 0%, #f6faff 100%);
border: 1px solid #e5efff; margin-bottom: 12px;
}
.toss-title { font-size: 28px; font-weight: 800; color: var(--text); letter-spacing: -0.02em; margin: 0; }
.toss-sub { color: var(--muted); margin-top: 6px; font-size: 14px; }
.toss-card {
background: var(--card); border: 1px solid var(--stroke);
border-radius: var(--radius); box-shadow: 0 8px 24px rgba(15,23,42,0.06); padding: 18px;
}
.toss-primary { background: var(--primary) !important; color: white !important; border-radius: 12px !important; font-weight: 700 !important; }
.toss-input textarea, .toss-input input, .toss-input select { border-radius: 14px !important; }
.toss-note { color: var(--muted); font-size: 12px; }
footer { display: none !important; }
label { font-weight: 700 !important; }
.gr-chatbot { border-radius: var(--radius); border: 1px solid var(--stroke); }
.gr-chatbot .message { border-radius: 14px !important; }
.gr-chatbot .message.user { background: #eef5ff !important; }
.gr-chatbot .message.bot { background: #f7f8fb !important; }
"""
with gr.Blocks(title="์œจํ•˜ SW๋ฏธ๋ž˜์˜์žฌ์ปดํ“จํ„ฐํ•™์› โ€” ์˜ˆ์•ฝยท์ถ”์ฒœยทQ&A (HF Router)", css=CSS, theme=gr.themes.Soft()) as demo:
with gr.Column():
gr.HTML(f"""
<div class="toss-header">
<div class="toss-title">์œจํ•˜ SW๋ฏธ๋ž˜์˜์žฌ์ปดํ“จํ„ฐํ•™์› โ€” ์˜ˆ์•ฝยท์ถ”์ฒœยทQ&A</div>
<div class="toss-sub">
์ดˆ/์ค‘/๊ณ  ๋งž์ถคํ˜• AIยท์ฝ”๋”ฉ โ€” Python ยท C ยท ์•„๋‘์ด๋…ธ ยท ์›น๊ฐœ๋ฐœ ยท ์˜์ƒํŽธ์ง‘ ยท ITQ/GTQ ยท AI/๋ฐ์ดํ„ฐ ยท ๊ฒฝ์ง„๋Œ€ํšŒ
<br>์—”๋“œํฌ์ธํŠธ: <code>{API_BASE}</code> ยท ๋ชจ๋ธ: <code>{MODEL_QA}</code>
</div>
</div>
""")
with gr.Tabs():
# 1) ์˜ˆ์•ฝยท์ถ”์ฒœ
with gr.Tab("์˜ˆ์•ฝ ยท ์ถ”์ฒœ"):
with gr.Row():
# Left: Recommendation
with gr.Column(scale=5):
with gr.Group(elem_classes=["toss-card", "toss-input"]):
gr.Markdown("### ๋งž์ถค ์ถ”์ฒœ")
grade = gr.Radio(["์ดˆ๋“ฑ", "์ค‘๋“ฑ", "๊ณ ๋“ฑ", "์ผ๋ฐ˜"], value="์ค‘๋“ฑ", label="ํ•™๋…„")
focus = gr.CheckboxGroup(
["ํŒŒ์ด์ฌ", "C", "์•„๋‘์ด๋…ธ", "์›น๊ฐœ๋ฐœ", "์˜์ƒํŽธ์ง‘",
"์ฝ”๋”ฉ์ž๊ฒฉ์ฆ(ITQ/GTQ)", "AI/๋ฐ์ดํ„ฐ", "๊ฒฝ์ง„๋Œ€ํšŒ"],
label="๊ด€์‹ฌ ๋ถ„์•ผ", value=["ํŒŒ์ด์ฌ"]
)
level = gr.Radio(["์ž…๋ฌธ", "๊ธฐ์ดˆ", "์ค‘๊ธ‰", "์‹ฌํ™”"], value="๊ธฐ์ดˆ", label="์ˆ˜์ค€")
goals = gr.Textbox(label="๋ชฉํ‘œ(์˜ˆ: ๋Œ€ํšŒ ์ž…์ƒ/์ž๊ฒฉ์ฆ/ํฌํŠธํด๋ฆฌ์˜ค/์ˆ˜ํ–‰ํ‰๊ฐ€ ๋“ฑ)", lines=2)
with gr.Row():
weeks = gr.Slider(2, 16, value=8, step=1, label="๊ธฐ๊ฐ„(์ฃผ)")
hpw = gr.Slider(1, 6, value=2, step=1, label="์ฃผ๋‹น ์‹œ๊ฐ„(์‹œ๊ฐ„)")
constraints = gr.Textbox(label="์ œ์•ฝ(์‹œ๊ฐ„/์˜ˆ์‚ฐ/์ง„๋„ ๋“ฑ)", lines=1, placeholder="์˜ˆ: ํ‰์ผ ์ €๋…๋งŒ ๊ฐ€๋Šฅ")
with gr.Row():
r_temp = gr.Slider(0.0, 1.0, value=0.2, step=0.05, label="temperature")
r_max = gr.Slider(64, 1024, value=320, step=32, label="max_tokens")
btn_rec = gr.Button("ํ•™์Šต ์ถ”์ฒœ ๋ฐ›๊ธฐ", elem_classes=["toss-primary"])
with gr.Group(elem_classes=["toss-card"]):
rec_out = gr.Textbox(label="์ถ”์ฒœ ๊ฒฐ๊ณผ", lines=18)
# Right: Booking
with gr.Column(scale=4):
with gr.Group(elem_classes=["toss-card", "toss-input"]):
gr.Markdown("### ์˜ˆ์•ฝ")
name = gr.Textbox(label="ํ•™์ƒ ์ด๋ฆ„", placeholder="์˜ˆ: ํ™๊ธธ๋™")
contact = gr.Textbox(label="์—ฐ๋ฝ์ฒ˜", placeholder="์ „ํ™”/์นด์นด์˜ค/์ด๋ฉ”์ผ")
b_grade = gr.Dropdown(["์ดˆ๋“ฑ", "์ค‘๋“ฑ", "๊ณ ๋“ฑ", "์ผ๋ฐ˜"], value="์ค‘๋“ฑ", label="ํ•™๋…„")
b_focus = gr.CheckboxGroup(
["ํŒŒ์ด์ฌ", "C", "์•„๋‘์ด๋…ธ", "์›น๊ฐœ๋ฐœ", "์˜์ƒํŽธ์ง‘",
"์ฝ”๋”ฉ์ž๊ฒฉ์ฆ(ITQ/GTQ)", "AI/๋ฐ์ดํ„ฐ", "๊ฒฝ์ง„๋Œ€ํšŒ"],
label="๊ด€์‹ฌ ๋ถ„์•ผ", value=["ํŒŒ์ด์ฌ"]
)
b_level = gr.Dropdown(["์ž…๋ฌธ", "๊ธฐ์ดˆ", "์ค‘๊ธ‰", "์‹ฌํ™”"], value="๊ธฐ์ดˆ", label="๋ ˆ๋ฒจ")
slot = gr.Dropdown(choices=available_slots(), label="์˜ˆ์•ฝ ์Šฌ๋กฏ(์›”~ํ† , 15/17/19์‹œ)")
notes = gr.Textbox(label="๋น„๊ณ (์„ ํ˜ธ ์ฃผ์ œ/ํŠน์ด์‚ฌํ•ญ)", lines=2)
with gr.Row():
btn_refresh = gr.Button("์Šฌ๋กฏ ์ƒˆ๋กœ๊ณ ์นจ")
btn_book = gr.Button("์˜ˆ์•ฝ ํ™•์ •", elem_classes=["toss-primary"])
with gr.Group(elem_classes=["toss-card"]):
gr.Markdown("### ํ˜„์žฌ ์˜ˆ์•ฝ ํ˜„ํ™ฉ")
table = gr.Dataframe(
headers=["์Šฌ๋กฏ", "์ด๋ฆ„", "ํ•™๋…„", "๊ด€์‹ฌ ๋ถ„์•ผ", "๋ ˆ๋ฒจ", "์—ฐ๋ฝ์ฒ˜", "๋น„๊ณ ", "์˜ˆ์•ฝ์‹œ๊ฐ"],
value=reservation_rows(load_reservations()),
interactive=False,
)
res_msg = gr.Markdown("", elem_classes=["toss-note"])
# Recommend
btn_rec.click(
fn=recommend_plan,
inputs=[grade, focus, level, goals, weeks, hpw, constraints, r_temp, r_max],
outputs=[rec_out],
)
# Refresh slots
btn_refresh.click(fn=refresh_slots_update, outputs=[slot])
# Booking
btn_book.click(
fn=submit_booking,
inputs=[name, b_grade, b_focus, b_level, contact, slot, notes],
outputs=[res_msg, table, slot],
)
# 2) ์งˆ๋ฌธํ•˜๊ธฐ (์ฑ„ํŒ…)
with gr.Tab("์งˆ๋ฌธํ•˜๊ธฐ (์ฑ„ํŒ…)"):
chat_history = gr.State([]) # list of (user, assistant)
with gr.Row():
with gr.Column(scale=5):
chat = gr.Chatbot(label="๋Œ€ํ™”", type="tuples", height=540, show_label=True)
with gr.Column(scale=4):
with gr.Group(elem_classes=["toss-card", "toss-input"]):
q = gr.Textbox(
label="์งˆ๋ฌธ ์ž…๋ ฅ",
placeholder="์˜ˆ) ์ดˆ๋“ฑ ํŒŒ์ด์ฌ ์ฒซ ์ˆ˜์—… ์ปค๋ฆฌํ˜๋Ÿผ์„ 4์ฃผ๋กœ ์งœ์ค˜",
lines=6,
)
with gr.Row():
temp = gr.Slider(0.0, 1.0, value=0.2, step=0.05, label="temperature")
max_tok = gr.Slider(64, 1024, value=256, step=32, label="max_tokens")
with gr.Row():
btn_send = gr.Button("๋‹ต๋ณ€ ์ƒ์„ฑ", size="lg", elem_classes=["toss-primary"])
btn_clear = gr.Button("์ƒˆ ๋Œ€ํ™”", size="lg")
with gr.Group(elem_classes=["toss-card"]):
gr.Markdown("**๋น ๋ฅธ ์ž…๋ ฅ(์ƒ˜ํ”Œ ์งˆ๋ฌธ)**")
with gr.Row():
b1 = gr.Button("์ดˆ๋“ฑ ํŒŒ์ด์ฌ 4์ฃผ ์ปค๋ฆฌํ˜๋Ÿผ")
b2 = gr.Button("์ค‘๋“ฑ ์•„๋‘์ด๋…ธ ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด 5๊ฐœ")
with gr.Row():
b3 = gr.Button("๊ณ ๋“ฑ AI ๊ฒฝ์ง„๋Œ€ํšŒ ๋Œ€๋น„ ๋กœ๋“œ๋งต")
b4 = gr.Button("์ฝ”๋”ฉ์ž๊ฒฉ์ฆ(ITQ/GTQ) ๋‹จ๊ธฐ ํ•ฉ๊ฒฉ ์ „๋žต")
b1.click(lambda: "์ดˆ๋“ฑ ํŒŒ์ด์ฌ ์ฒซ ์ˆ˜์—…๋ถ€ํ„ฐ 4์ฃผ ์ปค๋ฆฌํ˜๋Ÿผ์„ ์ฃผ์ฐจ๋ณ„ ๋ชฉํ‘œ/๊ต์žฌ/๊ณผ์ œ๋กœ ์ •๋ฆฌํ•ด์ค˜.", outputs=q)
b2.click(lambda: "์ค‘ํ•™์ƒ ์ˆ˜์ค€์—์„œ ๊ฐ€๋Šฅํ•œ ์•„๋‘์ด๋…ธ ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด 5๊ฐœ๋ฅผ ๋‚œ์ด๋„/๋ถ€ํ’ˆ/ํ•™์Šต๋ชฉํ‘œ์™€ ํ•จ๊ป˜ ํ‘œ๋กœ ์ •๋ฆฌํ•ด์ค˜.", outputs=q)
b3.click(lambda: "๊ณ ๋“ฑํ•™์ƒ ๊ธฐ์ค€ AI/๋ฐ์ดํ„ฐ ๊ฒฝ์ง„๋Œ€ํšŒ ๋Œ€๋น„ ๋กœ๋“œ๋งต์„ 8์ฃผ ํ”Œ๋žœ์œผ๋กœ ์ƒ์„ธํžˆ ๋งŒ๋“ค์–ด์ค˜.", outputs=q)
b4.click(lambda: "ITQ(ํ•œ๊ธ€/์—‘์…€)๊ณผ GTQ ํฌํ† ์ƒต ๋‹จ๊ธฐ ํ•ฉ๊ฒฉ ์ „๋žต์„ ์ฃผ์ฐจ๋ณ„ ํ•™์Šต๊ณ„ํš๊ณผ ๊ธฐ์ถœ ํฌ์ธํŠธ๋กœ ์š”์•ฝํ•ด์ค˜.", outputs=q)
btn_send.click(fn=chat_qa, inputs=[q, chat_history, temp, max_tok], outputs=[chat, q, chat_history])
q.submit(fn=chat_qa, inputs=[q, chat_history, temp, max_tok], outputs=[chat, q, chat_history])
btn_clear.click(fn=reset_chat, outputs=[chat, chat_history])
gr.Markdown('<div class="toss-note">โ€ข ๋Œ€ํ™” ๊ธฐ๋ก์€ ์œ„ ์ฑ„ํŒ… ์˜์—ญ์— ์ˆœ์„œ๋Œ€๋กœ ๋ˆ„์ ๋ฉ๋‹ˆ๋‹ค.'
'<br>โ€ข ๊ณผ๊ธˆ ์ฃผ์˜: Router ์‚ฌ์šฉ๋Ÿ‰/ํ† ํฐ์— ๋”ฐ๋ผ ๋น„์šฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</div>')
# 3) ํ•™์› ์†Œ๊ฐœ
with gr.Tab("ํ•™์› ์†Œ๊ฐœ"):
with gr.Group(elem_classes=["toss-card"]):
gr.Markdown("""
### ์™œ ์œจํ•˜ SW๋ฏธ๋ž˜์˜์žฌ์ปดํ“จํ„ฐํ•™์›์ธ๊ฐ€?
- **์‹ค์ „ ์ค‘์‹ฌ ์ปค๋ฆฌํ˜๋Ÿผ**: ํŒŒ์ด์ฌยทCยท์•„๋‘์ด๋…ธยท์›นยท์˜์ƒํŽธ์ง‘๊นŒ์ง€ ํ”„๋กœ์ ํŠธ๋กœ ๋ฐฐ์šฐ๋Š” ๊ณผ์ •
- **๋งž์ถคํ˜• ์ง€๋„**: ์ดˆ/์ค‘/๊ณ  ํ•™๋…„ยท์ˆ˜์ค€ยท์ง„๋กœ์— ๋งž์ถ˜ ๊ฐœ๋ณ„ ํ”Œ๋žœ
- **์‹ค์  ์ง€ํ–ฅ**: ์ฝ”๋”ฉ ์ž๊ฒฉ์ฆ(ITQ/GTQ ๋“ฑ)ยท๊ต๋‚ด์™ธ ๋Œ€ํšŒยทํฌํŠธํด๋ฆฌ์˜ค ์ค€๋น„
### ์ถ”์ฒœ ํŠธ๋ž™
1) **๊ธฐ์ดˆ ๋‹ค์ง€๊ธฐ**: ํŒŒ์ด์ฌ ๊ธฐ์ดˆยท๋ฌธ์ œํ•ด๊ฒฐยท์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ฒดํ—˜
2) **๋ฉ”์ดํ‚น/IoT**: ์•„๋‘์ด๋…ธยท์„ผ์„œยท๊ฐ„๋‹จํ•œ ์ž๋™ํ™” ํ”„๋กœ์ ํŠธ
3) **๋ฏธ๋””์–ด/๋””์ž์ธ**: ํ”„๋ฆฌ๋ฏธ์–ดยทํฌํ† ์ƒตยท์ฝ˜ํ…์ธ  ์ œ์ž‘
4) **์‹ฌํ™”/๊ฒฝ์ง„๋Œ€ํšŒ**: ๋ฐ์ดํ„ฐยทAI ๊ธฐ์ดˆ, ๋Œ€ํšŒ ์ค€๋น„ ๋ฐ ์ž‘ํ’ˆ ์™„์„ฑ
> ์ƒ๋‹ด/์ฒดํ—˜ ์ˆ˜์—… ๋ฌธ์˜๋Š” '์˜ˆ์•ฝ ยท ์ถ”์ฒœ' ํƒญ์—์„œ ์ง„ํ–‰ํ•˜์„ธ์š”.
""")
# 4) ์ƒํƒœ ์ ๊ฒ€
with gr.Tab("์ƒํƒœ ์ ๊ฒ€"):
with gr.Group(elem_classes=["toss-card"]):
diag_btn = gr.Button("์—”๋“œํฌ์ธํŠธ ์ ๊ฒ€", elem_classes=["toss-primary"])
diag_out = gr.Textbox(label="๊ฒฐ๊ณผ", lines=16)
diag_btn.click(fn=diagnose, outputs=diag_out)
gr.Markdown('<div class="toss-note">ํ™˜๊ฒฝ๋ณ€์ˆ˜: <code>HF_TOKEN</code> (ํ•„์ˆ˜), '
'<code>HF_ENDPOINT_URL</code> / <code>HF_MODEL_ID</code> (์„ ํƒ)</div>')
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860, share=True)