Skip to content

Commit

Permalink
feat: add interrupt streaming output demo
Browse files Browse the repository at this point in the history
  • Loading branch information
ppbl authored Dec 18, 2024
1 parent 56ed94f commit 3599871
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 0 deletions.
7 changes: 7 additions & 0 deletions components/useXChat/demo/stream-cancel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## zh-CN

打断正在流式输出的内容。

## en-US

Interrupt the ongoing streaming output.
113 changes: 113 additions & 0 deletions components/useXChat/demo/stream-cancel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { UserOutlined } from '@ant-design/icons';
import { Bubble, Sender, useXAgent, useXChat, XStream } from '@ant-design/x';
import { Flex, type GetProp } from 'antd';
import React, { useRef } from 'react';

const roles: GetProp<typeof Bubble.List, 'roles'> = {
ai: {
placement: 'start',
avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
},
local: {
placement: 'end',
avatar: { icon: <UserOutlined />, style: { background: '#87d068' } },
},
};

const contentChunks = [
'He',
'llo',
', w',
'or',
'ld!',
' Ant',
' Design',
' X',
' is',
' the',
' best',
'!',
];

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, 300));
controller.enqueue(new TextEncoder().encode(chunk));
}
controller.close();
},
});
}

const App = () => {
const [content, setContent] = React.useState('');

const abortRef = useRef(() => {});

// Agent for request
const [agent] = useXAgent({
request: async ({}, { onSuccess, onUpdate }) => {
const stream = XStream({
readableStream: mockReadableStream(),
});

const reader = stream.getReader();
abortRef.current = () => {
reader?.cancel();
};

let current = '';
while (reader) {
const { value, done } = await reader.read();
if (done) {
onSuccess(current);
break;
}
if (!value) continue;
const data = JSON.parse(value.data);
current += data.content || '';
onUpdate(current);
}
},
});

// Chat messages
const { onRequest, messages } = useXChat({
agent,
});

return (
<Flex vertical gap="middle">
<Bubble.List
roles={roles}
style={{ maxHeight: 300 }}
items={messages.map(({ id, message, status }) => ({
key: id,
role: status === 'local' ? 'local' : 'ai',
content: message,
}))}
/>
<Sender
loading={agent.isRequesting()}
value={content}
onChange={setContent}
onSubmit={(nextContent) => {
onRequest(nextContent);
setContent('');
}}
onCancel={() => abortRef.current()}
/>
</Flex>
);
};

export default App;
1 change: 1 addition & 0 deletions components/useXChat/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Use Agent to manage conversation data and produce data for page rendering.
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">Basic</code>
<code src="./demo/stream.tsx">Streaming</code>
<code src="./demo/stream-cancel.tsx">Interrupt the output</code>
<code src="./demo/suggestions.tsx">Multiple Suggestion</code>

## API
Expand Down
1 change: 1 addition & 0 deletions components/useXChat/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ demo:
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">基本</code>
<code src="./demo/stream.tsx">流式输出</code>
<code src="./demo/stream-cancel.tsx">打断输出</code>
<code src="./demo/suggestions.tsx">多项建议</code>

## API
Expand Down

0 comments on commit 3599871

Please sign in to comment.