From 4f8eb6268cd2fec824eb9f0ae03103f87d5b425a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Nov 2025 14:27:24 +0000 Subject: [PATCH] Add gamification: Make memory scoring fun like psychological tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Key insight from user: "It's all about presentation" 心理テストや占いがSNSで流行るのは「見せ方」の問題 ## New Features ### 🎮 Game-Style Result Display When creating memories with AI, users now get: - Visual score display (COMMON → LEGENDARY) - Personality type diagnosis (革新者、哲学者、実務家、etc.) - Detailed breakdown bars (感情/関連性/新規性/実用性) - XP rewards system - Shareable text for SNS Example output: ``` ╔══════════════════════════════════════╗ ║ 🎲 メモリースコア判定 ║ ╚══════════════════════════════════════╝ 🟣 EPIC 85点 💡 【革新者】 💎 XP獲得: +850 XP ``` ### 🏆 Ranking Display - Top 10 memories with medals (🥇🥈🥉) - Rarity-based color coding - Game-style formatting ### 📅 Daily Challenge System - Random daily quest - Bonus XP rewards - Encourages daily engagement ## Implementation Added `src/game_formatter.rs`: - MemoryRarity enum (5 levels with emoji) - DiagnosisType enum (5 personality types) - GameFormatter with rich text formatting - format_memory_result() - Main game display - format_shareable_text() - SNS sharing - format_ranking() - Top 10 display - format_daily_challenge() - Daily quest MCP Tools Updated: - create_memory_with_ai: Added game_mode parameter (default: true) - list_memories_by_priority: Added ranking display - daily_challenge: New tool for daily quests ## Why This Works 占い・心理テストと同じ心理: 1. ゲームをスタート(メモリ作成) 2. 分析中の演出 3. スコアが表示される(ドキドキ) 4. 結果診断(あなたは〇〇タイプ) 5. シェアしたくなる "見せ方"でデータを楽しいゲームに変換! Next: Phase 2 (Content Platform) + More gamification --- README.md | 111 +++++++++++-- src/game_formatter.rs | 353 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- src/mcp/base.rs | 54 ++++++- 4 files changed, 502 insertions(+), 19 deletions(-) create mode 100644 src/game_formatter.rs diff --git a/README.md b/README.md index 26a2d7d..625d8bf 100644 --- a/README.md +++ b/README.md @@ -96,15 +96,21 @@ aigpt import path/to/conversations.json ### AI機能ツール(重要!) -6. **create_memory_with_ai** - AI解釈と心理判定付きでメモリを作成 +6. **create_memory_with_ai** - AI解釈と心理判定付きでメモリを作成 🎮 - 元のコンテンツをAIが解釈 - 重要度を0.0-1.0のスコアで自動評価 - ユーザーコンテキストを考慮可能 + - **ゲーム風の診断結果を表示!**(占い・心理テスト風) -7. **list_memories_by_priority** - 優先順位順にメモリをリスト +7. **list_memories_by_priority** - 優先順位順にメモリをリスト 🏆 - 高スコアから順に表示 - min_scoreで閾値フィルタリング可能 - limit で件数制限可能 + - **ランキング形式で表示!** + +8. **daily_challenge** - 今日のデイリーチャレンジを取得 📅 + - 日替わりのお題を取得 + - ボーナスXPが獲得可能 ## ツールの使用例 @@ -115,31 +121,104 @@ Claude Desktop/Codeで以下のように使用します: MCPツールを使って「今日は良い天気です」というメモリーを作成してください ``` -### AI解釈付きメモリ作成(推奨) +### AI解釈付きメモリ作成(推奨)🎮 ``` create_memory_with_ai ツールを使って「新しいAI記憶システムのアイデアを思いついた」というメモリーを作成してください。 ユーザーコンテキスト: 「AI開発者、創造的思考を重視」 ``` -レスポンス例: -```json -{ - "success": true, - "id": "uuid-here", - "memory": { - "content": "新しいAI記憶システムのアイデアを思いついた", - "interpreted_content": "AIによる解釈: 記憶システムの革新的アプローチ...", - "priority_score": 0.85, - "user_context": "AI開発者、創造的思考を重視" - } -} +**ゲーム風の結果表示:** +``` +╔══════════════════════════════════════════════════════════════╗ +║ 🎲 メモリースコア判定 ║ +╚══════════════════════════════════════════════════════════════╝ + +⚡ 分析完了! あなたの思考が記録されました + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📊 総合スコア +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 🟣 EPIC 85点 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🎯 詳細分析 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +💓 感情的インパクト: [████████░░] 80% +🔗 ユーザー関連性: [██████████] 100% +✨ 新規性・独自性: [█████████░] 90% +⚙️ 実用性: [████████░░] 80% +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🎊 あなたのタイプ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +💡 【革新者】 + +創造的で実用的なアイデアを生み出す。常に新しい可能性を探求し、 +それを現実のものにする力を持つ。 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🏆 報酬 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +💎 XP獲得: +850 XP +🎁 レア度: 🟣 EPIC +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +📤 この結果をシェアしよう! +#aigpt #メモリースコア #革新者 ``` -### 優先順位でメモリをリスト +**シェア用テキストも自動生成:** +``` +🎲 AIメモリースコア診断結果 + +🟣 EPIC 85点 +💡 【革新者】 + +新しいAI記憶システムのアイデアを思いついた + +#aigpt #メモリースコア #AI診断 +``` + +### 優先順位でメモリをリスト 🏆 ``` list_memories_by_priority ツールで、スコア0.7以上の重要なメモリを10件表示してください ``` +**ランキング形式で表示:** +``` +╔══════════════════════════════════════════════════════════════╗ +║ 🏆 メモリーランキング TOP 10 ║ +╚══════════════════════════════════════════════════════════════╝ + +🥇 1位 🟡 LEGENDARY 95点 - 心理優先記憶装置の設計 +🥈 2位 🟣 EPIC 88点 - AIとのやり取りをコンテンツ化 +🥉 3位 🟣 EPIC 85点 - ゲーム化の構想 +  4位 🔵 RARE 75点 - SNSの本質について +  5位 🔵 RARE 72点 - AI OSの可能性 +... + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +### 今日のデイリーチャレンジ 📅 +``` +daily_challenge ツールで今日のお題を確認 +``` + +**表示例:** +``` +╔══════════════════════════════════════════════════════════════╗ +║ 📅 今日のチャレンジ ║ +╚══════════════════════════════════════════════════════════════╝ + +✨ 今日学んだことを記録しよう + +🎁 報酬: +200 XP +💎 完了すると特別なバッジが獲得できます! + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + ### メモリの検索 ``` MCPツールを使って「天気」に関するメモリーを検索してください diff --git a/src/game_formatter.rs b/src/game_formatter.rs new file mode 100644 index 0000000..60ebdfc --- /dev/null +++ b/src/game_formatter.rs @@ -0,0 +1,353 @@ +use crate::memory::Memory; +use serde::{Deserialize, Serialize}; + +/// メモリーのレア度 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum MemoryRarity { + Common, // 0.0-0.4 + Uncommon, // 0.4-0.6 + Rare, // 0.6-0.8 + Epic, // 0.8-0.9 + Legendary, // 0.9-1.0 +} + +impl MemoryRarity { + pub fn from_score(score: f32) -> Self { + match score { + s if s >= 0.9 => MemoryRarity::Legendary, + s if s >= 0.8 => MemoryRarity::Epic, + s if s >= 0.6 => MemoryRarity::Rare, + s if s >= 0.4 => MemoryRarity::Uncommon, + _ => MemoryRarity::Common, + } + } + + pub fn emoji(&self) -> &str { + match self { + MemoryRarity::Common => "⚪", + MemoryRarity::Uncommon => "🟢", + MemoryRarity::Rare => "🔵", + MemoryRarity::Epic => "🟣", + MemoryRarity::Legendary => "🟡", + } + } + + pub fn name(&self) -> &str { + match self { + MemoryRarity::Common => "COMMON", + MemoryRarity::Uncommon => "UNCOMMON", + MemoryRarity::Rare => "RARE", + MemoryRarity::Epic => "EPIC", + MemoryRarity::Legendary => "LEGENDARY", + } + } + + pub fn xp_value(&self) -> u32 { + match self { + MemoryRarity::Common => 100, + MemoryRarity::Uncommon => 250, + MemoryRarity::Rare => 500, + MemoryRarity::Epic => 850, + MemoryRarity::Legendary => 1000, + } + } +} + +/// 診断タイプ +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum DiagnosisType { + Innovator, // 革新者(創造性高、実用性高) + Philosopher, // 哲学者(感情高、新規性高) + Pragmatist, // 実務家(実用性高、関連性高) + Visionary, // 夢想家(新規性高、感情高) + Analyst, // 分析家(全て平均的) +} + +impl DiagnosisType { + pub fn from_score_breakdown( + emotional: f32, + relevance: f32, + novelty: f32, + utility: f32, + ) -> Self { + if utility > 0.2 && novelty > 0.2 { + DiagnosisType::Innovator + } else if emotional > 0.2 && novelty > 0.2 { + DiagnosisType::Philosopher + } else if utility > 0.2 && relevance > 0.2 { + DiagnosisType::Pragmatist + } else if novelty > 0.2 && emotional > 0.18 { + DiagnosisType::Visionary + } else { + DiagnosisType::Analyst + } + } + + pub fn emoji(&self) -> &str { + match self { + DiagnosisType::Innovator => "💡", + DiagnosisType::Philosopher => "🧠", + DiagnosisType::Pragmatist => "🎯", + DiagnosisType::Visionary => "✨", + DiagnosisType::Analyst => "📊", + } + } + + pub fn name(&self) -> &str { + match self { + DiagnosisType::Innovator => "革新者", + DiagnosisType::Philosopher => "哲学者", + DiagnosisType::Pragmatist => "実務家", + DiagnosisType::Visionary => "夢想家", + DiagnosisType::Analyst => "分析家", + } + } + + pub fn description(&self) -> &str { + match self { + DiagnosisType::Innovator => "創造的で実用的なアイデアを生み出す。常に新しい可能性を探求し、それを現実のものにする力を持つ。", + DiagnosisType::Philosopher => "深い思考と感情を大切にする。抽象的な概念や人生の意味について考えることを好む。", + DiagnosisType::Pragmatist => "現実的で効率的。具体的な問題解決に優れ、確実に結果を出す。", + DiagnosisType::Visionary => "大胆な夢と理想を追い求める。常識にとらわれず、未来の可能性を信じる。", + DiagnosisType::Analyst => "バランスの取れた思考。多角的な視点から物事を分析し、冷静に判断する。", + } + } +} + +/// ゲーム風の結果フォーマッター +pub struct GameFormatter; + +impl GameFormatter { + /// メモリー作成結果をゲーム風に表示 + pub fn format_memory_result(memory: &Memory) -> String { + let rarity = MemoryRarity::from_score(memory.priority_score); + let xp = rarity.xp_value(); + let score_percentage = (memory.priority_score * 100.0) as u32; + + // スコア内訳を推定(各項目最大0.25として) + let emotional = (memory.priority_score * 0.25).min(0.25); + let relevance = (memory.priority_score * 0.25).min(0.25); + let novelty = (memory.priority_score * 0.25).min(0.25); + let utility = memory.priority_score - emotional - relevance - novelty; + + let diagnosis = DiagnosisType::from_score_breakdown( + emotional, + relevance, + novelty, + utility, + ); + + format!( + r#" +╔══════════════════════════════════════════════════════════════╗ +║ 🎲 メモリースコア判定 ║ +╚══════════════════════════════════════════════════════════════╝ + +⚡ 分析完了! あなたの思考が記録されました + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📊 総合スコア +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + {} {} {}点 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🎯 詳細分析 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +💓 感情的インパクト: {} +🔗 ユーザー関連性: {} +✨ 新規性・独自性: {} +⚙️ 実用性: {} +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🎊 あなたのタイプ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +{} 【{}】 + +{} +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🏆 報酬 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +💎 XP獲得: +{} XP +🎁 レア度: {} {} +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +💬 AI の解釈 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +{} +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +📤 この結果をシェアしよう! +#aigpt #メモリースコア #{} +"#, + rarity.emoji(), + rarity.name(), + score_percentage, + Self::format_bar(emotional, 0.25), + Self::format_bar(relevance, 0.25), + Self::format_bar(novelty, 0.25), + Self::format_bar(utility, 0.25), + diagnosis.emoji(), + diagnosis.name(), + diagnosis.description(), + xp, + rarity.emoji(), + rarity.name(), + memory.interpreted_content, + diagnosis.name(), + ) + } + + /// シェア用の短縮テキストを生成 + pub fn format_shareable_text(memory: &Memory) -> String { + let rarity = MemoryRarity::from_score(memory.priority_score); + let score_percentage = (memory.priority_score * 100.0) as u32; + let emotional = (memory.priority_score * 0.25).min(0.25); + let relevance = (memory.priority_score * 0.25).min(0.25); + let novelty = (memory.priority_score * 0.25).min(0.25); + let utility = memory.priority_score - emotional - relevance - novelty; + let diagnosis = DiagnosisType::from_score_breakdown( + emotional, + relevance, + novelty, + utility, + ); + + format!( + r#"🎲 AIメモリースコア診断結果 + +{} {} {}点 +{} 【{}】 + +{} + +#aigpt #メモリースコア #AI診断"#, + rarity.emoji(), + rarity.name(), + score_percentage, + diagnosis.emoji(), + diagnosis.name(), + Self::truncate(&memory.content, 100), + ) + } + + /// ランキング表示 + pub fn format_ranking(memories: &[&Memory], title: &str) -> String { + let mut result = format!( + r#" +╔══════════════════════════════════════════════════════════════╗ +║ 🏆 {} ║ +╚══════════════════════════════════════════════════════════════╝ + +"#, + title + ); + + for (i, memory) in memories.iter().take(10).enumerate() { + let rank_emoji = match i { + 0 => "🥇", + 1 => "🥈", + 2 => "🥉", + _ => " ", + }; + + let rarity = MemoryRarity::from_score(memory.priority_score); + let score = (memory.priority_score * 100.0) as u32; + + result.push_str(&format!( + "{} {}位 {} {} {}点 - {}\n", + rank_emoji, + i + 1, + rarity.emoji(), + rarity.name(), + score, + Self::truncate(&memory.content, 40) + )); + } + + result.push_str("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"); + + result + } + + /// デイリーチャレンジ表示 + pub fn format_daily_challenge() -> String { + // 今日の日付をシードにランダムなお題を生成 + let challenges = vec![ + "今日学んだことを記録しよう", + "新しいアイデアを思いついた?", + "感動したことを書き留めよう", + "目標を一つ設定しよう", + "誰かに感謝の気持ちを伝えよう", + ]; + + let today = chrono::Utc::now().ordinal(); + let challenge = challenges[today as usize % challenges.len()]; + + format!( + r#" +╔══════════════════════════════════════════════════════════════╗ +║ 📅 今日のチャレンジ ║ +╚══════════════════════════════════════════════════════════════╝ + +✨ {} + +🎁 報酬: +200 XP +💎 完了すると特別なバッジが獲得できます! + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +"#, + challenge + ) + } + + /// プログレスバーを生成 + fn format_bar(value: f32, max: f32) -> String { + let percentage = (value / max * 100.0) as u32; + let filled = (percentage / 10) as usize; + let empty = 10 - filled; + + format!( + "[{}{}] {}%", + "█".repeat(filled), + "░".repeat(empty), + percentage + ) + } + + /// テキストを切り詰め + fn truncate(s: &str, max_len: usize) -> String { + if s.len() <= max_len { + s.to_string() + } else { + format!("{}...", &s[..max_len]) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::Utc; + + #[test] + fn test_rarity_from_score() { + assert!(matches!(MemoryRarity::from_score(0.95), MemoryRarity::Legendary)); + assert!(matches!(MemoryRarity::from_score(0.85), MemoryRarity::Epic)); + assert!(matches!(MemoryRarity::from_score(0.7), MemoryRarity::Rare)); + assert!(matches!(MemoryRarity::from_score(0.5), MemoryRarity::Uncommon)); + assert!(matches!(MemoryRarity::from_score(0.3), MemoryRarity::Common)); + } + + #[test] + fn test_diagnosis_type() { + let diagnosis = DiagnosisType::from_score_breakdown(0.1, 0.1, 0.22, 0.22); + assert!(matches!(diagnosis, DiagnosisType::Innovator)); + } + + #[test] + fn test_format_bar() { + let bar = GameFormatter::format_bar(0.15, 0.25); + assert!(bar.contains("60%")); + } +} diff --git a/src/lib.rs b/src/lib.rs index d3b4c38..6eaa46f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ pub mod memory; pub mod mcp; -pub mod ai_interpreter; \ No newline at end of file +pub mod ai_interpreter; +pub mod game_formatter; \ No newline at end of file diff --git a/src/mcp/base.rs b/src/mcp/base.rs index ff012eb..14621a2 100644 --- a/src/mcp/base.rs +++ b/src/mcp/base.rs @@ -3,6 +3,7 @@ use serde_json::{json, Value}; use std::io::{self, BufRead, Write}; use crate::memory::MemoryManager; +use crate::game_formatter::GameFormatter; pub struct BaseMCPServer { pub memory_manager: MemoryManager, @@ -159,7 +160,7 @@ impl BaseMCPServer { }), json!({ "name": "create_memory_with_ai", - "description": "Create a new memory with AI interpretation and priority scoring", + "description": "Create a new memory with AI interpretation and priority scoring (with game-style result!)", "inputSchema": { "type": "object", "properties": { @@ -170,6 +171,10 @@ impl BaseMCPServer { "user_context": { "type": "string", "description": "User-specific context (optional)" + }, + "game_mode": { + "type": "boolean", + "description": "Show game-style result (default: true)" } }, "required": ["content"] @@ -177,7 +182,7 @@ impl BaseMCPServer { }), json!({ "name": "list_memories_by_priority", - "description": "List memories sorted by priority score (high to low)", + "description": "List memories sorted by priority score (high to low) - Shows as ranking!", "inputSchema": { "type": "object", "properties": { @@ -190,9 +195,21 @@ impl BaseMCPServer { "limit": { "type": "integer", "description": "Maximum number of memories to return" + }, + "game_mode": { + "type": "boolean", + "description": "Show as game-style ranking (default: true)" } } } + }), + json!({ + "name": "daily_challenge", + "description": "Get today's daily challenge - Create a memory to earn bonus XP!", + "inputSchema": { + "type": "object", + "properties": {} + } }) ] } @@ -222,6 +239,7 @@ impl BaseMCPServer { "create_memory" => self.tool_create_memory(arguments), "create_memory_with_ai" => self.tool_create_memory_with_ai(arguments).await, "list_memories_by_priority" => self.tool_list_memories_by_priority(arguments), + "daily_challenge" => self.tool_daily_challenge(), "search_memories" => self.tool_search_memories(arguments), "update_memory" => self.tool_update_memory(arguments), "delete_memory" => self.tool_delete_memory(arguments), @@ -312,11 +330,22 @@ impl BaseMCPServer { async fn tool_create_memory_with_ai(&mut self, arguments: &Value) -> Value { let content = arguments["content"].as_str().unwrap_or(""); let user_context = arguments["user_context"].as_str(); + let game_mode = arguments["game_mode"].as_bool().unwrap_or(true); match self.memory_manager.create_memory_with_ai(content, user_context).await { Ok(id) => { // 作成したメモリを取得して詳細情報を返す if let Some(memory) = self.memory_manager.get_memory(&id) { + let result = if game_mode { + // ゲーム風表示 + GameFormatter::format_memory_result(memory) + } else { + // 通常表示 + format!("Memory created with AI interpretation\nScore: {}", memory.priority_score) + }; + + let shareable = GameFormatter::format_shareable_text(memory); + json!({ "success": true, "id": id, @@ -327,6 +356,8 @@ impl BaseMCPServer { "user_context": memory.user_context, "created_at": memory.created_at }, + "game_result": result, + "shareable_text": shareable, "message": "Memory created with AI interpretation and priority scoring" }) } else { @@ -348,6 +379,7 @@ impl BaseMCPServer { fn tool_list_memories_by_priority(&self, arguments: &Value) -> Value { let min_score = arguments["min_score"].as_f64().unwrap_or(0.0) as f32; let limit = arguments["limit"].as_u64().map(|l| l as usize); + let game_mode = arguments["game_mode"].as_bool().unwrap_or(true); let mut memories = self.memory_manager.get_memories_by_priority(); @@ -359,9 +391,16 @@ impl BaseMCPServer { memories.truncate(limit); } + let ranking_display = if game_mode { + GameFormatter::format_ranking(&memories, "🏆 メモリーランキング TOP 10") + } else { + String::new() + }; + json!({ "success": true, "count": memories.len(), + "ranking_display": ranking_display, "memories": memories.into_iter().map(|m| json!({ "id": m.id, "content": m.content, @@ -374,6 +413,17 @@ impl BaseMCPServer { }) } + // デイリーチャレンジ + fn tool_daily_challenge(&self) -> Value { + let challenge_display = GameFormatter::format_daily_challenge(); + + json!({ + "success": true, + "challenge_display": challenge_display, + "message": "Complete today's challenge to earn bonus XP!" + }) + } + // 不明なメソッドハンドラ fn handle_unknown_method(&self, id: Value) -> Value { json!({