Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# app.py
|
| 2 |
-
# @title 啤酒游戏最终整合版 (Streamlit 交互应用 + Hugging Face 日志上传)
|
| 3 |
|
| 4 |
# -----------------------------------------------------------------------------
|
| 5 |
# 1. 导入必要的库
|
|
@@ -19,6 +19,12 @@ from pathlib import Path
|
|
| 19 |
from datetime import datetime
|
| 20 |
from huggingface_hub import HfApi, upload_file
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
# -----------------------------------------------------------------------------
|
| 23 |
# 2. 配置游戏核心参数和API密钥
|
| 24 |
# -----------------------------------------------------------------------------
|
|
@@ -50,12 +56,14 @@ try:
|
|
| 50 |
else:
|
| 51 |
hf_api = None
|
| 52 |
except Exception as e:
|
| 53 |
-
st.error
|
| 54 |
-
st.
|
| 55 |
client = None
|
| 56 |
HF_TOKEN = None
|
| 57 |
HF_REPO_ID = None
|
| 58 |
hf_api = None
|
|
|
|
|
|
|
| 59 |
|
| 60 |
|
| 61 |
# -----------------------------------------------------------------------------
|
|
@@ -311,61 +319,65 @@ def save_logs_and_upload(state: dict):
|
|
| 311 |
# -----------------------------------------------------------------------------
|
| 312 |
# 4. Streamlit UI 界面
|
| 313 |
# -----------------------------------------------------------------------------
|
| 314 |
-
st.set_page_config(page_title="啤酒游戏-人机协作版", layout="wide")
|
| 315 |
st.title("🍺 啤酒游戏:人机协作挑战")
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# app.py
|
| 2 |
+
# @title 啤酒游戏最终整合版 (Streamlit 交互应用 + Hugging Face 日志上传) - 已修复版本
|
| 3 |
|
| 4 |
# -----------------------------------------------------------------------------
|
| 5 |
# 1. 导入必要的库
|
|
|
|
| 19 |
from datetime import datetime
|
| 20 |
from huggingface_hub import HfApi, upload_file
|
| 21 |
|
| 22 |
+
# -----------------------------------------------------------------------------
|
| 23 |
+
# 0. 页面配置 (必须是第一个Streamlit命令)
|
| 24 |
+
# -----------------------------------------------------------------------------
|
| 25 |
+
st.set_page_config(page_title="啤酒游戏-人机协作版", layout="wide")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
# -----------------------------------------------------------------------------
|
| 29 |
# 2. 配置游戏核心参数和API密钥
|
| 30 |
# -----------------------------------------------------------------------------
|
|
|
|
| 56 |
else:
|
| 57 |
hf_api = None
|
| 58 |
except Exception as e:
|
| 59 |
+
# 将错误信息显示在主页面,而不是在加载配置时就调用st.error
|
| 60 |
+
st.session_state.initialization_error = f"启动时读取Secrets出错: {e}. 请确保在Streamlit的Secrets中设置了 OPENAI_API_KEY。可选设置 HF_TOKEN 和 HF_REPO_ID 用于上传日志。"
|
| 61 |
client = None
|
| 62 |
HF_TOKEN = None
|
| 63 |
HF_REPO_ID = None
|
| 64 |
hf_api = None
|
| 65 |
+
else:
|
| 66 |
+
st.session_state.initialization_error = None
|
| 67 |
|
| 68 |
|
| 69 |
# -----------------------------------------------------------------------------
|
|
|
|
| 319 |
# -----------------------------------------------------------------------------
|
| 320 |
# 4. Streamlit UI 界面
|
| 321 |
# -----------------------------------------------------------------------------
|
|
|
|
| 322 |
st.title("🍺 啤酒游戏:人机协作挑战")
|
| 323 |
+
|
| 324 |
+
# 检查初始化时是否有错误
|
| 325 |
+
if st.session_state.get('initialization_error'):
|
| 326 |
+
st.error(st.session_state.initialization_error)
|
| 327 |
+
else:
|
| 328 |
+
st.markdown("你将扮演供应链中的一个角色,与另外三个由大语言模型(LLM)驱动的AI代理合作。")
|
| 329 |
+
|
| 330 |
+
# --- 游戏设置和初始化 ---
|
| 331 |
+
if 'game_state' not in st.session_state or not st.session_state.game_state.get('game_running', False):
|
| 332 |
+
st.header("🎮 开始新游戏")
|
| 333 |
+
col1, col2 = st.columns(2)
|
| 334 |
+
with col1:
|
| 335 |
+
llm_personality = st.selectbox("AI '性格'", ('human_like', 'perfect_rational'), format_func=lambda x: x.replace('_', ' ').title())
|
| 336 |
+
with col2:
|
| 337 |
+
info_sharing = st.selectbox("信息共享", ('local', 'full'), format_func=lambda x: x.title())
|
| 338 |
+
if st.button("🚀 开始游戏", type="primary", disabled=(client is None)):
|
| 339 |
+
init_game_state(llm_personality, info_sharing)
|
| 340 |
+
st.rerun()
|
| 341 |
+
|
| 342 |
+
# --- 游戏主界面 ---
|
| 343 |
+
elif 'game_state' in st.session_state and st.session_state.game_state.get('game_running'):
|
| 344 |
+
state = st.session_state.game_state
|
| 345 |
+
week, human_role, echelons = state['week'], state['human_role'], state['echelons']
|
| 346 |
+
st.header(f"第 {week} 周 / 共 {WEEKS} 周")
|
| 347 |
+
st.subheader(f"你的角色: **{human_role}** | AI模式: **{state['llm_personality'].replace('_', ' ')}** | 信息: **{state['info_sharing']}**")
|
| 348 |
+
cols = st.columns(4)
|
| 349 |
+
for i, name in enumerate(["Retailer", "Wholesaler", "Distributor", "Factory"]):
|
| 350 |
+
with cols[i]:
|
| 351 |
+
e, title_icon = echelons[name], "👤" if name == human_role else "🤖"
|
| 352 |
+
st.markdown(f"### {title_icon} {name} {'(你)' if name == human_role else '(AI)'}")
|
| 353 |
+
st.metric("库存", e['inventory']); st.metric("缺货/积压", e['backlog'])
|
| 354 |
+
st.write(f"本周收到订单: **{e['incoming_order']}**")
|
| 355 |
+
st.write(f"下周到货: **{list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0}**")
|
| 356 |
+
st.markdown("---")
|
| 357 |
+
st.header("你的决策")
|
| 358 |
+
human_echelon_state = echelons[human_role]
|
| 359 |
+
prompt_sugg = get_llm_prompt(human_echelon_state, week, state['llm_personality'], state['info_sharing'], echelons)
|
| 360 |
+
ai_suggestion, _ = get_llm_order_decision(prompt_sugg, f"{human_role} (Suggestion)", week, state['llm_personality'])
|
| 361 |
+
st.info(f"💡 AI建议你 ({human_role}) 本周向上游订购 **{ai_suggestion}** 单位。")
|
| 362 |
+
with st.form(key="order_form"):
|
| 363 |
+
final_order = st.number_input("请输入你的最终订单数量:", min_value=0, step=1, value=ai_suggestion)
|
| 364 |
+
if st.form_submit_button(label="✅ 提交订单并进入下一周"):
|
| 365 |
+
step_game(int(final_order)); st.rerun()
|
| 366 |
+
st.sidebar.header("游戏信息")
|
| 367 |
+
st.sidebar.markdown(f"**游戏ID**: `{state['participant_id']}`")
|
| 368 |
+
st.sidebar.markdown(f"**当前周**: {week-1} (已完成)")
|
| 369 |
+
if st.sidebar.button("🔄 重置游戏"):
|
| 370 |
+
del st.session_state.game_state; st.rerun()
|
| 371 |
+
|
| 372 |
+
# --- 游戏结束界面 ---
|
| 373 |
+
if 'game_state' in st.session_state and not st.session_state.game_state.get('game_running', False) and st.session_state.game_state['week'] > WEEKS:
|
| 374 |
+
st.header("🎉 游戏结束!")
|
| 375 |
+
state = st.session_state.game_state
|
| 376 |
+
logs_df = pd.json_normalize(state['logs'])
|
| 377 |
+
title = f"Beer Game (Human: {state['human_role']})\n(AI: {state['llm_personality'].replace('_', ' ').title()} | Info: {state['info_sharing'].title()})"
|
| 378 |
+
fig = plot_results(logs_df, title)
|
| 379 |
+
st.pyplot(fig)
|
| 380 |
+
# 保存并上传日志
|
| 381 |
+
save_logs_and_upload(state)
|
| 382 |
+
if st.button("✨ 开始一局新游戏"):
|
| 383 |
+
del st.session_state.game_state; st.rerun()
|