@lobehub/chat 1.142.2 → 1.142.4

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 (107) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +8 -8
  3. package/README.zh-CN.md +8 -8
  4. package/changelog/v1.json +18 -0
  5. package/docs/development/database-schema.dbml +1 -0
  6. package/locales/ar/chat.json +4 -4
  7. package/locales/ar/file.json +1 -0
  8. package/locales/ar/models.json +1 -1
  9. package/locales/bg-BG/chat.json +4 -4
  10. package/locales/bg-BG/file.json +1 -0
  11. package/locales/bg-BG/models.json +1 -1
  12. package/locales/de-DE/chat.json +4 -4
  13. package/locales/de-DE/file.json +1 -0
  14. package/locales/de-DE/models.json +1 -1
  15. package/locales/en-US/chat.json +4 -4
  16. package/locales/en-US/file.json +1 -0
  17. package/locales/en-US/models.json +1 -1
  18. package/locales/es-ES/chat.json +4 -4
  19. package/locales/es-ES/file.json +1 -0
  20. package/locales/es-ES/models.json +1 -1
  21. package/locales/fa-IR/chat.json +4 -4
  22. package/locales/fa-IR/file.json +1 -0
  23. package/locales/fa-IR/models.json +1 -1
  24. package/locales/fr-FR/chat.json +4 -4
  25. package/locales/fr-FR/file.json +1 -0
  26. package/locales/fr-FR/models.json +1 -1
  27. package/locales/it-IT/chat.json +4 -4
  28. package/locales/it-IT/file.json +1 -0
  29. package/locales/ja-JP/chat.json +4 -4
  30. package/locales/ja-JP/file.json +1 -0
  31. package/locales/ja-JP/models.json +1 -1
  32. package/locales/ko-KR/chat.json +4 -4
  33. package/locales/ko-KR/file.json +1 -0
  34. package/locales/ko-KR/models.json +1 -1
  35. package/locales/nl-NL/chat.json +4 -4
  36. package/locales/nl-NL/file.json +1 -0
  37. package/locales/nl-NL/models.json +1 -1
  38. package/locales/pl-PL/chat.json +4 -4
  39. package/locales/pl-PL/file.json +1 -0
  40. package/locales/pl-PL/models.json +1 -1
  41. package/locales/pt-BR/chat.json +4 -4
  42. package/locales/pt-BR/file.json +1 -0
  43. package/locales/ru-RU/chat.json +4 -4
  44. package/locales/ru-RU/file.json +1 -0
  45. package/locales/ru-RU/models.json +1 -1
  46. package/locales/tr-TR/chat.json +4 -4
  47. package/locales/tr-TR/file.json +1 -0
  48. package/locales/tr-TR/models.json +1 -1
  49. package/locales/vi-VN/chat.json +4 -4
  50. package/locales/vi-VN/file.json +1 -0
  51. package/locales/vi-VN/models.json +1 -1
  52. package/locales/zh-CN/chat.json +4 -4
  53. package/locales/zh-CN/file.json +1 -0
  54. package/locales/zh-TW/chat.json +4 -4
  55. package/locales/zh-TW/file.json +1 -0
  56. package/locales/zh-TW/models.json +1 -1
  57. package/package.json +3 -2
  58. package/packages/const/src/file.ts +2 -0
  59. package/packages/database/migrations/0039_add_editor_data.sql +1 -0
  60. package/packages/database/migrations/meta/0039_snapshot.json +7586 -0
  61. package/packages/database/migrations/meta/_journal.json +7 -0
  62. package/packages/database/src/core/migrations.json +6 -0
  63. package/packages/database/src/schemas/document.ts +2 -0
  64. package/packages/database/src/utils/__tests__/groupMessages.test.ts +989 -0
  65. package/packages/database/src/utils/groupMessages.ts +359 -0
  66. package/packages/memory-extract/.env.example +3 -0
  67. package/packages/memory-extract/package.json +21 -0
  68. package/packages/memory-extract/vitest.config.mts +10 -0
  69. package/packages/model-runtime/src/core/streams/protocol.ts +3 -3
  70. package/packages/model-runtime/src/types/chat.ts +2 -2
  71. package/packages/obervability-otel/package.json +7 -7
  72. package/packages/types/src/message/common/base.ts +0 -1
  73. package/packages/types/src/message/common/metadata.ts +5 -5
  74. package/packages/types/src/message/common/tools.ts +17 -0
  75. package/packages/types/src/message/db/item.ts +23 -17
  76. package/packages/types/src/message/ui/chat.ts +22 -66
  77. package/packages/types/src/message/ui/index.ts +1 -0
  78. package/packages/types/src/message/ui/params.ts +65 -0
  79. package/packages/types/src/tool/builtin.ts +34 -0
  80. package/packages/types/src/tool/intervention.ts +39 -0
  81. package/packages/utils/src/fetch/fetchSSE.ts +4 -4
  82. package/renovate.json +14 -2
  83. package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/index.tsx +7 -0
  84. package/src/app/[variants]/(main)/settings/provider/features/ModelList/ModelConfigModal/index.tsx +7 -0
  85. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/Checker.tsx +7 -2
  86. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +21 -1
  87. package/src/features/ChatItem/components/Title.tsx +4 -3
  88. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +3 -16
  89. package/src/features/Conversation/Messages/Assistant/index.tsx +3 -3
  90. package/src/features/Conversation/{utils.test.ts → utils/markdown.test.ts} +1 -1
  91. package/src/features/Conversation/{utils.ts → utils/markdown.ts} +1 -1
  92. package/src/features/FileManager/FileList/FileListItem/index.tsx +3 -2
  93. package/src/features/FileManager/FileList/MasonryFileItem/MasonryItemWrapper.tsx +1 -1
  94. package/src/features/FileManager/FileList/MasonryFileItem/index.tsx +2 -4
  95. package/src/features/FileManager/FileList/MasonrySkeleton.tsx +11 -5
  96. package/src/features/FileManager/FileList/ToolBar/MultiSelectActions.tsx +1 -1
  97. package/src/features/FileManager/FileList/index.tsx +51 -9
  98. package/src/features/ModelSwitchPanel/index.tsx +1 -0
  99. package/src/locales/default/chat.ts +4 -4
  100. package/src/locales/default/file.ts +1 -0
  101. package/src/store/file/slices/fileManager/action.test.ts +136 -1
  102. package/src/store/file/slices/fileManager/action.ts +30 -8
  103. package/src/tools/web-browsing/Render/PageContent/index.tsx +2 -2
  104. package/src/tools/web-browsing/Render/index.tsx +5 -4
  105. package/src/utils/unzipFile.test.ts +128 -0
  106. package/src/utils/unzipFile.ts +122 -0
  107. package/vitest.config.mts +1 -0
@@ -1,4 +1,5 @@
1
1
  export * from './chat';
2
2
  export * from './extra';
3
+ export * from './params';
3
4
  export * from './rag';
4
5
  export * from './video';
@@ -0,0 +1,65 @@
1
+ import { UploadFileItem } from '../../files';
2
+ import { MessageSemanticSearchChunk } from '../../rag';
3
+ import { ChatMessageError } from '../common/base';
4
+ import { UIChatMessage, UIMessageRoleType } from './chat';
5
+
6
+ export interface CreateMessageParams
7
+ extends Partial<Omit<UIChatMessage, 'content' | 'role' | 'topicId' | 'chunksList'>> {
8
+ content: string;
9
+ error?: ChatMessageError | null;
10
+ fileChunks?: MessageSemanticSearchChunk[];
11
+ files?: string[];
12
+ fromModel?: string;
13
+ fromProvider?: string;
14
+ groupId?: string;
15
+ role: UIMessageRoleType;
16
+ sessionId: string;
17
+ targetId?: string | null;
18
+ threadId?: string | null;
19
+ topicId?: string;
20
+ traceId?: string;
21
+ }
22
+
23
+ export interface SendMessageParams {
24
+ /**
25
+ * create a thread
26
+ */
27
+ createThread?: boolean;
28
+ files?: UploadFileItem[];
29
+ /**
30
+ *
31
+ * https://github.com/lobehub/lobe-chat/pull/2086
32
+ */
33
+ isWelcomeQuestion?: boolean;
34
+ message: string;
35
+ /**
36
+ * Additional metadata for the message (e.g., mentioned users)
37
+ */
38
+ metadata?: Record<string, any>;
39
+ onlyAddUserMessage?: boolean;
40
+ }
41
+
42
+ export interface SendThreadMessageParams {
43
+ /**
44
+ * create a thread
45
+ */
46
+ createNewThread?: boolean;
47
+ // files?: UploadFileItem[];
48
+ message: string;
49
+ onlyAddUserMessage?: boolean;
50
+ }
51
+
52
+ export interface SendGroupMessageParams {
53
+ files?: UploadFileItem[];
54
+ groupId: string;
55
+ message: string;
56
+ /**
57
+ * Additional metadata for the message (e.g., mentioned users)
58
+ */
59
+ metadata?: Record<string, any>;
60
+ onlyAddUserMessage?: boolean;
61
+ /**
62
+ * for group chat
63
+ */
64
+ targetMemberId?: string | null;
65
+ }
@@ -1,5 +1,7 @@
1
1
  import { ReactNode } from 'react';
2
+ import { z } from 'zod';
2
3
 
4
+ import { HumanInterventionConfigSchema, HumanInterventionPolicySchema } from './intervention';
3
5
  import type { HumanInterventionConfig, HumanInterventionPolicy } from './intervention';
4
6
 
5
7
  interface Meta {
@@ -26,6 +28,14 @@ interface Meta {
26
28
  tags?: string[];
27
29
  title: string;
28
30
  }
31
+
32
+ const MetaSchema = z.object({
33
+ avatar: z.string().optional(),
34
+ description: z.string().optional(),
35
+ tags: z.array(z.string()).optional(),
36
+ title: z.string(),
37
+ });
38
+
29
39
  export interface LobeChatPluginApi {
30
40
  description: string;
31
41
  /**
@@ -46,6 +56,14 @@ export interface LobeChatPluginApi {
46
56
  url?: string;
47
57
  }
48
58
 
59
+ export const LobeChatPluginApiSchema = z.object({
60
+ description: z.string(),
61
+ humanIntervention: HumanInterventionConfigSchema.optional(),
62
+ name: z.string(),
63
+ parameters: z.record(z.string(), z.any()),
64
+ url: z.string().optional(),
65
+ });
66
+
49
67
  export interface BuiltinToolManifest {
50
68
  api: LobeChatPluginApi[];
51
69
 
@@ -74,6 +92,15 @@ export interface BuiltinToolManifest {
74
92
  type?: 'builtin';
75
93
  }
76
94
 
95
+ export const BuiltinToolManifestSchema = z.object({
96
+ api: z.array(LobeChatPluginApiSchema),
97
+ humanIntervention: HumanInterventionPolicySchema.optional(),
98
+ identifier: z.string(),
99
+ meta: MetaSchema,
100
+ systemRole: z.string(),
101
+ type: z.literal('builtin').optional(),
102
+ });
103
+
77
104
  export interface LobeBuiltinTool {
78
105
  hidden?: boolean;
79
106
  identifier: string;
@@ -81,6 +108,13 @@ export interface LobeBuiltinTool {
81
108
  type: 'builtin';
82
109
  }
83
110
 
111
+ export const LobeBuiltinToolSchema = z.object({
112
+ hidden: z.boolean().optional(),
113
+ identifier: z.string(),
114
+ manifest: BuiltinToolManifestSchema,
115
+ type: z.literal('builtin'),
116
+ });
117
+
84
118
  export interface BuiltinRenderProps<Content = any, Arguments = any, State = any> {
85
119
  apiName?: string;
86
120
  args: Arguments;
@@ -1,3 +1,5 @@
1
+ import { z } from 'zod';
2
+
1
3
  /**
2
4
  * Human Intervention Policy
3
5
  */
@@ -6,6 +8,8 @@ export type HumanInterventionPolicy =
6
8
  | 'always' // Always require intervention
7
9
  | 'first'; // Require intervention on first call only
8
10
 
11
+ export const HumanInterventionPolicySchema = z.enum(['never', 'always', 'first']);
12
+
9
13
  /**
10
14
  * Argument Matcher for parameter-level filtering
11
15
  * Supports wildcard patterns, prefix matching, and regex
@@ -22,6 +26,14 @@ export type ArgumentMatcher =
22
26
  type: 'exact' | 'prefix' | 'wildcard' | 'regex';
23
27
  };
24
28
 
29
+ export const ArgumentMatcherSchema: z.ZodType<ArgumentMatcher> = z.union([
30
+ z.string(),
31
+ z.object({
32
+ pattern: z.string(),
33
+ type: z.enum(['exact', 'prefix', 'wildcard', 'regex']),
34
+ }),
35
+ ]);
36
+
25
37
  /**
26
38
  * Human Intervention Rule
27
39
  * Used for parameter-level control of intervention behavior
@@ -42,6 +54,11 @@ export interface HumanInterventionRule {
42
54
  policy: HumanInterventionPolicy;
43
55
  }
44
56
 
57
+ export const HumanInterventionRuleSchema: z.ZodType<HumanInterventionRule> = z.object({
58
+ match: z.record(z.string(), ArgumentMatcherSchema).optional(),
59
+ policy: HumanInterventionPolicySchema,
60
+ });
61
+
45
62
  /**
46
63
  * Human Intervention Configuration
47
64
  * Can be either:
@@ -54,6 +71,11 @@ export interface HumanInterventionRule {
54
71
  */
55
72
  export type HumanInterventionConfig = HumanInterventionPolicy | HumanInterventionRule[];
56
73
 
74
+ export const HumanInterventionConfigSchema: z.ZodType<HumanInterventionConfig> = z.union([
75
+ HumanInterventionPolicySchema,
76
+ z.array(HumanInterventionRuleSchema),
77
+ ]);
78
+
57
79
  /**
58
80
  * Human Intervention Response
59
81
  * User's response to an intervention request
@@ -85,6 +107,16 @@ export interface HumanInterventionResponse {
85
107
  };
86
108
  }
87
109
 
110
+ export const HumanInterventionResponseSchema = z.object({
111
+ action: z.enum(['approve', 'reject', 'select']),
112
+ data: z
113
+ .object({
114
+ remember: z.boolean().optional(),
115
+ selected: z.union([z.string(), z.array(z.string())]).optional(),
116
+ })
117
+ .optional(),
118
+ });
119
+
88
120
  /**
89
121
  * Parameters for shouldIntervene method
90
122
  */
@@ -112,3 +144,10 @@ export interface ShouldInterveneParams {
112
144
  */
113
145
  toolKey?: string;
114
146
  }
147
+
148
+ export const ShouldInterveneParamsSchema = z.object({
149
+ config: HumanInterventionConfigSchema.optional(),
150
+ confirmedHistory: z.array(z.string()).optional(),
151
+ toolArgs: z.record(z.string(), z.any()).optional(),
152
+ toolKey: z.string().optional(),
153
+ });
@@ -6,8 +6,8 @@ import {
6
6
  ChatMessageError,
7
7
  GroundingSearch,
8
8
  MessageToolCall,
9
+ ModelPerformance,
9
10
  ModelReasoning,
10
- ModelSpeed,
11
11
  ModelUsage,
12
12
  ResponseAnimation,
13
13
  ResponseAnimationStyle,
@@ -26,7 +26,7 @@ export type OnFinishHandler = (
26
26
  images?: ChatImageChunk[];
27
27
  observationId?: string | null;
28
28
  reasoning?: ModelReasoning;
29
- speed?: ModelSpeed;
29
+ speed?: ModelPerformance;
30
30
  toolCalls?: MessageToolCall[];
31
31
  traceId?: string | null;
32
32
  type?: SSEFinishType;
@@ -40,7 +40,7 @@ export interface MessageUsageChunk {
40
40
  }
41
41
 
42
42
  export interface MessageSpeedChunk {
43
- speed: ModelSpeed;
43
+ speed: ModelPerformance;
44
44
  type: 'speed';
45
45
  }
46
46
 
@@ -265,7 +265,7 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
265
265
  let grounding: GroundingSearch | undefined = undefined;
266
266
  let usage: ModelUsage | undefined = undefined;
267
267
  let images: ChatImageChunk[] = [];
268
- let speed: ModelSpeed | undefined = undefined;
268
+ let speed: ModelPerformance | undefined = undefined;
269
269
 
270
270
  await fetchEventSource(url, {
271
271
  body: options.body,
package/renovate.json CHANGED
@@ -1,12 +1,24 @@
1
1
  {
2
2
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
- "automerge": false,
4
- "dependencyDashboard": true,
3
+ "extends": [
4
+ "config:recommended",
5
+ ":dependencyDashboard",
6
+ "schedule:weekly",
7
+ ":prHourlyLimitNone",
8
+ ":prConcurrentLimitNone",
9
+ ":semanticPrefixFixDepsChoreOthers",
10
+ "group:monorepos",
11
+ "group:recommended",
12
+ "group:allNonMajor",
13
+ "replacements:all",
14
+ "workarounds:all"
15
+ ],
5
16
  "ignoreDeps": [],
6
17
  "labels": ["dependencies"],
7
18
  "postUpdateOptions": ["yarnDedupeHighest"],
8
19
  "prConcurrentLimit": 30,
9
20
  "prHourlyLimit": 0,
21
+ "rangeStrategy": "bump",
10
22
  "rebaseWhen": "conflicted",
11
23
  "schedule": "on sunday before 6:00am",
12
24
  "timezone": "UTC"
@@ -64,6 +64,13 @@ const ModelConfigModal = memo<ModelConfigModalProps>(({ open, setOpen }) => {
64
64
  maskClosable
65
65
  onCancel={closeModal}
66
66
  open={open}
67
+ styles={{
68
+ content: {
69
+ display: 'flex',
70
+ flexDirection: 'column',
71
+ maxHeight: 'calc(100vh - 150px)',
72
+ },
73
+ }}
67
74
  title={t('providerModels.createNew.title')}
68
75
  zIndex={1251} // Select is 1150
69
76
  >
@@ -59,6 +59,13 @@ const ModelConfigModal = memo<ModelConfigModalProps>(({ id, open, setOpen }) =>
59
59
  maskClosable
60
60
  onCancel={closeModal}
61
61
  open={open}
62
+ styles={{
63
+ content: {
64
+ display: 'flex',
65
+ flexDirection: 'column',
66
+ maxHeight: 'calc(100vh - 150px)',
67
+ },
68
+ }}
62
69
  title={t('llm.customModelCards.modelConfig.modalTitle', { ns: 'setting' })}
63
70
  zIndex={1251} // Select is 1150
64
71
  >
@@ -19,11 +19,16 @@ const Error = memo<{ error: ChatMessageError }>(({ error }) => {
19
19
  const providerName = useProviderName(error.body?.provider);
20
20
 
21
21
  return (
22
- <Flexbox gap={8} style={{ width: '100%' }}>
22
+ <Flexbox gap={8} style={{ maxWidth: 600, width: '100%' }}>
23
23
  <Alert
24
24
  extra={
25
25
  <Flexbox>
26
- <Highlighter actionIconSize={'small'} language={'json'} variant={'borderless'}>
26
+ <Highlighter
27
+ actionIconSize={'small'}
28
+ language={'json'}
29
+ variant={'borderless'}
30
+ wrap={true}
31
+ >
27
32
  {JSON.stringify(error.body || error, null, 2)}
28
33
  </Highlighter>
29
34
  </Flexbox>
@@ -23,16 +23,36 @@ export interface ActionDropdownProps extends DropdownProps {
23
23
  maxHeight?: number | string;
24
24
  maxWidth?: number | string;
25
25
  minWidth?: number | string;
26
+ /**
27
+ * 是否在挂载时预渲染弹层,避免首次触发展开时的渲染卡顿
28
+ */
29
+ prefetch?: boolean;
26
30
  }
27
31
 
28
32
  const ActionDropdown = memo<ActionDropdownProps>(
29
- ({ menu, maxHeight, minWidth, maxWidth, children, placement = 'top', ...rest }) => {
33
+ ({
34
+ menu,
35
+ maxHeight,
36
+ minWidth,
37
+ maxWidth,
38
+ children,
39
+ placement = 'top',
40
+ prefetch = false,
41
+ destroyOnHidden,
42
+ forceRender,
43
+ ...rest
44
+ }) => {
30
45
  const { cx, styles } = useStyles();
31
46
  const isMobile = useIsMobile();
32
47
 
48
+ const dropdownForceRender = prefetch ? true : forceRender;
49
+ const dropdownDestroyOnHidden = prefetch ? false : destroyOnHidden;
50
+
33
51
  return (
34
52
  <Dropdown
35
53
  arrow={false}
54
+ destroyOnHidden={dropdownDestroyOnHidden}
55
+ forceRender={dropdownForceRender}
36
56
  menu={{
37
57
  ...menu,
38
58
  className: cx(styles.dropdownMenu, menu.className),
@@ -7,6 +7,7 @@ import { ChatItemProps } from '../type';
7
7
 
8
8
  export interface TitleProps {
9
9
  avatar: ChatItemProps['avatar'];
10
+ className?: string;
10
11
  placement?: ChatItemProps['placement'];
11
12
  showTitle?: ChatItemProps['showTitle'];
12
13
  time?: ChatItemProps['time'];
@@ -26,13 +27,13 @@ const formatTime = (time: number): string => {
26
27
  }
27
28
  };
28
29
 
29
- const Title = memo<TitleProps>(({ showTitle, placement, time, avatar, titleAddon }) => {
30
- const { styles } = useStyles({ placement, showTitle, time });
30
+ const Title = memo<TitleProps>(({ showTitle, placement, time, avatar, titleAddon, className }) => {
31
+ const { styles, cx } = useStyles({ placement, showTitle, time });
31
32
 
32
33
  return (
33
34
  <Flexbox
34
35
  align={'center'}
35
- className={styles.name}
36
+ className={cx(styles.name, className)}
36
37
  direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
37
38
  gap={4}
38
39
  >
@@ -1,15 +1,13 @@
1
+ import { LOADING_FLAT } from '@lobechat/const';
1
2
  import { UIChatMessage } from '@lobechat/types';
2
3
  import { ReactNode, memo } from 'react';
3
4
  import { Flexbox } from 'react-layout-kit';
4
5
 
5
- import { LOADING_FLAT } from '@/const/message';
6
- import { AssistantBlock } from '@/features/Conversation/Messages/Assistant/Block';
7
- import ImageFileListViewer from '@/features/Conversation/Messages/User/ImageFileListViewer';
8
- import VideoFileListViewer from '@/features/Conversation/Messages/User/VideoFileListViewer';
9
6
  import { useChatStore } from '@/store/chat';
10
7
  import { aiChatSelectors, chatSelectors } from '@/store/chat/selectors';
11
8
 
12
9
  import { DefaultMessage } from '../Default';
10
+ import ImageFileListViewer from '../User/ImageFileListViewer';
13
11
  import FileChunks from './FileChunks';
14
12
  import IntentUnderstanding from './IntentUnderstanding';
15
13
  import Reasoning from './Reasoning';
@@ -20,7 +18,7 @@ export const AssistantMessageContent = memo<
20
18
  UIChatMessage & {
21
19
  editableContent: ReactNode;
22
20
  }
23
- >(({ id, tools, content, chunksList, search, imageList, videoList, children, ...props }) => {
21
+ >(({ id, tools, content, chunksList, search, imageList, ...props }) => {
24
22
  const editing = useChatStore(chatSelectors.isMessageEditing(id));
25
23
  const generating = useChatStore(chatSelectors.isMessageGenerating(id));
26
24
 
@@ -32,7 +30,6 @@ export const AssistantMessageContent = memo<
32
30
 
33
31
  const showSearch = !!search && !!search.citations?.length;
34
32
  const showImageItems = !!imageList && imageList.length > 0;
35
- const showVideoItems = !!videoList && videoList.length > 0;
36
33
 
37
34
  // remove \n to avoid empty content
38
35
  // refs: https://github.com/lobehub/lobe-chat/pull/6153
@@ -42,15 +39,6 @@ export const AssistantMessageContent = memo<
42
39
 
43
40
  const showFileChunks = !!chunksList && chunksList.length > 0;
44
41
 
45
- if (children && children?.length > 0)
46
- return (
47
- <Flexbox gap={8}>
48
- {children.map((item) => (
49
- <AssistantBlock key={item.id} {...item} editableContent={props.editableContent} />
50
- ))}
51
- </Flexbox>
52
- );
53
-
54
42
  return editing ? (
55
43
  <DefaultMessage
56
44
  content={content}
@@ -79,7 +67,6 @@ export const AssistantMessageContent = memo<
79
67
  )
80
68
  )}
81
69
  {showImageItems && <ImageFileListViewer items={imageList} />}
82
- {showVideoItems && <VideoFileListViewer items={videoList} />}
83
70
  {tools && (
84
71
  <Flexbox gap={8}>
85
72
  {tools.map((toolCall, index) => (
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { LOADING_FLAT } from '@lobechat/const';
3
4
  import { UIChatMessage } from '@lobechat/types';
4
5
  import { Tag } from '@lobehub/ui';
5
6
  import { useResponsive } from 'antd-style';
@@ -8,7 +9,6 @@ import { useTranslation } from 'react-i18next';
8
9
  import { Flexbox } from 'react-layout-kit';
9
10
 
10
11
  import { HtmlPreviewAction } from '@/components/HtmlPreview';
11
- import { LOADING_FLAT } from '@/const/message';
12
12
  import Avatar from '@/features/ChatItem/components/Avatar';
13
13
  import BorderSpacing from '@/features/ChatItem/components/BorderSpacing';
14
14
  import ErrorContent from '@/features/ChatItem/components/ErrorContent';
@@ -17,7 +17,7 @@ import Title from '@/features/ChatItem/components/Title';
17
17
  import { useStyles } from '@/features/ChatItem/style';
18
18
  import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
19
19
  import { useAgentStore } from '@/store/agent';
20
- import { agentChatConfigSelectors } from '@/store/agent/slices/chat';
20
+ import { agentChatConfigSelectors } from '@/store/agent/selectors';
21
21
  import { useChatStore } from '@/store/chat';
22
22
  import { chatSelectors } from '@/store/chat/selectors';
23
23
  import { chatGroupSelectors, useChatGroupStore } from '@/store/chatGroup';
@@ -30,7 +30,7 @@ import { userGeneralSettingsSelectors, userProfileSelectors } from '@/store/user
30
30
  import ErrorMessageExtra, { useErrorContent } from '../../Error';
31
31
  import { markdownElements } from '../../MarkdownElements';
32
32
  import { useDoubleClickEdit } from '../../hooks/useDoubleClickEdit';
33
- import { normalizeThinkTags, processWithArtifact } from '../../utils';
33
+ import { normalizeThinkTags, processWithArtifact } from '../../utils/markdown';
34
34
  import { AssistantActionsBar } from './Actions';
35
35
  import { AssistantMessageExtra } from './Extra';
36
36
  import { AssistantMessageContent } from './MessageContent';
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
- import { processWithArtifact } from './utils';
3
+ import { processWithArtifact } from './markdown';
4
4
 
5
5
  describe('processWithArtifact', () => {
6
6
  it('should removeLineBreaks with closed tag', () => {
@@ -1,4 +1,4 @@
1
- import { ARTIFACT_TAG_REGEX, ARTIFACT_THINKING_TAG_REGEX } from '@/const/plugin';
1
+ import { ARTIFACT_TAG_REGEX, ARTIFACT_THINKING_TAG_REGEX } from '@lobechat/const';
2
2
 
3
3
  /**
4
4
  * Replace all line breaks in the matched `lobeArtifact` tag with an empty string
@@ -79,7 +79,7 @@ const useStyles = createStyles(({ css, token, cx, isDarkMode }) => {
79
79
  interface FileRenderItemProps extends FileListItem {
80
80
  index: number;
81
81
  knowledgeBaseId?: string;
82
- onSelectedChange: (id: string, selected: boolean) => void;
82
+ onSelectedChange: (id: string, selected: boolean, shiftKey: boolean, index: number) => void;
83
83
  selected?: boolean;
84
84
  }
85
85
 
@@ -100,6 +100,7 @@ const FileRenderItem = memo<FileRenderItemProps>(
100
100
  chunkingStatus,
101
101
  onSelectedChange,
102
102
  knowledgeBaseId,
103
+ index,
103
104
  }) => {
104
105
  const { t } = useTranslation('components');
105
106
  const { styles, cx } = useStyles();
@@ -140,7 +141,7 @@ const FileRenderItem = memo<FileRenderItemProps>(
140
141
  onClick={(e) => {
141
142
  e.stopPropagation();
142
143
 
143
- onSelectedChange(id, !selected);
144
+ onSelectedChange(id, !selected, e.shiftKey, index);
144
145
  }}
145
146
  style={{ paddingInline: 4 }}
146
147
  >
@@ -21,7 +21,7 @@ const MasonryItemWrapper = memo<MasonryItemWrapperProps>(({ data: item, context
21
21
  }
22
22
 
23
23
  return (
24
- <div style={{ padding: '8px' }}>
24
+ <div style={{ padding: '8px 4px' }}>
25
25
  <MasonryFileItem
26
26
  knowledgeBaseId={context.knowledgeBaseId}
27
27
  onSelectedChange={(id, checked) => {
@@ -111,8 +111,6 @@ const useStyles = createStyles(({ css, token }) => ({
111
111
  inset-block-end: 8px;
112
112
  inset-inline-end: 8px;
113
113
 
114
- padding-block: 4px;
115
- padding-inline: 8px;
116
114
  border-radius: ${token.borderRadius}px;
117
115
 
118
116
  opacity: 0;
@@ -320,8 +318,8 @@ const MasonryFileItem = memo<MasonryFileItemProps>(
320
318
  });
321
319
  },
322
320
  {
323
- rootMargin: '50px', // Start loading slightly before entering viewport
324
- threshold: 0.1,
321
+ rootMargin: '200px', // Increased margin to load content earlier
322
+ threshold: 0.01, // Lower threshold for earlier triggering
325
323
  },
326
324
  );
327
325
 
@@ -26,6 +26,9 @@ const MasonrySkeleton = memo<MasonrySkeletonProps>(({ columnCount }) => {
26
26
  // Generate varying heights for more natural masonry look
27
27
  const heights = [180, 220, 200, 190, 240, 210, 200, 230, 180, 220, 210, 190];
28
28
 
29
+ // Calculate number of items based on viewport and column count
30
+ const itemCount = Math.min(columnCount * 3, 12);
31
+
29
32
  return (
30
33
  <div
31
34
  className={styles.grid}
@@ -33,18 +36,21 @@ const MasonrySkeleton = memo<MasonrySkeletonProps>(({ columnCount }) => {
33
36
  gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
34
37
  }}
35
38
  >
36
- {Array.from({ length: 12 }).map((_, index) => (
39
+ {Array.from({ length: itemCount }).map((_, index) => (
37
40
  <div className={styles.card} key={index}>
38
41
  <Skeleton
39
42
  active
43
+ avatar={false}
40
44
  paragraph={{
41
- rows: 3,
42
- width: ['100%', '80%', '60%'],
45
+ rows: 4,
46
+ width: ['100%', '90%', '70%', '50%'],
43
47
  }}
44
48
  style={{
45
- height: heights[index],
49
+ height: heights[index % heights.length],
50
+ }}
51
+ title={{
52
+ width: '100%',
46
53
  }}
47
- title={false}
48
54
  />
49
55
  </div>
50
56
  ))}
@@ -148,7 +148,7 @@ const MultiSelectActions = memo<MultiSelectActionsProps>(
148
148
  size={'small'}
149
149
  variant={'filled'}
150
150
  >
151
- {t('batchDelete', { ns: 'common' })}
151
+ {t('delete', { ns: 'common' })}
152
152
  </Button>
153
153
  </Flexbox>
154
154
  )}