Skip to content

Commit

Permalink
✨ feat: 支持自定义 Error 渲染 & 配套文档 (ant-design#155)
Browse files Browse the repository at this point in the history
* ✨ feat: error support custom

* 💚 fix: ci fixed
  • Loading branch information
ONLY-yours authored Apr 22, 2024
1 parent c2e20ce commit 0a5ce85
Show file tree
Hide file tree
Showing 14 changed files with 250 additions and 49 deletions.
53 changes: 53 additions & 0 deletions docs/guide/demos/error-custom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* compact: true
* iframe: 1000
*/
import { ProChat, ProChatInstance } from '@ant-design/pro-chat';
import { Button, Card, Result } from 'antd';
import { useTheme } from 'antd-style';
import { useEffect, useRef } from 'react';
import { MockResponse } from './mocks/streamResponse';

export default () => {
const theme = useTheme();

const chatRef1 = useRef<ProChatInstance>();

useEffect(() => {
setTimeout(() => {
chatRef1.current.sendMessage('Hello!');
}, 500);
}, []);

return (
<>
<div style={{ background: theme.colorBgLayout }}>
<ProChat
request={async () => {
const mockResponse = new MockResponse('', 1000, true);
return mockResponse.getResponse();
}}
chatRef={chatRef1}
style={{ height: 500 }}
renderErrorMessages={(errorResponse) => {
return (
<Card>
<Result
status="error"
title="Something Error"
subTitle={errorResponse.message}
extra={[
<Button type="primary" key="console">
Try Again
</Button>,
<Button key="buy">Buy Token</Button>,
]}
/>
</Card>
);
}}
/>
</div>
</>
);
};
29 changes: 29 additions & 0 deletions docs/guide/demos/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* compact: true
*/
import { ProChat, ProChatInstance } from '@ant-design/pro-chat';
import { useTheme } from 'antd-style';
import { useEffect, useRef } from 'react';

export default () => {
const theme = useTheme();

const chatRef1 = useRef<ProChatInstance>();

useEffect(() => {
chatRef1.current.sendMessage('Hello!');
});

return (
<>
<div style={{ background: theme.colorBgLayout }}>
<ProChat
chatRef={chatRef1}
request={async () => {
return new Response('token 长度超限', { status: 500, statusText: 'limited of token' });
}}
/>
</div>
</>
);
};
36 changes: 31 additions & 5 deletions docs/guide/demos/mocks/streamResponse.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
export class MockResponse {
private controller!: ReadableStreamDefaultController<Uint8Array>;
private encoder = new TextEncoder();

private stream: ReadableStream<Uint8Array>;
private error: boolean;

constructor(
private data: string,
private delay: number = 300,
error: boolean = false, // 新增参数,默认为false
) {
this.error = error;

this.stream = new ReadableStream({
start: (controller) => {
this.controller = controller;
this.pushData();
if (!this.error) {
// 如果不是错误情况,则开始推送数据
setTimeout(() => this.pushData(), this.delay); // 延迟开始推送数据
}
},
cancel(reason) {
console.log('Stream canceled', reason);
},
});
}
Expand All @@ -27,10 +36,27 @@ export class MockResponse {

this.controller.enqueue(this.encoder.encode(chunk));

setTimeout(() => this.pushData(), this.delay);
if (this.data.length > 0) {
setTimeout(() => this.pushData(), this.delay);
} else {
// 数据全部发送完毕后关闭流
setTimeout(() => this.controller.close(), this.delay);
}
}

getResponse() {
return new Response(this.stream);
getResponse(): Promise<Response> {
return new Promise((resolve) => {
// 使用setTimeout来模拟网络延迟
setTimeout(() => {
if (this.error) {
const errorResponseOptions = { status: 500, statusText: 'Internal Server Error' };

// 返回模拟的网络错误响应,这里我们使用500状态码作为示例
resolve(new Response(null, errorResponseOptions));
} else {
resolve(new Response(this.stream));
}
}, this.delay); // 使用构造函数中设置的delay值作为延迟时间
});
}
}
8 changes: 3 additions & 5 deletions src/ChatItem/components/ErrorContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,16 @@ import { ChatItemProps } from '@/ChatItem';
import { useStyles } from '../style';

export interface ErrorContentProps {
error?: ChatItemProps['error'];
message?: ChatItemProps['errorMessage'];
message?: string;
placement?: ChatItemProps['placement'];
}

const ErrorContent = memo<ErrorContentProps>(({ message, error, placement }) => {
const ErrorContent = memo<ErrorContentProps>(({ message, placement }) => {
const { styles } = useStyles({ placement });

return (
<Flexbox gap={8}>
<Alert className={styles.alert} showIcon type={'error'} {...error} />
{message}
<Alert className={styles.alert} showIcon type={'error'} message={message} />
</Flexbox>
);
});
Expand Down
11 changes: 9 additions & 2 deletions src/ChatItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ const ChatItem = memo<ChatItemProps>((props) => {
messageExtra,
renderMessage,
text,
errorMessage,
chatItemRenderConfig,
renderErrorMessages,
markdownProps,
onDoubleClick,
originData,
Expand All @@ -55,6 +55,8 @@ const ChatItem = memo<ChatItemProps>((props) => {
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixClass = getPrefixCls('pro-chat');

const errorMessage = error?.message;

const avatarDom = useMemo(() => {
if (chatItemRenderConfig?.avatarRender === false) return null;
const dom = (
Expand All @@ -73,7 +75,11 @@ const ChatItem = memo<ChatItemProps>((props) => {
const messageContentDom = useMemo(() => {
if (chatItemRenderConfig?.contentRender === false) return null;
const dom = error ? (
<ErrorContent error={error} message={errorMessage} placement={placement} />
renderErrorMessages ? (
renderErrorMessages(error)
) : (
<ErrorContent message={errorMessage} placement={placement} />
)
) : (
<MessageContent
editing={editing}
Expand Down Expand Up @@ -108,6 +114,7 @@ const ChatItem = memo<ChatItemProps>((props) => {

const actionsDom = useMemo(() => {
if (chatItemRenderConfig?.actionsRender === false) return null;
if (error) return null;
const dom = (
<Actions
actions={actions}
Expand Down
8 changes: 4 additions & 4 deletions src/ChatItem/type.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { AlertProps } from 'antd';
import { ReactNode } from 'react';

import { EditableMessageProps } from '@/EditableMessage';
import { DivProps, MetaData } from '@/types';
import { ChatMessageError, DivProps, MetaData } from '@/types';
import { MarkdownProps } from '@ant-design/pro-editor';

export type WithFalse<T> = T | false;
Expand All @@ -28,8 +27,7 @@ export interface ChatItemProps<T = Record<string, any>> {
/**
* @description Props for Error render
*/
error?: AlertProps;
errorMessage?: ReactNode;
error?: ChatMessageError;
/**
* @description Whether the chat item is in loading state
*/
Expand Down Expand Up @@ -106,5 +104,7 @@ export interface ChatItemProps<T = Record<string, any>> {
>;
};

renderErrorMessages?: (data: ChatMessageError) => ReactNode;

originData?: T;
}
30 changes: 5 additions & 25 deletions src/ChatList/ChatListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FC, ReactNode, useMemo, useState } from 'react';
import { ActionEvent } from '@/ActionIconGroup';
import ChatItem, { type ChatItemProps } from '@/ChatItem';
import { LLMRoleType } from '@/types/llm';
import { ChatMessage } from '@/types/message';
import { ChatMessage, ChatMessageError } from '@/types/message';

import { useRefFunction } from '@/ProChat/hooks/useRefFunction';
import { MarkdownProps } from '@ant-design/pro-editor';
Expand Down Expand Up @@ -50,9 +50,7 @@ export interface ListItemProps<T = Record<string, any>> {
/**
* 渲染错误消息的函数。
*/
renderErrorMessages?: {
[errorType: 'default' | string]: RenderErrorMessage;
};
renderErrorMessages?: (data: ChatMessageError) => ReactNode;
/**
* 渲染列表项的函数。
*/
Expand Down Expand Up @@ -141,7 +139,6 @@ const ChatListItem = (props: ChatListItemProps) => {
markdownProps,
...item
} = props;

const [editing, setEditing] = useState(false);

const { message } = App.useApp();
Expand Down Expand Up @@ -192,22 +189,6 @@ const ChatListItem = (props: ChatListItemProps) => {
return <RenderFunction {...data} />;
});

/**
* 渲染错误消息的函数。
* @param data 聊天消息的数据。
* @returns 渲染错误消息的组件。
*/
const ErrorMessage = useRefFunction(({ data }: { data: ChatMessage }) => {
if (!renderErrorMessages || !item?.error?.type) return;
let RenderFunction;
if (renderErrorMessages?.[item.error.type])
RenderFunction = renderErrorMessages[item.error.type];
if (!RenderFunction && renderErrorMessages?.['default'])
RenderFunction = renderErrorMessages['default'];
if (!RenderFunction) return;
return <RenderFunction {...data} />;
});

/**
* 渲染操作按钮的函数。
* @param data 聊天消息的数据。
Expand Down Expand Up @@ -250,9 +231,7 @@ const ChatListItem = (props: ChatListItemProps) => {
*/
const error = useMemo(() => {
if (!item.error) return;
return {
message: item.error?.message,
};
return item.error;
}, [item.error]);

/**
Expand All @@ -269,7 +248,7 @@ const ChatListItem = (props: ChatListItemProps) => {
editing={editing}
originData={originData}
error={error}
errorMessage={<ErrorMessage data={item} />}
renderErrorMessages={renderErrorMessages}
loading={loading}
message={item.content}
messageExtra={<MessageExtra data={item} />}
Expand Down Expand Up @@ -300,6 +279,7 @@ const ChatListItem = (props: ChatListItemProps) => {
props.loading,
props.id,
(item as any).meta,
item.error,
item.updateAt || item.createAt,
editing,
]);
Expand Down
3 changes: 2 additions & 1 deletion src/ProChat/components/ChatList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface ListProps extends Partial<ChatListProps> {
}

const List = memo<ListProps>(
({ showTitle, itemShouldUpdate, chatItemRenderConfig, markdownProps }) => {
({ showTitle, itemShouldUpdate, chatItemRenderConfig, renderErrorMessages, markdownProps }) => {
const data = useStore(chatSelectors.currentChatsWithGuideMessage, isEqual);
const { localeObject: localeObj } = useProChatLocale();

Expand Down Expand Up @@ -96,6 +96,7 @@ const List = memo<ListProps>(
renderActions={renderActions}
// need support custom Render
renderMessages={renderMessages}
renderErrorMessages={renderErrorMessages}
renderMessagesExtra={renderMessagesExtra}
style={{ marginTop: 24 }}
chatItemRenderConfig={chatItemRenderConfig}
Expand Down
5 changes: 5 additions & 0 deletions src/ProChat/container/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import RcResizeObserver from 'rc-resize-observer';
import { CSSProperties, memo, useContext, useEffect, useRef, useState } from 'react';
import { Flexbox } from 'react-layout-kit';

import { ChatListItemProps } from '@/ChatList/ChatListItem';
import { ConfigProvider } from 'antd';
import ChatList from '../components/ChatList';
import ChatInputArea, { ChatInputAreaProps } from '../components/InputArea';
Expand Down Expand Up @@ -66,6 +67,8 @@ export interface ConversationProps extends ProChatProps<any> {
* @param defaultProps 默认的属性
*/
sendButtonRender?: ChatInputAreaProps['sendButtonRender'];

renderErrorMessages?: ChatListItemProps['renderErrorMessages'];
}

const App = memo<ConversationProps>(
Expand All @@ -80,6 +83,7 @@ const App = memo<ConversationProps>(
inputRender,
chatItemRenderConfig,
backToBottomConfig,
renderErrorMessages,
sendButtonRender,
markdownProps,
}) => {
Expand Down Expand Up @@ -136,6 +140,7 @@ const App = memo<ConversationProps>(
itemShouldUpdate={itemShouldUpdate}
chatItemRenderConfig={chatItemRenderConfig}
markdownProps={markdownProps}
renderErrorMessages={renderErrorMessages}
/>
<ChatScrollAnchor target={ref} />
</div>
Expand Down
9 changes: 8 additions & 1 deletion src/ProChat/container/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ export interface ProChatProps<T extends Record<string, any>> extends ChatProps<T
* @param defaultProps 默认的属性
*/
sendButtonRender?: ConversationProps['sendButtonRender'];

/**
* 聊天出现 Error 错误时候的自定义渲染
* @param defaultDom 默认的 DOM 元素
* @param defaultProps 默认的属性
*/
renderErrorMessages?: ConversationProps['renderErrorMessages'];
/**
* __PRO_CHAT_STORE_DEVTOOLS__ 是一个可选的布尔值或 DevtoolsOptions 对象,用于开启 ProChat 的开发者工具。
*/
Expand Down Expand Up @@ -88,6 +93,7 @@ export function ProChat<T extends Record<string, any> = Record<string, any>>({
appStyle,
inputRender,
markdownProps,
renderErrorMessages,
inputAreaRender,
sendButtonRender,
...props
Expand All @@ -111,6 +117,7 @@ export function ProChat<T extends Record<string, any> = Record<string, any>>({
chatRef={props.chatRef}
showTitle={showTitle}
style={style}
renderErrorMessages={renderErrorMessages}
backToBottomConfig={backToBottomConfig}
className={className}
markdownProps={markdownProps}
Expand Down
2 changes: 1 addition & 1 deletion src/ProChat/demos/doc-mode.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* iframe: 800
* iframe: 1000
* compact: true
*/
import { ProChat } from '@ant-design/pro-chat';
Expand Down
Loading

0 comments on commit 0a5ce85

Please sign in to comment.