@lobehub/chat 1.117.0 → 1.117.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -2
- package/src/components/ChatItem/ChatItem.tsx +183 -0
- package/src/components/ChatItem/components/Actions.tsx +25 -0
- package/src/components/ChatItem/components/Avatar.tsx +50 -0
- package/src/components/ChatItem/components/BorderSpacing.tsx +13 -0
- package/src/components/ChatItem/components/ErrorContent.tsx +24 -0
- package/src/components/ChatItem/components/Loading.tsx +26 -0
- package/src/components/ChatItem/components/MessageContent.tsx +76 -0
- package/src/components/ChatItem/components/Title.tsx +43 -0
- package/src/components/ChatItem/index.ts +2 -0
- package/src/components/ChatItem/style.ts +208 -0
- package/src/components/ChatItem/type.ts +80 -0
- package/src/features/ChatItem/index.tsx +1 -1
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,31 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.117.1](https://github.com/lobehub/lobe-chat/compare/v1.117.0...v1.117.1)
|
6
|
+
|
7
|
+
<sup>Released on **2025-08-29**</sup>
|
8
|
+
|
9
|
+
#### ♻ Code Refactoring
|
10
|
+
|
11
|
+
- **misc**: Move chat item into chat.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Code refactoring
|
19
|
+
|
20
|
+
- **misc**: Move chat item into chat, closes [#8970](https://github.com/lobehub/lobe-chat/issues/8970) ([e09817e](https://github.com/lobehub/lobe-chat/commit/e09817e))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
## [Version 1.117.0](https://github.com/lobehub/lobe-chat/compare/v1.116.4...v1.117.0)
|
6
31
|
|
7
32
|
<sup>Released on **2025-08-29**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.117.
|
3
|
+
"version": "1.117.1",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -300,7 +300,6 @@
|
|
300
300
|
"@types/chroma-js": "^3.1.1",
|
301
301
|
"@types/crypto-js": "^4.2.2",
|
302
302
|
"@types/debug": "^4.1.12",
|
303
|
-
"@types/diff": "^8.0.0",
|
304
303
|
"@types/fs-extra": "^11.0.4",
|
305
304
|
"@types/ip": "^1.1.3",
|
306
305
|
"@types/json-schema": "^7.0.15",
|
@@ -0,0 +1,183 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useResponsive } from 'antd-style';
|
4
|
+
import { memo, useEffect, useRef, useState } from 'react';
|
5
|
+
import { Flexbox } from 'react-layout-kit';
|
6
|
+
|
7
|
+
import Actions from './components/Actions';
|
8
|
+
import Avatar from './components/Avatar';
|
9
|
+
import BorderSpacing from './components/BorderSpacing';
|
10
|
+
import ErrorContent from './components/ErrorContent';
|
11
|
+
import MessageContent from './components/MessageContent';
|
12
|
+
import Title from './components/Title';
|
13
|
+
import { useStyles } from './style';
|
14
|
+
import type { ChatItemProps } from './type';
|
15
|
+
|
16
|
+
const MOBILE_AVATAR_SIZE = 32;
|
17
|
+
|
18
|
+
const ChatItem = memo<ChatItemProps>(
|
19
|
+
({
|
20
|
+
avatarAddon,
|
21
|
+
onAvatarClick,
|
22
|
+
avatarProps,
|
23
|
+
actions,
|
24
|
+
className,
|
25
|
+
primary,
|
26
|
+
loading,
|
27
|
+
message,
|
28
|
+
placeholderMessage = '...',
|
29
|
+
placement = 'left',
|
30
|
+
variant = 'bubble',
|
31
|
+
avatar,
|
32
|
+
error,
|
33
|
+
showTitle,
|
34
|
+
time,
|
35
|
+
editing,
|
36
|
+
onChange,
|
37
|
+
onEditingChange,
|
38
|
+
messageExtra,
|
39
|
+
renderMessage,
|
40
|
+
text,
|
41
|
+
errorMessage,
|
42
|
+
onDoubleClick,
|
43
|
+
fontSize,
|
44
|
+
aboveMessage,
|
45
|
+
belowMessage,
|
46
|
+
markdownProps,
|
47
|
+
actionsWrapWidth = 54,
|
48
|
+
...rest
|
49
|
+
}) => {
|
50
|
+
const { mobile } = useResponsive();
|
51
|
+
const { cx, styles } = useStyles({
|
52
|
+
editing,
|
53
|
+
placement,
|
54
|
+
primary,
|
55
|
+
showTitle,
|
56
|
+
time,
|
57
|
+
title: avatar.title,
|
58
|
+
variant,
|
59
|
+
});
|
60
|
+
|
61
|
+
// 在 ChatItem 组件中添加
|
62
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
63
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
64
|
+
const [layoutMode, setLayoutMode] = useState<'horizontal' | 'vertical'>(
|
65
|
+
variant === 'bubble' ? 'horizontal' : 'vertical',
|
66
|
+
);
|
67
|
+
|
68
|
+
// 使用 ResizeObserver 监控内容和容器尺寸
|
69
|
+
useEffect(() => {
|
70
|
+
if (variant === 'docs') {
|
71
|
+
setLayoutMode('vertical');
|
72
|
+
return;
|
73
|
+
}
|
74
|
+
|
75
|
+
if (!contentRef.current || !containerRef.current) return;
|
76
|
+
|
77
|
+
const observer = new ResizeObserver(() => {
|
78
|
+
if (!contentRef.current || !containerRef.current) return;
|
79
|
+
|
80
|
+
const containerWidth = containerRef.current.clientWidth;
|
81
|
+
const contentWidth = contentRef.current.scrollWidth; // 使用scrollWidth获取实际内容宽度
|
82
|
+
|
83
|
+
// 预留给Actions的最小空间 (根据实际Actions大小调整)
|
84
|
+
|
85
|
+
// 只有当内容宽度 + Actions最小宽度 > 容器宽度时才切换布局
|
86
|
+
setLayoutMode(contentWidth + actionsWrapWidth > containerWidth ? 'vertical' : 'horizontal');
|
87
|
+
});
|
88
|
+
|
89
|
+
observer.observe(contentRef.current);
|
90
|
+
observer.observe(containerRef.current);
|
91
|
+
|
92
|
+
return () => observer.disconnect();
|
93
|
+
}, [variant, actionsWrapWidth]);
|
94
|
+
|
95
|
+
return (
|
96
|
+
<Flexbox
|
97
|
+
className={cx(styles.container, className)}
|
98
|
+
direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
|
99
|
+
gap={mobile ? 6 : 12}
|
100
|
+
{...rest}
|
101
|
+
>
|
102
|
+
<Avatar
|
103
|
+
{...avatarProps}
|
104
|
+
addon={avatarAddon}
|
105
|
+
alt={avatarProps?.alt || avatar.title || 'avatar'}
|
106
|
+
avatar={avatar}
|
107
|
+
loading={loading}
|
108
|
+
onClick={onAvatarClick}
|
109
|
+
placement={placement}
|
110
|
+
size={mobile ? MOBILE_AVATAR_SIZE : undefined}
|
111
|
+
style={{
|
112
|
+
marginTop: 6,
|
113
|
+
...avatarProps?.style,
|
114
|
+
}}
|
115
|
+
/>
|
116
|
+
<Flexbox
|
117
|
+
align={placement === 'left' ? 'flex-start' : 'flex-end'}
|
118
|
+
className={styles.messageContainer}
|
119
|
+
ref={containerRef}
|
120
|
+
>
|
121
|
+
<Title avatar={avatar} placement={placement} showTitle={showTitle} time={time} />
|
122
|
+
{aboveMessage}
|
123
|
+
<Flexbox
|
124
|
+
align={placement === 'left' ? 'flex-start' : 'flex-end'}
|
125
|
+
className={styles.messageContent}
|
126
|
+
data-layout={layoutMode} // 添加数据属性以方便样式选择
|
127
|
+
direction={
|
128
|
+
layoutMode === 'horizontal'
|
129
|
+
? placement === 'left'
|
130
|
+
? 'horizontal'
|
131
|
+
: 'horizontal-reverse'
|
132
|
+
: 'vertical'
|
133
|
+
}
|
134
|
+
gap={8}
|
135
|
+
>
|
136
|
+
<Flexbox ref={contentRef} width={'100%'}>
|
137
|
+
{error && (message === placeholderMessage || !message) ? (
|
138
|
+
<ErrorContent error={error} message={errorMessage} placement={placement} />
|
139
|
+
) : (
|
140
|
+
<MessageContent
|
141
|
+
editing={editing}
|
142
|
+
fontSize={fontSize}
|
143
|
+
markdownProps={markdownProps}
|
144
|
+
message={message}
|
145
|
+
messageExtra={
|
146
|
+
<>
|
147
|
+
{error && (
|
148
|
+
<ErrorContent error={error} message={errorMessage} placement={placement} />
|
149
|
+
)}
|
150
|
+
{messageExtra}
|
151
|
+
</>
|
152
|
+
}
|
153
|
+
onChange={onChange}
|
154
|
+
onDoubleClick={onDoubleClick}
|
155
|
+
onEditingChange={onEditingChange}
|
156
|
+
placement={placement}
|
157
|
+
primary={primary}
|
158
|
+
renderMessage={renderMessage}
|
159
|
+
text={text}
|
160
|
+
variant={variant}
|
161
|
+
/>
|
162
|
+
)}
|
163
|
+
</Flexbox>
|
164
|
+
{actions && (
|
165
|
+
<Actions
|
166
|
+
actions={actions}
|
167
|
+
editing={editing}
|
168
|
+
placement={placement}
|
169
|
+
variant={variant}
|
170
|
+
/>
|
171
|
+
)}
|
172
|
+
</Flexbox>
|
173
|
+
{belowMessage}
|
174
|
+
</Flexbox>
|
175
|
+
{mobile && variant === 'bubble' && <BorderSpacing borderSpacing={MOBILE_AVATAR_SIZE} />}
|
176
|
+
</Flexbox>
|
177
|
+
);
|
178
|
+
},
|
179
|
+
);
|
180
|
+
|
181
|
+
export default ChatItem;
|
182
|
+
|
183
|
+
export type { ChatItemProps } from './type';
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { type Ref, memo } from 'react';
|
2
|
+
import { Flexbox } from 'react-layout-kit';
|
3
|
+
|
4
|
+
import { useStyles } from '../style';
|
5
|
+
import { ChatItemProps } from '../type';
|
6
|
+
|
7
|
+
export interface ActionsProps {
|
8
|
+
actions: ChatItemProps['actions'];
|
9
|
+
editing?: boolean;
|
10
|
+
placement?: ChatItemProps['placement'];
|
11
|
+
ref?: Ref<HTMLDivElement>;
|
12
|
+
variant?: ChatItemProps['variant'];
|
13
|
+
}
|
14
|
+
|
15
|
+
const Actions = memo<ActionsProps>(({ actions, placement, variant, editing, ref }) => {
|
16
|
+
const { styles } = useStyles({ editing, placement, variant });
|
17
|
+
|
18
|
+
return (
|
19
|
+
<Flexbox align={'flex-start'} className={styles.actions} ref={ref} role="menubar">
|
20
|
+
{actions}
|
21
|
+
</Flexbox>
|
22
|
+
);
|
23
|
+
});
|
24
|
+
|
25
|
+
export default Actions;
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import { Avatar as A } from '@lobehub/ui';
|
2
|
+
import { type CSSProperties, memo } from 'react';
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
4
|
+
|
5
|
+
import { useStyles } from '../style';
|
6
|
+
import type { ChatItemProps } from '../type';
|
7
|
+
import Loading from './Loading';
|
8
|
+
|
9
|
+
export interface AvatarProps {
|
10
|
+
addon?: ChatItemProps['avatarAddon'];
|
11
|
+
alt?: string;
|
12
|
+
avatar: ChatItemProps['avatar'];
|
13
|
+
loading?: ChatItemProps['loading'];
|
14
|
+
onClick?: ChatItemProps['onAvatarClick'];
|
15
|
+
placement?: ChatItemProps['placement'];
|
16
|
+
size?: number;
|
17
|
+
style?: CSSProperties;
|
18
|
+
unoptimized?: boolean;
|
19
|
+
}
|
20
|
+
|
21
|
+
const Avatar = memo<AvatarProps>(
|
22
|
+
({ loading, avatar, placement, unoptimized, addon, onClick, size = 40, style, alt }) => {
|
23
|
+
const { styles } = useStyles({ avatarSize: size });
|
24
|
+
const avatarContent = (
|
25
|
+
<div className={styles.avatarContainer} style={style}>
|
26
|
+
<A
|
27
|
+
alt={alt || avatar.title}
|
28
|
+
animation={loading}
|
29
|
+
avatar={avatar.avatar}
|
30
|
+
background={avatar.backgroundColor}
|
31
|
+
onClick={onClick}
|
32
|
+
size={size}
|
33
|
+
title={avatar.title}
|
34
|
+
unoptimized={unoptimized}
|
35
|
+
/>
|
36
|
+
<Loading loading={loading} placement={placement} />
|
37
|
+
</div>
|
38
|
+
);
|
39
|
+
|
40
|
+
if (!addon) return avatarContent;
|
41
|
+
return (
|
42
|
+
<Flexbox align={'center'} className={styles.avatarGroupContainer} gap={8}>
|
43
|
+
{avatarContent}
|
44
|
+
{addon}
|
45
|
+
</Flexbox>
|
46
|
+
);
|
47
|
+
},
|
48
|
+
);
|
49
|
+
|
50
|
+
export default Avatar;
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { memo } from 'react';
|
2
|
+
|
3
|
+
export interface BorderSpacingProps {
|
4
|
+
borderSpacing?: number;
|
5
|
+
}
|
6
|
+
|
7
|
+
const BorderSpacing = memo<BorderSpacingProps>(({ borderSpacing }) => {
|
8
|
+
if (!borderSpacing) return null;
|
9
|
+
|
10
|
+
return <div style={{ flex: 'none', width: borderSpacing }} />;
|
11
|
+
});
|
12
|
+
|
13
|
+
export default BorderSpacing;
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { Alert } from '@lobehub/ui';
|
2
|
+
import { memo } from 'react';
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
4
|
+
|
5
|
+
import { useStyles } from '../style';
|
6
|
+
import { ChatItemProps } from '../type';
|
7
|
+
|
8
|
+
export interface ErrorContentProps {
|
9
|
+
error?: ChatItemProps['error'];
|
10
|
+
message?: ChatItemProps['errorMessage'];
|
11
|
+
placement?: ChatItemProps['placement'];
|
12
|
+
}
|
13
|
+
|
14
|
+
const ErrorContent = memo<ErrorContentProps>(({ message, error, placement }) => {
|
15
|
+
const { styles } = useStyles({ placement });
|
16
|
+
|
17
|
+
return (
|
18
|
+
<Flexbox className={styles.errorContainer}>
|
19
|
+
<Alert closable={false} extra={message} showIcon type={'error'} {...error} />
|
20
|
+
</Flexbox>
|
21
|
+
);
|
22
|
+
});
|
23
|
+
|
24
|
+
export default ErrorContent;
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
2
|
+
import { Loader2 } from 'lucide-react';
|
3
|
+
import { memo } from 'react';
|
4
|
+
import { Flexbox } from 'react-layout-kit';
|
5
|
+
|
6
|
+
import { useStyles } from '../style';
|
7
|
+
import { ChatItemProps } from '../type';
|
8
|
+
|
9
|
+
export interface LoadingProps {
|
10
|
+
loading?: ChatItemProps['loading'];
|
11
|
+
placement?: ChatItemProps['placement'];
|
12
|
+
}
|
13
|
+
|
14
|
+
const Loading = memo<LoadingProps>(({ loading, placement }) => {
|
15
|
+
const { styles } = useStyles({ placement });
|
16
|
+
|
17
|
+
if (!loading) return null;
|
18
|
+
|
19
|
+
return (
|
20
|
+
<Flexbox align={'center'} className={styles.loading} justify={'center'}>
|
21
|
+
<Icon icon={Loader2} size={{ size: 12, strokeWidth: 3 }} spin />
|
22
|
+
</Flexbox>
|
23
|
+
);
|
24
|
+
});
|
25
|
+
|
26
|
+
export default Loading;
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import { MarkdownProps } from '@lobehub/ui';
|
2
|
+
import { EditableMessage } from '@lobehub/ui/chat';
|
3
|
+
import { useResponsive } from 'antd-style';
|
4
|
+
import { type ReactNode, memo } from 'react';
|
5
|
+
import { Flexbox } from 'react-layout-kit';
|
6
|
+
|
7
|
+
import { useStyles } from '../style';
|
8
|
+
import { ChatItemProps } from '../type';
|
9
|
+
|
10
|
+
export interface MessageContentProps {
|
11
|
+
editing?: ChatItemProps['editing'];
|
12
|
+
fontSize?: number;
|
13
|
+
markdownProps?: Omit<MarkdownProps, 'className' | 'style' | 'children'>;
|
14
|
+
message?: ReactNode;
|
15
|
+
messageExtra?: ChatItemProps['messageExtra'];
|
16
|
+
onChange?: ChatItemProps['onChange'];
|
17
|
+
onDoubleClick?: ChatItemProps['onDoubleClick'];
|
18
|
+
onEditingChange?: ChatItemProps['onEditingChange'];
|
19
|
+
placement?: ChatItemProps['placement'];
|
20
|
+
primary?: ChatItemProps['primary'];
|
21
|
+
renderMessage?: ChatItemProps['renderMessage'];
|
22
|
+
text?: ChatItemProps['text'];
|
23
|
+
variant?: ChatItemProps['variant'];
|
24
|
+
}
|
25
|
+
|
26
|
+
const MessageContent = memo<MessageContentProps>(
|
27
|
+
({
|
28
|
+
editing,
|
29
|
+
onChange,
|
30
|
+
onEditingChange,
|
31
|
+
text,
|
32
|
+
message,
|
33
|
+
placement,
|
34
|
+
messageExtra,
|
35
|
+
renderMessage,
|
36
|
+
variant,
|
37
|
+
primary,
|
38
|
+
onDoubleClick,
|
39
|
+
fontSize,
|
40
|
+
markdownProps,
|
41
|
+
}) => {
|
42
|
+
const { cx, styles } = useStyles({ editing, placement, primary, variant });
|
43
|
+
const { mobile } = useResponsive();
|
44
|
+
|
45
|
+
const content = (
|
46
|
+
<EditableMessage
|
47
|
+
classNames={{ input: styles.editingInput }}
|
48
|
+
editButtonSize={'small'}
|
49
|
+
editing={editing}
|
50
|
+
fontSize={fontSize}
|
51
|
+
fullFeaturedCodeBlock
|
52
|
+
markdownProps={markdownProps}
|
53
|
+
onChange={onChange}
|
54
|
+
onEditingChange={onEditingChange}
|
55
|
+
openModal={mobile ? editing : undefined}
|
56
|
+
text={text}
|
57
|
+
value={message ? String(message) : ''}
|
58
|
+
/>
|
59
|
+
);
|
60
|
+
const messageContent = renderMessage ? renderMessage(content) : content;
|
61
|
+
|
62
|
+
return (
|
63
|
+
<Flexbox
|
64
|
+
className={cx(styles.message, editing && styles.editingContainer)}
|
65
|
+
onDoubleClick={onDoubleClick}
|
66
|
+
>
|
67
|
+
{messageContent}
|
68
|
+
{messageExtra && !editing ? (
|
69
|
+
<div className={styles.messageExtra}>{messageExtra}</div>
|
70
|
+
) : null}
|
71
|
+
</Flexbox>
|
72
|
+
);
|
73
|
+
},
|
74
|
+
);
|
75
|
+
|
76
|
+
export default MessageContent;
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import dayjs from 'dayjs';
|
2
|
+
import { memo } from 'react';
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
4
|
+
|
5
|
+
import { useStyles } from '../style';
|
6
|
+
import { ChatItemProps } from '../type';
|
7
|
+
|
8
|
+
export interface TitleProps {
|
9
|
+
avatar: ChatItemProps['avatar'];
|
10
|
+
placement?: ChatItemProps['placement'];
|
11
|
+
showTitle?: ChatItemProps['showTitle'];
|
12
|
+
time?: ChatItemProps['time'];
|
13
|
+
}
|
14
|
+
|
15
|
+
const formatTime = (time: number): string => {
|
16
|
+
const now = dayjs();
|
17
|
+
const target = dayjs(time);
|
18
|
+
|
19
|
+
if (target.isSame(now, 'day')) {
|
20
|
+
return target.format('HH:mm:ss');
|
21
|
+
} else if (target.isSame(now, 'year')) {
|
22
|
+
return target.format('MM-DD HH:mm:ss');
|
23
|
+
} else {
|
24
|
+
return target.format('YYYY-MM-DD HH:mm:ss');
|
25
|
+
}
|
26
|
+
};
|
27
|
+
|
28
|
+
const Title = memo<TitleProps>(({ showTitle, placement, time, avatar }) => {
|
29
|
+
const { styles } = useStyles({ placement, showTitle, time });
|
30
|
+
|
31
|
+
return (
|
32
|
+
<Flexbox
|
33
|
+
className={styles.name}
|
34
|
+
direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
|
35
|
+
gap={4}
|
36
|
+
>
|
37
|
+
{showTitle ? avatar.title || 'untitled' : undefined}
|
38
|
+
{time && <time>{formatTime(time)}</time>}
|
39
|
+
</Flexbox>
|
40
|
+
);
|
41
|
+
});
|
42
|
+
|
43
|
+
export default Title;
|
@@ -0,0 +1,208 @@
|
|
1
|
+
import { createStyles } from 'antd-style';
|
2
|
+
import { rgba } from 'polished';
|
3
|
+
|
4
|
+
export const useStyles = createStyles(
|
5
|
+
(
|
6
|
+
{ cx, css, token, responsive },
|
7
|
+
{
|
8
|
+
placement,
|
9
|
+
variant,
|
10
|
+
title,
|
11
|
+
avatarSize,
|
12
|
+
editing,
|
13
|
+
time,
|
14
|
+
}: {
|
15
|
+
avatarSize?: number;
|
16
|
+
editing?: boolean;
|
17
|
+
placement?: 'left' | 'right';
|
18
|
+
primary?: boolean;
|
19
|
+
showTitle?: boolean;
|
20
|
+
time?: number;
|
21
|
+
title?: string;
|
22
|
+
variant?: 'bubble' | 'docs';
|
23
|
+
},
|
24
|
+
) => {
|
25
|
+
const blockStylish = css`
|
26
|
+
padding-block: 8px;
|
27
|
+
padding-inline: 12px;
|
28
|
+
border: 1px solid ${rgba(token.colorBorderSecondary, 0.66)};
|
29
|
+
border-radius: ${token.borderRadiusLG}px;
|
30
|
+
|
31
|
+
background-color: ${token.colorBgContainer};
|
32
|
+
`;
|
33
|
+
|
34
|
+
const rawStylish = css`
|
35
|
+
padding-block-start: ${title ? 0 : '6px'};
|
36
|
+
`;
|
37
|
+
|
38
|
+
const rawContainerStylish = css`
|
39
|
+
margin-block-end: -16px;
|
40
|
+
transition: background-color 100ms ${token.motionEaseOut};
|
41
|
+
`;
|
42
|
+
|
43
|
+
const typeStylish = variant === 'bubble' ? blockStylish : rawStylish;
|
44
|
+
|
45
|
+
const editingStylish =
|
46
|
+
editing &&
|
47
|
+
css`
|
48
|
+
width: 100%;
|
49
|
+
`;
|
50
|
+
|
51
|
+
return {
|
52
|
+
actions: cx(
|
53
|
+
css`
|
54
|
+
flex: none;
|
55
|
+
align-self: ${variant === 'bubble'
|
56
|
+
? 'flex-end'
|
57
|
+
: placement === 'left'
|
58
|
+
? 'flex-start'
|
59
|
+
: 'flex-end'};
|
60
|
+
justify-content: ${placement === 'left' ? 'flex-end' : 'flex-start'};
|
61
|
+
`,
|
62
|
+
editing &&
|
63
|
+
css`
|
64
|
+
pointer-events: none !important;
|
65
|
+
opacity: 0 !important;
|
66
|
+
`,
|
67
|
+
),
|
68
|
+
avatarContainer: css`
|
69
|
+
position: relative;
|
70
|
+
flex: none;
|
71
|
+
width: ${avatarSize}px;
|
72
|
+
height: ${avatarSize}px;
|
73
|
+
`,
|
74
|
+
avatarGroupContainer: css`
|
75
|
+
width: ${avatarSize}px;
|
76
|
+
`,
|
77
|
+
container: cx(
|
78
|
+
variant === 'docs' && rawContainerStylish,
|
79
|
+
css`
|
80
|
+
position: relative;
|
81
|
+
|
82
|
+
width: 100%;
|
83
|
+
max-width: 100vw;
|
84
|
+
padding-block: 24px 12px;
|
85
|
+
padding-inline: 12px;
|
86
|
+
|
87
|
+
time {
|
88
|
+
display: inline-block;
|
89
|
+
white-space: nowrap;
|
90
|
+
}
|
91
|
+
|
92
|
+
div[role='menubar'] {
|
93
|
+
display: flex;
|
94
|
+
}
|
95
|
+
|
96
|
+
time,
|
97
|
+
div[role='menubar'] {
|
98
|
+
pointer-events: none;
|
99
|
+
opacity: 0;
|
100
|
+
transition: opacity 200ms ${token.motionEaseOut};
|
101
|
+
}
|
102
|
+
|
103
|
+
&:hover {
|
104
|
+
time,
|
105
|
+
div[role='menubar'] {
|
106
|
+
pointer-events: unset;
|
107
|
+
opacity: 1;
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
${responsive.mobile} {
|
112
|
+
padding-block-start: ${variant === 'docs' ? '16px' : '12px'};
|
113
|
+
padding-inline: 8px;
|
114
|
+
}
|
115
|
+
`,
|
116
|
+
),
|
117
|
+
editingContainer: cx(
|
118
|
+
editingStylish,
|
119
|
+
css`
|
120
|
+
padding-block: 8px 12px;
|
121
|
+
padding-inline: 12px;
|
122
|
+
border: 1px solid ${token.colorBorderSecondary};
|
123
|
+
|
124
|
+
&:active,
|
125
|
+
&:hover {
|
126
|
+
border-color: ${token.colorBorder};
|
127
|
+
}
|
128
|
+
`,
|
129
|
+
variant === 'docs' &&
|
130
|
+
css`
|
131
|
+
border-radius: ${token.borderRadius}px;
|
132
|
+
background: ${token.colorFillQuaternary};
|
133
|
+
`,
|
134
|
+
),
|
135
|
+
editingInput: css`
|
136
|
+
width: 100%;
|
137
|
+
`,
|
138
|
+
errorContainer: css`
|
139
|
+
position: relative;
|
140
|
+
overflow: hidden;
|
141
|
+
width: 100%;
|
142
|
+
`,
|
143
|
+
|
144
|
+
loading: css`
|
145
|
+
position: absolute;
|
146
|
+
inset-block-end: 0;
|
147
|
+
inset-inline: ${placement === 'right' ? '-4px' : 'unset'}
|
148
|
+
${placement === 'left' ? '-4px' : 'unset'};
|
149
|
+
|
150
|
+
width: 16px;
|
151
|
+
height: 16px;
|
152
|
+
border-radius: 50%;
|
153
|
+
|
154
|
+
color: ${token.colorBgLayout};
|
155
|
+
|
156
|
+
background: ${token.colorPrimary};
|
157
|
+
`,
|
158
|
+
message: cx(
|
159
|
+
typeStylish,
|
160
|
+
css`
|
161
|
+
position: relative;
|
162
|
+
overflow: hidden;
|
163
|
+
max-width: 100%;
|
164
|
+
|
165
|
+
${responsive.mobile} {
|
166
|
+
width: 100%;
|
167
|
+
}
|
168
|
+
`,
|
169
|
+
),
|
170
|
+
messageContainer: cx(
|
171
|
+
editingStylish,
|
172
|
+
css`
|
173
|
+
position: relative;
|
174
|
+
overflow: hidden;
|
175
|
+
max-width: 100%;
|
176
|
+
margin-block-start: ${time ? -16 : 0}px;
|
177
|
+
|
178
|
+
${responsive.mobile} {
|
179
|
+
overflow-x: auto;
|
180
|
+
}
|
181
|
+
`,
|
182
|
+
),
|
183
|
+
messageContent: cx(
|
184
|
+
editingStylish,
|
185
|
+
css`
|
186
|
+
position: relative;
|
187
|
+
overflow: hidden;
|
188
|
+
max-width: 100%;
|
189
|
+
|
190
|
+
${responsive.mobile} {
|
191
|
+
flex-direction: column !important;
|
192
|
+
}
|
193
|
+
`,
|
194
|
+
),
|
195
|
+
messageExtra: cx('message-extra'),
|
196
|
+
name: css`
|
197
|
+
pointer-events: none;
|
198
|
+
|
199
|
+
margin-block-end: 6px;
|
200
|
+
|
201
|
+
font-size: 12px;
|
202
|
+
line-height: 1;
|
203
|
+
color: ${token.colorTextDescription};
|
204
|
+
text-align: ${placement === 'left' ? 'left' : 'right'};
|
205
|
+
`,
|
206
|
+
};
|
207
|
+
},
|
208
|
+
);
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import { AlertProps, AvatarProps, DivProps, MarkdownProps } from '@lobehub/ui';
|
2
|
+
import { EditableMessageProps, MetaData } from '@lobehub/ui/chat';
|
3
|
+
import { ReactNode } from 'react';
|
4
|
+
import { FlexboxProps } from 'react-layout-kit';
|
5
|
+
|
6
|
+
export interface ChatItemProps extends Omit<FlexboxProps, 'children' | 'onChange'> {
|
7
|
+
aboveMessage?: ReactNode;
|
8
|
+
/**
|
9
|
+
* @description Actions to be displayed in the chat item
|
10
|
+
*/
|
11
|
+
actions?: ReactNode;
|
12
|
+
actionsWrapWidth?: number;
|
13
|
+
/**
|
14
|
+
* @description Metadata for the avatar
|
15
|
+
*/
|
16
|
+
avatar: MetaData;
|
17
|
+
avatarAddon?: ReactNode;
|
18
|
+
avatarProps?: AvatarProps;
|
19
|
+
belowMessage?: ReactNode;
|
20
|
+
/**
|
21
|
+
* @description Whether the chat item is in editing mode
|
22
|
+
*/
|
23
|
+
editing?: boolean;
|
24
|
+
/**
|
25
|
+
* @description Props for Error render
|
26
|
+
*/
|
27
|
+
error?: AlertProps;
|
28
|
+
errorMessage?: ReactNode;
|
29
|
+
fontSize?: number;
|
30
|
+
/**
|
31
|
+
* @description Whether the chat item is in loading state
|
32
|
+
*/
|
33
|
+
loading?: boolean;
|
34
|
+
markdownProps?: Omit<MarkdownProps, 'className' | 'style' | 'children'>;
|
35
|
+
/**
|
36
|
+
* @description The message content of the chat item
|
37
|
+
*/
|
38
|
+
message?: ReactNode;
|
39
|
+
messageExtra?: ReactNode;
|
40
|
+
onAvatarClick?: () => void;
|
41
|
+
/**
|
42
|
+
* @description Callback when the message content changes
|
43
|
+
* @param value - The new message content
|
44
|
+
*/
|
45
|
+
onChange?: (value: string) => void;
|
46
|
+
onDoubleClick?: DivProps['onDoubleClick'];
|
47
|
+
/**
|
48
|
+
* @description Callback when the editing mode changes
|
49
|
+
* @param editing - The new editing mode
|
50
|
+
*/
|
51
|
+
onEditingChange?: (editing: boolean) => void;
|
52
|
+
/**
|
53
|
+
* @default "..."
|
54
|
+
*/
|
55
|
+
placeholderMessage?: string;
|
56
|
+
/**
|
57
|
+
* @description The placement of the chat item
|
58
|
+
* @default 'left'
|
59
|
+
*/
|
60
|
+
placement?: 'left' | 'right';
|
61
|
+
/**
|
62
|
+
* @description Whether the chat item is primary
|
63
|
+
*/
|
64
|
+
primary?: boolean;
|
65
|
+
renderMessage?: (content: ReactNode) => ReactNode;
|
66
|
+
/**
|
67
|
+
* @description Whether to show the title of the chat item
|
68
|
+
*/
|
69
|
+
showTitle?: boolean;
|
70
|
+
text?: EditableMessageProps['text'];
|
71
|
+
/**
|
72
|
+
* @description The timestamp of the chat item
|
73
|
+
*/
|
74
|
+
time?: number;
|
75
|
+
/**
|
76
|
+
* @description The type of the chat item
|
77
|
+
* @default 'bubble'
|
78
|
+
*/
|
79
|
+
variant?: 'bubble' | 'docs';
|
80
|
+
}
|
@@ -1,9 +1,9 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import { ChatItemProps, ChatItem as ChatItemRaw } from '@lobehub/ui/chat';
|
4
3
|
import isEqual from 'fast-deep-equal';
|
5
4
|
import { memo, useMemo } from 'react';
|
6
5
|
|
6
|
+
import { ChatItemProps, ChatItem as ChatItemRaw } from '@/components/ChatItem';
|
7
7
|
import { isDesktop } from '@/const/version';
|
8
8
|
import { useElectronStore } from '@/store/electron';
|
9
9
|
import { electronSyncSelectors } from '@/store/electron/selectors';
|