"""
ポチ(犬)アシスタントコンポーネント
画面右下に固定配置され、本音表示機能を提供する
"""
import streamlit as st
import logging
import time
logger = logging.getLogger(__name__)
class DogAssistant:
"""ポチ(犬)アシスタントクラス"""
def __init__(self):
"""初期化"""
self.default_message = "ポチは麻理の本音を察知したようだ・・・"
self.active_message = "ワンワン!本音が見えてるワン!"
def render_dog_component(self, tutorial_manager=None):
"""画面右下に固定配置される犬のコンポーネントを描画"""
try:
# 現在の状態を取得
current_show_all_hidden = st.session_state.get('show_all_hidden', False)
# 犬のコンポーネントのCSS(レスポンシブ対応)
dog_css = """
"""
# 現在の状態を使用(関数開始時に取得済み)
is_active = current_show_all_hidden
bubble_text = self.active_message if is_active else self.default_message
button_class = "dog-button active" if is_active else "dog-button"
# JavaScriptでクリックイベントを処理
dog_js = f"""
"""
# HTMLコンポーネント
dog_html = f"""
"""
# HTMLコンポーネント(ボタン以外)を表示
dog_display_html = f"""
"""
st.markdown(dog_css + dog_display_html, unsafe_allow_html=True)
# Streamlitボタンを固定位置に配置
button_css = """
"""
st.markdown(button_css, unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
# ボタンクリック処理(キーを固定して状態変更の問題を解決)
button_key = "dog_fixed_button" # 固定キー
button_help = "本音を隠す" if is_active else "本音を見る"
# ボタンの状態を視覚的に反映するためのスタイル
button_style = """
""" if is_active else ""
if button_style:
st.markdown(button_style, unsafe_allow_html=True)
if st.button("🐕", key=button_key, help=button_help, type="primary" if is_active else "secondary"):
self.handle_dog_button_click(tutorial_manager)
logger.info("右下の犬のボタンがクリックされました")
st.markdown('
', unsafe_allow_html=True)
logger.debug(f"犬のコンポーネントを描画しました (active: {is_active})")
except Exception as e:
logger.error(f"犬のコンポーネント描画エラー: {e}")
def handle_dog_button_click(self, tutorial_manager=None):
"""犬のボタンクリック処理(1回クリック対応版)"""
try:
# 重複クリック防止(短時間での連続クリックを防ぐ)
current_time = time.time()
last_click_time = st.session_state.get('dog_button_last_click', 0)
if current_time - last_click_time < 0.5: # 0.5秒以内の連続クリックを防ぐ
logger.debug("犬のボタン連続クリックを防止しました")
return
st.session_state.dog_button_last_click = current_time
# 本音表示機能のトリガー
if 'show_all_hidden' not in st.session_state:
st.session_state.show_all_hidden = False
# 現在の状態を確実に取得
current_state = st.session_state.get('show_all_hidden', False)
new_state = not current_state
# 状態を即座に更新(複数のフラグを同時に設定)
st.session_state.show_all_hidden = new_state
st.session_state.show_all_hidden_changed = True # チャット履歴の強制再表示フラグ
st.session_state.dog_button_clicked = True # UI更新フラグ
st.session_state.force_chat_rerender = True # 強制再描画フラグ
logger.info(f"🐕 ポチボタンクリック: {current_state} → {new_state}")
# 全メッセージのフリップ状態を即座に更新
if 'message_flip_states' not in st.session_state:
st.session_state.message_flip_states = {}
# 現在のメッセージに対してフリップ状態を設定
if 'chat' in st.session_state and 'messages' in st.session_state.chat:
# 初期メッセージが存在することを確認
messages = st.session_state.chat['messages']
if not any(msg.get('is_initial', False) for msg in messages):
logger.warning("犬のボタン押下時に初期メッセージが見つかりません - 復元します")
initial_message = {"role": "assistant", "content": "何の用?遊びに来たの?", "is_initial": True}
st.session_state.chat['messages'].insert(0, initial_message)
logger.info("犬のボタン押下時に初期メッセージを復元しました")
# 全てのアシスタントメッセージのフリップ状態を更新
for i, message in enumerate(st.session_state.chat['messages']):
if message['role'] == 'assistant':
message_id = f"msg_{i}"
st.session_state.message_flip_states[message_id] = new_state
logger.debug(f"メッセージ {message_id} のフリップ状態を {new_state} に設定")
else:
logger.warning("犬のボタン押下時にチャットセッションが存在しません - 初期化します")
# チャットセッションが存在しない場合は初期化
initial_message = {"role": "assistant", "content": "何の用?遊びに来たの?", "is_initial": True}
if 'chat' not in st.session_state:
st.session_state.chat = {
"messages": [initial_message],
"affection": 30,
"scene_params": {"theme": "default"},
"limiter_state": {},
"scene_change_pending": None,
"ura_mode": False
}
else:
st.session_state.chat['messages'] = [initial_message]
logger.info("犬のボタン押下時にチャットセッションを初期化しました")
# チュートリアルステップ2を完了(tutorial_managerが渡された場合)
if tutorial_manager:
tutorial_manager.check_step_completion(2, True)
# 通知メッセージ(一度だけ表示)
if new_state:
st.success("🐕 ポチが麻理の本音を察知しました!")
else:
st.info("🐕 ポチが通常モードに戻りました。")
logger.info(f"犬のボタン状態変更完了: {current_state} → {new_state}")
# 状態変更を即座に反映するため再描画を実行
st.rerun()
except Exception as e:
logger.error(f"犬のボタンクリック処理エラー: {e}")
import traceback
logger.error(f"犬のボタンクリック処理エラーの詳細: {traceback.format_exc()}")
def render_with_streamlit_button(self):
"""Streamlitのボタンを使用した代替実装(フォールバック用)"""
try:
# 固定位置のCSS
fallback_css = """
"""
st.markdown(fallback_css, unsafe_allow_html=True)
# コンテナの開始
st.markdown('', unsafe_allow_html=True)
# 状態表示
is_active = st.session_state.get('show_all_hidden', False)
status_text = "本音モード中" if is_active else "通常モード"
st.caption(f"🐕 {status_text}")
# ボタン
button_text = "🔄 戻す" if is_active else "🐕 本音を見る"
if st.button(button_text, key="dog_assistant_btn"):
# チュートリアルマネージャーを取得(可能な場合)
tutorial_manager = None
try:
# セッション状態からチュートリアルマネージャーを取得する試み
# (完全ではないが、フォールバック用)
pass
except:
pass
self.handle_dog_button_click(tutorial_manager)
# コンテナの終了
st.markdown('
', unsafe_allow_html=True)
except Exception as e:
logger.error(f"犬のフォールバック描画エラー: {e}")
def get_current_state(self):
"""現在の犬の状態を取得"""
return {
'is_active': st.session_state.get('show_all_hidden', False),
'message': self.active_message if st.session_state.get('show_all_hidden', False) else self.default_message
}