@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 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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Move chat item into chat."
6
+ ]
7
+ },
8
+ "date": "2025-08-29",
9
+ "version": "1.117.1"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "features": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.117.0",
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,2 @@
1
+ export { default as ChatItem } from './ChatItem';
2
+ export type * from './type';
@@ -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';