探索基于 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 具备高度的灵活性和可定制性,能够满足各种不同项目的需求。

相关推荐
Moment12 分钟前
一份没有项目展示的简历,是怎样在面试里输掉的?开源项目或许是你的救命稻草 😭😭😭
前端·后端·面试
CreatorRay14 分钟前
受控组件和非受控组件的区别
前端·javascript·react.js
2501_9068012038 分钟前
BY组态-低代码web可视化组件
前端·物联网·低代码·数学建模·前端框架
sma2mmm1 小时前
微前端实现方案对比Qiankun VS npm组件
前端·前端框架·npm
月起星九1 小时前
为什么package.json里的npm和npm -v版本不一致?
前端·npm·node.js
孤客网络科技工作室1 小时前
每天学一个 Linux 命令(7):cd
java·linux·前端
努力的搬砖人.1 小时前
Vue 2 和 Vue 3 有什么区别
前端·vue.js·经验分享·面试
Json_181790144801 小时前
python采集淘宝拍立淘按图搜索API接口,json数据示例参考
服务器·前端·数据库
珹洺2 小时前
Java-servlet(十)使用过滤器,请求调度程序和Servlet线程(附带图谱表格更好对比理解)
java·开发语言·前端·hive·hadoop·servlet·html
熙曦Sakura2 小时前
【C++】map
前端·c++