mari-chat-3 / components_status_display.py
sirochild's picture
Upload 57 files
a73fa4e verified
"""
ステータス表示コンポーネント
好感度ゲージと関係ステージの表示を担当する
"""
import streamlit as st
import logging
from typing import Dict, Tuple
logger = logging.getLogger(__name__)
class StatusDisplay:
"""ステータス表示を管理するクラス"""
def __init__(self):
"""ステータス表示の初期化"""
self.stage_colors = {
"敵対": {"color": "#ff4757", "emoji": "🔴", "bg_color": "rgba(255, 71, 87, 0.1)"},
"警戒": {"color": "#ff6348", "emoji": "🟠", "bg_color": "rgba(255, 99, 72, 0.1)"},
"中立": {"color": "#ffa502", "emoji": "🟡", "bg_color": "rgba(255, 165, 2, 0.1)"},
"好意": {"color": "#2ed573", "emoji": "🟢", "bg_color": "rgba(46, 213, 115, 0.1)"},
"親密": {"color": "#a55eea", "emoji": "💜", "bg_color": "rgba(165, 94, 234, 0.1)"}
}
def get_affection_color(self, affection: int) -> str:
"""
好感度に基づいて色を取得する
Args:
affection: 好感度値 (0-100)
Returns:
色のHEXコード
"""
if affection < 20:
return "#ff4757" # 赤
elif affection < 40:
return "#ff6348" # オレンジ
elif affection < 60:
return "#ffa502" # 黄色
elif affection < 80:
return "#2ed573" # 緑
else:
return "#a55eea" # 紫
def get_relationship_stage_info(self, affection: int) -> Dict[str, str]:
"""
好感度から関係性ステージの情報を取得する
Args:
affection: 好感度値 (0-100)
Returns:
ステージ情報の辞書
"""
if affection < 20:
stage = "敵対"
elif affection < 40:
stage = "警戒"
elif affection < 60:
stage = "中立"
elif affection < 80:
stage = "好意"
else:
stage = "親密"
# 古いキー形式との互換性を保つため、新しいキーで検索し、見つからない場合は中立を返す
stage_info = None
for key, value in self.stage_colors.items():
if stage in key:
stage_info = value
break
return stage_info or self.stage_colors.get("中立", {"color": "#ffa502", "emoji": "🟡", "bg_color": "rgba(255, 165, 2, 0.1)"})
def render_affection_gauge(self, affection: int) -> None:
"""
好感度ゲージを表示する
Args:
affection: 好感度値 (0-100)
"""
try:
# 好感度の値を0-100の範囲に制限
affection = max(0, min(100, affection))
# 好感度メトリック表示
col1, col2 = st.columns([2, 1])
with col1:
st.metric("好感度", f"{affection}/100")
with col2:
# 好感度の変化を表示(前回の値と比較)
prev_affection = st.session_state.get('prev_affection', affection)
delta = affection - prev_affection
if delta != 0:
st.metric("変化", f"{delta:+d}")
st.session_state.prev_affection = affection
# プログレスバー
progress_value = affection / 100.0
affection_color = self.get_affection_color(affection)
# カスタムプログレスバーのCSS
progress_css = f"""
<style>
.affection-progress {{
width: 100%;
height: 25px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 12px;
overflow: hidden;
margin: 10px 0;
border: 1px solid rgba(255, 255, 255, 0.2);
}}
.affection-fill {{
height: 100%;
background: linear-gradient(90deg,
#ff4757 0%,
#ff6348 25%,
#ffa502 50%,
#2ed573 75%,
#a55eea 100%);
width: {progress_value * 100}%;
transition: width 0.5s ease-in-out;
position: relative;
}}
.affection-text {{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0,0,0,0.7);
font-size: 12px;
}}
</style>
"""
st.markdown(progress_css, unsafe_allow_html=True)
# プログレスバーのHTML
progress_html = f"""
<div class="affection-progress">
<div class="affection-fill">
<div class="affection-text">{affection}%</div>
</div>
</div>
"""
st.markdown(progress_html, unsafe_allow_html=True)
# Streamlitの標準プログレスバーも表示(フォールバック)
st.progress(progress_value)
except Exception as e:
logger.error(f"好感度ゲージ表示エラー: {e}")
# フォールバック表示
st.metric("好感度", f"{affection}/100")
st.progress(affection / 100.0)
def render_relationship_stage(self, affection: int) -> None:
"""
関係性ステージを表示する
Args:
affection: 好感度値 (0-100)
"""
try:
stage_info = self.get_relationship_stage_info(affection)
# ステージ名を取得
if affection < 20:
stage_name = "ステージ1:敵対"
stage_description = "麻理はあなたを敵視している"
elif affection < 40:
stage_name = "ステージ2:警戒"
stage_description = "麻理はあなたを警戒している"
elif affection < 60:
stage_name = "ステージ3:中立"
stage_description = "麻理はあなたに対して中立的"
elif affection < 80:
stage_name = "ステージ4:好意"
stage_description = "麻理はあなたに好意を持っている"
else:
stage_name = "ステージ5:親密"
stage_description = "麻理はあなたと親密な関係"
# ステージ表示のCSS
stage_css = f"""
<style>
.relationship-stage {{
background: {stage_info['bg_color']};
border: 2px solid {stage_info['color']};
border-radius: 10px;
padding: 15px;
margin: 10px 0;
text-align: center;
}}
.stage-emoji {{
font-size: 24px;
margin-bottom: 5px;
}}
.stage-name {{
color: {stage_info['color']};
font-weight: bold;
font-size: 16px;
margin-bottom: 5px;
}}
.stage-description {{
color: {stage_info['color']};
font-size: 12px;
opacity: 0.8;
}}
</style>
"""
st.markdown(stage_css, unsafe_allow_html=True)
# ステージ表示のHTML
stage_html = f"""
<div class="relationship-stage">
<div class="stage-emoji">{stage_info['emoji']}</div>
<div class="stage-name">{stage_name}</div>
<div class="stage-description">{stage_description}</div>
</div>
"""
st.markdown(stage_html, unsafe_allow_html=True)
# フォールバック表示
st.write(f"{stage_info['emoji']} **関係性**: {stage_name}")
except Exception as e:
logger.error(f"関係性ステージ表示エラー: {e}")
# フォールバック表示
if affection < 20:
st.write("🔴 **関係性**: ステージ1:敵対")
elif affection < 40:
st.write("🟠 **関係性**: ステージ2:中立")
elif affection < 60:
st.write("🟡 **関係性**: ステージ3:好意")
elif affection < 80:
st.write("🟢 **関係性**: ステージ4:親密")
else:
st.write("💜 **関係性**: ステージ5:最接近")
def render_affection_history(self, max_history: int = 10) -> None:
"""
好感度の履歴を表示する(デバッグモード用)
Args:
max_history: 表示する履歴の最大数
"""
try:
if not st.session_state.get('debug_mode', False):
return
# 好感度履歴を取得
affection_history = st.session_state.get('affection_history', [])
if not affection_history:
st.write("好感度の履歴がありません")
return
# 最新の履歴を表示
recent_history = affection_history[-max_history:]
st.subheader("📈 好感度履歴")
for i, entry in enumerate(reversed(recent_history)):
timestamp = entry.get('timestamp', 'Unknown')
affection = entry.get('affection', 0)
change = entry.get('change', 0)
message = entry.get('message', '')
change_str = f"({change:+d})" if change != 0 else ""
st.write(f"{i+1}. {affection}/100 {change_str} - {timestamp[:19]}")
if message:
st.caption(f"メッセージ: {message[:50]}...")
except Exception as e:
logger.error(f"好感度履歴表示エラー: {e}")
def update_affection_history(self, old_affection: int, new_affection: int,
message: str = "") -> None:
"""
好感度履歴を更新する
Args:
old_affection: 変更前の好感度
new_affection: 変更後の好感度
message: 関連するメッセージ
"""
try:
if 'affection_history' not in st.session_state:
st.session_state.affection_history = []
# 履歴エントリを作成
history_entry = {
'timestamp': st.session_state.get('current_timestamp', ''),
'affection': new_affection,
'change': new_affection - old_affection,
'message': message[:100] if message else '' # メッセージを100文字に制限
}
st.session_state.affection_history.append(history_entry)
# 履歴の長さを制限(最大50エントリ)
if len(st.session_state.affection_history) > 50:
st.session_state.affection_history = st.session_state.affection_history[-50:]
except Exception as e:
logger.error(f"好感度履歴更新エラー: {e}")
def get_affection_statistics(self) -> Dict[str, float]:
"""
好感度の統計情報を取得する
Returns:
統計情報の辞書
"""
try:
affection_history = st.session_state.get('affection_history', [])
if not affection_history:
return {
'current': st.session_state.get('affection', 30),
'average': 30.0,
'max': 30,
'min': 30,
'total_changes': 0
}
affections = [entry['affection'] for entry in affection_history]
changes = [entry['change'] for entry in affection_history if entry['change'] != 0]
return {
'current': st.session_state.get('affection', 30),
'average': sum(affections) / len(affections),
'max': max(affections),
'min': min(affections),
'total_changes': len(changes),
'positive_changes': len([c for c in changes if c > 0]),
'negative_changes': len([c for c in changes if c < 0])
}
except Exception as e:
logger.error(f"好感度統計取得エラー: {e}")
return {
'current': st.session_state.get('affection', 30),
'average': 30.0,
'max': 30,
'min': 30,
'total_changes': 0
}
def apply_status_styles(self) -> None:
"""
ステータス表示用のカスタムスタイルを適用する
"""
try:
status_css = """
<style>
/* ステータス表示全体のスタイル */
.status-container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(15px);
border-radius: 15px;
padding: 20px;
margin: 15px 0;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.status-container:hover {
background: rgba(255, 255, 255, 0.15);
transform: translateY(-3px);
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.15);
}
/* メトリクス表示の改善 */
.stMetric {
background: rgba(255, 255, 255, 0.05);
padding: 10px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.stMetric > div {
color: white !important;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7);
}
/* 好感度ゲージのアニメーション */
.affection-progress {
position: relative;
overflow: hidden;
}
.affection-progress::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.3),
transparent);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { left: -100%; }
100% { left: 100%; }
}
/* 関係性ステージのアニメーション */
.relationship-stage {
animation: stageGlow 3s ease-in-out infinite alternate;
}
@keyframes stageGlow {
0% { box-shadow: 0 0 5px rgba(255, 255, 255, 0.2); }
100% { box-shadow: 0 0 20px rgba(255, 255, 255, 0.4); }
}
/* ステージ変更時のエフェクト */
.stage-change-effect {
animation: stageChange 1s ease-in-out;
}
@keyframes stageChange {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
/* 好感度変化のエフェクト */
.affection-change-positive {
animation: positiveChange 0.8s ease-out;
}
.affection-change-negative {
animation: negativeChange 0.8s ease-out;
}
@keyframes positiveChange {
0% { color: #2ed573; transform: scale(1); }
50% { color: #2ed573; transform: scale(1.1); }
100% { color: inherit; transform: scale(1); }
}
@keyframes negativeChange {
0% { color: #ff4757; transform: scale(1); }
50% { color: #ff4757; transform: scale(1.1); }
100% { color: inherit; transform: scale(1); }
}
/* デバッグ情報のスタイル */
.debug-info {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 10px;
margin: 10px 0;
font-family: 'Courier New', monospace;
font-size: 12px;
}
.debug-info pre {
color: #00ff00;
margin: 0;
}
/* 履歴表示のスタイル */
.history-item {
background: rgba(255, 255, 255, 0.05);
border-left: 3px solid rgba(255, 255, 255, 0.3);
padding: 8px 12px;
margin: 5px 0;
border-radius: 0 8px 8px 0;
transition: all 0.3s ease;
}
.history-item:hover {
background: rgba(255, 255, 255, 0.1);
border-left-color: rgba(255, 255, 255, 0.5);
transform: translateX(5px);
}
.history-positive {
border-left-color: #2ed573;
}
.history-negative {
border-left-color: #ff4757;
}
.history-neutral {
border-left-color: #ffa502;
}
</style>
"""
st.markdown(status_css, unsafe_allow_html=True)
logger.debug("ステータス表示用スタイルを適用しました")
except Exception as e:
logger.error(f"ステータススタイル適用エラー: {e}")
def render_enhanced_status_display(self, affection: int) -> None:
"""
拡張されたステータス表示を描画する
Args:
affection: 現在の好感度
"""
try:
# カスタムスタイルを適用
self.apply_status_styles()
# ステータスコンテナの開始
st.markdown('<div class="status-container">', unsafe_allow_html=True)
# 好感度ゲージ
self.render_affection_gauge(affection)
# 関係性ステージ
self.render_relationship_stage(affection)
# ステータスコンテナの終了
st.markdown('</div>', unsafe_allow_html=True)
except Exception as e:
logger.error(f"拡張ステータス表示エラー: {e}")
# フォールバック:通常の表示
self.render_affection_gauge(affection)
self.render_relationship_stage(affection)
def show_affection_change_notification(self, old_affection: int,
new_affection: int, reason: str = "") -> None:
"""
好感度変化の通知を表示する
Args:
old_affection: 変更前の好感度
new_affection: 変更後の好感度
reason: 変化の理由
"""
try:
change = new_affection - old_affection
if change == 0:
return
# 変化の方向に応じてスタイルを決定
if change > 0:
icon = "📈"
color = "#2ed573"
change_text = f"+{change}"
css_class = "affection-change-positive"
else:
icon = "📉"
color = "#ff4757"
change_text = str(change)
css_class = "affection-change-negative"
# 通知メッセージを作成
notification_html = f"""
<div class="{css_class}" style="
background: rgba(255, 255, 255, 0.1);
border: 1px solid {color};
border-radius: 8px;
padding: 10px;
margin: 10px 0;
color: {color};
text-align: center;
animation: slideInFromTop 0.5s ease-out;
">
{icon} 好感度が変化しました: {change_text}
{f'<br><small>{reason}</small>' if reason else ''}
</div>
"""
st.markdown(notification_html, unsafe_allow_html=True)
# 自動で消える通知(JavaScript)
auto_hide_js = """
<script>
setTimeout(function() {
const notifications = document.querySelectorAll('.affection-change-positive, .affection-change-negative');
notifications.forEach(function(notification) {
notification.style.transition = 'opacity 0.5s ease-out';
notification.style.opacity = '0';
setTimeout(function() {
notification.remove();
}, 500);
});
}, 3000);
</script>
"""
st.markdown(auto_hide_js, unsafe_allow_html=True)
except Exception as e:
logger.error(f"好感度変化通知エラー: {e}")
def get_status_display_config(self) -> Dict[str, any]:
"""
ステータス表示の設定情報を取得する
Returns:
設定情報の辞書
"""
try:
current_affection = st.session_state.get('affection', 30)
stage_info = self.get_relationship_stage_info(current_affection)
return {
"current_affection": current_affection,
"affection_color": self.get_affection_color(current_affection),
"stage_info": stage_info,
"history_count": len(st.session_state.get('affection_history', [])),
"statistics": self.get_affection_statistics(),
"styles_applied": True
}
except Exception as e:
logger.error(f"ステータス表示設定取得エラー: {e}")
return {
"current_affection": 30,
"affection_color": "#ffa502",
"stage_info": self.stage_colors["中立"],
"history_count": 0,
"statistics": {},
"styles_applied": False
}