ll7098ll commited on
Commit
7ef6ba4
·
verified ·
1 Parent(s): ebe915b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -123
app.py CHANGED
@@ -435,7 +435,7 @@ HISTORICAL_ASSIGNMENTS = {
435
  {"action_key": "action_518_t1_opt1_text", "cost_freedom_risk": 15, "safety_risk": 25, "info_key": "info_518_t1_opt1_got"},
436
  {"action_key": "action_518_t1_opt2_text", "cost_freedom_risk": 10, "safety_risk": 20, "info_key": "info_518_t1_opt2_got"},
437
  {"action_key": "action_518_t1_opt3_text", "cost_freedom_risk": 20, "safety_risk": 30, "info_key": "info_518_t1_opt3_got"},
438
- ], "article_writing_phase": False},
439
  {"turn": 2, "assignment_key": "event_518_t2_assignment", "source_key": "event_518_t2_source",
440
  "options": [
441
  {"action_key": "action_518_t2_opt1_text", "cost_freedom_risk": 30, "safety_risk": 40, "info_key": "info_518_t2_opt1_got"},
@@ -641,13 +641,13 @@ def evaluate_article_and_get_feedback_openai(article, game_state, assignment_dat
641
  article_tone_text = get_text_func(f"article_tone_{article['tone']}")
642
 
643
  prompt = f"""
644
- 당신은 어린이 신문사의 친절한 편집장입니다. 새내기 기자가 아래와 같은 취재 지시를 받고, 수집한 노트 내용을 바탕으로 기사를 작성했습니다.
645
- 신입 기자는 초등학생도 이해할 수 있도록 쉽고 명확한 단어와 문장을 사용하려고 노력하고 있습니다. 이 점을 감안하여 피드백을 제공해주세요.
646
 
647
  [오늘의 할 일]
648
  {current_assignment_text}
649
 
650
- [기자가 적은 내용]
651
  {reporter_notes_str}
652
 
653
  [기자가 쓴 기사]
@@ -655,45 +655,57 @@ def evaluate_article_and_get_feedback_openai(article, game_state, assignment_dat
655
  - 내용 요약: {article['body_summary']}
656
  - 선택한 분위기: {article_tone_text}
657
 
658
- [평가 방법]
659
- 1. **내용이 진짜인가? 중요한 걸 다 썼나?:** 기사가 기자가 적은 내용과 오늘 할 일의 중요한 점을 얼마나 잘 담고 있나요? 중요한 이야기가 빠지거나 틀리진 않았나요? (40점 만점)
660
- 2. **말이 이어지고 이해하기 쉬운가?:** 기사 내용이 순서대로이어지고, 어린이 독자가 이해하기 쉽게 쓰여졌나요? (30점 만점)
661
- 3. **분위기는 적절한가? 어떤 영향을 줄까?:** 선택한 분위기가 지금 상황과 기사 내용에 맞나요? 이 기사가 사람들에게 어떤 느낌을 줄까요? (기자님 안전도 생각해주세요) (30점 만점)
 
662
 
663
- 평가 방법에 따라 각 항목별로 짧은 피드백과 함께 점수를 주세요. 그리고 총점(100점 만점)과 전체적인 피드백(2-3문장)을 알려주세요.
664
- 피드백은 기자가 잘할있도록 쉽고 따뜻한 말로 써주세요.
 
 
665
 
666
- 출력은 이렇게 JSON 모양으로 주세요:
 
 
 
 
 
 
 
 
 
667
  {{
668
  "score_fact": 점수(숫자),
669
- "comment_fact": "코멘트(문자열)",
670
  "score_logic": 점수(숫자),
671
- "comment_logic": "코멘트(문자열)",
672
  "score_tone": 점수(숫자),
673
- "comment_tone": "코멘트(문자열)",
674
  "total_score": 총점(숫자),
675
- "overall_feedback": "전체적인 피드백(문자열)"
676
  }}
677
  """
678
  try:
679
  with st.spinner("AI 편집장님이 기사를 읽고 있어요..."):
680
  response = client.chat.completions.create(
681
- model="gpt-4o-mini",
682
  messages=[{"role": "user", "content": prompt}],
683
  response_format={"type": "json_object"},
684
- temperature=0.3, # 좀 더 일관된 피드백을 위해 약간 낮춤
685
- max_tokens=700
686
  )
687
  ai_evaluation_str = response.choices[0].message.content
688
  ai_evaluation = json.loads(ai_evaluation_str)
689
 
690
  total_score = ai_evaluation.get("total_score", 0)
691
- overall_feedback = ai_evaluation.get("overall_feedback", "AI 편집장님 연결에 문제가 생겨서 점수를 못 매겼어요. 하지만 썼을 거예요!")
692
 
693
  except Exception as e:
694
  st.error(get_text_func("error_openai_api").format(error=str(e)))
695
  total_score = random.randint(40, 70)
696
- overall_feedback = "AI 편집장님 시스템에 문제가 생겨서 자동으로 점수를 매겼어요. 내용은 좋지만, 진짜 있었던 일인지 확인하면 좋겠어요."
697
 
698
  article['ai_score'] = total_score # 기사 딕셔너리에 AI 점수 저장
699
 
@@ -719,7 +731,9 @@ def evaluate_article_and_get_feedback_openai(article, game_state, assignment_dat
719
  game_state['submitted_articles'].append(article) # 점수가 포함된 article 저장
720
  game_state['reporter_notebook'] = []
721
 
722
- return overall_feedback
 
 
723
 
724
  # --- UI 표시 함수들 ---
725
  def display_reporter_dashboard(game_state):
@@ -755,8 +769,7 @@ def display_historical_glossary_for_reporter(scenario_key):
755
 
756
  for key in glossary_keys_for_scenario:
757
  term_definition = get_text_for_ui(key)
758
- # ALL_TEXTS가 이미 "용어: 설명" 형태의 문자열을 직접 가지고 있으므로, 분리할 필요 없음
759
- st.sidebar.markdown(term_definition) # 전체를 마크다운으로 표시
760
  st.sidebar.markdown("---")
761
 
762
  # --- 세션 상태 초기화 ---
@@ -784,7 +797,7 @@ def reporter_simulation_main():
784
  }
785
  for key, scenario_info in SCENARIOS.items():
786
  with st.container(border=True):
787
- st.subheader(get_text(scenario_info["display_name"])) # SCENARIOS의 display_name은 키가 아니므로 get_text 사용
788
  st.caption(f"언제: {scenario_info['start_year']}년, 나는 누구?: {scenario_info['player_role']}")
789
  st.markdown(scenario_descriptions.get(key, "이 시대에는 어떤 일이 있었을까요?"))
790
  if st.button(get_text_main("scenario_select_button"), key=f"select_{key}"):
@@ -802,7 +815,7 @@ def reporter_simulation_main():
802
  if st.session_state.game_mode != 'scenario_select' and st.session_state.game_state:
803
  game_state = st.session_state.game_state
804
  scenario_key = st.session_state.current_scenario_key
805
- get_text_main = lambda key: get_text(key)
806
 
807
  # --- 사이드바 구성 ---
808
  with st.sidebar:
@@ -813,20 +826,25 @@ def reporter_simulation_main():
813
  display_reporter_dashboard(game_state)
814
  st.markdown("---")
815
 
 
 
 
 
 
 
 
 
 
 
 
 
816
  if st.button(get_text_main("button_next_day"), use_container_width=True, key="next_day_button_sidebar",
817
- disabled=(st.session_state.game_mode == 'article_writing' or
818
- (st.session_state.game_mode == 'reporter_action' and
819
- st.session_state.actions_taken_this_turn < ACTIONS_PER_TURN_LIMIT and
820
- st.session_state.current_assignment_data and
821
- len(st.session_state.current_assignment_data.get("options", [])) > 0
822
- )
823
- )
824
- ):
825
  current_turn = game_state['current_turn']
826
  max_turns_for_scenario = SCENARIOS[scenario_key]['max_turns']
827
 
828
  if current_turn >= max_turns_for_scenario or \
829
- (st.session_state.current_assignment_data and st.session_state.current_assignment_data.get("is_final_turn_event", False)):
830
  st.session_state.game_mode = 'assignment_over'
831
  if game_state.get('event_log') is not None:
832
  game_state['event_log'].append(get_text_main("log_assignment_over").format(scenario_name=get_text(SCENARIOS[scenario_key]['display_name'])))
@@ -839,15 +857,16 @@ def reporter_simulation_main():
839
  st.session_state.current_assignment_data = None
840
  st.session_state.actions_taken_this_turn = 0
841
  st.session_state.desk_feedback_message = None
842
- st.session_state.active_main_tab = "오늘 할 일 보기"
843
  st.rerun()
844
 
845
  st.markdown("---")
846
  display_historical_glossary_for_reporter(scenario_key)
847
 
848
  st.subheader(get_text_main("sidebar_current_source_title"))
849
- if st.session_state.current_assignment_data and st.session_state.current_assignment_data.get('source_text') and st.session_state.current_assignment_data.get('source_text') != "[알아두면 좋아요]": # 빈 source_text 방지
850
- st.sidebar.info(st.session_state.current_assignment_data.get('source_text'))
 
851
  else:
852
  st.sidebar.caption(get_text_main("sidebar_no_source"))
853
 
@@ -895,108 +914,127 @@ def reporter_simulation_main():
895
  if st.session_state.current_assignment_data is None:
896
  spinner_text = get_text_main('status_loading_assignment').format(year=game_state['game_year'], turn=game_state['current_turn'])
897
  with st.spinner(spinner_text):
898
- time.sleep(0.5)
899
  assignment = get_next_assignment(scenario_key, game_state['current_turn'], game_state)
900
  if assignment:
901
  st.session_state.current_assignment_data = assignment
902
  st.session_state.actions_taken_this_turn = 0
903
- st.session_state.desk_feedback_message = None
904
  st.session_state.game_mode = 'reporter_action'
905
- st.session_state.active_main_tab = "🎤 무엇을 할까요?"
 
 
 
 
906
  st.rerun()
907
- else:
908
  st.session_state.game_mode = 'assignment_over'
909
  if game_state.get('event_log') is not None:
910
  game_state['event_log'].append(get_text_main("log_assignment_over").format(scenario_name=get_text(SCENARIOS[scenario_key]['display_name'])))
911
  st.rerun()
912
 
913
- if st.session_state.current_assignment_data:
914
- assignment_data = st.session_state.current_assignment_data
915
  st.header(get_text_main("current_assignment_title"))
916
- st.subheader(f"{assignment_data['assignment_text']}")
917
  st.markdown("---")
918
 
919
- tab_titles = ["🎤 무엇을 할까요?"]
920
- if st.session_state.current_assignment_data and st.session_state.current_assignment_data.get("article_writing_phase"):
921
- tab_titles.append("🖋️ 기사 쓰기")
922
- if st.session_state.desk_feedback_message:
923
- tab_titles.append("📢 AI 편집장님의 한마디")
924
-
925
- if 'active_main_tab' not in st.session_state or st.session_state.active_main_tab not in tab_titles:
926
- st.session_state.active_main_tab = tab_titles[0]
927
-
928
- # 현재 활성화된 탭 인덱스 찾기
929
- try:
930
- current_tab_index = tab_titles.index(st.session_state.active_main_tab)
931
- except ValueError:
932
- current_tab_index = 0 # 기본값
933
- st.session_state.active_main_tab = tab_titles[0]
934
-
935
-
936
- tabs_ui = st.tabs(tab_titles)
937
 
938
- with tabs_ui[0]: # 무엇을 할까요? 탭
939
- if st.session_state.game_mode == 'reporter_action' and st.session_state.current_assignment_data:
940
- assignment_data = st.session_state.current_assignment_data
941
- if st.session_state.actions_taken_this_turn < ACTIONS_PER_TURN_LIMIT:
942
- st.write(f"남은 활동 횟수: {ACTIONS_PER_TURN_LIMIT - st.session_state.actions_taken_this_turn}번")
943
-
944
- # 위험도 문자열 생성 로직
945
- for i, opt in enumerate(assignment_data['options']):
946
- total_risk_score = opt.get('cost_freedom_risk',0) + opt.get('safety_risk',0)
947
- if total_risk_score > 40 : risk_str = "엄청 조심!!"
948
- elif total_risk_score > 20 : risk_str = "많이 조심!"
949
- elif total_risk_score > 0 : risk_str = "조금 조심"
950
- else: risk_str = "안전��"
951
-
952
- button_label = get_text_main("action_button_label").format(action=opt['action_text'], risk_str=risk_str)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
953
 
954
- if st.button(button_label, key=f"rep_action_{game_state['current_turn']}_{i}_{scenario_key}", use_container_width=True):
955
- process_reporter_action(opt, game_state)
956
- st.rerun()
957
- else:
958
- st.info(get_text_main("status_actions_taken"))
959
- if assignment_data.get("article_writing_phase") and "🖋️ 기사 쓰기" in tab_titles:
960
- st.success("오늘 일을 다 했어요. '기사 쓰기' 탭으로 가서 기사를 써보세요!")
961
- elif not assignment_data.get("article_writing_phase"):
962
- st.success("오늘 일을 다 했어요. 옆 메뉴에서 '다음 날로 가기' 버튼을 눌러주세요.")
963
-
964
- with st.expander("📝 내가 적은 내용 보기", expanded=False):
965
- display_reporter_notebook(game_state)
966
-
967
-
968
- if "🖋️ 기사 쓰기" in tab_titles:
969
- with tabs_ui[tab_titles.index("🖋️ 기사 쓰기")]:
970
- if st.session_state.game_mode in ['reporter_action', 'article_writing'] and \
971
- st.session_state.current_assignment_data and \
972
- st.session_state.current_assignment_data.get("article_writing_phase") and \
973
- st.session_state.actions_taken_this_turn >= ACTIONS_PER_TURN_LIMIT:
974
-
975
- if st.session_state.game_mode != 'article_writing':
976
- st.session_state.game_mode = 'article_writing'
977
-
978
- assignment_data = st.session_state.current_assignment_data
979
- submitted_article = generate_article_interface(game_state)
980
- if submitted_article:
981
- feedback = evaluate_article_and_get_feedback_openai(submitted_article, game_state, assignment_data)
982
- st.session_state.desk_feedback_message = feedback
983
- st.session_state.game_mode = 'reporter_action'
984
- st.session_state.active_main_tab = "📢 AI 편집장님의 한마디" if "📢 AI 편집장님의 한마디" in tab_titles else "🎤 무엇을 할까요?"
985
- st.rerun()
986
- elif not (st.session_state.current_assignment_data and st.session_state.current_assignment_data.get("article_writing_phase")):
987
- st.info("이번엔 기사 쓰는 날이 아니에요.")
988
- elif st.session_state.actions_taken_this_turn < ACTIONS_PER_TURN_LIMIT:
989
- st.info(f"아직 오늘 할 일이 남았어요. (남은 활동: {ACTIONS_PER_TURN_LIMIT - st.session_state.actions_taken_this_turn}번)")
990
-
991
-
992
- if "📢 AI 편집장님의 한마디" in tab_titles:
993
- with tabs_ui[tab_titles.index("📢 AI 편집장님의 한마디")]:
994
- if st.session_state.desk_feedback_message:
995
- st.subheader(get_text_main("desk_feedback_title"))
996
- st.warning(st.session_state.desk_feedback_message)
997
- st.info("편집장님 말씀을 다 봤으면 옆 메뉴에서 '다음 날로 가기' 버튼을 눌러주세요.")
998
- else:
999
- st.info("아직 편집장님께 보여드린 기사가 없어요. 기사를 먼저 써서 보내주세요.")
1000
 
1001
  st.markdown("---")
1002
  with st.expander(get_text_main("term_event_log") + " (최근 10개)", expanded=False):
 
435
  {"action_key": "action_518_t1_opt1_text", "cost_freedom_risk": 15, "safety_risk": 25, "info_key": "info_518_t1_opt1_got"},
436
  {"action_key": "action_518_t1_opt2_text", "cost_freedom_risk": 10, "safety_risk": 20, "info_key": "info_518_t1_opt2_got"},
437
  {"action_key": "action_518_t1_opt3_text", "cost_freedom_risk": 20, "safety_risk": 30, "info_key": "info_518_t1_opt3_got"},
438
+ ], "article_writing_phase": False}, # 5.18 첫 턴은 기사 작성 없음
439
  {"turn": 2, "assignment_key": "event_518_t2_assignment", "source_key": "event_518_t2_source",
440
  "options": [
441
  {"action_key": "action_518_t2_opt1_text", "cost_freedom_risk": 30, "safety_risk": 40, "info_key": "info_518_t2_opt1_got"},
 
641
  article_tone_text = get_text_func(f"article_tone_{article['tone']}")
642
 
643
  prompt = f"""
644
+ 당신은 어린이 신문사의 매우 친절하고 격려를 잘 해주는 편집장입니다. 우리 신문사의 새내기 기자가 아래와 같은 취재 지시를 받고, 수집한 노트 내용을 바탕으로 기사를 작성했습니다.
645
+ 이 기자는 초등학생 독자들도 쉽게 이해할 수 있도록 쉽고 명확한 단어와 문장을 사용하려고 애쓰고 있습니다. 이 점을 기억하고, 기자의 노력을 칭찬하면서 구체적인 조언을 해주세요.
646
 
647
  [오늘의 할 일]
648
  {current_assignment_text}
649
 
650
+ [기자가 적은 내용 (취재 노트)]
651
  {reporter_notes_str}
652
 
653
  [기자가 쓴 기사]
 
655
  - 내용 요약: {article['body_summary']}
656
  - 선택한 분위기: {article_tone_text}
657
 
658
+ [평가 항목 및 피드백 가이드]
659
+ 1. **내용의 정확성과 중요도 (40점 만점):**
660
+ * 기자가 취재한 내용(노트)과 오늘 일의 핵심 내용을 기사에 담았나요?
661
+ * 빠뜨린 중요한 사실은 없나요? 혹시 잘못 이해한 부분은 없을까요?
662
+ * **피드백 작성 시:** 어떤 점이 좋았는지, 어떤 사실을 추가하면 기사가 더 풍부해질지 구체적으로 알려주세요. "기자님이 OOO 내용을 잘 찾아내서 기사에 담아주니 독자들이 사건을 더 잘 이해할 수 있을 거예요!" 와 같이 칭찬으로 시작하고, "혹시 XXX 내용을 조금 더 자세히 설명해주거나, YYY 관점도 살짝 넣어보면 어떨까요?" 와 같이 부드럽게 제안해주세요.
663
 
664
+ 2. **글의 흐름과 이해도 (30점 만점):**
665
+ * 기사 내용이 자연스럽게 이어지나요? 초등학생 독자들이 읽고 바로 이해할 있을 만큼 쉽고 명확하게 쓰였나요?
666
+ * 어려운 단어나 표현은 없었나요? 있다면 어떻게 바꾸면 좋을지 예시와 함께 알려주세요.
667
+ * **피드백 작성 시:** "문장들이 술술 읽혀서 정말 좋았어요. 특히 'OOO'라는 표현은 어린이 독자들도 재미있게 읽을 수 있겠어요!" 처럼 긍정적인 부분을 먼저 언급하고, "혹시 'XXX'라는 단어는 'YYY'나 'ZZZ'처럼 더 쉬운 말로 바꿔보면 어떨까요? 그러면 더 많은 친구들이 기사를 쉽게 이해할 수 있을 거예요." 와 같이 구체적인 대안을 제시하며 조언해주세요.
668
 
669
+ 3. **기사 분위기의 적절성과 영향력 (30점 만점):**
670
+ * 선택한 기사 분위기가 지금 상황과 기사 내용에 잘 어울리나요?
671
+ * 이 기사가 독자들에게 어떤 생각이나 느낌을 줄 수 있을까요? (기자님의 안전도 고려해서 조언해주세요. 너무 강한 비판은 위험할 수 있다는 점도 부드럽게 언급 가능합니다.)
672
+ * **피드백 작성 시:** "기자님이 선택한 '{article_tone_text}' 분위기가 이번 사건의 OOO한 면을 잘 보여주는 것 같아요." 라고 공감해주고, "이런 분위기의 기사는 독자들이 XXX 감정을 느끼게 해서 사건에 더 관심을 갖게 만들 수 있어요. 다만, 너무 강한 표현은 기자님이 위험해질 수도 있으니, YYY처럼 조금 더 조심스럽게 표현하는 것도 좋은 방법이랍니다." 와 같이 안전을 고려한 조언을 덧붙여주세요.
673
+
674
+ [출력 형식]
675
+ 위 평가 항목에 따라 각 항목별 점수와 함께, **칭찬과 구체적인 조언이 담긴 따뜻한 피드백**을 작성해주세요.
676
+ 그리고 총점(100점 만점)과 함께, 기자에게 용기를 주고 앞으로 더 성장할 수 있도록 돕는 **종합적인 격려의 메시지 (2-3문장)**를 "overall_feedback"으로 제공해주세요. "기자님, 이번 기사 정말 잘 썼어요! 특히 OOO 부분이 인상 깊었고, 앞으로 XXX 부분만 조금 더 신경 써주면 더욱 훌륭한 기자가 될 수 있을 거예요. 항상 응원할게요!" 와 같은 느낌으로요.
677
+
678
+ 반드시 아래 JSON 형식으로 응답해주세요:
679
  {{
680
  "score_fact": 점수(숫자),
681
+ "comment_fact": "사실 및 중요도에 대한 칭찬과 구체적 조언 (문자열)",
682
  "score_logic": 점수(숫자),
683
+ "comment_logic": "글의 흐름과 이해도에 대한 칭찬과 구체적 조언 (문자열)",
684
  "score_tone": 점수(숫자),
685
+ "comment_tone": "기사 분위기와 영향력에 대한 칭찬과 구체적 조언 (문자열, 안전 고려 포함)",
686
  "total_score": 총점(숫자),
687
+ "overall_feedback": "종합적인 격려와 응원의 메시지 (문자열)"
688
  }}
689
  """
690
  try:
691
  with st.spinner("AI 편집장님이 기사를 읽고 있어요..."):
692
  response = client.chat.completions.create(
693
+ model="gpt-4o-mini", # 또는 gpt-4o
694
  messages=[{"role": "user", "content": prompt}],
695
  response_format={"type": "json_object"},
696
+ temperature=0.4, # 약간 높여서 좀 더 다채로운 격려 유도
697
+ max_tokens=800 # 피드백이 길어질 수 있으므로 넉넉하게
698
  )
699
  ai_evaluation_str = response.choices[0].message.content
700
  ai_evaluation = json.loads(ai_evaluation_str)
701
 
702
  total_score = ai_evaluation.get("total_score", 0)
703
+ overall_feedback = ai_evaluation.get("overall_feedback", "AI 편집장님 연결에 문제가 생겨서 점수를 못 매겼어요. 하지만 기자님의 노력은 분명 빛날 거예요! 다음 기사도 기대할게요!")
704
 
705
  except Exception as e:
706
  st.error(get_text_func("error_openai_api").format(error=str(e)))
707
  total_score = random.randint(40, 70)
708
+ overall_feedback = "AI 편집장님 시스템에 문제가 생겼지만, 기자님의 열정은 느껴져요! 이번 경험을 바탕으로 다음엔 멋진 기사를 있을 거예요. 화이팅!"
709
 
710
  article['ai_score'] = total_score # 기사 딕셔너리에 AI 점수 저장
711
 
 
731
  game_state['submitted_articles'].append(article) # 점수가 포함된 article 저장
732
  game_state['reporter_notebook'] = []
733
 
734
+ # UI에 전체 피드백 객체를 전달할 수도 있지만, 현재는 overall_feedback만 사용
735
+ # return ai_evaluation
736
+ return overall_feedback
737
 
738
  # --- UI 표시 함수들 ---
739
  def display_reporter_dashboard(game_state):
 
769
 
770
  for key in glossary_keys_for_scenario:
771
  term_definition = get_text_for_ui(key)
772
+ st.sidebar.markdown(term_definition)
 
773
  st.sidebar.markdown("---")
774
 
775
  # --- 세션 상태 초기화 ---
 
797
  }
798
  for key, scenario_info in SCENARIOS.items():
799
  with st.container(border=True):
800
+ st.subheader(get_text(scenario_info["display_name"]))
801
  st.caption(f"언제: {scenario_info['start_year']}년, 나는 누구?: {scenario_info['player_role']}")
802
  st.markdown(scenario_descriptions.get(key, "이 시대에는 어떤 일이 있었을까요?"))
803
  if st.button(get_text_main("scenario_select_button"), key=f"select_{key}"):
 
815
  if st.session_state.game_mode != 'scenario_select' and st.session_state.game_state:
816
  game_state = st.session_state.game_state
817
  scenario_key = st.session_state.current_scenario_key
818
+ get_text_main = lambda key: get_text(key) # 로컬 get_text_main 재정의
819
 
820
  # --- 사이드바 구성 ---
821
  with st.sidebar:
 
826
  display_reporter_dashboard(game_state)
827
  st.markdown("---")
828
 
829
+ can_proceed_to_next_day = True
830
+ if st.session_state.game_mode == 'article_writing':
831
+ can_proceed_to_next_day = False
832
+ elif st.session_state.game_mode == 'reporter_action':
833
+ assignment_data = st.session_state.current_assignment_data
834
+ if assignment_data and len(assignment_data.get("options", [])) > 0: # 취재할 것이 남아있다면
835
+ if st.session_state.actions_taken_this_turn < ACTIONS_PER_TURN_LIMIT: # 아직 행동 횟수가 남았다면
836
+ can_proceed_to_next_day = False
837
+ # 행동 횟수를 다 썼더라도, 기사 작성 단계가 있고 아직 기사를 안 썼다면 넘어가지 못하게 할 수 있음
838
+ # (현재 로직은 기사 작성은 선택사항으로 두고 넘어갈 수 있게 함)
839
+ # 취재할 것이 없는 턴 (예: 마지막 턴)은 바로 넘어갈 수 있어야 함
840
+
841
  if st.button(get_text_main("button_next_day"), use_container_width=True, key="next_day_button_sidebar",
842
+ disabled=not can_proceed_to_next_day):
 
 
 
 
 
 
 
843
  current_turn = game_state['current_turn']
844
  max_turns_for_scenario = SCENARIOS[scenario_key]['max_turns']
845
 
846
  if current_turn >= max_turns_for_scenario or \
847
+ (st.session_state.current_assignment_data and st.session_state.current_assignment_data.get("is_final_turn_event", False) and st.session_state.desk_feedback_message is not None): # 마지막 턴 이벤트 후 피드백까지 봤다면
848
  st.session_state.game_mode = 'assignment_over'
849
  if game_state.get('event_log') is not None:
850
  game_state['event_log'].append(get_text_main("log_assignment_over").format(scenario_name=get_text(SCENARIOS[scenario_key]['display_name'])))
 
857
  st.session_state.current_assignment_data = None
858
  st.session_state.actions_taken_this_turn = 0
859
  st.session_state.desk_feedback_message = None
860
+ st.session_state.active_main_tab = "오늘 할 일 보기" # 탭 초기화
861
  st.rerun()
862
 
863
  st.markdown("---")
864
  display_historical_glossary_for_reporter(scenario_key)
865
 
866
  st.subheader(get_text_main("sidebar_current_source_title"))
867
+ current_assignment_data_sidebar = st.session_state.current_assignment_data
868
+ if current_assignment_data_sidebar and current_assignment_data_sidebar.get('source_text') and current_assignment_data_sidebar.get('source_text') != "[알아두면 좋아요]":
869
+ st.sidebar.info(current_assignment_data_sidebar.get('source_text'))
870
  else:
871
  st.sidebar.caption(get_text_main("sidebar_no_source"))
872
 
 
914
  if st.session_state.current_assignment_data is None:
915
  spinner_text = get_text_main('status_loading_assignment').format(year=game_state['game_year'], turn=game_state['current_turn'])
916
  with st.spinner(spinner_text):
917
+ time.sleep(0.5) # 시각적 효과
918
  assignment = get_next_assignment(scenario_key, game_state['current_turn'], game_state)
919
  if assignment:
920
  st.session_state.current_assignment_data = assignment
921
  st.session_state.actions_taken_this_turn = 0
922
+ st.session_state.desk_feedback_message = None # 새 턴 시작 시 피드백 초기화
923
  st.session_state.game_mode = 'reporter_action'
924
+ # 마지막 턴이면서 취재 옵션이 없는 경우 바로 기사쓰기로 가도록 설정
925
+ if assignment.get("is_final_turn_event", False) and not assignment.get("options"):
926
+ st.session_state.active_main_tab = "🖋️ 기사 쓰기" if assignment.get("article_writing_phase") else "🎤 무엇을 할까요?"
927
+ else:
928
+ st.session_state.active_main_tab = "🎤 무엇을 할까요?"
929
  st.rerun()
930
+ else: # 더 이상 과제가 없으면 게임 종료
931
  st.session_state.game_mode = 'assignment_over'
932
  if game_state.get('event_log') is not None:
933
  game_state['event_log'].append(get_text_main("log_assignment_over").format(scenario_name=get_text(SCENARIOS[scenario_key]['display_name'])))
934
  st.rerun()
935
 
936
+ current_assignment_data_main = st.session_state.current_assignment_data
937
+ if current_assignment_data_main:
938
  st.header(get_text_main("current_assignment_title"))
939
+ st.subheader(f"{current_assignment_data_main['assignment_text']}")
940
  st.markdown("---")
941
 
942
+ tab_titles = []
943
+ # 취재 옵션이 있거나, 마지막 턴이면서 취재 옵션이 없는 경우(바로 기사쓰기) "무엇을 할까요?" 탭 추가
944
+ if current_assignment_data_main.get("options") or \
945
+ (current_assignment_data_main.get("is_final_turn_event") and not current_assignment_data_main.get("options")):
946
+ tab_titles.append(get_text_main("reporter_actions_title")) # "🎤 무엇을 할까요?"
 
 
 
 
 
 
 
 
 
 
 
 
 
947
 
948
+ if current_assignment_data_main.get("article_writing_phase"):
949
+ tab_titles.append(get_text_main("article_writing_title")) # "🖋️ 기사 쓰기"
950
+
951
+ if st.session_state.desk_feedback_message:
952
+ tab_titles.append(get_text_main("desk_feedback_title")) # "📢 AI 편집장님의 한마디"
953
+
954
+ if not tab_titles: # 표시할 탭이 없는 예외 상황 방지
955
+ st.info("현재 진행할 있는 활동이 없거나, 다음 날로 진행해야 합니다.")
956
+ else:
957
+ if 'active_main_tab' not in st.session_state or st.session_state.active_main_tab not in tab_titles:
958
+ st.session_state.active_main_tab = tab_titles[0]
959
+
960
+ try:
961
+ current_tab_index = tab_titles.index(st.session_state.active_main_tab)
962
+ except ValueError:
963
+ current_tab_index = 0
964
+ st.session_state.active_main_tab = tab_titles[0]
965
+
966
+ tabs_ui = st.tabs(tab_titles)
967
+ tab_map = {title: i for i, title in enumerate(tab_titles)}
968
+
969
+
970
+ # "🎤 무엇을 할까요?" 탭
971
+ if get_text_main("reporter_actions_title") in tab_map:
972
+ with tabs_ui[tab_map[get_text_main("reporter_actions_title")]]:
973
+ if st.session_state.game_mode == 'reporter_action' and current_assignment_data_main:
974
+ # 마지막 턴이고 취재 옵션이 없는 경우 (바로 기사쓰기로 유도)
975
+ if current_assignment_data_main.get("is_final_turn_event", False) and not current_assignment_data_main.get("options"):
976
+ st.info("이번이 마지막 날이에요! 그동안 모은 정보로 특별 기사를 작성해주세요. '기사 쓰기' 탭으로 이동하세요.")
977
+ st.session_state.actions_taken_this_turn = ACTIONS_PER_TURN_LIMIT # 행동 완료 처리
978
+ elif st.session_state.actions_taken_this_turn < ACTIONS_PER_TURN_LIMIT:
979
+ st.write(f"남은 활동 횟수: {ACTIONS_PER_TURN_LIMIT - st.session_state.actions_taken_this_turn}번")
980
+ for i, opt in enumerate(current_assignment_data_main['options']):
981
+ total_risk_score = opt.get('cost_freedom_risk',0) + opt.get('safety_risk',0)
982
+ if total_risk_score > 40 : risk_str = "엄청 조심!!"
983
+ elif total_risk_score > 20 : risk_str = "많이 조심!"
984
+ elif total_risk_score > 0 : risk_str = "조금 조심"
985
+ else: risk_str = "안전함"
986
+ button_label = get_text_main("action_button_label").format(action=opt['action_text'], risk_str=risk_str)
987
+ if st.button(button_label, key=f"rep_action_{game_state['current_turn']}_{i}_{scenario_key}", use_container_width=True):
988
+ process_reporter_action(opt, game_state)
989
+ st.rerun()
990
+ else: # 행동 다 했을 때
991
+ st.info(get_text_main("status_actions_taken"))
992
+ if current_assignment_data_main.get("article_writing_phase") and get_text_main("article_writing_title") in tab_map:
993
+ st.success("오늘 할 일을 다 했어요. '기사 쓰기' 탭으로 가서 기사를 써보세요!")
994
+ elif not current_assignment_data_main.get("article_writing_phase"):
995
+ st.success("오늘 할 일을 다 했어요. 옆 메뉴에서 '다음 날로 가기' 버튼을 눌러주세요.")
996
 
997
+ with st.expander("📝 내가 적은 내용 보기", expanded=False):
998
+ display_reporter_notebook(game_state)
999
+
1000
+ # "🖋️ 기사 쓰기" 탭
1001
+ if get_text_main("article_writing_title") in tab_map:
1002
+ with tabs_ui[tab_map[get_text_main("article_writing_title")]]:
1003
+ allow_article_writing = False
1004
+ if st.session_state.game_mode in ['reporter_action', 'article_writing'] and \
1005
+ current_assignment_data_main and \
1006
+ current_assignment_data_main.get("article_writing_phase"):
1007
+ # 마지막 턴이고 취재 옵션이 없는 경우, 또는 일반적인 행동 완료 후
1008
+ if (current_assignment_data_main.get("is_final_turn_event", False) and not current_assignment_data_main.get("options")) or \
1009
+ st.session_state.actions_taken_this_turn >= ACTIONS_PER_TURN_LIMIT:
1010
+ allow_article_writing = True
1011
+
1012
+ if allow_article_writing:
1013
+ if st.session_state.game_mode != 'article_writing':
1014
+ st.session_state.game_mode = 'article_writing' # 기사 작성 모드로 변경
1015
+
1016
+ submitted_article = generate_article_interface(game_state)
1017
+ if submitted_article:
1018
+ feedback = evaluate_article_and_get_feedback_openai(submitted_article, game_state, current_assignment_data_main)
1019
+ st.session_state.desk_feedback_message = feedback
1020
+ st.session_state.game_mode = 'reporter_action' # 피드백 확인 후 다시 reporter_action (다음날로 가기 활성화 위함)
1021
+ st.session_state.active_main_tab = get_text_main("desk_feedback_title") if get_text_main("desk_feedback_title") in tab_map else get_text_main("reporter_actions_title")
1022
+ st.rerun()
1023
+ elif not (current_assignment_data_main and current_assignment_data_main.get("article_writing_phase")):
1024
+ st.info("이번엔 기사 쓰는 날이 아니에요.")
1025
+ elif st.session_state.actions_taken_this_turn < ACTIONS_PER_TURN_LIMIT:
1026
+ st.info(f"아직 오늘 할 일이 남았어요. '무엇을 할까요?' 탭에서 활동을 먼저 완료해주세요. (남은 활동: {ACTIONS_PER_TURN_LIMIT - st.session_state.actions_taken_this_turn}번)")
1027
+
1028
+
1029
+ # "📢 AI 편집장님의 한마디"
1030
+ if get_text_main("desk_feedback_title") in tab_map:
1031
+ with tabs_ui[tab_map[get_text_main("desk_feedback_title")]]:
1032
+ if st.session_state.desk_feedback_message:
1033
+ st.subheader(get_text_main("desk_feedback_title"))
1034
+ st.info(st.session_state.desk_feedback_message) # st.warning 대신 st.info 사용
1035
+ st.success("편집장님 말씀을 봤으면 메뉴에서 '다음 날로 가기' 버튼을 눌러주세요.")
1036
+ else:
1037
+ st.info("아직 편집장님께 보여드린 기사가 없어요. 기사를 먼저 써서 보내주세요.")
 
 
 
 
 
1038
 
1039
  st.markdown("---")
1040
  with st.expander(get_text_main("term_event_log") + " (최근 10개)", expanded=False):