@lobehub/chat 0.118.4 → 0.118.6

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.
Files changed (91) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +8 -8
  3. package/README.zh-CN.md +8 -8
  4. package/docs/Development/Chat-API.zh-CN.md +90 -52
  5. package/next.config.mjs +0 -1
  6. package/package.json +1 -1
  7. package/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx +1 -1
  8. package/src/features/Conversation/{ChatList/Actions → Actions}/Assistant.tsx +3 -1
  9. package/src/features/Conversation/{ChatList/Actions → Actions}/Error.tsx +3 -1
  10. package/src/features/Conversation/{ChatList/Actions → Actions}/Fallback.tsx +4 -1
  11. package/src/features/Conversation/{ChatList/Actions → Actions}/Function.tsx +4 -1
  12. package/src/features/Conversation/{ChatList/Actions → Actions}/User.tsx +3 -1
  13. package/src/features/Conversation/{ChatList/Actions → Actions}/index.ts +1 -2
  14. package/src/features/Conversation/{ChatList/Error → Error}/InvalidAccess.tsx +2 -1
  15. package/src/features/Conversation/{ChatList/Error → Error}/OpenAPIKey.tsx +1 -1
  16. package/src/features/Conversation/{ChatList/Error → Error}/OpenAiBizError.tsx +2 -1
  17. package/src/features/Conversation/{ChatList/Error → Error}/Plugin/PluginError.tsx +3 -1
  18. package/src/features/Conversation/{ChatList/Error → Error}/Plugin/PluginSettings.tsx +2 -1
  19. package/src/features/Conversation/{ChatList/Error → Error}/index.tsx +1 -1
  20. package/src/features/Conversation/{ChatList/Extras → Extras}/Translate.tsx +1 -1
  21. package/src/features/Conversation/{ChatList/Extras → Extras}/index.ts +1 -2
  22. package/src/features/Conversation/{ChatList/Messages → Messages}/Default.tsx +1 -1
  23. package/src/features/Conversation/{ChatList/Messages → Messages}/User.tsx +1 -1
  24. package/src/features/Conversation/{ChatList/Messages → Messages}/index.ts +5 -5
  25. package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/Loading.tsx +10 -11
  26. package/src/features/Conversation/components/ChatList/ActionsBar.tsx +27 -0
  27. package/src/features/Conversation/components/ChatList/HistoryDivider.tsx +23 -0
  28. package/src/features/Conversation/components/ChatList/Item.tsx +236 -0
  29. package/src/features/Conversation/components/ChatList/index.tsx +88 -0
  30. package/src/features/Conversation/components/ChatList/style.ts +10 -0
  31. package/src/features/Conversation/{ChatList → container}/index.tsx +6 -7
  32. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +43 -0
  33. package/src/features/Conversation/index.tsx +3 -3
  34. package/src/hooks/useIsPWA.ts +3 -4
  35. package/src/store/chat/store.ts +20 -6
  36. package/src/store/middleware/createHyperStorage/index.test.ts +341 -0
  37. package/src/store/middleware/createHyperStorage/index.ts +8 -6
  38. package/src/store/middleware/createHyperStorage/indexedDB.test.ts +64 -0
  39. package/src/store/middleware/createHyperStorage/keyMapper.ts +4 -4
  40. package/src/store/middleware/createHyperStorage/type.ts +6 -2
  41. package/src/store/middleware/createHyperStorage/urlStorage.test.ts +84 -0
  42. package/src/store/middleware/createHyperStorage/urlStorage.ts +25 -8
  43. package/src/store/session/store.ts +24 -9
  44. package/src/utils/matchMedia.ts +10 -0
  45. package/src/utils/screen.ts +14 -0
  46. /package/src/features/Conversation/{ChatList/Actions → Actions}/customAction.ts +0 -0
  47. /package/src/features/Conversation/{ChatList/Error → Error}/ApiKeyForm.tsx +0 -0
  48. /package/src/features/Conversation/{ChatList/Error → Error}/ErrorActionContainer.tsx +0 -0
  49. /package/src/features/Conversation/{ChatList/Error → Error}/style.tsx +0 -0
  50. /package/src/features/Conversation/{ChatList/Extras → Extras}/Assistant.test.tsx +0 -0
  51. /package/src/features/Conversation/{ChatList/Extras → Extras}/Assistant.tsx +0 -0
  52. /package/src/features/Conversation/{ChatList/Extras → Extras}/ExtraContainer.tsx +0 -0
  53. /package/src/features/Conversation/{ChatList/Extras → Extras}/TTS/FilePlayer.tsx +0 -0
  54. /package/src/features/Conversation/{ChatList/Extras → Extras}/TTS/InitPlayer.tsx +0 -0
  55. /package/src/features/Conversation/{ChatList/Extras → Extras}/TTS/Player.tsx +0 -0
  56. /package/src/features/Conversation/{ChatList/Extras → Extras}/TTS/index.tsx +0 -0
  57. /package/src/features/Conversation/{ChatList/Extras → Extras}/User.tsx +0 -0
  58. /package/src/features/Conversation/{ChatList/Extras → Extras}/type.ts +0 -0
  59. /package/src/features/Conversation/{ChatList/Messages → Messages}/Assistant.tsx +0 -0
  60. /package/src/features/Conversation/{ChatList/Messages → Messages}/Function.tsx +0 -0
  61. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Inspector/PluginResultJSON.tsx +0 -0
  62. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Inspector/Settings.tsx +0 -0
  63. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Inspector/index.tsx +0 -0
  64. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Inspector/style.ts +0 -0
  65. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/BuiltinType/index.test.tsx +0 -0
  66. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/BuiltinType/index.tsx +0 -0
  67. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/DefaultType/IFrameRender/index.tsx +0 -0
  68. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/DefaultType/SystemJsRender/index.tsx +0 -0
  69. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/DefaultType/SystemJsRender/utils.ts +0 -0
  70. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/DefaultType/index.tsx +0 -0
  71. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/MarkdownType/index.tsx +0 -0
  72. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/StandaloneType/Iframe.tsx +0 -0
  73. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/StandaloneType/index.tsx +0 -0
  74. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/index.tsx +0 -0
  75. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/useParseContent.ts +0 -0
  76. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/utils/iframeOnReady.test.ts +0 -0
  77. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/utils/iframeOnReady.ts +0 -0
  78. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/utils/listenToPlugin.test.ts +0 -0
  79. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/utils/listenToPlugin.ts +0 -0
  80. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/utils/pluginSettings.test.ts +0 -0
  81. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/utils/pluginSettings.ts +0 -0
  82. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/utils/pluginState.test.ts +0 -0
  83. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/utils/pluginState.ts +0 -0
  84. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/utils/postMessage.test.ts +0 -0
  85. /package/src/features/Conversation/{ChatList/Plugins → Plugins}/Render/utils/postMessage.ts +0 -0
  86. /package/src/features/Conversation/{ChatList/Loading.tsx → components/BubblesLoading.tsx} +0 -0
  87. /package/src/features/Conversation/{ChatList → components}/OTPInput.tsx +0 -0
  88. /package/src/features/Conversation/{ScrollAnchor → components/ScrollAnchor}/index.tsx +0 -0
  89. /package/src/features/Conversation/{ScrollAnchor → components/ScrollAnchor}/useAtBottom.ts +0 -0
  90. /package/src/features/Conversation/{ChatList → components}/SkeletonList.tsx +0 -0
  91. /package/src/features/Conversation/{useInitConversation.ts → hooks/useInitConversation.ts} +0 -0
@@ -0,0 +1,23 @@
1
+ import { Icon, Tag } from '@lobehub/ui';
2
+ import { Divider } from 'antd';
3
+ import { Timer } from 'lucide-react';
4
+ import { memo } from 'react';
5
+
6
+ interface HistoryDividerProps {
7
+ enable?: boolean;
8
+ text?: string;
9
+ }
10
+
11
+ const HistoryDivider = memo<HistoryDividerProps>(({ enable, text }) => {
12
+ if (!enable) return null;
13
+
14
+ return (
15
+ <div style={{ padding: '0 20px' }}>
16
+ <Divider>
17
+ <Tag icon={<Icon icon={Timer} />}>{text || 'History Message'}</Tag>
18
+ </Divider>
19
+ </div>
20
+ );
21
+ });
22
+
23
+ export default HistoryDivider;
@@ -0,0 +1,236 @@
1
+ import { type AlertProps, ChatItem, type ChatItemProps } from '@lobehub/ui';
2
+ import { ActionEvent, copyToClipboard } from '@lobehub/ui';
3
+ import { App } from 'antd';
4
+ import { FC, ReactNode, memo, useCallback, useMemo, useState } from 'react';
5
+
6
+ import { LLMRoleType } from '@/types/llm';
7
+ import { ChatMessage } from '@/types/message';
8
+
9
+ import ActionsBar, { type ActionsBarProps } from './ActionsBar';
10
+
11
+ export type OnMessageChange = (id: string, content: string) => void;
12
+ export type OnActionsClick = (action: ActionEvent, message: ChatMessage) => void;
13
+ export type OnAvatatsClick = (role: RenderRole) => ChatItemProps['onAvatarClick'];
14
+ export type RenderRole = LLMRoleType | 'default' | string;
15
+ export type RenderItem = FC<{ key: string } & ChatMessage & ListItemProps>;
16
+ export type RenderMessage = FC<ChatMessage & { editableContent: ReactNode }>;
17
+ export type RenderMessageExtra = FC<ChatMessage>;
18
+ export interface RenderErrorMessage {
19
+ Render?: FC<ChatMessage>;
20
+ config?: AlertProps;
21
+ }
22
+ export type RenderAction = FC<ActionsBarProps & ChatMessage>;
23
+
24
+ export interface ListItemProps {
25
+ groupNav?: ChatItemProps['avatarAddon'];
26
+ loading?: boolean;
27
+ /**
28
+ * @description 点击操作按钮的回调函数
29
+ */
30
+ onActionsClick?: OnActionsClick;
31
+ onAvatarsClick?: OnAvatatsClick;
32
+ /**
33
+ * @description 消息变化的回调函数
34
+ */
35
+ onMessageChange?: OnMessageChange;
36
+ renderActions?: {
37
+ [actionKey: string]: RenderAction;
38
+ };
39
+ /**
40
+ * @description 渲染错误消息的函数
41
+ */
42
+ renderErrorMessages?: {
43
+ [errorType: 'default' | string]: RenderErrorMessage;
44
+ };
45
+ renderItems?: {
46
+ [role: RenderRole]: RenderItem;
47
+ };
48
+ /**
49
+ * @description 渲染消息的函数
50
+ */
51
+ renderMessages?: {
52
+ [role: RenderRole]: RenderMessage;
53
+ };
54
+ /**
55
+ * @description 渲染消息额外内容的函数
56
+ */
57
+ renderMessagesExtra?: {
58
+ [role: RenderRole]: RenderMessageExtra;
59
+ };
60
+ /**
61
+ * @description 是否显示聊天项的名称
62
+ * @default false
63
+ */
64
+ showTitle?: boolean;
65
+ /**
66
+ * @description 文本内容
67
+ */
68
+ text?: ChatItemProps['text'] &
69
+ ActionsBarProps['text'] & {
70
+ copySuccess?: string;
71
+ history?: string;
72
+ } & {
73
+ [key: string]: string;
74
+ };
75
+ /**
76
+ * @description 聊天列表的类型
77
+ * @default 'chat'
78
+ */
79
+ type?: 'docs' | 'chat';
80
+ }
81
+
82
+ export type ChatListItemProps = ChatMessage & ListItemProps;
83
+
84
+ const Item = memo<ChatListItemProps>((props) => {
85
+ const {
86
+ renderMessagesExtra,
87
+ showTitle,
88
+ onActionsClick,
89
+ onAvatarsClick,
90
+ onMessageChange,
91
+ type,
92
+ text,
93
+ renderMessages,
94
+ renderErrorMessages,
95
+ renderActions,
96
+ loading,
97
+ groupNav,
98
+ renderItems,
99
+ ...item
100
+ } = props;
101
+
102
+ const [editing, setEditing] = useState(false);
103
+
104
+ const { message } = App.useApp();
105
+
106
+ const RenderItem = useMemo(() => {
107
+ if (!renderItems || !item?.role) return;
108
+ let renderFunction;
109
+ if (renderItems?.[item.role]) renderFunction = renderItems[item.role];
110
+ if (!renderFunction && renderItems?.['default']) renderFunction = renderItems['default'];
111
+ if (!renderFunction) return;
112
+ return renderFunction;
113
+ }, [renderItems?.[item.role]]);
114
+
115
+ const RenderMessage = useCallback(
116
+ ({ editableContent, data }: { data: ChatMessage; editableContent: ReactNode }) => {
117
+ if (!renderMessages || !item?.role) return;
118
+ let RenderFunction;
119
+ if (renderMessages?.[item.role]) RenderFunction = renderMessages[item.role];
120
+ if (!RenderFunction && renderMessages?.['default'])
121
+ RenderFunction = renderMessages['default'];
122
+ if (!RenderFunction) return;
123
+ return <RenderFunction {...data} editableContent={editableContent} />;
124
+ },
125
+ [renderMessages?.[item.role]],
126
+ );
127
+
128
+ const MessageExtra = useCallback(
129
+ ({ data }: { data: ChatMessage }) => {
130
+ if (!renderMessagesExtra || !item?.role) return;
131
+ let RenderFunction;
132
+ if (renderMessagesExtra?.[item.role]) RenderFunction = renderMessagesExtra[item.role];
133
+ if (renderMessagesExtra?.['default']) RenderFunction = renderMessagesExtra['default'];
134
+ if (!RenderFunction) return;
135
+ return <RenderFunction {...data} />;
136
+ },
137
+ [renderMessagesExtra?.[item.role]],
138
+ );
139
+
140
+ const ErrorMessage = useCallback(
141
+ ({ data }: { data: ChatMessage }) => {
142
+ if (!renderErrorMessages || !item?.error?.type) return;
143
+ let RenderFunction;
144
+ if (renderErrorMessages?.[item.error.type])
145
+ RenderFunction = renderErrorMessages[item.error.type].Render;
146
+ if (!RenderFunction && renderErrorMessages?.['default'])
147
+ RenderFunction = renderErrorMessages['default'].Render;
148
+ if (!RenderFunction) return;
149
+ return <RenderFunction {...data} />;
150
+ },
151
+ [renderErrorMessages?.[item?.error?.type]],
152
+ );
153
+
154
+ const Actions = useCallback(
155
+ ({ data }: { data: ChatMessage }) => {
156
+ if (!renderActions || !item?.role) return;
157
+ let RenderFunction;
158
+ if (renderActions?.[item.role]) RenderFunction = renderActions[item.role];
159
+ if (renderActions?.['default']) RenderFunction = renderActions['default'];
160
+ if (!RenderFunction) RenderFunction = ActionsBar;
161
+
162
+ const handleActionClick: ListItemProps['onActionsClick'] = async (action, data) => {
163
+ switch (action.key) {
164
+ case 'copy': {
165
+ await copyToClipboard(data.content);
166
+ message.success(text?.copySuccess || 'Copy Success');
167
+ break;
168
+ }
169
+ case 'edit': {
170
+ setEditing(true);
171
+ }
172
+ }
173
+
174
+ onActionsClick?.(action, data);
175
+ };
176
+
177
+ return (
178
+ <RenderFunction
179
+ {...data}
180
+ onActionClick={(actionKey) => handleActionClick?.(actionKey, data)}
181
+ text={text}
182
+ />
183
+ );
184
+ },
185
+ [renderActions?.[item.role], text, onActionsClick],
186
+ );
187
+
188
+ const error = useMemo(() => {
189
+ if (!item.error) return;
190
+ const message = item.error?.message;
191
+ let alertConfig = {};
192
+ if (item.error.type && renderErrorMessages?.[item.error.type]) {
193
+ alertConfig = renderErrorMessages[item.error.type]?.config as AlertProps;
194
+ }
195
+ return {
196
+ message,
197
+ ...alertConfig,
198
+ };
199
+ }, [renderErrorMessages, item.error]);
200
+
201
+ if (RenderItem) return <RenderItem key={item.id} {...props} />;
202
+
203
+ return (
204
+ <ChatItem
205
+ actions={<Actions data={item} />}
206
+ avatar={item.meta}
207
+ avatarAddon={groupNav}
208
+ editing={editing}
209
+ error={error}
210
+ errorMessage={<ErrorMessage data={item} />}
211
+ loading={loading}
212
+ message={item.content}
213
+ messageExtra={<MessageExtra data={item} />}
214
+ onAvatarClick={onAvatarsClick?.(item.role)}
215
+ onChange={(value) => onMessageChange?.(item.id, value)}
216
+ onDoubleClick={(e) => {
217
+ if (item.id === 'default' || item.error) return;
218
+ if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
219
+ setEditing(true);
220
+ }
221
+ }}
222
+ onEditingChange={setEditing}
223
+ placement={type === 'chat' ? (item.role === 'user' ? 'right' : 'left') : 'left'}
224
+ primary={item.role === 'user'}
225
+ renderMessage={(editableContent) => (
226
+ <RenderMessage data={item} editableContent={editableContent} />
227
+ )}
228
+ showTitle={showTitle}
229
+ text={text}
230
+ time={item.updateAt || item.createAt}
231
+ type={type === 'chat' ? 'block' : 'pure'}
232
+ />
233
+ );
234
+ });
235
+
236
+ export default Item;
@@ -0,0 +1,88 @@
1
+ import { Fragment, memo } from 'react';
2
+ import { Flexbox } from 'react-layout-kit';
3
+
4
+ import type { ChatMessage } from '@/types/message';
5
+
6
+ import HistoryDivider from './HistoryDivider';
7
+ import Item, { ListItemProps } from './Item';
8
+ import { useStyles } from './style';
9
+
10
+ export interface ChatListProps extends ListItemProps {
11
+ className?: string;
12
+ /**
13
+ * @description Data of chat messages to be displayed
14
+ */
15
+ data: ChatMessage[];
16
+ enableHistoryCount?: boolean;
17
+ historyCount?: number;
18
+ loadingId?: string;
19
+ }
20
+ export type {
21
+ OnActionsClick,
22
+ OnAvatatsClick,
23
+ OnMessageChange,
24
+ RenderAction,
25
+ RenderErrorMessage,
26
+ RenderItem,
27
+ RenderMessage,
28
+ RenderMessageExtra,
29
+ } from './Item';
30
+
31
+ const ChatList = memo<ChatListProps>(
32
+ ({
33
+ onActionsClick,
34
+ onAvatarsClick,
35
+ renderMessagesExtra,
36
+ className,
37
+ data,
38
+ type = 'chat',
39
+ text,
40
+ showTitle,
41
+ onMessageChange,
42
+ renderMessages,
43
+ renderErrorMessages,
44
+ loadingId,
45
+ renderItems,
46
+ enableHistoryCount,
47
+ renderActions,
48
+ historyCount = 0,
49
+ }) => {
50
+ const { cx, styles } = useStyles();
51
+
52
+ return (
53
+ <Flexbox className={cx(styles.container, className)}>
54
+ {data.map((item, index) => {
55
+ const itemProps = {
56
+ loading: loadingId === item.id,
57
+ onActionsClick,
58
+ onAvatarsClick,
59
+ onMessageChange,
60
+ renderActions,
61
+ renderErrorMessages,
62
+ renderItems,
63
+ renderMessages,
64
+ renderMessagesExtra,
65
+ showTitle,
66
+ text,
67
+ type,
68
+ };
69
+
70
+ const historyLength = data.length;
71
+ const enableHistoryDivider =
72
+ enableHistoryCount &&
73
+ historyLength > historyCount &&
74
+ historyCount === historyLength - index + 1;
75
+
76
+ return (
77
+ <Fragment key={item.id}>
78
+ <HistoryDivider enable={enableHistoryDivider} text={text?.history} />
79
+ <Item {...itemProps} {...item} />
80
+ </Fragment>
81
+ );
82
+ })}
83
+ </Flexbox>
84
+ );
85
+ },
86
+ );
87
+
88
+ export default ChatList;
@@ -0,0 +1,10 @@
1
+ import { createStyles } from 'antd-style';
2
+
3
+ export const useStyles = createStyles(({ css }) => {
4
+ return {
5
+ container: css`
6
+ position: relative;
7
+ margin-top: 24px;
8
+ `,
9
+ };
10
+ });
@@ -1,4 +1,3 @@
1
- import { ChatList } from '@lobehub/ui';
2
1
  import isEqual from 'fast-deep-equal';
3
2
  import { memo } from 'react';
4
3
  import { useHotkeys } from 'react-hotkeys-hook';
@@ -10,11 +9,12 @@ import { chatSelectors } from '@/store/chat/selectors';
10
9
  import { useSessionStore } from '@/store/session';
11
10
  import { agentSelectors } from '@/store/session/selectors';
12
11
 
13
- import { renderActions, useActionsClick } from './Actions';
14
- import { renderErrorMessages } from './Error';
15
- import { renderMessagesExtra } from './Extras';
16
- import { renderMessages, useAvatarsClick } from './Messages';
17
- import SkeletonList from './SkeletonList';
12
+ import { renderActions, useActionsClick } from '../Actions';
13
+ import { renderErrorMessages } from '../Error';
14
+ import { renderMessagesExtra } from '../Extras';
15
+ import { renderMessages, useAvatarsClick } from '../Messages';
16
+ import ChatList from '../components/ChatList';
17
+ import SkeletonList from '../components/SkeletonList';
18
18
 
19
19
  const List = memo(() => {
20
20
  const { t } = useTranslation('common');
@@ -65,7 +65,6 @@ const List = memo(() => {
65
65
  renderErrorMessages={renderErrorMessages}
66
66
  renderMessages={renderMessages}
67
67
  renderMessagesExtra={renderMessagesExtra}
68
- style={{ marginTop: 24 }}
69
68
  text={{
70
69
  cancel: t('cancel'),
71
70
  confirm: t('ok'),
@@ -0,0 +1,43 @@
1
+ import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
2
+ import { Copy, Edit, RotateCw, Trash } from 'lucide-react';
3
+
4
+ interface ChatListActionsBar {
5
+ copy: ActionIconGroupItems;
6
+ del: ActionIconGroupItems;
7
+ divider: { type: 'divider' };
8
+ edit: ActionIconGroupItems;
9
+ regenerate: ActionIconGroupItems;
10
+ }
11
+
12
+ export const useChatListActionsBar = (text?: {
13
+ copy?: string;
14
+ delete?: string;
15
+ edit?: string;
16
+ regenerate?: string;
17
+ }): ChatListActionsBar => {
18
+ return {
19
+ copy: {
20
+ icon: Copy,
21
+ key: 'copy',
22
+ label: text?.copy || 'Copy',
23
+ },
24
+ del: {
25
+ icon: Trash,
26
+ key: 'del',
27
+ label: text?.delete || 'Delete',
28
+ },
29
+ divider: {
30
+ type: 'divider',
31
+ },
32
+ edit: {
33
+ icon: Edit,
34
+ key: 'edit',
35
+ label: text?.edit || 'Edit',
36
+ },
37
+ regenerate: {
38
+ icon: RotateCw,
39
+ key: 'regenerate',
40
+ label: text?.regenerate || 'Regenerate',
41
+ },
42
+ };
43
+ };
@@ -8,9 +8,9 @@ import SafeSpacing from '@/components/SafeSpacing';
8
8
  import { useGlobalStore } from '@/store/global';
9
9
  import { settingsSelectors } from '@/store/global/selectors';
10
10
 
11
- import ChatList from './ChatList';
12
- import ChatScrollAnchor from './ScrollAnchor';
13
- import { useInitConversation } from './useInitConversation';
11
+ import ChatScrollAnchor from './components/ScrollAnchor';
12
+ import ChatList from './container';
13
+ import { useInitConversation } from './hooks/useInitConversation';
14
14
 
15
15
  const useStyles = createStyles(({ css, responsive, stylish, cx }, fontSize: number = 14) =>
16
16
  cx(
@@ -1,13 +1,12 @@
1
1
  import { useEffect, useState } from 'react';
2
2
 
3
+ import { isInStandaloneMode } from '@/utils/matchMedia';
4
+
3
5
  export const useIsPWA = () => {
4
6
  const [isPWA, setIsPWA] = useState(false);
5
7
 
6
8
  useEffect(() => {
7
- const isInStandaloneMode = () => window.matchMedia('(display-mode: standalone)').matches;
8
- if (isInStandaloneMode()) {
9
- setIsPWA(true);
10
- }
9
+ setIsPWA(isInStandaloneMode());
11
10
  }, []);
12
11
 
13
12
  return isPWA;
@@ -5,6 +5,8 @@ import { StateCreator } from 'zustand/vanilla';
5
5
 
6
6
  import { createHyperStorage } from '@/store/middleware/createHyperStorage';
7
7
  import { isDev } from '@/utils/env';
8
+ import { isInStandaloneMode } from '@/utils/matchMedia';
9
+ import { isMobileScreen } from '@/utils/screen';
8
10
 
9
11
  import { ChatStoreState, initialState } from './initialState';
10
12
  import { ChatEnhanceAction, chatEnhance } from './slices/enchance/action';
@@ -40,12 +42,24 @@ const persistOptions: PersistOptions<ChatStore> = {
40
42
  // 手动控制 Hydration ,避免 ssr 报错
41
43
  skipHydration: true,
42
44
 
43
- storage: createHyperStorage({
44
- localStorage: false,
45
- url: {
46
- mode: 'hash',
47
- selectors: [{ activeTopicId: 'topic' }],
48
- },
45
+ storage: createHyperStorage(() => {
46
+ const isMobileAndPWA = isMobileScreen() && isInStandaloneMode();
47
+ const selectors = [{ activeTopicId: 'topic' }];
48
+
49
+ if (isMobileAndPWA)
50
+ return {
51
+ localStorage: {
52
+ selectors,
53
+ },
54
+ };
55
+
56
+ return {
57
+ localStorage: false,
58
+ url: {
59
+ mode: 'hash',
60
+ selectors,
61
+ },
62
+ };
49
63
  }),
50
64
  version: 0,
51
65
  };