1、sender组件
2、flex弹性布局
3、useEffect钩子
State Hook
状态帮助组件 “记住”用户输入的信息。例如,一个表单组件可以使用状态存储输入值,而一个图像库组件可以使用状态存储所选的图像索引。
使用以下 Hook 以向组件添加状态:
- 使用 useState 声明可以直接更新的状态变量。
- 使用 useReducer 在 reducer 函数 中声明带有更新逻辑的 state 变量。
useEffect 是一个 React Hook,它允许你 将组件与外部系统同步。
useEffect(setup, dependencies?)
在组件的顶层调用 useEffect 来声明一个 Effect:
useEffect(setup, dependencies?)
在组件的顶层调用 useEffect 来声明一个 Effect:
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
4、从 Less 变量到 Design Token
5、bubble 构件
6、Conversion是管理对话记录
7、useXChat主要用于管理会话
通过 Agent 进行会话数据管理,并产出供页面渲染使用的数据。
管理对话
import { UserOutlined } from '@ant-design/icons';
import { Bubble, Sender, useXAgent, useXChat } from '@ant-design/x';
import { Flex, type GetProp } from 'antd';
import React from 'react';
const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000));
const roles: GetProp<typeof Bubble.List, 'roles'> = {
ai: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
typing: { step: 5, interval: 20 },
style: {
maxWidth: 600,
},
},
local: {
placement: 'end',
avatar: { icon: <UserOutlined />, style: { background: '#87d068' } },
},
};
let mockSuccess = false;
const App = () => {
const [content, setContent] = React.useState('');
// Agent for request
const [agent] = useXAgent<string, { message: string }, string>({
request: async ({ message }, { onSuccess, onError }) => {
await sleep();
mockSuccess = !mockSuccess;
if (mockSuccess) {
onSuccess([`Mock success return. You said: ${message}`]);
}
onError(new Error('Mock request failed'));
},
});
// Chat messages
const { onRequest, messages } = useXChat({
agent,
requestPlaceholder: 'Waiting...',
requestFallback: 'Mock failed return. Please try again later.',
});
return (
<Flex vertical gap="middle">
<Bubble.List
roles={roles}
style={{ maxHeight: 300 }}
items={messages.map(({ id, message, status }) => ({
key: id,
loading: status === 'loading',
role: status === 'local' ? 'local' : 'ai',
content: message,
}))}
/>
<Sender
loading={agent.isRequesting()}
value={content}
onChange={setContent}
onSubmit={(nextContent) => {
onRequest(nextContent);
setContent('');
}}
/>
</Flex>
);
};
export default App;
7、useXAgent用于模型调度的 Agent 钩子。
import { LoadingOutlined, TagsOutlined } from '@ant-design/icons';
import { ThoughtChain, useXAgent } from '@ant-design/x';
import { Button, Descriptions, Splitter } from 'antd';
import React from 'react';
import type { ThoughtChainItem } from '@ant-design/x';
/**
* 🔔 Please replace the BASE_URL, PATH, MODEL, API_KEY with your own values.
*/
const BASE_URL = 'https://api.example.com';
const PATH = '/chat';
const MODEL = 'gpt-3.5-turbo';
/** 🔥🔥 Its dangerously! */
// const API_KEY = '';
interface YourMessageType {
role: string;
content: string;
}
const App = () => {
const [status, setStatus] = React.useState<ThoughtChainItem['status']>();
const [lines, setLines] = React.useState<any[]>([]);
const [agent] = useXAgent<YourMessageType>({
baseURL: BASE_URL + PATH,
model: MODEL,
// dangerouslyApiKey: API_KEY
});
async function request() {
setStatus('pending');
agent.request(
{
messages: [{ role: 'user', content: 'hello, who are u?' }],
stream: true,
},
{
onSuccess: (chunks) => {
setStatus('success');
console.log('onSuccess', chunks);
},
onError: (error) => {
setStatus('error');
console.error('onError', error);
},
onUpdate: (chunk) => {
setLines((pre) => [...pre, chunk]);
console.log('onUpdate', chunk);
},
},
);
}
return (
<Splitter>
<Splitter.Panel>
<Button type="primary" disabled={status === 'pending'} onClick={request}>
Agent Request
</Button>
</Splitter.Panel>
<Splitter.Panel>
<ThoughtChain
style={{ marginLeft: 16 }}
items={[
{
title: 'Agent Request Log',
status: status,
icon: status === 'pending' ? <LoadingOutlined /> : <TagsOutlined />,
description:
status === 'error' &&
agent.config.baseURL === BASE_URL + PATH &&
'Please replace the BASE_URL, PATH, MODEL, API_KEY with your own values.',
content: (
<Descriptions column={1}>
<Descriptions.Item label="Status">{status || '-'}</Descriptions.Item>
<Descriptions.Item label="Update Times">{lines.length}</Descriptions.Item>
</Descriptions>
),
},
]}
/>
</Splitter.Panel>
</Splitter>
);
};
export default App;
8、XStream 流 转换可读数据流
XStream 默认的 transformStream 是用于 SSE 协议的流转换器。readableStream 接收一个 new ReadableStream(...) 实例,常见的如 await fetch(...).body
import { TagsOutlined } from '@ant-design/icons';
import { Bubble, ThoughtChain, XStream } from '@ant-design/x';
import { Button, Splitter } from 'antd';
import React from 'react';
const contentChunks = ['He', 'llo', ', w', 'or', 'ld!'];
function mockReadableStream() {
const sseChunks: string[] = [];
for (let i = 0; i < contentChunks.length; i++) {
const sseEventPart = `event: message\ndata: {"id":"${i}","content":"${contentChunks[i]}"}\n\n`;
sseChunks.push(sseEventPart);
}
return new ReadableStream({
async start(controller) {
for (const chunk of sseChunks) {
await new Promise((resolve) => setTimeout(resolve, 100));
controller.enqueue(new TextEncoder().encode(chunk));
}
controller.close();
},
});
}
const App = () => {
const [lines, setLines] = React.useState<Record<string, string>[]>([]);
const content = lines.map((line) => JSON.parse(line.data).content).join('');
async function readStream() {
// 🌟 Read the stream
for await (const chunk of XStream({
readableStream: mockReadableStream(),
})) {
console.log(chunk);
setLines((pre) => [...pre, chunk]);
}
}
return (
<Splitter>
<Splitter.Panel>
{/* -------------- Emit -------------- */}
<Button type="primary" onClick={readStream} style={{ marginBottom: 16 }}>
Mock Default Protocol - SSE
</Button>
{/* -------------- Content Concat -------------- */}
{content && <Bubble content={content} />}
</Splitter.Panel>
{/* -------------- Log -------------- */}
<Splitter.Panel style={{ marginLeft: 16 }}>
<ThoughtChain
items={
lines.length
? [
{
title: 'Mock Default Protocol - Log',
status: 'success',
icon: <TagsOutlined />,
content: (
<pre style={{ overflow: 'scroll' }}>
{lines.map((i) => (
<code key={i.data}>{i.data}</code>
))}
</pre>
),
},
]
: []
}
/>
</Splitter.Panel>
</Splitter>
);
};
export default App;
8、箭头函数学习了
lines.map(...)
map方法:遍历数组lines,对每个元素执行回调函数,并返回新数组。- 回调函数:
(line) => JSON.parse(line.data).contentline.data:假设lines中的每个元素是对象,且包含data属性(通常为 JSON 字符串)。JSON.parse(line.data):将 JSON 字符串解析为 JavaScript 对象。.content:从解析后的对象中提取content属性的值
9、学习了chat控件,用来管理对话管理的内容
10、question
以下是用户提问后,大模型回答并最终显示文字的完整流程梳理:
1. 用户输入问题
- 用户在界面上通过
Sender组件输入内容。 - 输入的内容被存储在状态变量
inputValue中。
tsx
<Sender value={inputValue} ... onChange={setInputValue} />
2. 触发提交事件
- 当用户点击发送按钮或按下回车键时,调用 onSubmit 函数。
- 此函数检查是否有正在进行的请求(避免重复提交),然后调用
onRequest发起请求。
tsx
onSubmit(() => { onSubmit(inputValue); // 调用处理函数 setInputValue(''); // 清空输入框 })
3. 发起请求
onRequest是由useXChat提供的 API,用于向大模型发起请求。- 请求参数包含:
stream: true:表示使用流式响应。message: 包含用户输入内容和角色(通常是"user")。
tsx
onRequest({ stream: true, message: { role: 'user', content: val }, });
4. 大模型返回数据
- 大模型开始返回数据,通常以 chunk 形式进行流式传输。
transformMessage回调函数处理每个 chunk 数据,提取推理中 (reasoning_content) 和实际输出 (content) 的内容,并拼接成完整的回复。
tsx
transformMessage: (info) => { const currentThink = message?.choices?.[0]?.delta?.reasoning_content; const currentContent = message?.choices?.[0]?.delta?.content; ... return { content, role: 'assistant' }; }
5. 更新消息列表
- 返回的数据经过处理后,会更新到
messages状态中。 messages是一个数组,包含了历史对话记录,结构为:
ts
{ status: 'loading' | 'done', message: { role: 'user' | 'assistant', content: string } }
6. 渲染聊天记录
Bubble.List组件负责将messages数组中的每条消息渲染出来。- 每条消息根据角色(
role)决定显示在左侧(AI)还是右侧(用户)。
tsx
<Bubble.List items={messages.map(i => ({ ...i.message, typing: i.status === 'loading' ? { step: 5, interval: 20 } : false, }))} />
7. 加载状态与动态更新
- 如果当前仍在等待大模型返回数据,则
status === 'loading',此时显示打字动画。 - 当数据完全接收完毕,状态变为
'done',停止动画并展示完整回复。
✅ 总结流程图
| 阶段 | 描述 |
|---|---|
| 1️⃣ 用户输入 | 在 Sender 中输入问题,值存入 inputValue |
| 2️⃣ 提交 | 点击发送按钮触发 onSubmit,调用 onRequest |
| 3️⃣ 请求 | 向大模型 API 发送请求,启用 stream 模式 |
| 4️⃣ 流式返回 | 大模型逐步返回数据,通过 transformMessage 解析 |
| 5️⃣ 更新状态 | 将解析后的回复更新至 messages 状态 |
| 6️⃣ 渲染 | 使用 Bubble.List 动态渲染聊天记录 |
| 7️⃣ 加载控制 | 根据 status 显示加载动画或完整内容 |
12
完整逻辑
- 当
info存在时:提取info.originMessage和info.chunk。 - 当
info不存在时:使用空对象{}作为默认值,此时originMessage和chunk都为undefined。
13、useXAgent使用XRrequest请求
XAgent的钩子函数
XRequest的内容
1113

被折叠的 条评论
为什么被折叠?



