use-x-chat について
use-x-chat は、@ant-design/x-sdk の useXChat Hook で React の AI 会話アプリを構築する開発者向けガイドです。メッセージとリクエストの管理、プレースホルダーや失敗時の処理、変換、中断、複数会話に対応し、事前に Chat Provider が必要です。
主な機能
- 01メッセージとリクエスト状態の管理
- 02プレースホルダーと失敗時の代替表示の設定
- 03UI コンポーネント向けのメッセージ変換
- 04リクエストの中断と応答の再生成
- 05useXConversations による会話管理
活用シーン
- 01React のチャット画面へのメッセージ状態の接続
- 02失敗または中断時の代替メッセージ表示
- 03会話切り替えと初回メッセージのキュー送信
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ant-design/ x --skill use-x-chatインストール後、Claude Code で/use-x-chatを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
reference/API.mdCORE.mdEXAMPLES.md詳細
🎯 スキルの位置づけ
主な位置づけ:
useXChatHook を使って、プロフェッショナルな AI 会話アプリケーションを構築します。前提条件:カスタム Chat Provider をすでに用意していること(x-chat-provider skill を参照)
目次
🚀 クイックスタート
1. 依存関係の管理
- @ant-design/x-sdk:2.2.2+
- @ant-design/x:最新バージョン(UI コンポーネント)
npm install @ant-design/x-sdk@latest @ant-design/x@latest
2. 3 ステップでの統合
ステップ 1:Provider を準備する
x-chat-provider スキルで対応します。XRequest に manual: true を渡す必要がある点に注意してください。
import { MyChatProvider } from './MyChatProvider';
import { XRequest } from '@ant-design/x-sdk';
// ⚠️ manual: true is required
const provider = new MyChatProvider({
request: XRequest('https://your-api.com/chat', { manual: true }),
});
ステップ 2:基本的な使い方
import { useXChat } from '@ant-design/x-sdk';
const ChatComponent = () => {
const { messages, onRequest, isRequesting } = useXChat({
provider,
requestPlaceholder: (_, { messages }) => ({
content: 'Thinking...',
role: 'assistant',
}),
requestFallback: (_, { error, messageInfo }) => {
if (error.name === 'AbortError') {
return { content: messageInfo?.message?.content || 'Reply cancelled', role: 'assistant' };
}
return { content: 'Network error, please try again later', role: 'assistant' };
},
});
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
{msg.message.role}: {msg.message.content}
</div>
))}
<button onClick={() => onRequest({ query: 'Hello' })}>Send</button>
</div>
);
};
ステップ 3:UI との統合
⚠️
messagesはMessageInfo<ChatMessage>[]であり、そのままBubble.Listに渡すことはできません。{ key, role, content, loading }形式にマッピングする必要があります。Bubble.Listではroleprop を使用して(rolesではありません)、ロールのスタイルを設定します。
import { Bubble, Sender } from '@ant-design/x';
const ChatUI = () => {
const { messages, onRequest, isRequesting, abort } = useXChat({ provider });
return (
<div style={{ height: 600 }}>
<Bubble.List
// ✅ Correct: use role (not roles)
role={{
user: { placement: 'end' },
assistant: { placement: 'start' },
}}
items={messages.map(({ id, message, status }) => ({
key: id,
role: message.role, // matches role config key
content: message.content, // message content
loading: status === 'loading', // loading animation
}))}
/>
<Sender
loading={isRequesting}
onSubmit={(content) => onRequest({ query: content })}
onCancel={abort}
/>
</div>
);
};
ChatMessage がオブジェクト型の場合(文字列ではない場合)
ChatMessage が複雑なオブジェクト(content、attachments フィールドなど)の場合は、contentRender を使用します。
<Bubble.List
role={{
assistant: {
placement: 'start',
// contentRender receives content param, which is the message field itself
contentRender(content: MyMessage) {
return (
<div>
<div>{content.content}</div>
{content.attachments?.map((a) => (
<FileCard key={a.url} name={a.name} />
))}
</div>
);
},
},
user: {
placement: 'end',
contentRender(content: MyMessage) {
return content.content;
},
},
}}
items={messages.map(({ id, message, status }) => ({
key: id,
role: message.role,
content: message, // ⚠️ Pass the entire message object; contentRender handles rendering
loading: status === 'loading',
}))}
/>
🧩 コアコンセプト
データモデル
⚠️ 重要:
messagesの型はMessageInfo<ChatMessage>[]です。メッセージの内容はmsg.messageに格納されています。
interface MessageInfo<ChatMessage> {
id: number | string; // Message unique identifier
message: ChatMessage; // Actual message content (your ChatMessage type)
status: MessageStatus; // Message status
extraInfo?: AnyObject; // Extended info (note: extraInfo, not extra)
}
type MessageStatus = 'local' | 'loading' | 'updating' | 'success' | 'error' | 'abort';
// local: locally sent user message
// loading: AI reply placeholder (corresponds to requestPlaceholder)
// updating: AI streaming output in progress
// success: AI reply complete
// error: request failed
// abort: user actively cancelled
useXChat の設定オプション
| オプション | 型 | 説明 |
|---|---|---|
provider | AbstractChatProvider<ChatMessage, Input, Output> | 必須。Provider インスタンス |
conversationKey | string | 会話の一意な識別子。複数の会話を扱う場合に必須 |
defaultMessages | DefaultMessageInfo[] | () => ... | async () => ... | デフォルトで表示するメッセージ。非同期読み込みに対応 |
requestPlaceholder | ChatMessage | (requestParams, { messages }) => ChatMessage | リクエスト中に表示するプレースホルダーメッセージ |
requestFallback | ChatMessage | (requestParams, { error, errorInfo, messages, messageInfo }) => ChatMessage | Promise<ChatMessage> | リクエスト失敗時または中断時のフォールバックメッセージ |
parser | (message: ChatMessage) => BubbleMessage | BubbleMessage[] | ChatMessage をコンポーネントで使用できる形式に変換します。1 対多の変換に対応 |
requestFallbackのmessageInfoの型はMessageInfo<ChatMessage>で、リクエスト失敗時に更新されるメッセージです。requestFallbackはネットワークエラー(error)とユーザーによる中断(error.name === 'AbortError')の両方に対応します。
useXChat の戻り値
| 戻り値 | 型 | 説明 |
|---|---|---|
messages | MessageInfo<ChatMessage>[] | メッセージリストです。Bubble.List に渡す前にマッピングする必要があります |
parsedMessages | MessageInfo<ParsedMessage>[] | parser による変換後のメッセージリストです(parser が設定されている場合に使用します) |
onRequest | (params: Partial<Input>, opts?: { extraInfo: AnyObject }) => void | メッセージを追加してリクエストを実行します |
isRequesting | boolean | リクエストが進行中かどうかを示します |
abort | () => void | 現在のリクエストを中止します |
setMessages | (messages: Partial<MessageInfo<ChatMessage>>[]) => void | メッセージリストを直接変更します。リクエストは実行されません |
setMessage | (id: string | number, info: Partial<MessageInfo<ChatMessage>>) => void | 単一のメッセージを変更します。リクエストは実行されません |
removeMessage | (id: string | number) => boolean | メッセージを削除し、削除に成功したかどうかを返します |
onReload | (id: string | number, params: Partial<Input>, opts?: { extraInfo: AnyObject }) => void | AI の返信を再生成します |
queueRequest | (conversationKey: string | symbol, params: Partial<Input>, opts?: { extraInfo: AnyObject }) => void | リクエストをキューに追加し、会話の初期化後に送信します |
isDefaultMessagesRequesting | boolean | デフォルトメッセージを非同期で読み込み中かどうかを示します |
🔧 コア関数の詳細
コア機能のリファレンス:CORE.md
🗂️ 複数会話の管理
useXConversations Hook
useXConversations は、@ant-design/x-sdk が提供する会話リスト管理用の Hook です。複数会話を管理するために useXChat と組み合わせて使用します。
import { useXConversations } from '@ant-design/x-sdk';
import type { ConversationData } from '@ant-design/x-sdk';
const {
conversations, // ConversationData[]: conversation list
activeConversationKey, // string: currently active conversation key
setActiveConversationKey, // (key: string) => void: switch conversation
addConversation, // (ConversationData, placement?) => boolean
removeConversation, // (key: string) => boolean
setConversation, // (key: string, ConversationData) => boolean
getConversation, // (key: string) => ConversationData | undefined
setConversations, // (list: ConversationData[]) => void
getMessages, // (key: string) => MessageInfo[] | undefined (read messages across components)
} = useXConversations({
defaultConversations: [
{ key: 'conv-1', label: 'Conversation 1' },
{ key: 'conv-2', label: 'Conversation 2' },
],
defaultActiveConversationKey: 'conv-1',
});
複数会話の全体的なパターン
import { useXChat, useXConversations } from '@ant-design/x-sdk';
import { OpenAIChatProvider, XRequest } from '@ant-design/x-sdk';
import { Bubble, Conversations, Sender } from '@ant-design/x';
import React, { useEffect, useRef } from 'react';
// ⚠️ Each conversation must have its own Provider instance, otherwise state mixes
const providerCache = new Map<string, OpenAIChatProvider>();
function getProvider(key: string): OpenAIChatProvider {
if (!providerCache.has(key)) {
providerCache.set(
key,
new OpenAIChatProvider({
request: XRequest(BASE_URL, { manual: true, params: { model: 'gpt-4o', stream: true } }),
}),
);
}
return providerCache.get(key)!;
}
const App = () => {
const senderRef = useRef<any>(null);
const { conversations, activeConversationKey, setActiveConversationKey, addConversation } =
useXConversations({
defaultConversations: [{ key: 'conv-1', label: 'New Conversation' }],
defaultActiveConversationKey: 'conv-1',
});
const { messages, onRequest, isRequesting, abort, queueRequest } = useXChat({
provider: getProvider(activeConversationKey),
conversationKey: activeConversationKey,
// Async load default messages
defaultMessages: async ({ conversationKey }) => {
// Load history from server based on conversationKey
return [];
},
requestFallback: (_, { error, messageInfo }) => {
if (error.name === 'AbortError') {
return { content: messageInfo?.message?.content || 'Cancelled', role: 'assistant' };
}
return { content: 'Request failed', role: 'assistant' };
},
});
// Clear input on conversation switch
useEffect(() => {
senderRef.current?.clear?.();
}, [activeConversationKey]);
const handleNewConversation = () => {
const newKey = `conv-${Date.now()}`;
addConversation({ key: newKey, label: `New Conversation ${conversations.length + 1}` });
setActiveConversationKey(newKey);
};
return (
<div style={{ display: 'flex', height: '100vh' }}>
<Conversations
items={conversations}
activeKey={activeConversationKey}
onActiveChange={setActiveConversationKey}
creation={{ onClick: handleNewConversation }}
/>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
<Bubble.List
role={{ assistant: { placement: 'start' }, user: { placement: 'end' } }}
items={messages.map(({ id, message, status }) => ({
key: id,
role: message.role,
content: message.content,
loading: status === 'loading',
}))}
/>
<Sender
ref={senderRef}
loading={isRequesting}
onCancel={abort}
onSubmit={(val) => {
onRequest({ messages: [{ role: 'user', content: val }] });
}}
/>
</div>
</div>
);
};
queueRequest:会話の切り替え後に遅延送信
// Scenario: user switches to a new conversation and triggers an initial message simultaneously
// queueRequest waits for defaultMessages async loading to complete, then sends the request
const handleNewConversationWithFirstMessage = () => {
const newKey = `conv-${Date.now()}`;
addConversation({ key: newKey, label: 'New Conversation' });
setActiveConversationKey(newKey);
// Queue the message; sent automatically after newKey conversation's defaultMessages finish loading
queueRequest(newKey, {
messages: [{ role: 'user', content: 'Hello! Please introduce yourself.' }],
});
};
📋 前提条件と依存関係
| 使用シナリオ | 必要なスキル/プロバイダー | 順序 |
|---|---|---|
| プライベート API の適応 | x-chat-provider → use-x-chat | 先に Provider を作成 |
| 標準 API | 組み込み Provider + use-x-chat | そのまま使用 |
| 複数会話 | Provider ファクトリー + useXConversations + useXChat | 組み合わせて使用 |
🚨 開発ルール
use-x-chat を使用する前に、次の点を確認してください。
- Provider があること(カスタムまたは組み込みの Provider)
- Provider の XRequest で
manual: trueが設定されていること -
MessageInfoのデータ構造を理解していること(メッセージの内容はmsg.messageにあります) -
Bubble.Listではroleプロパティを使用すること(rolesではありません) - 複数会話のシナリオでは、各会話に専用の Provider インスタンスがあること
テストケースのルール
- ユーザーがテストケースを明示的に必要としていない場合、テストファイルを追加しないでください
コード品質ルール
- 完了後は必ず型をチェックしてください:
tsc --noEmitを実行し、型エラーがないことを確認します - コードをクリーンに保ってください:未使用の変数とインポートをすべて削除します
🔗 参考リソース
📚 コアリファレンスドキュメント
- API.md - API リファレンスの全内容
- CORE.md - コア関数の詳細
- EXAMPLES.md - 実践的なコード例
🌐 SDK 公式ドキュメント
💻 サンプルコード
- with-x-chat.tsx - 複数の会話を扱う完全なサンプル
- openai-callback.tsx - コールバックと removeMessage のサンプル
- developer.tsx - developer ロールを使用したシステムプロンプト
- custom-provider-width-ui.tsx - カスタムプロバイダーの完全なサンプル