Update app.py
Browse files
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.
|
| 660 |
-
|
| 661 |
-
|
|
|
|
| 662 |
|
| 663 |
-
|
| 664 |
-
|
|
|
|
|
|
|
| 665 |
|
| 666 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 685 |
-
max_tokens=
|
| 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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 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"]))
|
| 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=
|
| 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 |
-
|
| 850 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 914 |
-
|
| 915 |
st.header(get_text_main("current_assignment_title"))
|
| 916 |
-
st.subheader(f"{
|
| 917 |
st.markdown("---")
|
| 918 |
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 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 |
-
|
| 939 |
-
|
| 940 |
-
|
| 941 |
-
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
|
| 945 |
-
|
| 946 |
-
|
| 947 |
-
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
|
| 951 |
-
|
| 952 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 953 |
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
|
| 959 |
-
|
| 960 |
-
|
| 961 |
-
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
| 982 |
-
st.session_state.
|
| 983 |
-
|
| 984 |
-
|
| 985 |
-
|
| 986 |
-
|
| 987 |
-
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
| 994 |
-
|
| 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):
|