@lobehub/lobehub 2.0.0-next.23 → 2.0.0-next.25

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 (82) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/labs.json +4 -0
  4. package/locales/bg-BG/labs.json +4 -0
  5. package/locales/de-DE/labs.json +4 -0
  6. package/locales/en-US/labs.json +4 -0
  7. package/locales/es-ES/labs.json +4 -0
  8. package/locales/fa-IR/labs.json +4 -0
  9. package/locales/fr-FR/labs.json +4 -0
  10. package/locales/it-IT/labs.json +4 -0
  11. package/locales/ja-JP/labs.json +4 -0
  12. package/locales/ko-KR/labs.json +4 -0
  13. package/locales/nl-NL/labs.json +4 -0
  14. package/locales/pl-PL/labs.json +4 -0
  15. package/locales/pt-BR/labs.json +4 -0
  16. package/locales/ru-RU/labs.json +4 -0
  17. package/locales/tr-TR/labs.json +4 -0
  18. package/locales/vi-VN/labs.json +4 -0
  19. package/locales/zh-CN/labs.json +4 -0
  20. package/locales/zh-TW/labs.json +4 -0
  21. package/package.json +1 -1
  22. package/packages/const/src/user.ts +5 -2
  23. package/packages/types/src/index.ts +0 -1
  24. package/packages/types/src/user/index.ts +2 -88
  25. package/packages/types/src/user/preference.ts +105 -0
  26. package/renovate.json +1 -6
  27. package/src/app/[variants]/(main)/labs/components/LabCard.tsx +5 -5
  28. package/src/app/[variants]/(main)/labs/page.tsx +19 -22
  29. package/src/app/[variants]/(main)/settings/provider/detail/azure/index.tsx +1 -1
  30. package/src/app/[variants]/(main)/settings/provider/detail/azureai/index.tsx +1 -1
  31. package/src/app/[variants]/(main)/settings/provider/detail/bedrock/index.tsx +1 -1
  32. package/src/app/[variants]/(main)/settings/provider/detail/cloudflare/index.tsx +1 -1
  33. package/src/app/[variants]/(main)/settings/provider/detail/comfyui/index.tsx +1 -1
  34. package/src/app/[variants]/(main)/settings/provider/detail/github/index.tsx +1 -1
  35. package/src/app/[variants]/(main)/settings/provider/detail/vertexai/index.tsx +1 -1
  36. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +2 -4
  37. package/src/components/Skeleton/SkeletonSwitch.tsx +13 -0
  38. package/src/components/Skeleton/index.ts +2 -0
  39. package/src/features/ChatInput/ActionBar/index.tsx +2 -2
  40. package/src/features/ChatInput/InputEditor/index.tsx +2 -2
  41. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +152 -0
  42. package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +70 -0
  43. package/src/features/Conversation/Messages/Group/Actions/index.tsx +21 -0
  44. package/src/features/Conversation/Messages/Group/ContentBlock.tsx +91 -0
  45. package/src/features/Conversation/Messages/Group/EditState.tsx +51 -0
  46. package/src/features/Conversation/Messages/Group/Error/index.tsx +53 -0
  47. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +73 -0
  48. package/src/features/Conversation/Messages/Group/MessageContent.tsx +39 -0
  49. package/src/features/Conversation/Messages/Group/Tool/Inspector/BuiltinPluginTitle.tsx +49 -0
  50. package/src/features/Conversation/Messages/Group/Tool/Inspector/Debug.tsx +70 -0
  51. package/src/features/Conversation/Messages/Group/Tool/Inspector/PluginResult.tsx +34 -0
  52. package/src/features/Conversation/Messages/Group/Tool/Inspector/PluginState.tsx +18 -0
  53. package/src/features/Conversation/Messages/Group/Tool/Inspector/Settings.tsx +40 -0
  54. package/src/features/Conversation/Messages/Group/Tool/Inspector/ToolTitle.tsx +92 -0
  55. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +176 -0
  56. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/ObjectEntity.tsx +81 -0
  57. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/ValueCell.tsx +43 -0
  58. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/index.tsx +134 -0
  59. package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +88 -0
  60. package/src/features/Conversation/Messages/Group/Tool/Render/ErrorResponse.tsx +35 -0
  61. package/src/features/Conversation/Messages/Group/Tool/Render/LoadingPlaceholder/index.tsx +29 -0
  62. package/src/features/Conversation/Messages/Group/Tool/Render/PluginSettings.tsx +66 -0
  63. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +105 -0
  64. package/src/features/Conversation/Messages/Group/Tool/index.tsx +75 -0
  65. package/src/features/Conversation/Messages/Group/Tools.tsx +46 -0
  66. package/src/features/Conversation/Messages/Group/index.tsx +140 -0
  67. package/src/features/Conversation/Messages/index.tsx +12 -0
  68. package/src/features/Conversation/components/ShareMessageModal/ShareImage/Preview.tsx +2 -2
  69. package/src/locales/default/labs.ts +4 -0
  70. package/src/server/routers/lambda/message.ts +5 -20
  71. package/src/services/chat/contextEngineering.ts +6 -5
  72. package/src/services/message/server.ts +10 -10
  73. package/src/services/message/type.ts +0 -2
  74. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +309 -2
  75. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +2 -22
  76. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +272 -14
  77. package/src/store/user/selectors.ts +1 -1
  78. package/src/store/user/slices/preference/action.ts +8 -1
  79. package/src/store/user/slices/preference/selectors/index.ts +2 -0
  80. package/src/store/user/slices/preference/selectors/labPrefer.ts +13 -0
  81. package/src/store/user/slices/preference/{selectors.ts → selectors/preference.ts} +0 -2
  82. /package/src/{app/[variants]/(main)/settings/provider/features/ProviderConfig → components/Skeleton}/SkeletonInput.tsx +0 -0
@@ -6,11 +6,11 @@ import { ModelProvider } from 'model-bank';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import { FormInput, FormPassword } from '@/components/FormInput';
9
+ import { SkeletonInput } from '@/components/Skeleton';
9
10
  import { AzureProviderCard } from '@/config/modelProviders';
10
11
  import { aiModelSelectors, aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
11
12
 
12
13
  import { KeyVaultsConfigKey, LLMProviderApiTokenKey, LLMProviderBaseUrlKey } from '../../const';
13
- import { SkeletonInput } from '../../features/ProviderConfig';
14
14
  import { ProviderItem } from '../../type';
15
15
  import ProviderDetail from '../default';
16
16
 
@@ -4,11 +4,11 @@ import { ModelProvider } from 'model-bank';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { FormInput, FormPassword } from '@/components/FormInput';
7
+ import { SkeletonInput } from '@/components/Skeleton';
7
8
  import { AzureAIProviderCard } from '@/config/modelProviders';
8
9
  import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
9
10
 
10
11
  import { KeyVaultsConfigKey, LLMProviderApiTokenKey, LLMProviderBaseUrlKey } from '../../const';
11
- import { SkeletonInput } from '../../features/ProviderConfig';
12
12
  import { ProviderItem } from '../../type';
13
13
  import ProviderDetail from '../default';
14
14
 
@@ -4,12 +4,12 @@ import { Select } from '@lobehub/ui';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { FormPassword } from '@/components/FormInput';
7
+ import { SkeletonInput } from '@/components/Skeleton';
7
8
  import { BedrockProviderCard } from '@/config/modelProviders';
8
9
  import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
9
10
  import { GlobalLLMProviderKey } from '@/types/user/settings';
10
11
 
11
12
  import { KeyVaultsConfigKey } from '../../const';
12
- import { SkeletonInput } from '../../features/ProviderConfig';
13
13
  import { ProviderItem } from '../../type';
14
14
  import ProviderDetail from '../default';
15
15
 
@@ -3,12 +3,12 @@
3
3
  import { useTranslation } from 'react-i18next';
4
4
 
5
5
  import { FormInput, FormPassword } from '@/components/FormInput';
6
+ import { SkeletonInput } from '@/components/Skeleton';
6
7
  import { CloudflareProviderCard } from '@/config/modelProviders';
7
8
  import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
8
9
  import { GlobalLLMProviderKey } from '@/types/user/settings';
9
10
 
10
11
  import { KeyVaultsConfigKey } from '../../const';
11
- import { SkeletonInput } from '../../features/ProviderConfig';
12
12
  import { ProviderItem } from '../../type';
13
13
  import ProviderDetail from '../default';
14
14
 
@@ -5,12 +5,12 @@ import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { FormInput, FormPassword } from '@/components/FormInput';
7
7
  import KeyValueEditor from '@/components/KeyValueEditor';
8
+ import { SkeletonInput } from '@/components/Skeleton';
8
9
  import { ComfyUIProviderCard } from '@/config/modelProviders';
9
10
  import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
10
11
  import { GlobalLLMProviderKey } from '@/types/user/settings';
11
12
 
12
13
  import { KeyVaultsConfigKey } from '../../const';
13
- import { SkeletonInput } from '../../features/ProviderConfig';
14
14
  import { ProviderItem } from '../../type';
15
15
  import ProviderDetail from '../default';
16
16
 
@@ -5,12 +5,12 @@ import { createStyles } from 'antd-style';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import { FormPassword } from '@/components/FormInput';
8
+ import { SkeletonInput } from '@/components/Skeleton';
8
9
  import { GithubProviderCard } from '@/config/modelProviders';
9
10
  import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
10
11
  import { GlobalLLMProviderKey } from '@/types/user/settings';
11
12
 
12
13
  import { KeyVaultsConfigKey, LLMProviderApiTokenKey } from '../../const';
13
- import { SkeletonInput } from '../../features/ProviderConfig';
14
14
  import { ProviderItem } from '../../type';
15
15
  import ProviderDetail from '../default';
16
16
 
@@ -5,12 +5,12 @@ import { createStyles } from 'antd-style';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import { FormPassword } from '@/components/FormInput';
8
+ import { SkeletonInput } from '@/components/Skeleton';
8
9
  import { VertexAIProviderCard } from '@/config/modelProviders';
9
10
  import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
10
11
  import { GlobalLLMProviderKey } from '@/types/user/settings';
11
12
 
12
13
  import { KeyVaultsConfigKey, LLMProviderApiTokenKey } from '../../const';
13
- import { SkeletonInput } from '../../features/ProviderConfig';
14
14
  import { ProviderItem } from '../../type';
15
15
  import ProviderDetail from '../default';
16
16
 
@@ -21,6 +21,7 @@ import urlJoin from 'url-join';
21
21
  import { z } from 'zod';
22
22
 
23
23
  import { FormInput, FormPassword } from '@/components/FormInput';
24
+ import { SkeletonInput, SkeletonSwitch } from '@/components/Skeleton';
24
25
  import { FORM_STYLE } from '@/const/layoutTokens';
25
26
  import { AES_GCM_URL, BASE_PROVIDER_DOC_URL } from '@/const/url';
26
27
  import { isDesktop, isServerMode } from '@/const/version';
@@ -34,7 +35,6 @@ import {
34
35
  import { KeyVaultsConfigKey, LLMProviderApiTokenKey, LLMProviderBaseUrlKey } from '../../const';
35
36
  import Checker, { CheckErrorRender } from './Checker';
36
37
  import EnableSwitch from './EnableSwitch';
37
- import { SkeletonInput } from './SkeletonInput';
38
38
  import UpdateProviderInfo from './UpdateProviderInfo';
39
39
 
40
40
  const useStyles = createStyles(({ css, prefixCls, responsive, token }) => ({
@@ -298,7 +298,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
298
298
  (showApiKey && isProviderApiKeyNotEmpty));
299
299
  const clientFetchItem = showClientFetch && {
300
300
  children: isLoading ? (
301
- <Skeleton.Button active className={styles.switchLoading} />
301
+ <SkeletonSwitch />
302
302
  ) : (
303
303
  <Switch checked={isFetchOnClient} disabled={configUpdating} />
304
304
  ),
@@ -424,5 +424,3 @@ const ProviderConfig = memo<ProviderConfigProps>(
424
424
  );
425
425
 
426
426
  export default ProviderConfig;
427
-
428
- export { SkeletonInput } from './SkeletonInput';
@@ -0,0 +1,13 @@
1
+ import { Skeleton } from 'antd';
2
+ import { css, cx } from 'antd-style';
3
+
4
+ const switchLoading = cx(css`
5
+ width: 44px !important;
6
+ min-width: 44px !important;
7
+ height: 22px !important;
8
+ border-radius: 12px !important;
9
+ `);
10
+
11
+ export const SkeletonSwitch = () => {
12
+ return <Skeleton.Button active className={switchLoading} />;
13
+ };
@@ -0,0 +1,2 @@
1
+ export * from './SkeletonInput';
2
+ export * from './SkeletonSwitch';
@@ -4,7 +4,7 @@ import { memo, useMemo } from 'react';
4
4
  import { useGlobalStore } from '@/store/global';
5
5
  import { systemStatusSelectors } from '@/store/global/selectors';
6
6
  import { useUserStore } from '@/store/user';
7
- import { preferenceSelectors } from '@/store/user/slices/preference/selectors';
7
+ import { labPreferSelectors } from '@/store/user/slices/preference/selectors';
8
8
 
9
9
  import { ActionKeys, actionMap } from '../ActionBar/config';
10
10
  import { useChatInputStore } from '../store';
@@ -44,7 +44,7 @@ const ActionToolbar = memo(() => {
44
44
  systemStatusSelectors.expandInputActionbar(s),
45
45
  s.toggleExpandInputActionbar,
46
46
  ]);
47
- const enableRichRender = useUserStore(preferenceSelectors.inputMarkdownRender);
47
+ const enableRichRender = useUserStore(labPreferSelectors.enableInputMarkdown);
48
48
 
49
49
  const leftActions = useChatInputStore((s) =>
50
50
  s.leftActions.filter((item) => (enableRichRender ? true : item !== 'typo')),
@@ -21,7 +21,7 @@ import { useHotkeysContext } from 'react-hotkeys-hook';
21
21
  import { useTranslation } from 'react-i18next';
22
22
 
23
23
  import { useUserStore } from '@/store/user';
24
- import { preferenceSelectors, settingsSelectors } from '@/store/user/selectors';
24
+ import { labPreferSelectors, preferenceSelectors, settingsSelectors } from '@/store/user/selectors';
25
25
 
26
26
  import { useChatInputStore, useStoreApi } from '../store';
27
27
  import Placeholder from './Placeholder';
@@ -69,7 +69,7 @@ const InputEditor = memo<{ defaultRows?: number }>(({ defaultRows = 2 }) => {
69
69
  };
70
70
  }, [state.isEmpty]);
71
71
 
72
- const enableRichRender = useUserStore(preferenceSelectors.inputMarkdownRender);
72
+ const enableRichRender = useUserStore(labPreferSelectors.enableInputMarkdown);
73
73
 
74
74
  const richRenderProps = useMemo(
75
75
  () =>
@@ -0,0 +1,152 @@
1
+ import { AssistantContentBlock, UIChatMessage } from '@lobechat/types';
2
+ import { ActionIconGroup, type ActionIconGroupEvent, ActionIconGroupItemType } from '@lobehub/ui';
3
+ import { App } from 'antd';
4
+ import { useSearchParams } from 'next/navigation';
5
+ import { memo, use, useCallback, useContext, useMemo, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import ShareMessageModal from '@/features/Conversation/components/ShareMessageModal';
9
+ import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
10
+ import { useChatStore } from '@/store/chat';
11
+ import { threadSelectors } from '@/store/chat/selectors';
12
+ import { useSessionStore } from '@/store/session';
13
+ import { sessionSelectors } from '@/store/session/selectors';
14
+
15
+ import { InPortalThreadContext } from '../../../context/InPortalThreadContext';
16
+ import { useChatListActionsBar } from '../../../hooks/useChatListActionsBar';
17
+
18
+ interface GroupActionsProps {
19
+ contentBlock?: AssistantContentBlock;
20
+ data: UIChatMessage;
21
+ id: string;
22
+ index: number;
23
+ }
24
+
25
+ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }) => {
26
+ const { tools } = data;
27
+ const [isThreadMode, hasThread] = useChatStore((s) => [
28
+ !!s.activeThreadId,
29
+ threadSelectors.hasThreadBySourceMsgId(id)(s),
30
+ ]);
31
+ const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
32
+ const [showShareModal, setShareModal] = useState(false);
33
+
34
+ const { edit, delAndRegenerate, copy, divider, del, branching, share } = useChatListActionsBar({
35
+ hasThread,
36
+ });
37
+
38
+ const hasTools = !!tools;
39
+
40
+ const inPortalThread = useContext(InPortalThreadContext);
41
+ const inThread = isThreadMode || inPortalThread;
42
+
43
+ const items = useMemo(() => {
44
+ if (hasTools) return [delAndRegenerate, copy];
45
+
46
+ return [edit, copy, inThread || isGroupSession ? null : branching].filter(
47
+ Boolean,
48
+ ) as ActionIconGroupItemType[];
49
+ }, [inThread, hasTools, isGroupSession]);
50
+
51
+ const { t } = useTranslation('common');
52
+ const searchParams = useSearchParams();
53
+ const topic = searchParams.get('topic');
54
+ const [
55
+ deleteMessage,
56
+ translateMessage,
57
+ delAndRegenerateMessage,
58
+ copyMessage,
59
+ openThreadCreator,
60
+ delAndResendThreadMessage,
61
+ toggleMessageEditing,
62
+ ] = useChatStore((s) => [
63
+ s.deleteMessage,
64
+ s.translateMessage,
65
+ s.delAndRegenerateMessage,
66
+ s.copyMessage,
67
+ s.openThreadCreator,
68
+ s.delAndResendThreadMessage,
69
+ s.toggleMessageEditing,
70
+ ]);
71
+ const { message } = App.useApp();
72
+ const virtuosoRef = use(VirtuosoContext);
73
+
74
+ const onActionClick = useCallback(
75
+ async (action: ActionIconGroupEvent) => {
76
+ switch (action.key) {
77
+ case 'edit': {
78
+ toggleMessageEditing(id, true);
79
+
80
+ virtuosoRef?.current?.scrollIntoView({ align: 'start', behavior: 'auto', index });
81
+ }
82
+ }
83
+ if (!data) return;
84
+
85
+ switch (action.key) {
86
+ case 'copy': {
87
+ if (!contentBlock) return;
88
+ await copyMessage(id, contentBlock.content);
89
+ message.success(t('copySuccess', { defaultValue: 'Copy Success' }));
90
+ break;
91
+ }
92
+ case 'branching': {
93
+ if (!topic) {
94
+ message.warning(t('branchingRequiresSavedTopic'));
95
+ break;
96
+ }
97
+ openThreadCreator(id);
98
+ break;
99
+ }
100
+
101
+ case 'del': {
102
+ deleteMessage(id);
103
+ break;
104
+ }
105
+
106
+ case 'delAndRegenerate': {
107
+ if (inPortalThread) {
108
+ delAndResendThreadMessage(id);
109
+ } else {
110
+ delAndRegenerateMessage(id);
111
+ }
112
+ break;
113
+ }
114
+
115
+ case 'share': {
116
+ setShareModal(true);
117
+ break;
118
+ }
119
+ }
120
+
121
+ if (action.keyPath.at(-1) === 'translate') {
122
+ // click the menu data with translate data, the result is:
123
+ // key: 'en-US'
124
+ // keyPath: ['en-US','translate']
125
+ const lang = action.keyPath[0];
126
+ translateMessage(id, lang);
127
+ }
128
+ },
129
+ [data, topic],
130
+ );
131
+
132
+ return (
133
+ <>
134
+ <ActionIconGroup
135
+ items={items}
136
+ menu={{
137
+ items: [edit, copy, divider, share, divider, delAndRegenerate, del],
138
+ }}
139
+ onActionClick={onActionClick}
140
+ />
141
+ <ShareMessageModal
142
+ message={data!}
143
+ onCancel={() => {
144
+ setShareModal(false);
145
+ }}
146
+ open={showShareModal}
147
+ />
148
+ </>
149
+ );
150
+ });
151
+
152
+ export default WithContentId;
@@ -0,0 +1,70 @@
1
+ import { UIChatMessage } from '@lobechat/types';
2
+ import { ActionIconGroup, type ActionIconGroupEvent, ActionIconGroupItemType } from '@lobehub/ui';
3
+ import { useSearchParams } from 'next/navigation';
4
+ import { memo, useCallback, useContext, useMemo } from 'react';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+ import { threadSelectors } from '@/store/chat/selectors';
8
+ import { useSessionStore } from '@/store/session';
9
+ import { sessionSelectors } from '@/store/session/selectors';
10
+
11
+ import { InPortalThreadContext } from '../../../context/InPortalThreadContext';
12
+ import { useChatListActionsBar } from '../../../hooks/useChatListActionsBar';
13
+
14
+ interface GroupActionsProps {
15
+ data: UIChatMessage;
16
+ id: string;
17
+ }
18
+
19
+ const WithoutContentId = memo<GroupActionsProps>(({ id, data }) => {
20
+ const [isThreadMode, hasThread] = useChatStore((s) => [
21
+ !!s.activeThreadId,
22
+ threadSelectors.hasThreadBySourceMsgId(id)(s),
23
+ ]);
24
+ const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
25
+
26
+ const { delAndRegenerate, del } = useChatListActionsBar({ hasThread });
27
+
28
+ const inPortalThread = useContext(InPortalThreadContext);
29
+ const inThread = isThreadMode || inPortalThread;
30
+
31
+ const items = useMemo(() => {
32
+ return [delAndRegenerate, del].filter(Boolean) as ActionIconGroupItemType[];
33
+ }, [inThread, isGroupSession]);
34
+
35
+ const searchParams = useSearchParams();
36
+ const topic = searchParams.get('topic');
37
+
38
+ const [deleteMessage, delAndRegenerateMessage, delAndResendThreadMessage] = useChatStore((s) => [
39
+ s.deleteMessage,
40
+ s.delAndRegenerateMessage,
41
+ s.delAndResendThreadMessage,
42
+ ]);
43
+
44
+ const onActionClick = useCallback(
45
+ async (action: ActionIconGroupEvent) => {
46
+ if (!data) return;
47
+
48
+ switch (action.key) {
49
+ case 'del': {
50
+ deleteMessage(id);
51
+ break;
52
+ }
53
+
54
+ case 'delAndRegenerate': {
55
+ if (inPortalThread) {
56
+ delAndResendThreadMessage(id);
57
+ } else {
58
+ delAndRegenerateMessage(id);
59
+ }
60
+ break;
61
+ }
62
+ }
63
+ },
64
+ [data, topic],
65
+ );
66
+
67
+ return <ActionIconGroup items={items} onActionClick={onActionClick} />;
68
+ });
69
+
70
+ export default WithoutContentId;
@@ -0,0 +1,21 @@
1
+ import { AssistantContentBlock, UIChatMessage } from '@lobechat/types';
2
+ import { memo } from 'react';
3
+
4
+ import WithContentId from './WithContentId';
5
+ import WithoutContentId from './WithoutContentId';
6
+
7
+ interface GroupActionsProps {
8
+ contentBlock?: AssistantContentBlock;
9
+ contentId?: string;
10
+ data: UIChatMessage;
11
+ id: string;
12
+ index: number;
13
+ }
14
+
15
+ export const GroupActionsBar = memo<GroupActionsProps>(
16
+ ({ id, data, contentBlock, index, contentId }) => {
17
+ if (!contentId) return <WithoutContentId data={data} id={id} />;
18
+
19
+ return <WithContentId contentBlock={contentBlock} data={data} id={contentId} index={index} />;
20
+ },
21
+ );
@@ -0,0 +1,91 @@
1
+ import { AssistantContentBlock } from '@lobechat/types';
2
+ import { MarkdownProps } from '@lobehub/ui';
3
+ import { memo, useMemo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { LOADING_FLAT } from '@/const/message';
7
+ import { markdownElements } from '@/features/Conversation/MarkdownElements';
8
+ import Reasoning from '@/features/Conversation/Messages/Assistant/Reasoning';
9
+ import { useChatStore } from '@/store/chat';
10
+ import { aiChatSelectors, messageStateSelectors } from '@/store/chat/selectors';
11
+ import { useUserStore } from '@/store/user';
12
+ import { userGeneralSettingsSelectors } from '@/store/user/selectors';
13
+
14
+ import ImageFileListViewer from '../User/ImageFileListViewer';
15
+ import ErrorContent from './Error';
16
+ import MessageContent from './MessageContent';
17
+ import { Tools } from './Tools';
18
+
19
+ const rehypePlugins = markdownElements.map((element) => element.rehypePlugin).filter(Boolean);
20
+ const remarkPlugins = markdownElements.map((element) => element.remarkPlugin).filter(Boolean);
21
+
22
+ interface ContentBlockProps extends AssistantContentBlock {
23
+ index: number;
24
+ }
25
+
26
+ export const ContentBlock = memo<ContentBlockProps>((props) => {
27
+ const { id, tools, content, imageList, reasoning, error } = props;
28
+ const showImageItems = !!imageList && imageList.length > 0;
29
+ const isReasoning = useChatStore(aiChatSelectors.isMessageInReasoning(id));
30
+
31
+ const hasTools = tools && tools.length > 0;
32
+ const showReasoning =
33
+ (!!reasoning && reasoning.content?.trim() !== '') || (!reasoning && isReasoning);
34
+
35
+ const { transitionMode, highlighterTheme, mermaidTheme } = useUserStore(
36
+ userGeneralSettingsSelectors.config,
37
+ );
38
+
39
+ const generating = useChatStore(messageStateSelectors.isMessageGenerating(id));
40
+
41
+ const animated = transitionMode === 'fadeIn' && generating;
42
+
43
+ const components = useMemo(
44
+ () =>
45
+ Object.fromEntries(
46
+ markdownElements.map((element) => {
47
+ const Component = element.Component;
48
+
49
+ return [element.tag, (props: any) => <Component {...props} id={id} />];
50
+ }),
51
+ ),
52
+ [id],
53
+ );
54
+
55
+ const markdownProps: Omit<MarkdownProps, 'className' | 'style' | 'children'> = useMemo(
56
+ () => ({
57
+ animated,
58
+ componentProps: {
59
+ highlight: {
60
+ theme: highlighterTheme,
61
+ },
62
+ mermaid: { theme: mermaidTheme },
63
+ },
64
+ components,
65
+ enableCustomFootnotes: true,
66
+ rehypePlugins,
67
+ remarkPlugins,
68
+ }),
69
+ [animated, components, highlighterTheme, mermaidTheme],
70
+ );
71
+
72
+ if (error && (content === LOADING_FLAT || !content))
73
+ return <ErrorContent error={error} id={id} />;
74
+
75
+ return (
76
+ <Flexbox gap={8} id={id}>
77
+ {showReasoning && <Reasoning {...reasoning} id={id} />}
78
+
79
+ {/* Content - markdown text */}
80
+ {content && (
81
+ <MessageContent content={content} hasTools={hasTools} markdownProps={markdownProps} />
82
+ )}
83
+
84
+ {/* Image files */}
85
+ {showImageItems && <ImageFileListViewer items={imageList} />}
86
+
87
+ {/* Tools */}
88
+ {hasTools && <Tools messageId={id} tools={tools} />}
89
+ </Flexbox>
90
+ );
91
+ });
@@ -0,0 +1,51 @@
1
+ import { MessageInput } from '@lobehub/ui/chat';
2
+ import { memo, useMemo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+
8
+ export interface EditStateProps {
9
+ content: string;
10
+ id: string;
11
+ }
12
+
13
+ const EditState = memo<EditStateProps>(({ id, content }) => {
14
+ const { t } = useTranslation('common');
15
+
16
+ const text = useMemo(
17
+ () => ({
18
+ cancel: t('cancel'),
19
+ confirm: t('ok'),
20
+ edit: t('edit'),
21
+ }),
22
+ [],
23
+ );
24
+
25
+ const [toggleMessageEditing, updateMessageContent] = useChatStore((s) => [
26
+ s.toggleMessageEditing,
27
+ s.modifyMessageContent,
28
+ ]);
29
+
30
+ const onEditingChange = (value: string) => {
31
+ updateMessageContent(id, value);
32
+ toggleMessageEditing(id, false);
33
+ };
34
+
35
+ return (
36
+ <Flexbox paddingBlock={'0 8px'}>
37
+ <MessageInput
38
+ defaultValue={content ? String(content) : ''}
39
+ editButtonSize={'small'}
40
+ onCancel={() => {
41
+ toggleMessageEditing(id, false);
42
+ }}
43
+ onConfirm={onEditingChange}
44
+ text={text}
45
+ variant={'outlined'}
46
+ />
47
+ </Flexbox>
48
+ );
49
+ });
50
+
51
+ export default EditState;
@@ -0,0 +1,53 @@
1
+ import { ChatMessageError } from '@lobechat/types';
2
+ import { Alert } from '@lobehub/ui';
3
+ import { Button } from 'antd';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import ErrorMessageExtra, { useErrorContent } from '@/features/Conversation/Error';
9
+ import { useChatStore } from '@/store/chat';
10
+
11
+ export interface ErrorContentProps {
12
+ error?: ChatMessageError;
13
+ id: string;
14
+ }
15
+
16
+ const ErrorContent = memo<ErrorContentProps>(({ error, id }) => {
17
+ const { t } = useTranslation('common');
18
+ const errorProps = useErrorContent(error);
19
+
20
+ const [deleteMessage] = useChatStore((s) => [s.deleteMessage]);
21
+ const message = <ErrorMessageExtra block data={{ error, id }} />;
22
+
23
+ if (!error?.message) {
24
+ if (!message) return null;
25
+ return <Flexbox>{message}</Flexbox>;
26
+ }
27
+
28
+ return (
29
+ <Flexbox>
30
+ <Alert
31
+ action={
32
+ <Button
33
+ color={'default'}
34
+ onClick={() => {
35
+ deleteMessage(id);
36
+ }}
37
+ size={'small'}
38
+ variant={'filled'}
39
+ >
40
+ {t('delete')}
41
+ </Button>
42
+ }
43
+ closable={false}
44
+ extra={message}
45
+ showIcon
46
+ type={'error'}
47
+ {...errorProps}
48
+ />
49
+ </Flexbox>
50
+ );
51
+ });
52
+
53
+ export default ErrorContent;