Spaces:
Runtime error
Runtime error
Upload main_app.py
Browse files- 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
|
| 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 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
try:
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1415 |
-
|
|
|
|
| 1416 |
|
| 1417 |
-
|
| 1418 |
-
|
| 1419 |
-
|
| 1420 |
-
|
| 1421 |
-
|
| 1422 |
-
|
| 1423 |
-
|
| 1424 |
-
|
| 1425 |
-
|
| 1426 |
-
|
| 1427 |
-
|
| 1428 |
-
|
| 1429 |
-
|
| 1430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1431 |
else:
|
| 1432 |
-
|
| 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.
|
| 1470 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 1493 |
|
| 1494 |
-
|
| 1495 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1496 |
|
| 1497 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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("✅ ゲームデータを保存しました!")
|