Skip to content

Commit

Permalink
♻️ refactor(chat): redesign ChatItem ChatList props, add time sup…
Browse files Browse the repository at this point in the history
…port
  • Loading branch information
canisminor1990 committed Jun 12, 2023
1 parent ecf69a3 commit 6a230e2
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 25 deletions.
4 changes: 3 additions & 1 deletion src/ChatItem/demos/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ChatItem, ChatItemProps, StroyBook, useControls, useCreateStore } from '@lobehub/ui';

import { avatar } from './data';

export default () => {
const store = useCreateStore();
const control: ChatItemProps['alert'] | any = useControls(
Expand All @@ -16,7 +18,7 @@ export default () => {

return (
<StroyBook levaStore={store}>
<ChatItem alert={control} avatar="😅" />
<ChatItem alert={control} avatar={avatar} />
</StroyBook>
);
};
7 changes: 7 additions & 0 deletions src/ChatItem/demos/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { MetaData } from '@/types/meta';

export const avatar: MetaData = {
avatar: '😎',
title: 'Advertiser',
backgroundColor: '#E8DA5A',
};
8 changes: 5 additions & 3 deletions src/ChatItem/demos/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { ChatItem, ChatItemProps, StroyBook, useControls, useCreateStore } from '@lobehub/ui';

import { avatar } from './data';

export default () => {
const store = useCreateStore();
const control: ChatItemProps | any = useControls(
{
avatar: '😅',
name: '',
showTitle: false,
message: {
value:
"要使用 dayjs 的 fromNow 函数,需要先安装 dayjs 库并在代码中引入它。然后,可以使用以下语法来获取当前时间与给定时间之间的相对时间:\n\n```javascript\ndayjs().fromNow();\ndayjs('2021-05-01').fromNow();\n```",
rows: true,
},
time: 1686538950084,
primary: false,
placement: {
value: 'left',
Expand All @@ -28,7 +30,7 @@ export default () => {

return (
<StroyBook levaStore={store}>
<ChatItem {...control} />
<ChatItem avatar={avatar} {...control} />
</StroyBook>
);
};
50 changes: 39 additions & 11 deletions src/ChatItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import { memo } from 'react';

import { Avatar, Icon, Markdown } from '@/index';
import type { DivProps } from '@/types';
import { MetaData } from '@/types/meta';
import { formatTime } from '@/utils/formatTime';

import { useStyles } from './style';

const AVATAR_SIZE = 40;

export interface ChatItemProps extends DivProps {
/**
* @description Weather to show alert and alert config
* @description Whether to show alert and alert config
*/
alert?: AlertProps;
/**
* @description URL of the avatar image
* @description Avatar config
*/
avatar?: string;
avatar: MetaData;
/**
* @description Whether to add spacing between chat items
* @default true
Expand All @@ -32,10 +34,6 @@ export interface ChatItemProps extends DivProps {
* @description The message to be displayed
*/
message?: string;
/**
* @description The name of the chat item
*/
name?: string;
/**
* @description The placement of the chat item
* @default 'left'
Expand All @@ -46,6 +44,15 @@ export interface ChatItemProps extends DivProps {
* @default false
*/
primary?: boolean;
/**
* @description Whether to show name of the chat item
* @default false
*/
showTitle?: boolean;
/**
* @description Time of chat message
*/
time?: number;
/**
* @description The type of chat item
* @default 'block'
Expand All @@ -56,7 +63,7 @@ export interface ChatItemProps extends DivProps {
const ChatItem = memo<ChatItemProps>(
({
className,
name,
title,
primary,
borderSpacing = true,
loading,
Expand All @@ -65,21 +72,42 @@ const ChatItem = memo<ChatItemProps>(
type = 'block',
avatar,
alert,
showTitle,
time,
...props
}) => {
const { cx, styles } = useStyles({ placement, type, name, primary, avatarSize: AVATAR_SIZE });
const { cx, styles } = useStyles({
placement,
type,
title,
primary,
avatarSize: AVATAR_SIZE,
showTitle,
});
return (
<div className={cx(styles.container, className)} {...props}>
<div className={styles.avatarContainer}>
<Avatar avatar={avatar} size={AVATAR_SIZE} />
<Avatar
avatar={avatar.avatar}
background={avatar.backgroundColor}
size={AVATAR_SIZE}
title={avatar.title}
/>
{loading && (
<div className={styles.loading}>
<Icon icon={Loader2} size={{ fontSize: 12, strokeWidth: 3 }} spin />
</div>
)}
</div>
<div className={styles.messageContainer}>
{name && <div className={styles.name}>{name}</div>}
<div className={cx(styles.name, 'chat-item-name')}>
{showTitle ? avatar.title || 'untitled' : null}
{time && (
<span className={cx(type === 'pure' && !showTitle && styles.time, 'chat-item-time')}>
{formatTime(time)}
</span>
)}
</div>
{alert ? (
<Alert showIcon {...alert} />
) : (
Expand Down
44 changes: 40 additions & 4 deletions src/ChatItem/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ import { createStyles } from 'antd-style';

export const useStyles = createStyles(
(
{ cx, css, token },
{ cx, css, token, stylish },
{
placement,
type,
name,
title,
primary,
avatarSize,
showTitle,
}: {
avatarSize: number;
name?: string;
placement?: 'left' | 'right';
primary?: boolean;
showTitle?: boolean;
title?: string;
type?: 'block' | 'pure';
},
) => {
Expand All @@ -29,7 +31,7 @@ export const useStyles = createStyles(
`;

const pureStylish = css`
padding-top: ${name ? 0 : '6px'};
padding-top: ${title ? 0 : '6px'};
`;

const pureContainerStylish = css`
Expand Down Expand Up @@ -61,6 +63,16 @@ export const useStyles = createStyles(
width: 100%;
padding: 12px;
.chat-item-time {
display: none;
}
&:hover {
.chat-item-time {
display: inline-block;
}
}
`,
),
loading: css`
Expand Down Expand Up @@ -96,13 +108,37 @@ export const useStyles = createStyles(
}
`,
name: css`
position: ${showTitle ? 'relative' : 'absolute'};
top: ${showTitle ? 'unset' : '-16px'};
right: ${placement === 'right' ? '0' : 'unset'};
left: ${placement === 'left' ? '0' : 'unset'};
display: flex;
flex-direction: ${placement === 'left' ? 'row' : 'row-reverse'};
gap: 4px;
margin-bottom: 6px;
font-size: 12px;
line-height: 1;
color: ${token.colorTextDescription};
text-align: ${placement === 'left' ? 'left' : 'right'};
`,
time: cx(
stylish.blur,
css`
display: flex;
align-items: center;
justify-content: center;
padding: 4px 6px;
line-height: 1;
background: ${token.colorFillQuaternary};
border-radius: ${token.borderRadius}px;
`,
),
message: cx(
typeStylish,
css`
Expand Down
17 changes: 16 additions & 1 deletion src/ChatList/demos/data.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
import { ChatMessage } from '@/Chat';
import { ChatMessage } from '@/types/chatMessage';

export const data: ChatMessage[] = [
{
id: '1',
meta: {
avatar: 'https://avatars.githubusercontent.com/u/17870709?v=4',
title: 'CanisMinor',
},
role: 'user',
content: 'dayjs 如何使用 fromNow',
createAt: 1686437950084,
updateAt: 1686437950084,
},
{
id: '2',
meta: {
avatar: '😎',
title: 'Advertiser',
backgroundColor: '#E8DA5A',
},
role: 'assistant',
content:
'要使用 dayjs 的 fromNow 函数,需要先安装 dayjs 库并在代码中引入它。然后,可以使用以下语法来获取当前时间与给定时间之间的相对时间:\n\n```javascript\ndayjs().fromNow(); // 获取当前时间的相对时间\ndayjs(\'2021-05-01\').fromNow(); // 获取给定时间的相对时间\n```\n\n第一个示例将返回类似于 "几秒前"、"一分钟前"、"2 天前" 的相对时间字符串,表示当前时间与调用 fromNow 方法时的时间差。第二个示例将返回给定时间与当前时间的相对时间字符串。',
createAt: 1686538950084,
updateAt: 1686538950084,
},
];
1 change: 1 addition & 0 deletions src/ChatList/demos/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default () => {
value: 'chat',
options: ['doc', 'chat'],
},
showTitle: false,
},
{ store },
);
Expand Down
18 changes: 13 additions & 5 deletions src/ChatList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { memo } from 'react';

import { ChatMessage } from '@/Chat';
import { ChatItem } from '@/index';
import { ChatItem, ChatItemProps } from '@/index';
import type { DivProps } from '@/types';
import { ChatMessage } from '@/types/chatMessage';

import { useStyles } from './style';

Expand All @@ -11,25 +11,33 @@ export interface ChatListProps extends DivProps {
* @description Data of chat messages to be displayed
*/
data: ChatMessage[];
/**
* @description Whether to show name of the chat item
* @default false
*/
showTitle?: ChatItemProps['showTitle'];
/**
* @description Type of chat list
* @default 'chat'
*/
type?: 'docs' | 'chat';
}

const ChatList = memo<ChatListProps>(({ className, data, type = 'chat', ...props }) => {
const ChatList = memo<ChatListProps>(({ className, data, type = 'chat', showTitle, ...props }) => {
const { cx, styles } = useStyles();

return (
<div className={cx(styles.container, className)} {...props}>
{data.map((item, index) => (
{data.map((item) => (
<ChatItem
avatar={item.meta}
borderSpacing={type === 'chat'}
key={index}
key={item.id}
message={item.content}
placement={type === 'chat' ? (item.role === 'user' ? 'right' : 'left') : 'left'}
primary={item.role === 'user'}
showTitle={showTitle}
time={item.updateAt || item.createAt}
type={type === 'chat' ? 'block' : 'pure'}
/>
))}
Expand Down
41 changes: 41 additions & 0 deletions src/types/chatMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { LLMRoleType } from './llm';
import { BaseDataModel } from './meta';

/**
* 聊天消息错误对象
*/
export interface ChatMessageError {
/**
* 错误信息
*/
message: string;
status: number;
type: 'general' | 'llm';
}

export interface ChatMessage extends BaseDataModel {
/**
* @title 内容
* @description 消息内容
*/
content: string;

// 扩展字段
extra?: {
// 翻译
translate: {
target: string;
to: string;
};
// 语音
} & Record<string, any>;

parentId?: string;
// 引用
quotaId?: string;
/**
* 角色
* @description 消息发送者的角色
*/
role: LLMRoleType;
}
Loading

1 comment on commit 6a230e2

@vercel
Copy link

@vercel vercel bot commented on 6a230e2 Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lobe-ui – ./

lobe-ui.vercel.app
lobe-ui-git-master-lobehub.vercel.app
ui.lobehub.com
lobe-ui-lobehub.vercel.app

Please sign in to comment.