career-chatbot-exp / src /streamlit_app.py
jonghhhh's picture
Update src/streamlit_app.py
812abc4 verified
Raw
History Blame Contribute Delete
9.66 kB
# app.py
# ───────────────────────────────────────────────────────────────────────────
# 챗봇 μ‹€ν—˜ 메인 μ•± (SQLite 제거 버전).
# 흐름: intro β†’ pre(사전섀문) β†’ chat(λŒ€ν™”) β†’ post(사후섀문) β†’ done
# μ €μž₯: storage.py의 이쀑 μ•ˆμ „λ§(κ΅¬κΈ€μ‹œνŠΈ + λ©”λͺ¨λ¦¬). DB 파일이 μ—†λ‹€.
# μ‚¬μ΄λ“œλ°”: (μœ„) 연ꡬ μ•ˆλ‚΄ μ„€λͺ… / (μ•„λž˜) 데이터 CSV λ‹€μš΄λ‘œλ“œ λ°±μ—…
# ───────────────────────────────────────────────────────────────────────────
import uuid
import time
import streamlit as st
import storage
from prompts import SYSTEM_PROMPTS, TASK_INSTRUCTION
from llm import stream_response
st.set_page_config(page_title="μ§„λ‘œ 챗봇 λŒ€ν™” 연ꡬ", page_icon="πŸ’¬")
# ===== μ„Έμ…˜ μƒνƒœ μ΄ˆκΈ°ν™” =====
# Streamlit은 λ§€ μƒν˜Έμž‘μš©λ§ˆλ‹€ 슀크립트λ₯Ό μœ„β†’μ•„λž˜λ‘œ λ‹€μ‹œ μ‹€ν–‰ν•œλ‹€.
# λ”°λΌμ„œ 'μ§€κΈˆ μ–΄λŠ 단계인가'λ₯Ό session_state에 μ €μž₯ν•΄ 둬야 ν•œλ‹€.
if "stage" not in st.session_state:
st.session_state.stage = "intro" # ν˜„μž¬ 단계
st.session_state.participant_id = str(uuid.uuid4()) # 읡λͺ… μ‹λ³„μž(이름·이메일 μ•ˆ λ°›μŒ)
st.session_state.condition = None # 'A' λ˜λŠ” 'B'
st.session_state.messages = [] # ν™”λ©΄ ν‘œμ‹œμš© λŒ€ν™” 기둝
st.session_state.turn = 0 # λŒ€ν™” ν„΄ 수
storage.init_state(st.session_state) # λˆ„μ  리슀트 3개 μ€€λΉ„
# ===== μ‚¬μ΄λ“œλ°”: μœ„μͺ½ μ•ˆλ‚΄ + μ•„λž˜μͺ½ λ‹€μš΄λ‘œλ“œ =====
def render_sidebar():
with st.sidebar:
# ── (μœ„) 연ꡬ μ•ˆλ‚΄ μ„€λͺ… ──
st.header("πŸ“‹ 연ꡬ μ•ˆλ‚΄")
st.markdown(
"- **μ†Œμš”μ‹œκ°„**: μ•½ 10~15λΆ„\n"
"- **절차**: 사전 μ„€λ¬Έ β†’ 챗봇 λŒ€ν™”(5ν„΄+) β†’ 사후 μ„€λ¬Έ\n"
"- **읡λͺ…μ„±**: 이름·이메일을 μˆ˜μ§‘ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.\n"
"- **유의**: 챗봇은 AI이며, μ „λ¬Έ 자문이 ν•„μš”ν•˜λ©΄ μ „λ¬Έκ°€μ—κ²Œ 별도 λ¬Έμ˜ν•˜μ„Έμš”."
)
st.divider()
# ── (μ•„λž˜) 데이터 λ‹€μš΄λ‘œλ“œ λ°±μ—… ──
# μ•ˆμ „λ§ 2: HF Dataset μ—…λ‘œλ“œκ°€ μ‹€νŒ¨ν•΄λ„ 이 λ©”λͺ¨λ¦¬ λˆ„μ λΆ„μœΌλ‘œ 회수 κ°€λŠ₯.
# 주의: session_stateλŠ” 'ν˜„μž¬ λΈŒλΌμš°μ € μ„Έμ…˜'의 λ°μ΄ν„°λ§Œ λ‹΄λŠ”λ‹€.
# 전체 μ°Έμ—¬μž 톡합본은 HF Datasetμ—μ„œ λ°›λŠ”λ‹€(μ•ˆμ „λ§ 1).
st.header("⬇️ 데이터 λ‹€μš΄λ‘œλ“œ")
st.caption("이 μ„Έμ…˜μ— μŒ“μΈ 기둝(λ°±μ—…μš©). 전체 톡합본은 HF Dataset μ°Έμ‘°.")
n_p = len(st.session_state["rows_participants"])
n_m = len(st.session_state["rows_messages"])
n_s = len(st.session_state["rows_surveys"])
st.download_button("μ°Έμ—¬μž CSV", storage.to_csv_bytes(st.session_state["rows_participants"]),
"participants.csv", "text/csv", disabled=(n_p == 0))
st.download_button("λŒ€ν™” CSV", storage.to_csv_bytes(st.session_state["rows_messages"]),
"messages.csv", "text/csv", disabled=(n_m == 0))
st.download_button("μ„€λ¬Έ CSV", storage.to_csv_bytes(st.session_state["rows_surveys"]),
"surveys.csv", "text/csv", disabled=(n_s == 0))
st.caption(f"μ°Έμ—¬μž {n_p} Β· λŒ€ν™” {n_m}쀄 Β· μ„€λ¬Έ {n_s}쀄")
render_sidebar()
# ===== Stage 1: 인트둜 + λ™μ˜ =====
if st.session_state.stage == "intro":
st.title("AI μ§„λ‘œμƒλ‹΄ 챗봇 λŒ€ν™” 연ꡬ")
st.markdown(
"μ‘Έμ—… ν›„ μ§„λ‘œμ— λŒ€ν•΄ 챗봇과 λŒ€ν™”ν•˜λŠ” μ—°κ΅¬μž…λ‹ˆλ‹€. "
"사전 μ„€λ¬Έ β†’ λŒ€ν™” β†’ 사후 μ„€λ¬Έ 순으둜 μ§„ν–‰λ©λ‹ˆλ‹€."
)
if st.button("λ™μ˜ν•˜κ³  μ‹œμž‘ν•˜κΈ°", type="primary"):
# μ°Έμ—¬μžλ³„ 랜덀 λ°°μ • (μ„Έμ…˜μ΄ 뢄리돼 μžˆμ–΄ κ΅λŒ€ 배정이 λΆˆκ°€ν•˜λ―€λ‘œ)
import random
cond = random.choice(["A", "B"])
st.session_state.condition = cond
storage.add_participant(st.session_state, st.session_state.participant_id, cond)
st.session_state.stage = "pre"
st.rerun()
# ===== Stage 2: 사전쑰사 =====
elif st.session_state.stage == "pre":
st.title("사전 μ„€λ¬Έ")
with st.form("pre_survey"):
year = st.selectbox("ν•™λ…„", ["1ν•™λ…„", "2ν•™λ…„", "3ν•™λ…„", "4ν•™λ…„", "μ‘Έμ—…μœ μ˜ˆ/νœ΄ν•™", "λŒ€ν•™μ›μƒ"])
major = st.selectbox("전곡 계열", ["인문", "μ‚¬νšŒ", "상경", "곡학", "μžμ—°", "μ˜μ•½", "예체λŠ₯", "기타"])
d_stage = st.selectbox("ν˜„μž¬ μ§„λ‘œ κ²°μ • 단계",
["아직 탐색 쀑", "λ°©ν–₯이 μ–΄λŠ 정도 작힘", "ꡬ체적 κ³„νš 수립 쀑", "거의 ν™•μ •"])
anxiety = st.slider("졜근 μ§„λ‘œ λΆˆμ•ˆ 정도 (1: μ „ν˜€ μ—†μŒ ~ 7: 맀우 큼)", 1, 7, 4)
ai_freq = st.selectbox("AI 챗봇 μ‚¬μš© λΉˆλ„", ["거의 μ•ˆ 씀", "μ›” 1~2회", "μ£Ό 1~2회", "거의 맀일"])
if st.form_submit_button("λ‹€μŒ"):
pid, cond = st.session_state.participant_id, st.session_state.condition
for qid, val in [("year", year), ("major", major), ("decision_stage", d_stage),
("career_anxiety", anxiety), ("ai_usage", ai_freq)]:
storage.add_survey(st.session_state, pid, cond, "pre", qid, val)
st.session_state.stage = "chat"
st.rerun()
# ===== Stage 3: λŒ€ν™” =====
elif st.session_state.stage == "chat":
st.title("μ§„λ‘œ 챗봇과 λŒ€ν™”ν•˜κΈ°")
st.info(TASK_INSTRUCTION)
st.caption(f"μ΅œμ†Œ 5ν„΄ 이상 λŒ€ν™” ν›„ μ•„λž˜ λ²„νŠΌμ„ λˆ„λ₯΄μ„Έμš”. (ν˜„μž¬ {st.session_state.turn}ν„΄)")
# 이전 λ©”μ‹œμ§€ λ‹€μ‹œ 그리기(λ§€ μ‹€ν–‰λ§ˆλ‹€ 화면을 μƒˆλ‘œ κ·Έλ¦¬λ―€λ‘œ ν•„μš”)
for m in st.session_state.messages:
with st.chat_message(m["role"]):
st.markdown(m["content"])
if prompt := st.chat_input("λ©”μ‹œμ§€λ₯Ό μž…λ ₯ν•˜μ„Έμš”"):
pid, cond = st.session_state.participant_id, st.session_state.condition
st.session_state.turn += 1
# 1) μ‚¬μš©μž λ©”μ‹œμ§€: ν™”λ©΄ ν‘œμ‹œ + μ €μž₯
st.session_state.messages.append({"role": "user", "content": prompt})
storage.add_message(st.session_state, pid, cond, st.session_state.turn, "user", prompt)
with st.chat_message("user"):
st.markdown(prompt)
# 2) 챗봇 응닡: 슀트리밍으둜 ν•œ 청크씩 ν‘œμ‹œ
with st.chat_message("assistant"):
placeholder = st.empty()
full, t0 = "", time.time()
try:
for delta in stream_response(SYSTEM_PROMPTS[cond], st.session_state.messages):
full += delta
placeholder.markdown(full + "β–Œ") # μ»€μ„œ 효과
placeholder.markdown(full)
except Exception as e:
full = f"[였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {e}]"
placeholder.markdown(full)
latency = int((time.time() - t0) * 1000)
st.session_state.messages.append({"role": "assistant", "content": full})
storage.add_message(st.session_state, pid, cond, st.session_state.turn,
"assistant", full, latency)
# 5ν„΄ 이상이면 μ‚¬ν›„μ„€λ¬ΈμœΌλ‘œ λ„˜μ–΄κ°€λŠ” λ²„νŠΌ λ…ΈμΆœ
if st.session_state.turn >= 5:
if st.button("λŒ€ν™” μ’…λ£Œν•˜κ³  사후 μ„€λ¬ΈμœΌλ‘œ", type="primary"):
st.session_state.stage = "post"
st.rerun()
# ===== Stage 4: 사후쑰사 =====
elif st.session_state.stage == "post":
st.title("사후 μ„€λ¬Έ")
with st.form("post_survey"):
usefulness = st.slider("받은 정보가 μœ μš©ν–ˆλ‹€ (1~7)", 1, 7, 4)
warmth = st.slider("챗봇이 λ”°λœ»ν–ˆλ‹€ (1~7)", 1, 7, 4)
competence = st.slider("챗봇이 유λŠ₯ν–ˆλ‹€ (1~7)", 1, 7, 4)
trust = st.slider("챗봇을 μ‹ λ’°ν•œλ‹€ (1~7)", 1, 7, 4)
clarity = st.slider("λŒ€ν™” ν›„ μ§„λ‘œ 고민이 더 μ •λ¦¬λ˜μ—ˆλ‹€ (1~7)", 1, 7, 4)
recommend = st.slider("μΉœκ΅¬μ—κ²Œ μΆ”μ²œν•  의ν–₯ (1~7)", 1, 7, 4)
free_text = st.text_area("자유 의견: κ°€μž₯ 도움이 된 점, μ•„μ‰¬μ› λ˜ 점 (선택)")
if st.form_submit_button("제좜"):
pid, cond = st.session_state.participant_id, st.session_state.condition
for qid, val in [("usefulness", usefulness), ("warmth", warmth),
("competence", competence), ("trust", trust),
("clarity", clarity), ("recommend", recommend),
("free_text", free_text)]:
storage.add_survey(st.session_state, pid, cond, "post", qid, val)
# μ™„λ£Œ ν‘œμ‹œ(λ©”λͺ¨λ¦¬): ν•΄λ‹Ή μ°Έμ—¬μž ν–‰μ˜ completedλ₯Ό 1둜
for r in st.session_state["rows_participants"]:
if r["participant_id"] == pid:
r["completed"] = 1
# μ•ˆμ „λ§ 1: 이번 μ„Έμ…˜ λˆ„μ λΆ„μ„ HF Dataset에 일괄 μ—…λ‘œλ“œ
# (μ™„λ£Œ μ‹œμ μ— ν•œ 번만. μ‹€νŒ¨ν•΄λ„ λ©”λͺ¨λ¦¬ 백업이 λ‚¨μœΌλ―€λ‘œ 흐름은 계속)
storage.push_to_dataset(st.session_state)
st.session_state.stage = "done"
st.rerun()
# ===== Stage 5: μ™„λ£Œ =====
elif st.session_state.stage == "done":
st.title("μ°Έμ—¬ν•΄μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€ πŸ™")
st.markdown(f"μ°Έμ—¬μž ID: `{st.session_state.participant_id}`")
st.caption("응닡이 μ•ˆμ „ν•˜κ²Œ κΈ°λ‘λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")