mcpField
U
use-x-chat

use-x-chat

github.com/ant-design开发4.8k95 次安装2 天前更新
代码仓库

关于 use-x-chat

use-x-chat 是面向 React AI 对话应用开发者的技能指南,介绍如何用 @ant-design/x-sdk 的 useXChat Hook 管理消息和请求。它涵盖占位与错误处理、消息转换、请求中止及多会话管理,并要求先准备 Chat Provider。

主要功能

  1. 01管理对话消息与请求状态
  2. 02配置请求占位内容和失败回退
  3. 03转换消息以适配 UI 组件
  4. 04中止请求及重新生成回复
  5. 05结合 useXConversations 管理多会话

适用场景

  1. 01为 React 对话界面接入消息状态
  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 技能)

目录

🚀 快速开始

1. 依赖管理

  • @ant-design/x-sdk:2.2.2+
  • @ant-design/x:最新版本(UI 组件)
npm install @ant-design/x-sdk@latest @ant-design/x@latest

2. 三步集成

步骤 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 属性(而非 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 转换为组件可使用的格式,支持一对多转换

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 }) => void重新生成 AI 回复
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.' }],
  });
};

📋 前置条件和依赖项

使用场景所需技能/Provider顺序
私有 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 官方文档

💻 示例代码

评论

支持 Markdown