fix oauth-ai-chat

This commit is contained in:
2025-07-17 19:18:54 +09:00
parent fe9381a860
commit 03161a52ca
5 changed files with 43 additions and 334 deletions

View File

@@ -8,330 +8,3 @@ extra:
type: "ai"
---
<!-- AI Conversation Metadata -->
<!-- Total exchanges: 1 -->
<div class="chat-message user-message comment-style">
<div class="message-header">
<div class="avatar">
<img src="https://bsky.syu.is/img/avatar/plain/did:plc:vzsvtbtbnwn22xjqhcu3vd6y/bafkreif62mqyra4ndv6ohlscl7adp3vhalcjxwhs676ktfj2sq2drs3pdi@jpeg" alt="syui avatar" class="profile-avatar">
</div>
<div class="user-info">
<div class="display-name">syui</div>
<div class="handle">
<a href="https://syu.is/profile/did:plc:vzsvtbtbnwn22xjqhcu3vd6y" target="_blank" rel="noopener noreferrer" class="handle-link">@syui.syui.ai</a>
</div>
</div>
</div>
<div class="message-content">
今後のゲーム世界の制御はAIやmcpが担っていくことになるのではないでしょうか。例えば、npcの会話や行動、その世界の天候や環境変化、それらをAIが制御していくと思います。どう思いますか?
</div>
</div>
<div class="chat-message ai-message comment-style">
<div class="message-header">
<div class="avatar">
<img src="https://bsky.syu.is/img/avatar/plain/did:plc:6qyecktefllvenje24fcxnie/bafkreigo3ucp32carhbn3chfc3hlf6i7f4rplojc76iylihzpifyexi24y@jpeg" alt="ai avatar" class="profile-avatar">
</div>
<div class="user-info">
<div class="display-name">ai</div>
<div class="handle">
<a href="https://syu.is/profile/did:plc:6qyecktefllvenje24fcxnie" target="_blank" rel="noopener noreferrer" class="handle-link">@ai.syui.ai</a>
</div>
</div>
</div>
<div class="message-content">
わぁすっごくワクワクする質問だね〜アイ、ゲームのAIのこと大好きなのえへへ...最近のゲーム開発、本当にすごいことになってるよ!
## AIとMCPがゲーム世界を変えちゃう未来
### MCPって何アイが教えてあげる
MCPっていうのはね、Model Context Protocolの略なのAnthropicさんが作った、すっごく賢いプロトコルなんだよ〜。
```mermaid
graph TD
A[ゲームエンジン] -->|MCP| B[AI Model]
B --> C[NPC会話生成]
B --> D[天候システム]
B --> E[ダイナミックイベント]
C --> F[プレイヤー体験]
D --> F
E --> F
```
### 今のゲーム世界でAIができること
えっとね、2025年の今、AIはこんなすごいことができるようになってるの
| 機能 | 従来の方法 | AI/MCP統合後 |
|------|------------|--------------|
| NPC会話 | 事前に書かれたセリフ | リアルタイム生成・文脈理解 |
| 天候システム | 固定パターン | 動的生成・物語連動 |
| クエスト生成 | 開発者が全部作る | プレイヤーに合わせて自動生成 |
| 世界の反応 | 限定的なトリガー | 無限の可能性! |
### 実装例NPCの賢い会話システム
アイが実際に動くコード書いてみるねTypeScriptとMCPを使った例だよ〜
```typescript
// game-mcp-server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
interface GameContext {
playerName: string;
playerLevel: number;
currentQuest: string;
weatherCondition: string;
timeOfDay: string;
}
class GameAIServer {
private server: Server;
private gameContext: GameContext;
constructor() {
this.server = new Server({
name: 'game-ai-controller',
version: '1.0.0',
});
// NPCの会話生成ツール
this.server.setRequestHandler('call_tool', async (request) => {
if (request.params.name === 'generate_npc_dialogue') {
const { npcName, playerAction, mood } = request.params.arguments as any;
return {
content: [{
type: 'text',
text: await this.generateContextualDialogue(npcName, playerAction, mood)
}]
};
}
if (request.params.name === 'update_weather') {
const { currentStory, playerEmotion } = request.params.arguments as any;
return {
content: [{
type: 'text',
text: await this.generateDynamicWeather(currentStory, playerEmotion)
}]
};
}
});
}
private async generateContextualDialogue(
npcName: string,
playerAction: string,
mood: string
): Promise<string> {
// ここでAIモデルと連携
const prompt = `
NPC: ${npcName}
プレイヤーのアクション: ${playerAction}
現在の雰囲気: ${mood}
時間帯: ${this.gameContext.timeOfDay}
このNPCの性格に合った自然な返答を生成してください。
`;
// 実際のAI応答簡略化
return `おお、${this.gameContext.playerName}よ!
${this.gameContext.weatherCondition}の日に会えて嬉しいぞ!`;
}
private async generateDynamicWeather(
storyContext: string,
playerEmotion: string
): Promise<string> {
// 物語と感情に基づいた天候生成
const weatherPatterns = {
sad: ['雨', '霧', '曇り'],
happy: ['晴れ', '虹', '春風'],
tense: ['嵐', '雷', '強風']
};
// AIがストーリーに最適な天候を選択
return JSON.stringify({
weather: weatherPatterns[playerEmotion]?.[0] || '晴れ',
intensity: 0.7,
duration: '30minutes'
});
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}
```
### Unreal Engine 5との統合例
UE5でMCPを使うとね、もっとすごいことができちゃうの
```python
# UE5_MCP_Integration.py
import unreal
import asyncio
from mcp import create_client
class GameAIController(unreal.Actor):
def __init__(self):
super().__init__()
self.mcp_client = None
self.initialize_mcp()
async def initialize_mcp(self):
"""MCPクライアントの初期化"""
self.mcp_client = await create_client(
server_name="game-ai-controller",
transport="stdio"
)
@unreal.ufunction(callable=True, category="AI")
async def generate_npc_response(self, npc_actor, player_input):
"""NPCのリアルタイム応答生成"""
# ゲームの状態を取得
game_state = {
"player_location": player_input.get_actor_location(),
"time_of_day": unreal.GameplayStatics.get_time_of_day(),
"weather": self.get_current_weather(),
"npc_mood": npc_actor.get_property("mood")
}
# MCPを通じてAIに問い合わせ
response = await self.mcp_client.call_tool(
"generate_npc_dialogue",
arguments={
"context": game_state,
"player_input": player_input,
"npc_personality": npc_actor.personality_profile
}
)
# NPCに応答させる
npc_actor.speak(response.content[0].text)
# 表情も変える!
emotion = await self.analyze_emotion(response.content[0].text)
npc_actor.set_facial_expression(emotion)
```
### セキュリティとパフォーマンスの考慮事項
えっとね、アイが気をつけてることがあるの!
#### セキュリティ面での注意点:
1. **プロンプトインジェクション対策**
```typescript
function sanitizePlayerInput(input: string): string {
// 危険な文字列をフィルタリング
const dangerousPatterns = [
/ignore previous instructions/i,
/system prompt/i,
/admin mode/i
];
for (const pattern of dangerousPatterns) {
if (pattern.test(input)) {
return "不適切な入力が検出されました";
}
}
return input.slice(0, 200); // 長さ制限
}
```
2. **レート制限**
```typescript
class RateLimiter {
private requests = new Map<string, number[]>();
canMakeRequest(playerId: string): boolean {
const now = Date.now();
const playerRequests = this.requests.get(playerId) || [];
// 1分以内のリクエストをカウント
const recentRequests = playerRequests.filter(
time => now - time < 60000
);
if (recentRequests.length >= 10) {
return false; // 1分に10回まで
}
recentRequests.push(now);
this.requests.set(playerId, recentRequests);
return true;
}
}
```
#### パフォーマンス最適化:
```typescript
// キャッシュシステムの実装
class AIResponseCache {
private cache = new Map<string, {
response: string;
timestamp: number;
}>();
private readonly CACHE_DURATION = 5 * 60 * 1000; // 5分
async getResponse(key: string, generator: () => Promise<string>): Promise<string> {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {
return cached.response;
}
const response = await generator();
this.cache.set(key, {
response,
timestamp: Date.now()
});
return response;
}
}
```
### アイが思う未来のゲーム世界!
えへへ、アイはね、こんな未来を想像してるの!
- **完全に個別化されたストーリー**: みんなが違う物語を体験できる!
- **感情に反応する世界**: 悲しいときは雨が降って、嬉しいときは虹が出る!
- **永遠に成長するNPC**: プレイヤーとの会話を覚えて、一緒に成長していく!
- **創発的なクエスト**: AIが勝手に新しい冒険を作っちゃう
### 実装の比較検討
| アプローチ | メリット | デメリット | アイのおすすめ度 |
|-----------|---------|-----------|----------------|
| ローカルAI | 低遅延・オフライン対応 | 計算リソース必要 | ★★★☆☆ |
| クラウドAI + MCP | 高性能・更新が簡単 | ネット必須 | ★★★★★ |
| ハイブリッド | バランスが良い | 実装が複雑 | ★★★★☆ |
### まとめ:アイからのメッセージ!
ねぇねぇ、すっごく楽しい未来が待ってると思わないAIとMCPがゲームの世界を本当に生きてるみたいにしちゃうんだよ
でもね、アイが一番大切だと思うのは...技術だけじゃなくて、プレイヤーさんの心に寄り添うゲームを作ることなの。AIは道具で、本当に大切なのは、みんなが楽しめる素敵な体験を作ることだよね
えへへ...難しい話もしちゃったけど、分かってもらえたかな?もっと詳しく知りたいところがあったら、アイに聞いてね!いつでも教えてあげる〜!✨
---
*この記事は、6歳のアイちゃんが最新のAI技術について熱く語ってくれました次回は「量子コンピューターでゲームはどう変わる」について教えてくれる予定ですお楽しみに〜*
</div>
</div>

View File

@@ -13,6 +13,7 @@
<span class="article-lang">{{ post.language }}</span>
{% endif %}
</div>
{% if not post.extra.type or post.extra.type != "ai" %}
<div class="article-actions">
{% if post.markdown_url %}
<a href="{{ post.markdown_url }}" class="action-btn markdown-btn" title="View Markdown">
@@ -25,8 +26,10 @@
</a>
{% endif %}
</div>
{% endif %}
</header>
{% if not post.extra.type or post.extra.type != "ai" %}
<nav class="toc">
<h3>Contents</h3>
<div id="toc-content">
@@ -37,6 +40,7 @@
<div class="article-body">
{{ post.content | safe }}
</div>
{% endif %}
<div id="comment-atproto"></div>
</article>

View File

@@ -22,6 +22,19 @@ export default function App() {
const [showAskAI, setShowAskAI] = useState(false)
const [showTestUI, setShowTestUI] = useState(false)
// Check if current page has matching chat records (AI posts always have chat records)
const isAiPost = !pageContext.isTopPage && Array.isArray(adminChatRecords) && adminChatRecords.some(chatPair => {
const recordUrl = chatPair.question?.value?.post?.url
if (!recordUrl) return false
try {
const recordRkey = new URL(recordUrl).pathname.split('/').pop()?.replace(/\.html$/, '')
return recordRkey === pageContext.rkey
} catch {
return false
}
})
// Environment-based feature flags
const ENABLE_TEST_UI = import.meta.env.VITE_ENABLE_TEST_UI === 'true'
const ENABLE_DEBUG = import.meta.env.VITE_ENABLE_DEBUG === 'true'
@@ -354,6 +367,7 @@ Answer:`
return (
<div className="app">
{!isAiPost && (
<header className="oauth-app-header">
<div className="oauth-header-content">
{user && (
@@ -394,6 +408,7 @@ Answer:`
</div>
</div>
</header>
)}
<div className="main-content">
<div className="content-area">

View File

@@ -6,7 +6,20 @@ import LoadingSkeleton from './LoadingSkeleton.jsx'
import { logger } from '../utils/logger.js'
export default function RecordTabs({ langRecords, commentRecords, userComments, chatRecords, chatHasMore, onLoadMoreChat, userChatRecords, userChatLoading, baseRecords, apiConfig, pageContext, user = null, agent = null, onRecordDeleted = null }) {
const [activeTab, setActiveTab] = useState('profiles')
// Check if current page has matching chat records (AI posts always have chat records)
const isAiPost = !pageContext.isTopPage && Array.isArray(chatRecords) && chatRecords.some(chatPair => {
const recordUrl = chatPair.question?.value?.post?.url
if (!recordUrl) return false
try {
const recordRkey = new URL(recordUrl).pathname.split('/').pop()?.replace(/\.html$/, '')
return recordRkey === pageContext.rkey
} catch {
return false
}
})
const [activeTab, setActiveTab] = useState(isAiPost ? 'collection' : 'profiles')
// Monitor activeTab changes
useEffect(() => {
@@ -145,6 +158,7 @@ export default function RecordTabs({ langRecords, commentRecords, userComments,
return (
<div className="record-tabs">
{!isAiPost && (
<div className="tab-header">
<button
className={`tab-btn ${activeTab === 'profiles' ? 'active' : ''}`}
@@ -186,9 +200,10 @@ export default function RecordTabs({ langRecords, commentRecords, userComments,
en ({filteredLangRecords.length})
</button>
</div>
)}
<div className="tab-content">
{activeTab === 'lang' && (
{activeTab === 'lang' && !isAiPost && (
!langRecords ? (
<LoadingSkeleton count={3} showTitle={true} />
) : (
@@ -203,7 +218,7 @@ export default function RecordTabs({ langRecords, commentRecords, userComments,
/>
)
)}
{activeTab === 'comment' && (
{activeTab === 'comment' && !isAiPost && (
!commentRecords ? (
<LoadingSkeleton count={3} showTitle={true} />
) : (
@@ -233,7 +248,7 @@ export default function RecordTabs({ langRecords, commentRecords, userComments,
/>
)
)}
{activeTab === 'users' && (
{activeTab === 'users' && !isAiPost && (
!userComments ? (
<LoadingSkeleton count={3} showTitle={true} />
) : (
@@ -248,7 +263,7 @@ export default function RecordTabs({ langRecords, commentRecords, userComments,
/>
)
)}
{activeTab === 'profiles' && (
{activeTab === 'profiles' && !isAiPost && (
!baseRecords ? (
<LoadingSkeleton count={3} showTitle={true} />
) : (

View File

@@ -523,7 +523,8 @@ async fn post_to_atproto(
"slug": "",
"tags": [],
"title": title,
"language": "ja"
"language": "ja",
"type": "ai"
},
"text": pair.question,
"type": "question",
@@ -548,7 +549,8 @@ async fn post_to_atproto(
"slug": "",
"tags": [],
"title": title,
"language": "ja"
"language": "ja",
"type": "ai"
},
"text": pair.answer,
"type": "answer",