Claude commited on
Commit
aae78ee
·
unverified ·
1 Parent(s): 352ddf6

fix: 이미지 다운로드 txt 버그 수정 + 한글 폰트 즉시 적용

Browse files

- 이미지 bytes를 session_state에 캐시하여 첫 클릭에 바로 PNG 다운로드
- Dockerfile에 fonts-nanum 설치 추가 (베이스 이미지 재빌드 없이 즉시 적용)
- 교사 대시보드 이미지 다운로드도 동일 방식으로 통일

https://claude.ai/code/session_016DE3uHLsVmA5Uv7gZGGaKT

Dockerfile CHANGED
@@ -3,6 +3,9 @@ FROM ghcr.io/violetwizard-lim/talk-trace-ai:base
3
  WORKDIR /app
4
  COPY . .
5
 
 
 
 
6
  EXPOSE 7860
7
 
8
  CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"]
 
3
  WORKDIR /app
4
  COPY . .
5
 
6
+ RUN apt-get update && apt-get install -y --no-install-recommends fonts-nanum \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
  EXPOSE 7860
10
 
11
  CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"]
components/opinion_change.py CHANGED
@@ -1,9 +1,8 @@
1
  import streamlit as st
2
 
3
  from config import AI_MODEL_NAME, LIVE_BOARD_FETCH_LIMIT
4
- from utils import create_analysis_image, get_kst_now
5
  from db import (
6
- fetch_debate_status,
7
  fetch_live_messages,
8
  fetch_opinion_change,
9
  opinion_changes_available,
@@ -42,9 +41,9 @@ def render_post_opinion_section(supabase, room_name, student_name, act_type, cur
42
  return
43
 
44
  row = fetch_opinion_change(supabase, room_name, student_name)
45
- pre_opinion = (row or {}).get("pre_opinion") or ""
46
  post_opinion = (row or {}).get("post_opinion") or ""
47
- ai_analysis = (row or {}).get("ai_analysis") or ""
48
 
49
  st.subheader("🔄 토론 후 생각 변화 기록")
50
 
@@ -78,26 +77,42 @@ def render_post_opinion_section(supabase, room_name, student_name, act_type, cur
78
  if ai_analysis:
79
  st.info("🤖 **AI 배움 분석**")
80
  st.markdown(ai_analysis.replace("\n", "\n\n"))
81
- try:
82
- img_bytes = create_analysis_image(
83
- student_name, current_topic, pre_opinion, post_opinion, ai_analysis
84
- )
85
- filename = f"배움분석_{student_name}_{get_kst_now().strftime('%Y%m%d_%H%M')}.png"
86
- st.download_button(
87
- "🖼️ 분석 결과 이미지로 저장",
88
- data=img_bytes,
89
- file_name=filename,
90
- mime="image/png",
91
- use_container_width=True,
92
- )
93
- except Exception:
94
- pass
95
  else:
96
  if st.button("🤖 AI 배움 분석 받기", use_container_width=True):
97
  _trigger_analysis(supabase, room_name, student_name, act_type, current_topic, pre_opinion, post_opinion)
98
  st.rerun()
99
 
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  def _trigger_analysis(supabase, room_name, student_name, act_type, current_topic, pre_opinion, post_opinion):
102
  df_all = fetch_live_messages(supabase, room_name, LIVE_BOARD_FETCH_LIMIT)
103
  if not df_all.empty:
@@ -122,6 +137,9 @@ def _trigger_analysis(supabase, room_name, student_name, act_type, current_topic
122
  )
123
  if res_text:
124
  save_opinion_analysis(supabase, room_name, student_name, res_text)
 
 
 
125
  st.toast("✅ AI 분석 완료!", icon="🎉")
126
  else:
127
  st.toast("🚨 AI 분석에 실패했습니다.", icon="❌")
 
1
  import streamlit as st
2
 
3
  from config import AI_MODEL_NAME, LIVE_BOARD_FETCH_LIMIT
4
+ from utils import create_analysis_image
5
  from db import (
 
6
  fetch_live_messages,
7
  fetch_opinion_change,
8
  opinion_changes_available,
 
41
  return
42
 
43
  row = fetch_opinion_change(supabase, room_name, student_name)
44
+ pre_opinion = (row or {}).get("pre_opinion") or ""
45
  post_opinion = (row or {}).get("post_opinion") or ""
46
+ ai_analysis = (row or {}).get("ai_analysis") or ""
47
 
48
  st.subheader("🔄 토론 후 생각 변화 기록")
49
 
 
77
  if ai_analysis:
78
  st.info("🤖 **AI 배움 분석**")
79
  st.markdown(ai_analysis.replace("\n", "\n\n"))
80
+ _render_image_download(
81
+ student_name, current_topic, pre_opinion, post_opinion, ai_analysis,
82
+ session_key=f"img_{room_name}_{student_name}",
83
+ btn_key="dl_analysis_student",
84
+ )
 
 
 
 
 
 
 
 
 
85
  else:
86
  if st.button("🤖 AI 배움 분석 받기", use_container_width=True):
87
  _trigger_analysis(supabase, room_name, student_name, act_type, current_topic, pre_opinion, post_opinion)
88
  st.rerun()
89
 
90
 
91
+ def _render_image_download(student_name, topic, pre_opinion, post_opinion, ai_analysis,
92
+ session_key, btn_key):
93
+ """이미지 bytes를 session_state에 캐시 후 download_button 렌더링."""
94
+ cache_key = session_key + "_bytes"
95
+ if cache_key not in st.session_state:
96
+ try:
97
+ st.session_state[cache_key] = create_analysis_image(
98
+ student_name, topic, pre_opinion, post_opinion, ai_analysis
99
+ )
100
+ except Exception:
101
+ st.session_state[cache_key] = None
102
+
103
+ img_bytes = st.session_state.get(cache_key)
104
+ if img_bytes:
105
+ filename = f"배움분석_{student_name}.png"
106
+ st.download_button(
107
+ "🖼️ 분석 결과 이미지로 저장",
108
+ data=img_bytes,
109
+ file_name=filename,
110
+ mime="image/png",
111
+ use_container_width=True,
112
+ key=btn_key,
113
+ )
114
+
115
+
116
  def _trigger_analysis(supabase, room_name, student_name, act_type, current_topic, pre_opinion, post_opinion):
117
  df_all = fetch_live_messages(supabase, room_name, LIVE_BOARD_FETCH_LIMIT)
118
  if not df_all.empty:
 
137
  )
138
  if res_text:
139
  save_opinion_analysis(supabase, room_name, student_name, res_text)
140
+ # 캐시 초기화 — 새 분석 결과로 이미지 재생성
141
+ cache_key = f"img_{room_name}_{student_name}_bytes"
142
+ st.session_state.pop(cache_key, None)
143
  st.toast("✅ AI 분석 완료!", icon="🎉")
144
  else:
145
  st.toast("🚨 AI 분석에 실패했습니다.", icon="❌")
components/teacher_dashboard.py CHANGED
@@ -4,7 +4,8 @@ import plotly.express as px
4
  import streamlit as st
5
 
6
  from db import destroy_room_data, fetch_all_opinion_changes, fetch_debate_status, fetch_live_messages, opinion_changes_available, session_control_available, set_debate_status
7
- from utils import create_analysis_image, get_kst_now
 
8
  from validators import with_fallback_author_role
9
  from utils import log_audit
10
  from config import DASHBOARD_FETCH_LIMIT, ROOM_DESTROY_ENABLED, UI_FONT_FAMILY
@@ -69,19 +70,11 @@ def render_teacher_dashboard(supabase, room_name, user_role, student_name, curre
69
  if ai:
70
  st.caption("🤖 AI 배움 분석")
71
  st.markdown(ai.replace("\n", "\n\n"))
72
- try:
73
- img_bytes = create_analysis_image(selected, current_topic, pre, post, ai)
74
- filename = f"배움분석_{selected}_{get_kst_now().strftime('%Y%m%d_%H%M')}.png"
75
- st.download_button(
76
- "🖼️ 분석 결과 이미지로 저장",
77
- data=img_bytes,
78
- file_name=filename,
79
- mime="image/png",
80
- use_container_width=True,
81
- key="oc_img_download",
82
- )
83
- except Exception:
84
- pass
85
  else:
86
  st.caption("AI 분석이 아직 없습니다.")
87
 
 
4
  import streamlit as st
5
 
6
  from db import destroy_room_data, fetch_all_opinion_changes, fetch_debate_status, fetch_live_messages, opinion_changes_available, session_control_available, set_debate_status
7
+ from utils import create_analysis_image
8
+ from components.opinion_change import _render_image_download
9
  from validators import with_fallback_author_role
10
  from utils import log_audit
11
  from config import DASHBOARD_FETCH_LIMIT, ROOM_DESTROY_ENABLED, UI_FONT_FAMILY
 
70
  if ai:
71
  st.caption("🤖 AI 배움 분석")
72
  st.markdown(ai.replace("\n", "\n\n"))
73
+ _render_image_download(
74
+ selected, current_topic, pre, post, ai,
75
+ session_key=f"img_teacher_{room_name}_{selected}",
76
+ btn_key="dl_analysis_teacher",
77
+ )
 
 
 
 
 
 
 
 
78
  else:
79
  st.caption("AI 분석이 아직 없습니다.")
80