探索基于 Ant Design X 的智能交互界面实现

一、Ant Design X 是什么?

Ant Design X,即@ant-design/x,是遵循 Ant Design 设计体系的 React UI 库 ,旨在构建 AI 驱动的界面,能一键接入智能对话组件与 API 服务。在人工智能技术蓬勃发展的当下,AI 驱动的应用越来越多,像智能客服、智能写作助手、智能图像生成工具等。开发这类应用时,构建一个高效、美观且用户体验良好的界面至关重要。Ant Design X 的出现,极大地简化了 AI 界面的开发流程。它就像是一个装满各种开发 "零件" 的工具箱,开发者无需从头开始打造每一个界面元素,直接使用 Ant Design X 提供的组件,就能快速搭建出功能丰富的 AI 交互界面,大大节省开发时间和精力。

二、搭建前的准备工作

(一)环境要求

在使用 Ant Design X 搭建 AI 交互界面之前,需要确保开发环境满足以下要求:

  • Node.js:建议使用 Node.js 14.0.0 及以上版本 ,以获得更好的兼容性和性能。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,它允许开发者在服务器端运行 JavaScript 代码,为前端开发提供了强大的支持。
  • React 和 React - DOM:React 17.0.0 及以上版本,React - DOM 也需要相应的版本,它们是构建用户界面的核心库,提供了创建和管理 UI 组件的能力。
  • 包管理器:可以选择 npm(Node Package Manager)、yarn 或 pnpm。npm 是 Node.js 默认的包管理器,yarn 和 pnpm 则在性能和依赖管理方面有各自的优势,开发者可以根据项目需求和个人喜好进行选择。

(二)安装 Ant Design X

使用包管理器安装 Ant Design X 非常简单,以下是通过 npm、yarn、pnpm 安装的具体命令:

  • npm:npm install @ant-design/x --save
  • yarn:yarn add @ant-design/x
  • pnpm:pnpm add @ant-design/x

安装完成后,Ant Design X 及其依赖项就会被下载到项目的node_modules目录中。

(三)引入组件和工具

在项目中引入 Ant Design X 的组件和钩子工具也很方便。以一个简单的 React 组件为例,假设我们要使用Bubble(消息气泡)、Sender(发送框)组件,以及useXAgent(模型调度钩子)、useXChat(数据管理钩子)工具,可以这样引入:

javascript 复制代码
import React from'react';
import { Bubble, Sender, useXAgent, useXChat } from '@ant-design/x';

通过这样的方式,就可以在组件中使用这些功能强大的组件和工具,为搭建 AI 交互界面做好准备。

三、RICH 设计范式解析

Ant Design X 提出了独特的 RICH 设计范式,为 AI 交互界面的设计提供了清晰的指导思路,涵盖意图(Intention)、角色(Role)、会话(Conversation)和混合界面(Hybrid UI)四个核心要素。

(一)意图(Intention)

在 AI 交互中,明确用户意图至关重要。用户使用 AI 工具时,意图可能清晰,如让 AI 帮忙修改文档中的一段文字;也可能模糊,比如只是想了解某个领域的一些信息 。意图还可分为任务型,像完成一份项目策划;和咨询型,例如询问某个概念的含义。Ant Design X 通过提供引导性的提示和交互组件,帮助用户明确意图。比如在搜索框旁边设置一些热门搜索关键词或相关问题的示例,当用户不知道如何表达需求时,点击这些示例就能快速发起相关询问,让 AI 理解用户的大致方向,从而提供更准确的服务。

(二)角色(Role)

为 AI 设定不同角色能显著提升交互体验。在帮助中心,AI 可扮演亲和力强的客服角色,语言风格亲切、热情,使用诸如 "亲,有什么问题都可以问我哦" 这样的表述 ;在 BI 数据分析模块,AI 则扮演专业的数据分析师角色,使用专业术语,如 "根据这些数据的标准差和方差分析,我们可以得出......"。Ant Design X 提供了丰富的组件和配置选项来实现角色设计。可以通过设置 AI 的头像、语言风格、语气等方面来塑造角色形象。选择一个微笑的卡通形象作为帮助中心 AI 的头像,搭配温暖的语言,让用户感受到友好;而在专业分析场景中,使用简洁、严肃的界面风格和专业的图标,展现 AI 的专业性。

(三)会话(Conversation)

利用 Ant Design X 组件能有效管理对话流程和处理对话状态。在对话开始时,使用欢迎组件,如,向用户展示友好的问候和简单介绍,让用户快速了解 AI 的功能 。当用户提问后,AI 可能需要追问一些细节以更好地理解问题,此时可以通过特定的提示组件,如,给出追问的选项。在对话过程中,如果用户需要一些提示来继续对话,Ant Design X 的提示组件也能派上用场,提供相关的建议或引导。比如在用户与写作助手 AI 对话时,当用户思路中断,提示组件可以给出一些写作方向的建议,如 "可以从描述事件的起因开始"。

(四)混合界面(Hybrid UI)

Ant Design X 支持将多种交互模式融合,构建混合界面。在一个智能办公应用中,用户既可以通过传统的图形界面操作,如点击按钮、选择菜单来执行任务;也可以通过与 AI 对话的方式,让 AI 完成一些复杂的操作 。比如用户想要生成一份会议纪要,既可以在文档编辑界面手动输入内容,也可以直接对 AI 说 "帮我生成一份关于昨天会议的纪要,重点包括讨论的问题和决策结果"。Ant Design X 通过提供灵活的布局组件和交互组件,实现了这种混合界面的构建。开发者可以根据项目需求,将对话组件、操作按钮、信息展示区域等进行合理组合,为用户提供便捷、高效的交互体验。

四、核心组件实战

(一)消息气泡(Bubble)

消息气泡(Bubble)组件在 AI 交互界面中用于显示对话消息,就像我们日常使用的聊天软件中的聊天气泡一样,能让对话内容以直观、清晰的方式呈现。使用时,需先从@ant-design/x中引入Bubble组件:

javascript 复制代码
import { Bubble } from '@ant-design/x';

在实际应用中,通常会将消息存储在一个数组中,每个消息对象包含content(消息内容)和role(角色,如user或ai)等属性。比如:

ini 复制代码
const messages = [
  { content: '你好,今天天气如何?', role: 'user' },
  { content: '今天天气晴朗,适合外出活动。', role: 'ai' }
];

然后,通过Bubble.List组件来渲染这些消息:

ini 复制代码
<Bubble.List items={messages}>
  {({ item }) => (
    <Bubble
      key={item.id}
      placement={item.role === 'user'? 'end' :'start'}
      content={item.content}
      avatar={item.role === 'user'? { icon: <UserOutlined /> } : { icon: <RobotOutlined /> }}
    />
  )}
</Bubble.List>

在这段代码中,placement属性根据消息的角色设置气泡的位置,用户消息在右侧(end),AI 消息在左侧(start);avatar属性设置消息发送者的头像,使用UserOutlined图标表示用户,RobotOutlined图标表示 AI。通过这些设置,能让用户清晰地区分不同角色的消息,提升交互体验。

(二)输入框(Sender)

输入框(Sender)组件是用户与 AI 进行交互的重要入口,用户在此输入问题或指令,然后提交给 AI 进行处理。首先引入Sender组件:

javascript 复制代码
import { Sender } from '@ant-design/x';

为了处理用户输入和提交请求,需要定义一些状态和函数。例如,使用 React 的useState钩子来管理输入框的值和提交状态:

javascript 复制代码
import React, { useState } from'react';
const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = (value) => {
  setIsLoading(true);
  // 模拟向AI发送请求
  setTimeout(() => {
    setIsLoading(false);
    // 这里可以处理AI返回的结果
    console.log('AI response:', value);
  }, 2000);
  setInputValue('');
};

在Sender组件中,通过value属性绑定输入框的值,onChange事件处理函数更新输入框的值,onSubmit事件处理函数在用户点击提交按钮或按下回车键时触发,将用户输入的值传递给handleSubmit函数进行处理,loading属性用于控制输入框是否显示加载状态:

ini 复制代码
<Sender
  value={inputValue}
  onChange={(e) => setInputValue(e.target.value)}
  onSubmit={handleSubmit}
  loading={isLoading}
/>

这样,用户在输入框中输入内容并提交后,就能与 AI 进行交互,输入框还会在请求处理过程中显示加载状态,让用户了解交互的进展。

(三)提示集(Prompts)

提示集(Prompts)组件提供了预定义问题或建议,能引导用户与 AI 进行交互,尤其适用于用户不知道如何提问或需要一些灵感的情况。引入Prompts组件:

javascript 复制代码
import { Prompts } from '@ant-design/x';

定义提示集的内容,每个提示对象包含key(唯一标识)、label(显示的文本)和description(描述信息)等属性:

ini 复制代码
const promptsItems = [
  {
    key: '1',
    label: '热门电影推荐',
    description: '请AI推荐近期热门电影'
  },
  {
    key: '2',
    label: '旅游景点推荐',
    description: '让AI推荐国内热门旅游景点'
  }
];

在组件中使用Prompts组件,并通过onItemClick事件处理函数处理用户点击提示的操作,比如将提示的描述信息作为用户输入发送给 AI:

ini 复制代码
<Prompts
  items={promptsItems}
  onItemClick={(info) => {
    // 处理用户点击提示的逻辑,这里可以将提示信息发送给AI
    console.log('User clicked prompt:', info.data.description);
  }}
/>

这样,用户在界面上可以看到这些预定义的提示,点击提示就能快速发起相关的询问,降低用户的输入门槛,提高交互效率。

(四)会话管理(Conversations)

会话管理(Conversations)组件用于管理多个会话,方便用户查看历史会话记录,在多轮对话或需要保存对话记录的场景中非常实用。引入Conversations组件:

javascript 复制代码
import { Conversations } from '@ant-design/x';

定义会话列表的数据,每个会话对象包含key(唯一标识)和label(显示的名称)等属性:

ini 复制代码
const conversationsItems = [
  {
    key: '1',
    label: '与AI讨论工作安排'
  },
  {
    key: '2',
    label: '向AI咨询学习方法'
  }
];

使用Conversations组件展示会话列表,并通过onActiveChange事件处理函数处理用户切换会话的操作,比如根据用户选择的会话加载相应的历史消息:

ini 复制代码
<Conversations
  items={conversationsItems}
  onActiveChange={(key) => {
    // 处理用户切换会话的逻辑,这里可以根据key加载相应的历史消息
    console.log('User switched to conversation:', key);
  }}
/>

通过这样的方式,用户可以在不同的会话之间进行切换,随时查看之前与 AI 的交流内容,方便回顾和继续之前的话题,提升了 AI 交互的连贯性和便捷性。

五、模型集成与数据流管理

(一)模型调度(useXAgent)

在 AI 交互界面开发中,模型调度是至关重要的环节,它负责管理和调度 AI 模型,确保模型能够高效、准确地处理用户请求。Ant Design X 提供的useXAgent钩子在模型调度方面发挥着关键作用。useXAgent钩子允许开发者通过一个Agent对象来管理和调度不同的 AI 模型,它提供了简洁的接口来处理模型请求和响应,使得集成和使用 AI 模型变得更加简单。

以对接 Qwen 模型为例,具体代码实现如下:

typescript 复制代码
import React from'react';
import { useXAgent, Sender, XRequest } from '@ant-design/x';
const { create } = XRequest({
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',// Qwen模型的请求地址
  dangerouslyApiKey: process.env['DASHSCOPE_API_KEY'],// 阿里云DashScope平台的API密钥,从环境变量中获取
  model: 'qwen-plus'// 使用的Qwen模型版本
});
const Component: React.FC = () => {
  const [agent] = useXAgent({
    request: async (info, callbacks) => {
      const { messages, message } = info;
      const { onUpdate } = callbacks;
      let content: string = '';
      try {
        create({
          messages: [
            {
              role: 'user',
              content: message
            }
          ],
          stream: true
        }, {
          onSuccess: (chunks) => {
            console.log('sse chunk list', chunks);
          },
          onError: (error) => {
            console.log('error', error);
          },
          onUpdate: (chunk) => {
            console.log('sse object', chunk);
            const data = JSON.parse(chunk.data);
            content += data?.choices[0].delta.content;
            onUpdate(content);
          }
        });
      } catch (error) {
        // 处理错误
      }
    }
  });
  function onRequest(message: string) {
    agent.request({ message }, {
      onUpdate: () => { },
      onSuccess: () => { },
      onError: () => { }
    });
  }
  return <Sender onSubmit={onRequest} />;
};
export default Component;

在这段代码中,首先通过XRequest创建了一个请求对象create,配置了 Qwen 模型的请求地址、API 密钥和模型版本。然后在useXAgent中定义了request函数,当接收到用户消息时,通过create函数向 Qwen 模型发起请求。模型返回的结果通过onUpdate回调函数进行处理,实时更新对话内容。最后,在Sender组件的onSubmit事件中调用onRequest函数,将用户输入的消息发送给模型进行处理。通过这样的方式,实现了使用useXAgent钩子对接 Qwen 模型,完成模型调度的功能。

(二)数据管理(useXChat)

数据管理在 AI 交互界面中同样不可或缺,它主要负责管理会话数据,并产出供页面渲染使用的数据,确保对话数据流的有序处理和展示。Ant Design X 的useXChat钩子极大地简化了这一过程,让开发者可以专注于构建用户界面,而不必过多担心底层的数据逻辑。useXChat钩子通过与useXAgent配合,实现了会话数据的高效管理。它可以处理对话的各种状态,如加载中、成功、失败等,并将处理后的数据传递给页面组件进行渲染。

以对接 OpenAI 服务为例,代码实现如下:

typescript 复制代码
import React from'react';
import { useXAgent, useXChat, Sender, Bubble } from '@ant-design/x';
import OpenAI from 'openai';
const client = new OpenAI({
  apiKey: process.env['OPENAI_API_KEY'],
  dangerouslyAllowBrowser: true
});
const Demo: React.FC = () => {
  const [agent] = useXAgent({
    request: async (info, callbacks) => {
      const { messages, message } = info;
      const { onSuccess, onUpdate, onError } = callbacks;
      let content: string = '';
      try {
        const stream = await client.chat.completions.create({
          model: 'gpt-4o',
          messages: [
            {
              role: 'user',
              content: message
            }
          ],
          stream: true
        });
        for await (const chunk of stream) {
          content += chunk.choices[0]?.delta?.content || '';
          onUpdate(content);
        }
        onSuccess(content);
      } catch (error) {
        // 处理错误
      }
    }
  });
  const { onRequest, messages } = useXChat({ agent });
  const items = messages.map(({ message, id }) => ({
    key: id,
    content: message
  }));
  return (
    <div>
      <Bubble.List items={items} />
      <Sender onSubmit={onRequest} />
    </div>
  );
};
export default Demo;

在这段代码中,首先创建了 OpenAI 客户端实例,配置了 API 密钥并允许在浏览器环境中使用。在useXAgent的request函数中,向 OpenAI 的gpt - 4o模型发起请求,处理模型返回的流式数据,并通过onUpdate和onSuccess回调函数更新和完成对话内容。useXChat钩子则通过agent对象获取对话数据,messages包含了所有的对话消息,将其映射为适合Bubble.List组件渲染的格式。最后,在页面中通过Bubble.List展示对话消息,Sender组件用于用户输入,实现了完整的数据管理和页面渲染流程。

(三)请求工具(XRequest)

XRequest是 Ant Design X 提供的一个强大的请求工具,专门用于向符合 OpenAI 标准的大语言模型(LLM,Large Language Models)发起请求。它封装了请求的细节,使得开发者可以轻松地与 AI 模型进行交互,无需过多关注底层的网络请求和参数配置。在实际应用中,XRequest简化了与模型通信的过程。开发者只需提供模型的请求地址、API 密钥和模型名称等基本信息,就可以使用XRequest创建请求对象,并通过该对象向模型发送请求。例如,在对接 Qwen 模型时,通过XRequest配置好请求地址、阿里云 DashScope 平台的 API 密钥以及qwen - plus模型名称后,就可以方便地向 Qwen 模型发起请求,获取模型的响应。

XRequest还支持对请求参数的灵活配置,如请求的消息内容、是否使用流式响应等。在与模型交互过程中,开发者可以根据实际需求调整这些参数,以获得更好的交互效果。它为开发者提供了一种便捷、高效的方式来与符合 OpenAI 标准的模型进行通信,大大提升了开发效率和体验。

六、实例实战

(一)源码

react 复制代码
import {
  Attachments,
  Bubble,
  Conversations,
  Prompts,
  Sender,
  Welcome,
  ThoughtChain,
  useXAgent,
  useXChat,
} from '@ant-design/x';
import { CheckCircleOutlined, CloudUploadOutlined, CommentOutlined, EllipsisOutlined, FireOutlined, HeartOutlined, MoreOutlined, PaperClipOutlined, PlusOutlined, ReadOutlined, ShareAltOutlined, SmileOutlined, UserOutlined } from '@ant-design/icons';
import { createStyles } from 'antd-style';
import { Card, Typography, Space, Button, Flex, type GetProp, Badge } from 'antd';
import React, { useEffect, useState } from 'react';
const { Paragraph, Text } = Typography;

const renderTitle = (icon: React.ReactElement, title: string) => (
  <Space align="start">
    {icon}
    <span>{title}</span>
  </Space>
);
const defaultConversationsItems = [
  {
    key: '0',
    label: 'What is Ant Design X?',
  },
];

const useStyle = createStyles(({ token, css }) => {
  return {
    layout: css`
      width: 100%;
      min-width: 1000px;
      height: 100vh;
      overflow-y: hidden;
      border-radius: ${token.borderRadius}px;
      display: flex;
      background: ${token.colorBgContainer};
      font-family: AlibabaPuHuiTi, ${token.fontFamily}, sans-serif;

      .ant-prompts {
        color: ${token.colorText};
      }
    `,
    menu: css`
      background: ${token.colorBgLayout}80;
      width: 280px;
      height: 100%;
      display: flex;
      flex-direction: column;
    `,
    thoughtChain: css`
      background: ${token.colorBgLayout}80;
      width: 300px;
      height: 100%;
      overflow-x: hidden;
      overflow-y: scroll;
      display: flex;
      flex-direction: column;
    `,
    conversations: css`
      padding: 0 12px;
      flex: 1;
      overflow-y: auto;
    `,
    chat: css`
      height: 100%;
      width: 100%;
      margin: 0 auto;
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      padding: ${token.paddingLG}px;
      gap: 16px;
    `,
    messages: css`
      flex: 1;
    `,
    placeholder: css`
      padding-top: 32px;
    `,
    sender: css`
      box-shadow: ${token.boxShadow};
    `,
    logo: css`
      display: flex;
      height: 72px;
      align-items: center;
      justify-content: start;
      padding: 0 24px;
      box-sizing: border-box;

      img {
        width: 24px;
        height: 24px;
        display: inline-block;
      }

      span {
        display: inline-block;
        margin: 0 8px;
        font-weight: bold;
        color: ${token.colorText};
        font-size: 16px;
      }
    `,
    addBtn: css`
      background: #1677ff0f;
      border: 1px solid #1677ff34;
      width: calc(100% - 24px);
      margin: 0 12px 24px 12px;
    `,
  };
});

const placeholderPromptsItems: GetProp<typeof Prompts, 'items'> = [
  {
    key: '1',
    label: renderTitle(<FireOutlined style={{ color: '#FF4D4F' }} />, 'Hot Topics'),
    description: 'What are you interested in?',
    children: [
      {
        key: '1-1',
        description: `What's new in X?`,
      },
      {
        key: '1-2',
        description: `What's AGI?`,
      },
      {
        key: '1-3',
        description: `Where is the doc?`,
      },
    ],
  },
  {
    key: '2',
    label: renderTitle(<ReadOutlined style={{ color: '#1890FF' }} />, 'Design Guide'),
    description: 'How to design a good product?',
    children: [
      {
        key: '2-1',
        icon: <HeartOutlined />,
        description: `Know the well`,
      },
      {
        key: '2-2',
        icon: <SmileOutlined />,
        description: `Set the AI role`,
      },
      {
        key: '2-3',
        icon: <CommentOutlined />,
        description: `Express the feeling`,
      },
    ],
  },
];

const senderPromptsItems: GetProp<typeof Prompts, 'items'> = [
  {
    key: '1',
    description: 'Hot Topics',
    icon: <FireOutlined style={{ color: '#FF4D4F' }} />,
  },
  {
    key: '2',
    description: 'Design Guide',
    icon: <ReadOutlined style={{ color: '#1890FF' }} />,
  },
];

const roles: GetProp<typeof Bubble.List, 'roles'> = {
  ai: {
    placement: 'start',
    typing: true,
    avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
  },
  suggestion: {
    placement: 'start',
    avatar: { icon: <UserOutlined />, style: { visibility: 'hidden' } },
    variant: 'borderless',
    messageRender: (content) => (
      <Prompts
        vertical
        items={(content as any as string[]).map((text) => ({
          key: text,
          icon: <SmileOutlined style={{ color: '#FAAD14' }} />,
          description: text,
        }))}
      />
    ),
  },
  file: {
    placement: 'start',
    avatar: { icon: <UserOutlined />, style: { visibility: 'hidden' } },
    variant: 'borderless',
    messageRender: (items: any) => (
      <Flex vertical gap="middle">
        {(items as any[]).map((item) => (
          <Attachments.FileCard key={item} item={{
            uid: '9',
            name: 'markdown-file.md',
            size: 999999,
            description: 'Custom description here',
          }} />
        ))}
      </Flex>
    ),
  },
};
type AgentUserMessage = {
  type: 'user';
  content: string;
};

type AgentAIMessage = {
  type: 'ai';
  content?: string;
  list?: (
    | {
      type: 'text';
      content: string;
    }
    | {
      type: 'suggestion';
      content: string[];
    }
  )[];
};

type AgentMessage = AgentUserMessage | AgentAIMessage;

type BubbleMessage = {
  role: string;
};

const Independent: React.FC = () => {
  const { styles } = useStyle();

  const [headerOpen, setHeaderOpen] = React.useState(false);
  const [content, setContent] = React.useState('');
  const [conversationsItems, setConversationsItems] = React.useState(defaultConversationsItems);
  const [activeKey, setActiveKey] = React.useState(defaultConversationsItems[0].key);
  const [attachedFiles, setAttachedFiles] = React.useState<GetProp<typeof Attachments, 'items'>>([]);
  const [thoughtChainItems, setThoughtChainItems] = React.useState<GetProp<typeof ThoughtChain, 'items'>>([]);

  const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000));

  const [agent] = useXAgent<AgentMessage>({
    request: async ({ message }, { onSuccess }) => {
      await sleep();

      const { content } = message || {};
      onSuccess({
        type: 'ai',
        list: [
          {
            type: 'text',
            content: `Do you want?`,
          },
          {
            type: 'suggestion',
            content: [`Look at: ${content}`, `Search: ${content}`, `Try: ${content}`],
          },
          {
            type: 'file',
            content: [`Look at: ${content}`, `Search: ${content}`, `Try: ${content}`],
          },
        ],
      });

      // 动态设置思维链
      setThoughtChainItems((prevItems) => [
        // ...prevItems,
        {
          title: 'User Input',
          description: content,
          icon: <CheckCircleOutlined />,
          extra: <Button type="text" icon={<MoreOutlined />} />,
          footer: <Button block>Thought Chain Item Footer</Button>,
          content: (
            <Typography>
              <Paragraph>
                User input: {content}
              </Paragraph>
            </Typography>
          ),
        },
        {
          title: 'AI Response',
          description: 'AI response description',
          icon: <CheckCircleOutlined />,
          extra: <Button type="text" icon={<MoreOutlined />} />,
          footer: <Button block>Thought Chain Item Footer</Button>,
          content: (
            <Typography>
              <Paragraph>
                AI response: Do you want?
              </Paragraph>
              <Paragraph>
                Suggestions: Look at: {content}, Search: {content}, Try: {content}
              </Paragraph>
            </Typography>
          ),
        },
      ]);
    },
  });

  const { onRequest, parsedMessages, setMessages } = useXChat<AgentMessage, BubbleMessage>({
    agent,
    defaultMessages: [
      {
        id: 'init',
        message: {
          type: 'ai',
          content: 'Hello, what can I do for you?',
        },
        status: 'success',
      },
    ],
    requestPlaceholder: {
      type: 'ai',
      content: 'Waiting...',
    },
    parser: (agentMessages) => {
      const list = agentMessages.content ? [agentMessages] : (agentMessages as AgentAIMessage).list;

      return (list || []).map((msg) => ({
        role: msg.type,
        content: msg.content,
      }));
    },
  });

  useEffect(() => {
    if (activeKey !== undefined) {
      setMessages([]);
      setThoughtChainItems([]);
    }
  }, [activeKey]);

  const onSubmit = (nextContent: string) => {
    if (!nextContent) return;
    onRequest({
      type: 'user',
      content: nextContent,
    });
    setContent('');
  };

  const onPromptsItemClick: GetProp<typeof Prompts, 'onItemClick'> = (info) => {
    onRequest(info.data.description as string);
  };

  const onAddConversation = () => {
    setConversationsItems([
      ...conversationsItems,
      {
        key: `${conversationsItems.length}`,
        label: `New Conversation ${conversationsItems.length}`,
      },
    ]);
    setActiveKey(`${conversationsItems.length}`);
  };

  const onConversationClick: GetProp<typeof Conversations, 'onActiveChange'> = (key) => {
    setActiveKey(key);
  };

  const handleFileChange: GetProp<typeof Attachments, 'onChange'> = (info) =>
    setAttachedFiles(info.fileList);

  const placeholderNode = (
    <Space direction="vertical" size={16} className={styles.placeholder}>
      <Welcome
        variant="borderless"
        icon="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
        title="Hello, I'm Ant Design X"
        description="Base on Ant Design, AGI product interface solution, create a better intelligent vision~"
        extra={
          <Space>
            <Button icon={<ShareAltOutlined />} />
            <Button icon={<EllipsisOutlined />} />
          </Space>
        }
      />
      <Prompts
        title="Do you want?"
        items={placeholderPromptsItems}
        styles={{
          list: {
            width: '100%',
          },
          item: {
            flex: 1,
          },
        }}
        onItemClick={onPromptsItemClick}
      />
    </Space>
  );

  const attachmentsNode = (
    <Badge dot={attachedFiles.length > 0 && !headerOpen}>
      <Button type="text" icon={<PaperClipOutlined />} onClick={() => setHeaderOpen(!headerOpen)} />
    </Badge>
  );

  const senderHeader = (
    <Sender.Header
      title="Attachments"
      open={headerOpen}
      onOpenChange={setHeaderOpen}
      styles={{
        content: {
          padding: 0,
        },
      }}
    >
      <Attachments
        beforeUpload={() => false}
        items={attachedFiles}
        onChange={handleFileChange}
        placeholder={(type) =>
          type === 'drop'
            ? { title: 'Drop file here' }
            : {
              icon: <CloudUploadOutlined />,
              title: 'Upload files',
              description: 'Click or drag files to this area to upload',
            }
        }
      />
    </Sender.Header>
  );

  const logoNode = (
    <div className={styles.logo}>
      <img
        src="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*eco6RrQhxbMAAAAAAAAAAAAADgCCAQ/original"
        draggable={false}
        alt="logo"
      />
      <span>Ant Design X</span>
    </div>
  );

  return (
    <div className={styles.layout}>
      <div className={styles.menu}>
        {/*  Logo */}
        {logoNode}
        {/* 会话区 */}
        <Button
          onClick={onAddConversation}
          type="link"
          className={styles.addBtn}
          icon={<PlusOutlined />}
        >
          New Conversation
        </Button>
        {/* 对话列表 */}
        <Conversations
          items={conversationsItems}
          className={styles.conversations}
          activeKey={activeKey}
          onActiveChange={onConversationClick}
        />
      </div>
      <div className={styles.chat}>
        {/* 对话内容 */}
        <Bubble.List
          items={parsedMessages.map(({ id, message, status }) => ({
            key: id,
            loading: status === 'loading',
            ...message,
          }))}
          roles={roles}
          className={styles.messages}
        />
        {/* 提示 */}
        <Prompts items={senderPromptsItems} onItemClick={onPromptsItemClick} />
        {/* 发送框 */}
        <Sender
          value={content}
          header={senderHeader}
          onSubmit={onSubmit}
          onChange={setContent}
          prefix={attachmentsNode}
          loading={agent.isRequesting()}
          className={styles.sender}
        />
      </div>
      {
        thoughtChainItems.length ? <div className={styles.thoughtChain}> {/* 思维链 */}
          <ThoughtChain items={thoughtChainItems} />
        </div> : ''
      }

    </div>
  );
};

export default Independent;

效果

七、总结与展望

使用 Ant Design X 生成 AI 交互界面具有诸多显著优势。它提供的丰富组件和工具,极大地加速了开发进程,使开发者能够快速搭建出功能完备的界面。基于 RICH 设计范式,它为用户带来了更加流畅、智能的交互体验,有效提升了用户满意度。而且,Ant Design X 具备高度的灵活性和可定制性,能够满足各种不同项目的需求。

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax