""" ポチ(犬)アシスタントコンポーネント 画面右下に固定配置され、本音表示機能を提供する """ 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"""
{bubble_text}
""" # HTMLコンポーネント(ボタン以外)を表示 dog_display_html = f"""
{bubble_text}
""" 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 }