@lobehub/chat 1.68.11 → 1.69.0

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 (86) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/ar/chat.json +8 -0
  4. package/locales/bg-BG/chat.json +8 -0
  5. package/locales/de-DE/chat.json +8 -0
  6. package/locales/en-US/chat.json +8 -0
  7. package/locales/es-ES/chat.json +8 -0
  8. package/locales/fa-IR/chat.json +8 -0
  9. package/locales/fr-FR/chat.json +8 -0
  10. package/locales/it-IT/chat.json +8 -0
  11. package/locales/ja-JP/chat.json +8 -0
  12. package/locales/ko-KR/chat.json +8 -0
  13. package/locales/nl-NL/chat.json +8 -0
  14. package/locales/pl-PL/chat.json +8 -0
  15. package/locales/pt-BR/chat.json +8 -0
  16. package/locales/ru-RU/chat.json +8 -0
  17. package/locales/tr-TR/chat.json +8 -0
  18. package/locales/vi-VN/chat.json +8 -0
  19. package/locales/zh-CN/chat.json +8 -0
  20. package/locales/zh-TW/chat.json +8 -0
  21. package/next.config.ts +6 -0
  22. package/package.json +1 -1
  23. package/packages/web-crawler/src/crawImpl/naive.ts +19 -12
  24. package/packages/web-crawler/src/urlRules.ts +9 -1
  25. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +9 -18
  26. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx +2 -5
  27. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/HeaderAction.tsx +3 -2
  28. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Main.tsx +56 -30
  29. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Tags/HistoryLimitTags.tsx +26 -0
  30. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/{SearchTags.tsx → Tags/SearchTags.tsx} +7 -4
  31. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/{Tags.tsx → Tags/index.tsx} +4 -1
  32. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/index.tsx +1 -1
  33. package/src/config/aiModels/anthropic.ts +16 -1
  34. package/src/config/modelProviders/anthropic.ts +0 -2
  35. package/src/const/layoutTokens.test.ts +1 -1
  36. package/src/const/layoutTokens.ts +1 -1
  37. package/src/const/models.ts +27 -0
  38. package/src/features/ChatInput/ActionBar/History.tsx +6 -3
  39. package/src/features/ChatInput/ActionBar/Model/ContextCachingSwitch.tsx +20 -0
  40. package/src/features/ChatInput/ActionBar/Model/ControlsForm.tsx +49 -7
  41. package/src/features/ChatInput/ActionBar/Model/ReasoningTokenSlider.tsx +6 -14
  42. package/src/features/ChatInput/ActionBar/Search/ModelBuiltinSearch.tsx +2 -2
  43. package/src/features/ChatInput/ActionBar/Search/SwitchPanel.tsx +2 -2
  44. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +3 -5
  45. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +2 -0
  46. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +5 -1
  47. package/src/features/Conversation/Messages/Assistant/Tool/index.tsx +2 -0
  48. package/src/features/Conversation/components/ChatItem/index.tsx +3 -6
  49. package/src/features/Portal/Thread/Chat/ChatItem.tsx +4 -9
  50. package/src/hooks/useAgentEnableSearch.ts +2 -2
  51. package/src/libs/agent-runtime/anthropic/index.test.ts +36 -7
  52. package/src/libs/agent-runtime/anthropic/index.ts +30 -8
  53. package/src/libs/agent-runtime/azureOpenai/index.ts +4 -9
  54. package/src/libs/agent-runtime/azureai/index.ts +4 -9
  55. package/src/libs/agent-runtime/openai/index.ts +21 -38
  56. package/src/libs/agent-runtime/types/chat.ts +4 -0
  57. package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +55 -0
  58. package/src/libs/agent-runtime/utils/anthropicHelpers.ts +37 -3
  59. package/src/libs/langchain/loaders/code/__tests__/long.json +2 -2
  60. package/src/libs/langchain/loaders/code/__tests__/long.txt +1 -1
  61. package/src/locales/default/chat.ts +8 -0
  62. package/src/store/agent/initialState.ts +2 -2
  63. package/src/store/agent/selectors.ts +1 -1
  64. package/src/store/agent/slices/chat/{selectors.test.ts → selectors/agent.test.ts} +2 -2
  65. package/src/store/agent/slices/chat/{selectors.ts → selectors/agent.ts} +24 -33
  66. package/src/store/agent/slices/chat/selectors/chatConfig.test.ts +184 -0
  67. package/src/store/agent/slices/chat/selectors/chatConfig.ts +65 -0
  68. package/src/store/agent/slices/chat/selectors/index.ts +2 -0
  69. package/src/store/agent/store.ts +2 -2
  70. package/src/store/chat/helpers.test.ts +7 -7
  71. package/src/store/chat/helpers.ts +11 -7
  72. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +3 -3
  73. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +11 -2
  74. package/src/store/chat/slices/aiChat/actions/helpers.ts +6 -2
  75. package/src/store/chat/slices/builtinTool/actions/searXNG.ts +28 -20
  76. package/src/store/chat/slices/message/selectors.ts +7 -3
  77. package/src/store/chat/slices/thread/selectors/index.ts +7 -3
  78. package/src/tools/web-browsing/Render/PageContent/Result.tsx +4 -2
  79. package/src/tools/web-browsing/Render/index.tsx +2 -0
  80. package/src/types/agent/index.ts +4 -0
  81. package/src/types/aiModel.ts +1 -1
  82. package/src/types/aiProvider.ts +60 -31
  83. /package/packages/web-crawler/src/{__test__ → __tests__}/crawler.test.ts +0 -0
  84. /package/packages/web-crawler/src/crawImpl/{__test__ → __tests__}/jina.test.ts +0 -0
  85. /package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/{KnowledgeTag.tsx → Tags/KnowledgeTag.tsx} +0 -0
  86. /package/src/store/agent/slices/chat/{__snapshots__/selectors.test.ts.snap → selectors/__snapshots__/agent.test.ts.snap} +0 -0
@@ -4,6 +4,7 @@ import { ActionIcon } from '@lobehub/ui';
4
4
  import { PanelRightClose, PanelRightOpen } from 'lucide-react';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
7
8
 
8
9
  import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
9
10
  import { useGlobalStore } from '@/store/global';
@@ -24,7 +25,7 @@ const HeaderAction = memo(() => {
24
25
  const { isAgentEditable } = useServerConfigStore(featureFlagsSelectors);
25
26
 
26
27
  return (
27
- <>
28
+ <Flexbox gap={4} horizontal>
28
29
  <ShareButton />
29
30
  <ActionIcon
30
31
  icon={showAgentSettings ? PanelRightClose : PanelRightOpen}
@@ -33,7 +34,7 @@ const HeaderAction = memo(() => {
33
34
  title={t('roleAndArchive')}
34
35
  />
35
36
  {isAgentEditable && <SettingButton />}
36
- </>
37
+ </Flexbox>
37
38
  );
38
39
  });
39
40
 
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { ActionIcon, Avatar } from '@lobehub/ui';
4
- import { ChatHeaderTitle } from '@lobehub/ui/chat';
5
4
  import { Skeleton } from 'antd';
5
+ import { createStyles } from 'antd-style';
6
6
  import { PanelLeftClose, PanelLeftOpen } from 'lucide-react';
7
7
  import { parseAsBoolean, useQueryState } from 'nuqs';
8
8
  import { Suspense, memo } from 'react';
@@ -19,17 +19,38 @@ import { sessionMetaSelectors, sessionSelectors } from '@/store/session/selector
19
19
 
20
20
  import Tags from './Tags';
21
21
 
22
+ const useStyles = createStyles(({ css }) => ({
23
+ container: css`
24
+ position: relative;
25
+ overflow: hidden;
26
+ flex: 1;
27
+ max-width: 100%;
28
+ `,
29
+ tag: css`
30
+ flex: none;
31
+ align-items: baseline;
32
+ `,
33
+ title: css`
34
+ overflow: hidden;
35
+
36
+ font-size: 14px;
37
+ font-weight: bold;
38
+ line-height: 1;
39
+ text-overflow: ellipsis;
40
+ white-space: nowrap;
41
+ `,
42
+ }));
43
+
22
44
  const Main = memo(() => {
23
45
  const { t } = useTranslation('chat');
24
-
46
+ const { styles } = useStyles();
25
47
  useInitAgentConfig();
26
48
  const [isPinned] = useQueryState('pinned', parseAsBoolean);
27
49
 
28
- const [init, isInbox, title, description, avatar, backgroundColor] = useSessionStore((s) => [
50
+ const [init, isInbox, title, avatar, backgroundColor] = useSessionStore((s) => [
29
51
  sessionSelectors.isSomeSessionActive(s),
30
52
  sessionSelectors.isInboxSession(s),
31
53
  sessionMetaSelectors.currentAgentTitle(s),
32
- sessionMetaSelectors.currentAgentDescription(s),
33
54
  sessionMetaSelectors.currentAgentAvatar(s),
34
55
  sessionMetaSelectors.currentAgentBackgroundColor(s),
35
56
  ]);
@@ -37,34 +58,36 @@ const Main = memo(() => {
37
58
  const openChatSettings = useOpenChatSettings();
38
59
 
39
60
  const displayTitle = isInbox ? t('inbox.title') : title;
40
- const displayDesc = isInbox ? t('inbox.desc') : description;
41
61
  const showSessionPanel = useGlobalStore(systemStatusSelectors.showSessionPanel);
42
62
  const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
43
63
 
44
- return !init ? (
45
- <Flexbox gap={4} horizontal>
46
- {!isPinned && (
47
- <ActionIcon
48
- aria-label={t('agents')}
49
- icon={showSessionPanel ? PanelLeftClose : PanelLeftOpen}
50
- onClick={() => {
51
- updateSystemStatus({
52
- sessionsWidth: showSessionPanel ? 0 : 320,
53
- showSessionPanel: !showSessionPanel,
54
- });
55
- }}
56
- size={DESKTOP_HEADER_ICON_SIZE}
57
- title={t('agents')}
64
+ if (!init)
65
+ return (
66
+ <Flexbox align={'center'} gap={8} horizontal>
67
+ {!isPinned && (
68
+ <ActionIcon
69
+ aria-label={t('agents')}
70
+ icon={showSessionPanel ? PanelLeftClose : PanelLeftOpen}
71
+ onClick={() => {
72
+ updateSystemStatus({
73
+ sessionsWidth: showSessionPanel ? 0 : 320,
74
+ showSessionPanel: !showSessionPanel,
75
+ });
76
+ }}
77
+ size={DESKTOP_HEADER_ICON_SIZE}
78
+ title={t('agents')}
79
+ />
80
+ )}
81
+ <Skeleton
82
+ active
83
+ avatar={{ shape: 'circle', size: 28 }}
84
+ paragraph={false}
85
+ title={{ style: { margin: 0, marginTop: 4 }, width: 200 }}
58
86
  />
59
- )}
60
- <Skeleton
61
- active
62
- avatar={{ shape: 'circle', size: 'default' }}
63
- paragraph={false}
64
- title={{ style: { margin: 0, marginTop: 8 }, width: 200 }}
65
- />
66
- </Flexbox>
67
- ) : (
87
+ </Flexbox>
88
+ );
89
+
90
+ return (
68
91
  <Flexbox align={'center'} gap={4} horizontal>
69
92
  {!isPinned && (
70
93
  <ActionIcon
@@ -84,10 +107,13 @@ const Main = memo(() => {
84
107
  avatar={avatar}
85
108
  background={backgroundColor}
86
109
  onClick={() => openChatSettings()}
87
- size={40}
110
+ size={32}
88
111
  title={title}
89
112
  />
90
- <ChatHeaderTitle desc={displayDesc} tag={<Tags />} title={displayTitle} />
113
+ <Flexbox align={'center'} className={styles.container} gap={8} horizontal>
114
+ <div className={styles.title}>{displayTitle}</div>
115
+ <Tags />
116
+ </Flexbox>
91
117
  </Flexbox>
92
118
  );
93
119
  });
@@ -0,0 +1,26 @@
1
+ import { Icon, Tag, Tooltip } from '@lobehub/ui';
2
+ import { HistoryIcon } from 'lucide-react';
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { useAgentStore } from '@/store/agent';
8
+ import { agentChatConfigSelectors } from '@/store/agent/selectors';
9
+
10
+ const SearchTag = memo(() => {
11
+ const { t } = useTranslation('chat');
12
+ const historyCount = useAgentStore(agentChatConfigSelectors.historyCount);
13
+
14
+ return (
15
+ <Tooltip title={t('history.title', { count: historyCount })}>
16
+ <Flexbox height={22}>
17
+ <Tag>
18
+ <Icon icon={HistoryIcon} />
19
+ <span>{historyCount}</span>
20
+ </Tag>
21
+ </Flexbox>
22
+ </Tooltip>
23
+ );
24
+ });
25
+
26
+ export default SearchTag;
@@ -2,15 +2,18 @@ import { Icon, Tag } from '@lobehub/ui';
2
2
  import { Globe } from 'lucide-react';
3
3
  import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
5
6
 
6
7
  const SearchTag = memo(() => {
7
8
  const { t } = useTranslation('chat');
8
9
 
9
10
  return (
10
- <Tag>
11
- {<Icon icon={Globe} />}
12
- <div>{t('search.title')}</div>
13
- </Tag>
11
+ <Flexbox height={22}>
12
+ <Tag>
13
+ {<Icon icon={Globe} />}
14
+ <div>{t('search.title')}</div>
15
+ </Tag>
16
+ </Flexbox>
14
17
  );
15
18
  });
16
19
 
@@ -9,12 +9,13 @@ import PluginTag from '@/features/PluginTag';
9
9
  import { useAgentEnableSearch } from '@/hooks/useAgentEnableSearch';
10
10
  import { useModelSupportToolUse } from '@/hooks/useModelSupportToolUse';
11
11
  import { useAgentStore } from '@/store/agent';
12
- import { agentSelectors } from '@/store/agent/selectors';
12
+ import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
13
13
  import { useUserStore } from '@/store/user';
14
14
  import { authSelectors } from '@/store/user/selectors';
15
15
 
16
16
  import KnowledgeTag from './KnowledgeTag';
17
17
  import SearchTags from './SearchTags';
18
+ import HistoryLimitTags from './HistoryLimitTags';
18
19
 
19
20
  const TitleTags = memo(() => {
20
21
  const [model, provider, hasKnowledge, isLoading] = useAgentStore((s) => [
@@ -26,6 +27,7 @@ const TitleTags = memo(() => {
26
27
 
27
28
  const plugins = useAgentStore(agentSelectors.currentAgentPlugins, isEqual);
28
29
  const enabledKnowledge = useAgentStore(agentSelectors.currentEnabledKnowledge, isEqual);
30
+ const enableHistoryCount = useAgentStore(agentChatConfigSelectors.enableHistoryCount);
29
31
 
30
32
  const showPlugin = useModelSupportToolUse(model, provider);
31
33
  const isLogin = useUserStore(authSelectors.isLogin);
@@ -42,6 +44,7 @@ const TitleTags = memo(() => {
42
44
  {isAgentEnableSearch && <SearchTags />}
43
45
  {showPlugin && plugins?.length > 0 && <PluginTag plugins={plugins} />}
44
46
  {hasKnowledge && <KnowledgeTag data={enabledKnowledge} />}
47
+ {enableHistoryCount && <HistoryLimitTags />}
45
48
  </Flexbox>
46
49
  );
47
50
  });
@@ -16,7 +16,7 @@ const Header = () => {
16
16
  <ChatHeader
17
17
  left={<Main />}
18
18
  right={<HeaderAction />}
19
- style={{ minHeight: 64, position: 'initial', zIndex: 11 }}
19
+ style={{ height: 48, minHeight: 48, paddingInline: 8, position: 'initial', zIndex: 11 }}
20
20
  />
21
21
  )
22
22
  );
@@ -22,7 +22,7 @@ const anthropicChatModels: AIChatModelCard[] = [
22
22
  },
23
23
  releasedAt: '2025-02-24',
24
24
  settings: {
25
- extendParams: ['enableReasoning', 'reasoningBudgetToken'],
25
+ extendParams: ['disableContextCaching', 'enableReasoning', 'reasoningBudgetToken'],
26
26
  },
27
27
  type: 'chat',
28
28
  },
@@ -45,6 +45,9 @@ const anthropicChatModels: AIChatModelCard[] = [
45
45
  writeCacheInput: 1.25,
46
46
  },
47
47
  releasedAt: '2024-11-05',
48
+ settings: {
49
+ extendParams: ['disableContextCaching'],
50
+ },
48
51
  type: 'chat',
49
52
  },
50
53
  {
@@ -66,6 +69,9 @@ const anthropicChatModels: AIChatModelCard[] = [
66
69
  writeCacheInput: 3.75,
67
70
  },
68
71
  releasedAt: '2024-10-22',
72
+ settings: {
73
+ extendParams: ['disableContextCaching'],
74
+ },
69
75
  type: 'chat',
70
76
  },
71
77
  {
@@ -86,6 +92,9 @@ const anthropicChatModels: AIChatModelCard[] = [
86
92
  writeCacheInput: 3.75,
87
93
  },
88
94
  releasedAt: '2024-06-20',
95
+ settings: {
96
+ extendParams: ['disableContextCaching'],
97
+ },
89
98
  type: 'chat',
90
99
  },
91
100
  {
@@ -104,6 +113,9 @@ const anthropicChatModels: AIChatModelCard[] = [
104
113
  output: 1.25,
105
114
  },
106
115
  releasedAt: '2024-03-07',
116
+ settings: {
117
+ extendParams: ['disableContextCaching'],
118
+ },
107
119
  type: 'chat',
108
120
  },
109
121
  {
@@ -141,6 +153,9 @@ const anthropicChatModels: AIChatModelCard[] = [
141
153
  output: 75,
142
154
  },
143
155
  releasedAt: '2024-02-29',
156
+ settings: {
157
+ extendParams: ['disableContextCaching'],
158
+ },
144
159
  type: 'chat',
145
160
  },
146
161
  {
@@ -180,12 +180,10 @@ const Anthropic: ModelProviderCard = {
180
180
  sdkType: 'anthropic',
181
181
  showModelFetcher: true,
182
182
  smoothing: {
183
- speed: 5,
184
183
  text: true,
185
184
  },
186
185
  },
187
186
  smoothing: {
188
- speed: 5,
189
187
  text: true,
190
188
  },
191
189
  url: 'https://anthropic.com',
@@ -6,6 +6,6 @@ describe('HEADER_ICON_SIZE', () => {
6
6
  });
7
7
 
8
8
  it('desktop', () => {
9
- expect(HEADER_ICON_SIZE(false)).toEqual({ fontSize: 24 });
9
+ expect(HEADER_ICON_SIZE(false)).toEqual({ blockSize: 32, fontSize: 20 });
10
10
  });
11
11
  });
@@ -20,7 +20,7 @@ export const FORM_STYLE: FormProps = {
20
20
  style: { maxWidth: MAX_WIDTH, width: '100%' },
21
21
  };
22
22
  export const MOBILE_HEADER_ICON_SIZE = { blockSize: 36, fontSize: 22 };
23
- export const DESKTOP_HEADER_ICON_SIZE = { fontSize: 24 };
23
+ export const DESKTOP_HEADER_ICON_SIZE = { blockSize: 32, fontSize: 20 };
24
24
  export const HEADER_ICON_SIZE = (mobile?: boolean) =>
25
25
  mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE;
26
26
  export const PWA_INSTALL_ID = 'pwa-install';
@@ -0,0 +1,27 @@
1
+ export const systemToUserModels = new Set([
2
+ 'o1-preview',
3
+ 'o1-preview-2024-09-12',
4
+ 'o1-mini',
5
+ 'o1-mini-2024-09-12',
6
+ ]);
7
+
8
+ // TODO: 临时写法,后续要重构成 model card 展示配置
9
+ export const disableStreamModels = new Set(['o1', 'o1-2024-12-17']);
10
+
11
+ /**
12
+ * models support context caching
13
+ */
14
+ export const contextCachingModels = new Set([
15
+ 'claude-3-7-sonnet-latest',
16
+ 'claude-3-7-sonnet-20250219',
17
+ 'claude-3-5-sonnet-latest',
18
+ 'claude-3-5-sonnet-20241022',
19
+ 'claude-3-5-sonnet-20240620',
20
+ 'claude-3-5-haiku-latest',
21
+ 'claude-3-5-haiku-20241022',
22
+ ]);
23
+
24
+ export const thinkingWithToolClaudeModels = new Set([
25
+ 'claude-3-7-sonnet-latest',
26
+ 'claude-3-7-sonnet-20250219',
27
+ ]);
@@ -7,15 +7,18 @@ import { Flexbox } from 'react-layout-kit';
7
7
 
8
8
  import { useIsMobile } from '@/hooks/useIsMobile';
9
9
  import { useAgentStore } from '@/store/agent';
10
- import { agentSelectors } from '@/store/agent/selectors';
10
+ import { agentChatConfigSelectors } from '@/store/agent/selectors';
11
11
 
12
12
  const History = memo(() => {
13
13
  const { t } = useTranslation('setting');
14
14
  const [popoverOpen, setPopoverOpen] = useState(false);
15
15
 
16
16
  const [historyCount, enableHistoryCount, updateAgentConfig] = useAgentStore((s) => {
17
- const config = agentSelectors.currentAgentChatConfig(s);
18
- return [config.historyCount, config.enableHistoryCount, s.updateAgentChatConfig];
17
+ return [
18
+ agentChatConfigSelectors.historyCount(s),
19
+ agentChatConfigSelectors.enableHistoryCount(s),
20
+ s.updateAgentChatConfig,
21
+ ];
19
22
  });
20
23
 
21
24
  const title = t(
@@ -0,0 +1,20 @@
1
+ import { Switch } from 'antd';
2
+ import { memo } from 'react';
3
+
4
+ interface ContextCachingSwitchProps {
5
+ onChange?: (value: boolean) => void;
6
+ value?: boolean;
7
+ }
8
+
9
+ const ContextCachingSwitch = memo<ContextCachingSwitchProps>(({ value, onChange }) => {
10
+ return (
11
+ <Switch
12
+ onChange={(checked) => {
13
+ onChange?.(!checked);
14
+ }}
15
+ value={!value}
16
+ />
17
+ );
18
+ });
19
+
20
+ export default ContextCachingSwitch;
@@ -1,14 +1,16 @@
1
1
  import { Form } from '@lobehub/ui';
2
2
  import type { FormItemProps } from '@lobehub/ui';
3
- import { Switch } from 'antd';
3
+ import { Form as AntdForm, Switch } from 'antd';
4
4
  import isEqual from 'fast-deep-equal';
5
+ import Link from 'next/link';
5
6
  import { memo } from 'react';
6
- import { useTranslation } from 'react-i18next';
7
+ import { Trans, useTranslation } from 'react-i18next';
7
8
 
8
9
  import { useAgentStore } from '@/store/agent';
9
- import { agentSelectors } from '@/store/agent/slices/chat';
10
+ import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
10
11
  import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
11
12
 
13
+ import ContextCachingSwitch from './ContextCachingSwitch';
12
14
  import ReasoningTokenSlider from './ReasoningTokenSlider';
13
15
 
14
16
  const ControlsForm = memo(() => {
@@ -18,18 +20,57 @@ const ControlsForm = memo(() => {
18
20
  agentSelectors.currentAgentModelProvider(s),
19
21
  s.updateAgentChatConfig,
20
22
  ]);
21
- const config = useAgentStore(agentSelectors.currentAgentChatConfig, isEqual);
23
+ const [form] = Form.useForm();
24
+ const enableReasoning = AntdForm.useWatch(['enableReasoning'], form);
25
+
26
+ const config = useAgentStore(agentChatConfigSelectors.currentChatConfig, isEqual);
22
27
 
23
28
  const modelExtendParams = useAiInfraStore(aiModelSelectors.modelExtendParams(model, provider));
24
29
 
25
- const items: FormItemProps[] = [
30
+ const items = [
31
+ {
32
+ children: <ContextCachingSwitch />,
33
+ desc: (
34
+ <span style={{ display: 'inline-block', width: 300 }}>
35
+ <Trans i18nKey={'extendParams.disableContextCaching.desc'} ns={'chat'}>
36
+ 单条对话生成成本最高可降低 90%,响应速度提升 4 倍(
37
+ <Link
38
+ href={'https://www.anthropic.com/news/prompt-caching?utm_source=lobechat'}
39
+ rel={'nofollow'}
40
+ >
41
+ 了解更多
42
+ </Link>
43
+ )。开启后将自动禁用历史记录限制
44
+ </Trans>
45
+ </span>
46
+ ),
47
+ label: t('extendParams.disableContextCaching.title'),
48
+ minWidth: undefined,
49
+ name: 'disableContextCaching',
50
+ },
26
51
  {
27
52
  children: <Switch />,
53
+ desc: (
54
+ <span style={{ display: 'inline-block', width: 300 }}>
55
+ <Trans i18nKey={'extendParams.enableReasoning.desc'} ns={'chat'}>
56
+ 基于 Claude Thinking 机制限制(
57
+ <Link
58
+ href={
59
+ 'https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking?utm_source=lobechat#why-thinking-blocks-must-be-preserved'
60
+ }
61
+ rel={'nofollow'}
62
+ >
63
+ 了解更多
64
+ </Link>
65
+ ),开启后将自动禁用历史消息数限制
66
+ </Trans>
67
+ </span>
68
+ ),
28
69
  label: t('extendParams.enableReasoning.title'),
29
70
  minWidth: undefined,
30
71
  name: 'enableReasoning',
31
72
  },
32
- {
73
+ enableReasoning && {
33
74
  children: <ReasoningTokenSlider />,
34
75
  label: t('extendParams.reasoningBudgetToken.title'),
35
76
  layout: 'vertical',
@@ -39,10 +80,11 @@ const ControlsForm = memo(() => {
39
80
  paddingBottom: 0,
40
81
  },
41
82
  },
42
- ];
83
+ ].filter(Boolean) as FormItemProps[];
43
84
 
44
85
  return (
45
86
  <Form
87
+ form={form}
46
88
  initialValues={config}
47
89
  items={
48
90
  (modelExtendParams || [])
@@ -6,7 +6,6 @@ import useMergeState from 'use-merge-value';
6
6
  const Kibi = 1024;
7
7
 
8
8
  const exponent = (num: number) => Math.log2(num);
9
- const getRealValue = (num: number) => Math.round(Math.pow(2, num));
10
9
  const powerKibi = (num: number) => Math.round(Math.pow(2, num) * Kibi);
11
10
 
12
11
  interface MaxTokenSliderProps {
@@ -15,7 +14,7 @@ interface MaxTokenSliderProps {
15
14
  value?: number;
16
15
  }
17
16
 
18
- const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValue }) => {
17
+ const ReasoningTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValue }) => {
19
18
  const [token, setTokens] = useMergeState(0, {
20
19
  defaultValue,
21
20
  onChange,
@@ -30,7 +29,7 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
30
29
  const updateWithPowValue = (value: number) => {
31
30
  setPowValue(value);
32
31
 
33
- setTokens(powerKibi(value));
32
+ setTokens(Math.min(powerKibi(value), 64_000));
34
33
  };
35
34
 
36
35
  const updateWithRealValue = (value: number) => {
@@ -52,7 +51,7 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
52
51
  }, []);
53
52
 
54
53
  return (
55
- <Flexbox align={'center'} gap={12} horizontal>
54
+ <Flexbox align={'center'} gap={12} horizontal paddingInline={'4px 0'}>
56
55
  <Flexbox flex={1}>
57
56
  <Slider
58
57
  marks={marks}
@@ -60,21 +59,14 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
60
59
  min={exponent(1)}
61
60
  onChange={updateWithPowValue}
62
61
  step={null}
63
- tooltip={{
64
- formatter: (x) => {
65
- if (typeof x === 'undefined') return;
66
-
67
- let value = getRealValue(x);
68
-
69
- if (value < Kibi) return ((value * Kibi) / 1000).toFixed(0) + 'k';
70
- },
71
- }}
62
+ tooltip={{ open: false }}
72
63
  value={powValue}
73
64
  />
74
65
  </Flexbox>
75
66
  <div>
76
67
  <InputNumber
77
68
  changeOnWheel
69
+ max={64_000}
78
70
  min={0}
79
71
  onChange={(e) => {
80
72
  if (!e && e !== 0) return;
@@ -89,4 +81,4 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
89
81
  </Flexbox>
90
82
  );
91
83
  });
92
- export default MaxTokenSlider;
84
+ export default ReasoningTokenSlider;
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import { useAgentStore } from '@/store/agent';
10
- import { agentSelectors } from '@/store/agent/selectors';
10
+ import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
11
11
  import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
12
12
 
13
13
  import ExaIcon from './ExaIcon';
@@ -37,7 +37,7 @@ const ModelBuiltinSearch = memo(() => {
37
37
  const [model, provider, checked, updateAgentChatConfig] = useAgentStore((s) => [
38
38
  agentSelectors.currentAgentModel(s),
39
39
  agentSelectors.currentAgentModelProvider(s),
40
- agentSelectors.currentAgentChatConfig(s).useModelBuiltinSearch,
40
+ agentChatConfigSelectors.useModelBuiltinSearch(s),
41
41
  s.updateAgentChatConfig,
42
42
  ]);
43
43
 
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
10
  import { useAgentStore } from '@/store/agent';
11
- import { agentSelectors } from '@/store/agent/slices/chat';
11
+ import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/slices/chat';
12
12
  import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
13
13
  import { SearchMode } from '@/types/search';
14
14
 
@@ -84,7 +84,7 @@ const Item = memo<NetworkOption>(({ value, description, icon, label, disable })
84
84
  const { t } = useTranslation('chat');
85
85
  const { styles } = useStyles();
86
86
  const [mode, updateAgentChatConfig] = useAgentStore((s) => [
87
- agentSelectors.agentSearchMode(s),
87
+ agentChatConfigSelectors.agentSearchMode(s),
88
88
  s.updateAgentChatConfig,
89
89
  ]);
90
90
 
@@ -10,7 +10,7 @@ import { useModelContextWindowTokens } from '@/hooks/useModelContextWindowTokens
10
10
  import { useModelSupportToolUse } from '@/hooks/useModelSupportToolUse';
11
11
  import { useTokenCount } from '@/hooks/useTokenCount';
12
12
  import { useAgentStore } from '@/store/agent';
13
- import { agentSelectors } from '@/store/agent/selectors';
13
+ import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
14
14
  import { useChatStore } from '@/store/chat';
15
15
  import { topicSelectors } from '@/store/chat/selectors';
16
16
  import { useToolStore } from '@/store/tool';
@@ -31,15 +31,13 @@ const Token = memo<TokenTagProps>(({ total: messageString }) => {
31
31
  ]);
32
32
 
33
33
  const [systemRole, model, provider] = useAgentStore((s) => {
34
- const config = agentSelectors.currentAgentChatConfig(s);
35
-
36
34
  return [
37
35
  agentSelectors.currentAgentSystemRole(s),
38
36
  agentSelectors.currentAgentModel(s) as string,
39
37
  agentSelectors.currentAgentModelProvider(s) as string,
40
38
  // add these two params to enable the component to re-render
41
- config.historyCount,
42
- config.enableHistoryCount,
39
+ agentChatConfigSelectors.historyCount(s),
40
+ agentChatConfigSelectors.enableHistoryCount(s),
43
41
  ];
44
42
  });
45
43
 
@@ -57,4 +57,6 @@ const CustomRender = memo<CustomRenderProps>(
57
57
  },
58
58
  );
59
59
 
60
+ CustomRender.displayName = 'CustomRender';
61
+
60
62
  export default CustomRender;
@@ -1,3 +1,4 @@
1
+ import isEqual from 'fast-deep-equal';
1
2
  import { Suspense, memo } from 'react';
2
3
 
3
4
  import { LOADING_FLAT } from '@/const/message';
@@ -16,10 +17,11 @@ interface RenderProps {
16
17
  toolCallId: string;
17
18
  toolIndex: number;
18
19
  }
20
+
19
21
  const Render = memo<RenderProps>(
20
22
  ({ toolCallId, toolIndex, messageId, requestArgs, showPluginRender, setShowPluginRender }) => {
21
23
  const loading = useChatStore(chatSelectors.isToolCallStreaming(messageId, toolIndex));
22
- const toolMessage = useChatStore(chatSelectors.getMessageByToolCallId(toolCallId));
24
+ const toolMessage = useChatStore(chatSelectors.getMessageByToolCallId(toolCallId), isEqual);
23
25
 
24
26
  // 如果处于 loading 或者找不到 toolMessage 则展示 Arguments
25
27
  if (loading || !toolMessage) return <Arguments arguments={requestArgs} />;
@@ -48,4 +50,6 @@ const Render = memo<RenderProps>(
48
50
  },
49
51
  );
50
52
 
53
+ Render.displayName = 'ToolRender';
54
+
51
55
  export default Render;