sirochild commited on
Commit
ef72bcd
·
verified ·
1 Parent(s): a0844c3

Upload main_app.py

Browse files
Files changed (1) hide show
  1. main_app.py +116 -153
main_app.py CHANGED
@@ -12,12 +12,21 @@ import streamlit.components.v1 as components
12
  import requests
13
  from dotenv import load_dotenv
14
  from contextlib import contextmanager
15
- from huggingface_hub import whoami
16
 
17
  # --- HFユーザーIDによる永続ストレージ管理 ---
18
  import json
19
  import uuid
20
 
 
 
 
 
 
 
 
 
 
21
  st.set_page_config(
22
  page_title="麻理プロジェクト", page_icon="🤖",
23
  layout="centered", initial_sidebar_state="auto"
@@ -28,76 +37,46 @@ st.set_page_config(
28
  USER_DATA_DIR = "/mnt/data/mari_users"
29
  os.makedirs(USER_DATA_DIR, exist_ok=True)
30
 
31
- # ローカルストレージ操作用JavaScript
32
- def get_local_storage_js():
33
- return """
34
- <script>
35
- // ローカルストレージからトークンを取得
36
- function getStoredToken() {
37
- return localStorage.getItem('hf_token') || '';
38
- }
39
-
40
- // ローカルストレージにトークンを保存
41
- function saveToken(token) {
42
- if (token) {
43
- localStorage.setItem('hf_token', token);
44
- } else {
45
- localStorage.removeItem('hf_token');
46
- }
47
- }
48
-
49
- // ローカルストレージをクリア
50
- function clearStorage() {
51
- localStorage.removeItem('hf_token');
52
- localStorage.removeItem('hf_user');
53
- }
54
-
55
- // ユーザー情報を保存
56
- function saveUser(userInfo) {
57
- localStorage.setItem('hf_user', JSON.stringify(userInfo));
58
- }
59
-
60
- // ユーザー情報を取得
61
- function getStoredUser() {
62
- const user = localStorage.getItem('hf_user');
63
- return user ? JSON.parse(user) : null;
64
- }
65
-
66
- // Streamlitにメッセージを送信
67
- function sendToStreamlit(type, data) {
68
- window.parent.postMessage({
69
- type: type,
70
- data: data
71
- }, '*');
72
- }
73
-
74
- // ページ読み込み時に保存されたトークンを確認
75
- window.addEventListener('load', function() {
76
- const token = getStoredToken();
77
- const user = getStoredUser();
78
-
79
- if (token && user) {
80
- sendToStreamlit('stored_auth', {token: token, user: user});
81
- }
82
- });
83
- </script>
84
- """
85
 
86
- # JavaScript実行用コンポーネント
87
- def execute_js(js_code):
88
- components.html(f"""
89
- <script>
90
- {js_code}
91
- </script>
92
- """, height=0)
93
-
94
- # トークン検証関数
95
- def verify_hf_token(token):
 
 
 
 
 
 
 
 
 
96
  try:
97
- user_info = whoami(token)
98
- return True, user_info
99
- except Exception as e:
100
- return False, str(e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
  # --- 基本設定 ---
103
  # 非同期処理の問題を解決 (Windows向け)
@@ -367,9 +346,6 @@ def initialize_session_state(managers, force_reset_override=False):
367
 
368
  # 基本的なセッション状態を最初に初期化(Cookie処理より前)
369
  logger.info("🚀 基本セッション状態の早期初期化開始")
370
-
371
- # ローカルストレージ操作用JavaScriptを埋め込み
372
- components.html(get_local_storage_js(), height=0)
373
 
374
  # セッション状態の初期化
375
  if 'authenticated' not in st.session_state:
@@ -1411,96 +1387,83 @@ def render_chat_tab_content(managers):
1411
  st.markdown(f"**現在のシーン**: {current_theme_name}")
1412
 
1413
  # 認証状態の表示
1414
- if st.session_state.authenticated:
1415
- st.success(f"✅ ログイン中: **{st.session_state.user_info.get('name', 'Unknown')}**")
 
1416
 
1417
- col1, col2 = st.columns([3, 1])
1418
- with col2:
1419
- if st.button("ログアウト", type="secondary"):
1420
- # ローカルストレージをクリア
1421
- execute_js("clearStorage();")
1422
- # セッション状態をクリア
1423
- st.session_state.authenticated = False
1424
- st.session_state.user_info = None
1425
- st.session_state.token = None
1426
- st.rerun()
1427
- # ユーザー情報の表示
1428
- with st.expander("ユーザー情報"):
1429
- st.json(st.session_state.user_info)
1430
-
 
 
 
 
 
 
 
 
1431
  else:
1432
- st.info("Hugging Face トークンでログインしてください")
1433
-
1434
- with st.form("auth_form"):
1435
- token_input = st.text_input(
1436
- "Hugging Face Token",
1437
- type="password",
1438
- help="https://huggingface.co/settings/tokens からトークンを取得してください"
1439
- )
1440
- col1, col2 = st.columns([3,2])
1441
- with col1:
1442
- auth_button = st.form_submit_button("🔑 認証", type="primary")
1443
- with col2:
1444
- auto_login = st.checkbox("自動ログイン", value=True)
1445
-
1446
- # 認証処理
1447
- if auth_button and token_input:
1448
- with st.spinner("認証中..."):
1449
- is_valid, result = verify_hf_token(token_input)
1450
-
1451
- if is_valid:
1452
- st.session_state.authenticated = True
1453
- st.session_state.user_info = result
1454
- st.session_state.token = token_input
1455
-
1456
- # ローカルストレージに保存(自動ログインが有効な場合)
1457
- if auto_login:
1458
- execute_js(f"""
1459
- saveToken('{token_input}');
1460
- saveUser({json.dumps(result)});
1461
- """)
1462
-
1463
- st.success(f"認証成功: {result.get('name', 'Unknown')}")
1464
- st.rerun()
1465
- else:
1466
- st.error(f"認証失敗: {result}")
1467
 
1468
- # 保存されたトークンから自動ログイン
1469
- st.markdown("---")
1470
- st.subheader("💡 使い方")
1471
- st.markdown("""
1472
- 1. [Hugging Face Settings](https://huggingface.co/settings/tokens) でトークンを作成
1473
- 2. 上記フォームにトークンを入力
1474
- 3. 「自動ログイン」をチェックすると次回から自動でログイン
1475
- 4. ブラウザのローカルストレージにトークンが保存されます
1476
- """)
1477
-
1478
-
1479
- # 手動でローカルストレージから復元するボタン
1480
- if st.button("💾 保存済みトークンで復元", help="ローカルストレージに保存されたトークンで認証を試行"):
1481
- execute_js("""
1482
- const token = getStoredToken();
1483
- const user = getStoredUser();
1484
- if (token && user) {
1485
- // Streamlitに通知(実際の実装では適切なコールバックが必要)
1486
- alert('保存されたトークンが見つかりました。ページを再読み込みし��ください。');
1487
- } else {
1488
- alert('保存されたトークンが見つかりません。');
1489
- }
1490
- """)
1491
 
1492
- user_id_manager = managers["user_id_manager"] # フォールバック用
 
 
 
1493
 
1494
- has_saved_data = False
1495
- user_info = None
 
 
 
 
 
 
 
 
 
 
 
 
1496
 
1497
- try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1498
  # 永続ストレージから確認
1499
  # persistent_user_manager 完全廃止
1500
  has_saved_data = user_info is not None and "game_data" in user_info
1501
  if has_saved_data:
1502
  logger.debug("永続ストレージに保存データを確認")
1503
- except Exception as e:
1504
  logger.warning(f"永続ストレージ確認エラー、フォールバック使用: {e}")
1505
  # フォールバック: 従来のローカルファイル方式
1506
  has_saved_data = user_id_manager.is_user_data_exists()
@@ -1508,7 +1471,7 @@ def render_chat_tab_content(managers):
1508
  user_info = user_id_manager.get_user_info()
1509
  logger.debug("フォールバック: ローカルファイルに保存データを確認")
1510
 
1511
- if has_saved_data and user_info and "game_data" in user_info:
1512
  # 保存データがある場合の情報表示
1513
  game_data = user_info["game_data"]
1514
  if game_data:
@@ -1527,7 +1490,7 @@ def render_chat_tab_content(managers):
1527
  storage_type = "🌐 永続ストレージ" if user_info.get("storage_type") != "local" else "📁 ローカル"
1528
  st.info(f"💾 保存データあり ({storage_type})\n好感度: {saved_affection}/100\nメッセージ: {saved_messages}件\n保存日時: {saved_at}")
1529
 
1530
- if st.button("💾 ゲームデータを保存", help="現在の進行状況(好感度、チャット履歴など)をファイルに保存します", use_container_width=True):
1531
  success = save_game_data_to_file(managers)
1532
  if success:
1533
  st.success("✅ ゲームデータを保存しました!")
 
12
  import requests
13
  from dotenv import load_dotenv
14
  from contextlib import contextmanager
15
+ from urllib.parse import urlencode
16
 
17
  # --- HFユーザーIDによる永続ストレージ管理 ---
18
  import json
19
  import uuid
20
 
21
+ # --- 定数と環境変数の設定 ---
22
+ HF_ENDPOINT = "https://huggingface.co"
23
+
24
+ # README.mdで hf_oauth: true を設定すると、以下の環境変数が自動的に設定される
25
+ CLIENT_ID = os.environ.get("OAUTH_CLIENT_ID")
26
+ CLIENT_SECRET = os.environ.get("OAUTH_CLIENT_SECRET")
27
+ SPACE_ID = os.environ.get("SPACE_ID")
28
+
29
+
30
  st.set_page_config(
31
  page_title="麻理プロジェクト", page_icon="🤖",
32
  layout="centered", initial_sidebar_state="auto"
 
37
  USER_DATA_DIR = "/mnt/data/mari_users"
38
  os.makedirs(USER_DATA_DIR, exist_ok=True)
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ def get_redirect_uri():
42
+ """リダイレクトURIを動的に生成する"""
43
+ if SPACE_ID:
44
+ return f"https://huggingface.co/spaces/{SPACE_ID}"
45
+ else:
46
+ # ローカル開発用のフォールバック(必要に応じて変更)
47
+ # st.query_paramsはローカルではうまく機能しない場合がある
48
+ return "http://localhost:8501"
49
+
50
+ def get_hf_token(code: str, redirect_uri: str) -> dict | None:
51
+ """認可コード(code)をアクセストークンに交換する"""
52
+ url = f"{HF_ENDPOINT}/oauth/token"
53
+ payload = {
54
+ "grant_type": "authorization_code",
55
+ "code": code,
56
+ "redirect_uri": redirect_uri,
57
+ "client_id": CLIENT_ID,
58
+ "client_secret": CLIENT_SECRET,
59
+ }
60
  try:
61
+ response = requests.post(url, data=payload)
62
+ response.raise_for_status() # エラーがあれば例外を発生
63
+ return response.json()
64
+ except requests.exceptions.RequestException as e:
65
+ st.error(f"トークンの取得に失敗しました: {e}")
66
+ return None
67
+
68
+ def get_user_info(access_token: str) -> dict | None:
69
+ """アクセストークンを使ってユーザー情報を取得する"""
70
+ url = f"{HF_ENDPOINT}/api/whoami"
71
+ headers = {"Authorization": f"Bearer {access_token}"}
72
+ try:
73
+ response = requests.get(url, headers=headers)
74
+ response.raise_for_status()
75
+ return response.json()
76
+ except requests.exceptions.RequestException as e:
77
+ st.error(f"ユーザー情報の取得に失敗しました: {e}")
78
+ return None
79
+
80
 
81
  # --- 基本設定 ---
82
  # 非同期処理の問題を解決 (Windows向け)
 
346
 
347
  # 基本的なセッション状態を最初に初期化(Cookie処理より前)
348
  logger.info("🚀 基本セッション状態の早期初期化開始")
 
 
 
349
 
350
  # セッション状態の初期化
351
  if 'authenticated' not in st.session_state:
 
1387
  st.markdown(f"**現在のシーン**: {current_theme_name}")
1388
 
1389
  # 認証状態の表示
1390
+
1391
+
1392
+ user_id_manager = managers["user_id_manager"] # フォールバック用
1393
 
1394
+ has_saved_data = False
1395
+ user_info = None
1396
+
1397
+ if 'token_data' in st.session_state:
1398
+ # --- ログイン済みの画面 ---
1399
+ user_data = st.session_state.get('user_data', {})
1400
+
1401
+ st.success(f"ようこそ、{user_data.get('fullname', 'ユーザー')} さん!")
1402
+ if 'avatarUrl' in user_data:
1403
+ st.image(user_data['avatarUrl'], width=100, caption="プロフィール画像")
1404
+
1405
+ st.write("ログイン済みです。")
1406
+ st.json(user_data) # 取得したユーザー情報を表示
1407
+
1408
+ if st.button("ログアウト"):
1409
+ # セッション情報をクリアしてログアウト
1410
+ del st.session_state['token_data']
1411
+ if 'user_data' in st.session_state:
1412
+ del st.session_state['user_data']
1413
+ # ページを再読み込みしてクリーンな状態にする
1414
+ st.rerun()
1415
+
1416
  else:
1417
+ # --- 未ログインの画面 ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1418
 
1419
+ # 2. Hugging Faceからのリダイレクト直後か確認 (URLに 'code' があるか)
1420
+ query_params = st.query_params
1421
+ auth_code = query_params.get("code")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1422
 
1423
+ if auth_code:
1424
+ # 3. 認可コードがあれば、トークン取得とユーザー情報取得を実行
1425
+ redirect_uri = get_redirect_uri()
1426
+ token_data = get_hf_token(auth_code, redirect_uri)
1427
 
1428
+ if token_data:
1429
+ st.session_state['token_data'] = token_data
1430
+ user_data = get_user_info(token_data['access_token'])
1431
+
1432
+ if user_data:
1433
+ st.session_state['user_data'] = user_data
1434
+
1435
+ # URLから 'code' を消去してページを再読み込みし、ログイン後の画面を表示
1436
+ st.query_params.clear()
1437
+ st.rerun()
1438
+
1439
+ else:
1440
+ # 4. 初期アクセス時(未ログイン状態)
1441
+ st.warning("現在ログインしていません。")
1442
 
1443
+ if not CLIENT_ID or not CLIENT_SECRET:
1444
+ st.error("OAuthクライアントが設定されていません。SpaceのREADME.mdを確認し、再起動してください。")
1445
+ else:
1446
+ # ログインURLを生成
1447
+ redirect_uri = get_redirect_uri()
1448
+ params = {
1449
+ "client_id": CLIENT_ID,
1450
+ "redirect_uri": redirect_uri,
1451
+ "scope": "openid profile", # 取得したい情報の範囲
1452
+ "state": "STATE_STRING", # CSRF対策のためランダムな文字列を推奨
1453
+ "response_type": "code",
1454
+ }
1455
+ login_url = f"{HF_ENDPOINT}/oauth/authorize?{urlencode(params)}"
1456
+
1457
+ st.markdown(f'<a href="{login_url}" target="_self" style="display: inline-block; padding: 10px 20px; background-color: #FFD21E; color: black; text-align: center; text-decoration: none; border-radius: 5px; font-weight: bold;">🤗 Hugging Faceでログイン</a>', unsafe_allow_html=True)
1458
+
1459
+
1460
+ try:
1461
  # 永続ストレージから確認
1462
  # persistent_user_manager 完全廃止
1463
  has_saved_data = user_info is not None and "game_data" in user_info
1464
  if has_saved_data:
1465
  logger.debug("永続ストレージに保存データを確認")
1466
+ except Exception as e:
1467
  logger.warning(f"永続ストレージ確認エラー、フォールバック使用: {e}")
1468
  # フォールバック: 従来のローカルファイル方式
1469
  has_saved_data = user_id_manager.is_user_data_exists()
 
1471
  user_info = user_id_manager.get_user_info()
1472
  logger.debug("フォールバック: ローカルファイルに保存データを確認")
1473
 
1474
+ if has_saved_data and user_info and "game_data" in user_info:
1475
  # 保存データがある場合の情報表示
1476
  game_data = user_info["game_data"]
1477
  if game_data:
 
1490
  storage_type = "🌐 永続ストレージ" if user_info.get("storage_type") != "local" else "📁 ローカル"
1491
  st.info(f"💾 保存データあり ({storage_type})\n好感度: {saved_affection}/100\nメッセージ: {saved_messages}件\n保存日時: {saved_at}")
1492
 
1493
+ if st.button("💾 ゲームデータを保存", help="現在の進行状況(好感度、チャット履歴など)をファイルに保存します", use_container_width=True):
1494
  success = save_game_data_to_file(managers)
1495
  if success:
1496
  st.success("✅ ゲームデータを保存しました!")