Jongpal12 commited on
Commit
c2baf2c
·
verified ·
1 Parent(s): 4331b21

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +19 -52
app.py CHANGED
@@ -49,8 +49,6 @@ import requests
49
  from streamlit.components.v1 import html
50
  from css import render_message, render_chip_buttons, log_and_render, replay_log, _get_colors
51
 
52
- #st.success("🎉 앱이 성공적으로 시작되었습니다! 라이브러리 설치 성공!")
53
-
54
  # ──────────────────────────────── Dataset Repo 설정 ────────────────────────────────
55
  HF_DATASET_REPO = os.getenv("HF_DATASET_REPO", "emisdfde/moai-travel-data")
56
  HF_DATASET_REV = os.getenv("HF_DATASET_REV", "main")
@@ -171,6 +169,7 @@ Return ONLY a valid JSON object:
171
  }
172
  If unknown, use "none" or "" and NEVER add extra text outside JSON.
173
  """
 
174
  def to_llm_mode():
175
  # 같은 렌더 사이클에서 여러 번 호출되어도 1회만 동작하게 가드
176
  if not st.session_state.get("_llm_triggered"):
@@ -179,10 +178,11 @@ def to_llm_mode():
179
  st.session_state["llm_intro_needed"] = True
180
  st.rerun()
181
 
 
182
  def _ensure_llm_state():
183
- st.session_state.setdefault("llm_mode", False) # 풀스크린 모드용(기존)
184
- st.session_state.setdefault("llm_inline", False) # 인라인 표시용
185
- st.session_state.setdefault("llm_history", [])
186
  st.session_state.setdefault("llm_intro_needed", False)
187
  st.session_state.setdefault("llm_input", "")
188
 
@@ -245,13 +245,6 @@ def _llm_structured_extract(user_text: str):
245
  data.setdefault("notes", "")
246
  return data
247
 
248
- # ──────────────────────────────── Streamlit용 LLM 모드 UI ────────────────────────────────
249
- def _ensure_llm_state():
250
- st.session_state.setdefault("llm_mode", False)
251
- st.session_state.setdefault("llm_history", []) # [{'role':'user'|'assistant', 'content': str}, ...]
252
- st.session_state.setdefault("llm_intro_needed", False)
253
- st.session_state.setdefault("llm_input", "")
254
-
255
  def render_llm_followup(chat_container, inline=False):
256
  _ensure_llm_state()
257
  MAX_TURNS = 6
@@ -313,6 +306,8 @@ def render_llm_followup(chat_container, inline=False):
313
  log_and_render(q, sender="user", chat_container=chat_container,
314
  key=f"llm_user_{random.randint(1,999999)}")
315
  st.session_state.llm_history.append({"role": "user", "content": q})
 
 
316
 
317
  msgs = st.session_state.llm_history[-(MAX_TURNS-1):]
318
  a = _call_ollama_chat(
@@ -321,13 +316,19 @@ def render_llm_followup(chat_container, inline=False):
321
  temperature=0.8, top_p=0.9, top_k=40, repeat_penalty=1.1
322
  )
323
  if not a:
324
- log_and_render("⚠️ LLM 응답을 받지 못했습니다. Ollama 서버를 확인해 주세요.",
325
- sender="bot", chat_container=chat_container,
326
  key=f"llm_err_{random.randint(1,999999)}")
 
 
327
  else:
 
328
  log_and_render(a, sender="bot", chat_container=chat_container,
329
  key=f"llm_bot_{random.randint(1,999999)}")
330
  st.session_state.llm_history.append({"role": "assistant", "content": a})
 
 
 
331
  st.session_state["llm_input"] = ""
332
 
333
  # 하단 버튼: 인라인은 'LLM 패널 종료'만, 풀스크린은 'LLM 모드 종료'만
@@ -339,7 +340,6 @@ def render_llm_followup(chat_container, inline=False):
339
  st.session_state["llm_mode"] = False
340
  st.rerun()
341
 
342
-
343
  # 지연 초기화: import 시점에는 데이터 접근 금지, 여기서 한 번만 주입
344
  init_datasets(
345
  travel_df=travel_df,
@@ -350,6 +350,7 @@ init_datasets(
350
  master_df=master_df,
351
  theme_title_phrases=theme_title_phrases,
352
  )
 
353
  # ───────────────────────────────────── streamlit용 함수
354
  def init_session():
355
  if "chat_log" not in st.session_state:
@@ -377,9 +378,6 @@ st.markdown(
377
  unsafe_allow_html=True,
378
  )
379
 
380
- # 고정 이미지 URL
381
- #BG_URL = "https://plus.unsplash.com/premium_photo-1679830513869-cd3648acb1db?q=80&w=2127&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
382
-
383
  # === 배경 설정 UI (수정됨) ===
384
  st.sidebar.subheader("🎨 배경 설정")
385
  st.sidebar.toggle("배경 이미지 사용", key="bg_on", value=True)
@@ -413,7 +411,6 @@ else:
413
  value=palette[selected_color_name]
414
  )
415
 
416
-
417
  def apply_background():
418
  # 보호: 기존 ::before 배경이 있으면 끄기 (겹침/끊김 방지)
419
  base_reset_css = """
@@ -481,8 +478,6 @@ def apply_background():
481
  # 함수 호출
482
  apply_background()
483
 
484
-
485
-
486
  # ── P 글꼴 크기 14 px ───────────────────────────────────
487
  st.markdown("""
488
  <style>
@@ -517,7 +512,6 @@ def region_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
517
  st.session_state[prev_key] = set()
518
  st.session_state.pop(sample_key, None)
519
 
520
-
521
  # ────────────────── 1) restart 상태면 인트로만 출력하고 종료
522
  if st.session_state[step_key] == "restart":
523
  log_and_render(
@@ -569,7 +563,6 @@ def region_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
569
  st.rerun()
570
  return
571
 
572
-
573
  # 2.4) 샘플링 (이전 샘플이 없거나 비어 있으면 새로 추출)
574
  if sample_key not in st.session_state or st.session_state[sample_key].empty:
575
  sampled = remaining.sample(
@@ -629,7 +622,6 @@ def region_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
629
  st.session_state[step_key] = "detail"
630
  st.session_state.chat_log.append(("user", choice))
631
 
632
-
633
  # 실제로 선택된 여행지만 prev에 기록
634
  match = sampled[sampled["여행지"] == choice]
635
  if not match.empty:
@@ -707,7 +699,6 @@ def region_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
707
  st.stop()
708
  return
709
 
710
-
711
  # ────────────────── 4) 여행지 상세 단계
712
  if st.session_state[step_key] == "detail":
713
  chosen = st.session_state[region_key]
@@ -732,7 +723,6 @@ def region_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
732
  st.rerun()
733
  return
734
 
735
-
736
  # ────────────────── 5) 동행·연령 받기 단계
737
  elif st.session_state[step_key] == "companion":
738
  with chat_container:
@@ -803,7 +793,6 @@ def region_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
803
  st.rerun()
804
  return
805
 
806
-
807
  # ────────────────── 6) 동행·연령 필터링· 패키지 출력 단계
808
  elif st.session_state[step_key] == "package":
809
 
@@ -1234,13 +1223,13 @@ def intent_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
1234
  st.session_state[step_key] = "package_end"
1235
  show_llm_inline() # 플래그만 ON (rerun 없음)
1236
  render_llm_followup(chat_container, inline=True) # 👈 같은 사이클에서 바로 아래에 LLM 박스 출력
1237
- return
1238
 
1239
  # ────────────────── 7) 종료 단계
1240
  elif st.session_state[step_key] == "package_end":
1241
  log_and_render("필요하실 때 언제든지 또 찾아주세요! ✈️",
1242
  sender="bot", chat_container=chat_container,
1243
- key="goodbye")
1244
 
1245
  to_llm_mode()
1246
 
@@ -1263,7 +1252,6 @@ def emotion_ui(travel_df, external_score_df, festival_df, weather_df, package_df
1263
  st.session_state[prev_key] = set()
1264
  st.session_state.pop(sample_key, None)
1265
 
1266
-
1267
  # ────────────────── 1) restart 상태면 인트로만 출력하고 종료
1268
  if st.session_state[step_key] == "restart":
1269
  log_and_render(
@@ -1692,19 +1680,7 @@ def unknown_ui(country, city, chat_container, log_and_render):
1692
  chat_container=chat_container,
1693
  key="unknown_dest"
1694
  )
1695
-
1696
- # def _get_active_step_key():
1697
- # mode = st.session_state.get("mode", "unknown")
1698
- # mapping = {
1699
- # "region": "region_step",
1700
- # "intent": "intent_step",
1701
- # "emotion": "emotion_step",
1702
- # "theme_selection": "theme_step",
1703
- # "place_selection": "place_step",
1704
- # "user_info_input": "user_info_step",
1705
- # }
1706
- # # 매핑에 없으면 공용 키로
1707
- # return mapping.get(mode, "flow_step")
1708
  # ───────────────────────────────────── 챗봇 호출
1709
  def main():
1710
 
@@ -1721,12 +1697,6 @@ def main():
1721
  st.sidebar.selectbox("테마", ["피스타치오", "스카이블루", "크리미오트"], key="bubble_theme")
1722
  st.sidebar.toggle("타임스탬프 표시", value=False, key="show_time")
1723
 
1724
- # with st.sidebar.expander("DEBUG steps", expanded=False):
1725
- # st.write("mode:", st.session_state.get("mode"))
1726
- # st.write("step_key:", cur_step_key)
1727
- # st.write("state:", st.session_state.get(cur_step_key))
1728
-
1729
-
1730
  # ✅ 타자 효과 on/off 토글 (기본 ON)
1731
  st.sidebar.toggle("타자 효과", value=False, key="typewriter_on")
1732
 
@@ -1747,7 +1717,6 @@ def main():
1747
  )
1748
  st.session_state["greeting_rendered"] = True
1749
 
1750
-
1751
  # ───── 사용자 입력 & 추천 시작
1752
  # 1) 사용자 입력
1753
  user_input = st.text_input(
@@ -1778,7 +1747,6 @@ def main():
1778
  sender="user",
1779
  chat_container = chat_container,
1780
  key=f"user_input_{user_input}"
1781
-
1782
  )
1783
  st.session_state["user_input_rendered"] = True
1784
 
@@ -1803,7 +1771,6 @@ def main():
1803
  else:
1804
  mode = "emotion"
1805
  # 3) 고비용 단계: 정말 필요할 때만 감성(BERT) 실행
1806
- # with st.spinner("감정 분석 중..."): # UX 원하시면 스피너 추가
1807
  top_emotions, emotion_groups = analyze_emotion(user_input)
1808
 
1809
  # 4) 모드별 분기 (필요한 계산만 수행)
 
49
  from streamlit.components.v1 import html
50
  from css import render_message, render_chip_buttons, log_and_render, replay_log, _get_colors
51
 
 
 
52
  # ──────────────────────────────── Dataset Repo 설정 ────────────────────────────────
53
  HF_DATASET_REPO = os.getenv("HF_DATASET_REPO", "emisdfde/moai-travel-data")
54
  HF_DATASET_REV = os.getenv("HF_DATASET_REV", "main")
 
169
  }
170
  If unknown, use "none" or "" and NEVER add extra text outside JSON.
171
  """
172
+
173
  def to_llm_mode():
174
  # 같은 렌더 사이클에서 여러 번 호출되어도 1회만 동작하게 가드
175
  if not st.session_state.get("_llm_triggered"):
 
178
  st.session_state["llm_intro_needed"] = True
179
  st.rerun()
180
 
181
+ # ──────────────────────────────── LLM 상태: 단일 정의 (중복 제거) ────────────────────────────────
182
  def _ensure_llm_state():
183
+ st.session_state.setdefault("llm_mode", False) # 풀스크린 LLM 모드
184
+ st.session_state.setdefault("llm_inline", False) # 인라인 LLM 패널 표시 여부
185
+ st.session_state.setdefault("llm_history", []) # LLM 대화 원본
186
  st.session_state.setdefault("llm_intro_needed", False)
187
  st.session_state.setdefault("llm_input", "")
188
 
 
245
  data.setdefault("notes", "")
246
  return data
247
 
 
 
 
 
 
 
 
248
  def render_llm_followup(chat_container, inline=False):
249
  _ensure_llm_state()
250
  MAX_TURNS = 6
 
306
  log_and_render(q, sender="user", chat_container=chat_container,
307
  key=f"llm_user_{random.randint(1,999999)}")
308
  st.session_state.llm_history.append({"role": "user", "content": q})
309
+ # ✅ 재실행 후 복원을 위해 chat_log에도 저장
310
+ st.session_state.chat_log.append(("user", q))
311
 
312
  msgs = st.session_state.llm_history[-(MAX_TURNS-1):]
313
  a = _call_ollama_chat(
 
316
  temperature=0.8, top_p=0.9, top_k=40, repeat_penalty=1.1
317
  )
318
  if not a:
319
+ err = "⚠️ LLM 응답을 받지 못했습니다. Ollama 서버를 확인해 주세요."
320
+ log_and_render(err, sender="bot", chat_container=chat_container,
321
  key=f"llm_err_{random.randint(1,999999)}")
322
+ # ✅ 에러도 chat_log에 저장
323
+ st.session_state.chat_log.append(("bot", err))
324
  else:
325
+ # 2) 봇 버블
326
  log_and_render(a, sender="bot", chat_container=chat_container,
327
  key=f"llm_bot_{random.randint(1,999999)}")
328
  st.session_state.llm_history.append({"role": "assistant", "content": a})
329
+ # ✅ 답변도 chat_log에 저장
330
+ st.session_state.chat_log.append(("bot", a))
331
+ # 입력칸 초기화 (원하지 않으면 이 줄을 주석 처리)
332
  st.session_state["llm_input"] = ""
333
 
334
  # 하단 버튼: 인라인은 'LLM 패널 종료'만, 풀스크린은 'LLM 모드 종료'만
 
340
  st.session_state["llm_mode"] = False
341
  st.rerun()
342
 
 
343
  # 지연 초기화: import 시점에는 데이터 접근 금지, 여기서 한 번만 주입
344
  init_datasets(
345
  travel_df=travel_df,
 
350
  master_df=master_df,
351
  theme_title_phrases=theme_title_phrases,
352
  )
353
+
354
  # ───────────────────────────────────── streamlit용 함수
355
  def init_session():
356
  if "chat_log" not in st.session_state:
 
378
  unsafe_allow_html=True,
379
  )
380
 
 
 
 
381
  # === 배경 설정 UI (수정됨) ===
382
  st.sidebar.subheader("🎨 배경 설정")
383
  st.sidebar.toggle("배경 이미지 사용", key="bg_on", value=True)
 
411
  value=palette[selected_color_name]
412
  )
413
 
 
414
  def apply_background():
415
  # 보호: 기존 ::before 배경이 있으면 끄기 (겹침/끊김 방지)
416
  base_reset_css = """
 
478
  # 함수 호출
479
  apply_background()
480
 
 
 
481
  # ── P 글꼴 크기 14 px ───────────────────────────────────
482
  st.markdown("""
483
  <style>
 
512
  st.session_state[prev_key] = set()
513
  st.session_state.pop(sample_key, None)
514
 
 
515
  # ────────────────── 1) restart 상태면 인트로만 출력하고 종료
516
  if st.session_state[step_key] == "restart":
517
  log_and_render(
 
563
  st.rerun()
564
  return
565
 
 
566
  # 2.4) 샘플링 (이전 샘플이 없거나 비어 있으면 새로 추출)
567
  if sample_key not in st.session_state or st.session_state[sample_key].empty:
568
  sampled = remaining.sample(
 
622
  st.session_state[step_key] = "detail"
623
  st.session_state.chat_log.append(("user", choice))
624
 
 
625
  # 실제로 선택된 여행지만 prev에 기록
626
  match = sampled[sampled["여행지"] == choice]
627
  if not match.empty:
 
699
  st.stop()
700
  return
701
 
 
702
  # ────────────────── 4) 여행지 상세 단계
703
  if st.session_state[step_key] == "detail":
704
  chosen = st.session_state[region_key]
 
723
  st.rerun()
724
  return
725
 
 
726
  # ────────────────── 5) 동행·연령 받기 단계
727
  elif st.session_state[step_key] == "companion":
728
  with chat_container:
 
793
  st.rerun()
794
  return
795
 
 
796
  # ────────────────── 6) 동행·연령 필터링· 패키지 출력 단계
797
  elif st.session_state[step_key] == "package":
798
 
 
1223
  st.session_state[step_key] = "package_end"
1224
  show_llm_inline() # 플래그만 ON (rerun 없음)
1225
  render_llm_followup(chat_container, inline=True) # 👈 같은 사이클에서 바로 아래에 LLM 박스 출력
1226
+ return
1227
 
1228
  # ────────────────── 7) 종료 단계
1229
  elif st.session_state[step_key] == "package_end":
1230
  log_and_render("필요하실 때 언제든지 또 찾아주세요! ✈️",
1231
  sender="bot", chat_container=chat_container,
1232
+ key="goodbye")
1233
 
1234
  to_llm_mode()
1235
 
 
1252
  st.session_state[prev_key] = set()
1253
  st.session_state.pop(sample_key, None)
1254
 
 
1255
  # ────────────────── 1) restart 상태면 인트로만 출력하고 종료
1256
  if st.session_state[step_key] == "restart":
1257
  log_and_render(
 
1680
  chat_container=chat_container,
1681
  key="unknown_dest"
1682
  )
1683
+
 
 
 
 
 
 
 
 
 
 
 
 
1684
  # ───────────────────────────────────── 챗봇 호출
1685
  def main():
1686
 
 
1697
  st.sidebar.selectbox("테마", ["피스타치오", "스카이블루", "크리미오트"], key="bubble_theme")
1698
  st.sidebar.toggle("타임스탬프 표시", value=False, key="show_time")
1699
 
 
 
 
 
 
 
1700
  # ✅ 타자 효과 on/off 토글 (기본 ON)
1701
  st.sidebar.toggle("타자 효과", value=False, key="typewriter_on")
1702
 
 
1717
  )
1718
  st.session_state["greeting_rendered"] = True
1719
 
 
1720
  # ───── 사용자 입력 & 추천 시작
1721
  # 1) 사용자 입력
1722
  user_input = st.text_input(
 
1747
  sender="user",
1748
  chat_container = chat_container,
1749
  key=f"user_input_{user_input}"
 
1750
  )
1751
  st.session_state["user_input_rendered"] = True
1752
 
 
1771
  else:
1772
  mode = "emotion"
1773
  # 3) 고비용 단계: 정말 필요할 때만 감성(BERT) 실행
 
1774
  top_emotions, emotion_groups = analyze_emotion(user_input)
1775
 
1776
  # 4) 모드별 분기 (필요한 계산만 수행)