mcpField
U
use-x-chat

use-x-chat

github.com/ant-design開発4.8k95 回インストール2 日前に更新
リポジトリ

use-x-chat について

use-x-chat は、@ant-design/x-sdk の useXChat Hook で React の AI 会話アプリを構築する開発者向けガイドです。メッセージとリクエストの管理、プレースホルダーや失敗時の処理、変換、中断、複数会話に対応し、事前に Chat Provider が必要です。

主な機能

  1. 01メッセージとリクエスト状態の管理
  2. 02プレースホルダーと失敗時の代替表示の設定
  3. 03UI コンポーネント向けのメッセージ変換
  4. 04リクエストの中断と応答の再生成
  5. 05useXConversations による会話管理

活用シーン

  1. 01React のチャット画面へのメッセージ状態の接続
  2. 02失敗または中断時の代替メッセージ表示
  3. 03会話切り替えと初回メッセージのキュー送信

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ant-design/x --skill use-x-chat

インストール後、Claude Code で/use-x-chatを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

reference/
API.md
CORE.md
EXAMPLES.md

詳細

🎯 スキルの位置づけ

主な位置づけ:useXChat Hook を使って、プロフェッショナルな 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 では role prop を使用して(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 の設定オプション

オプション型説明
providerAbstractChatProvider<ChatMessage, Input, Output>必須。Provider インスタンス
conversationKeystring会話の一意な識別子。複数の会話を扱う場合に必須
defaultMessagesDefaultMessageInfo[] | () => ... | async () => ...デフォルトで表示するメッセージ。非同期読み込みに対応
requestPlaceholderChatMessage | (requestParams, { messages }) => ChatMessageリクエスト中に表示するプレースホルダーメッセージ
requestFallbackChatMessage | (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 の戻り値

戻り値型説明
messagesMessageInfo<ChatMessage>[]メッセージリストです。Bubble.List に渡す前にマッピングする必要があります
parsedMessagesMessageInfo<ParsedMessage>[]parser による変換後のメッセージリストです(parser が設定されている場合に使用します)
onRequest(params: Partial<Input>, opts?: { extraInfo: AnyObject }) => voidメッセージを追加してリクエストを実行します
isRequestingbooleanリクエストが進行中かどうかを示します
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 }) => voidAI の返信を再生成します
queueRequest(conversationKey: string | symbol, params: Partial<Input>, opts?: { extraInfo: AnyObject }) => voidリクエストをキューに追加し、会話の初期化後に送信します
isDefaultMessagesRequestingbooleanデフォルトメッセージを非同期で読み込み中かどうかを示します

🔧 コア関数の詳細

コア機能のリファレンス: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 を実行し、型エラーがないことを確認します
  • コードをクリーンに保ってください:未使用の変数とインポートをすべて削除します

🔗 参考リソース

📚 コアリファレンスドキュメント

🌐 SDK 公式ドキュメント

💻 サンプルコード

コメント