@lobehub/chat 1.69.6 → 1.70.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 (57) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/docs/self-hosting/advanced/auth/clerk.zh-CN.mdx +1 -1
  4. package/docs/self-hosting/server-database/vercel.zh-CN.mdx +1 -1
  5. package/locales/ar/chat.json +7 -1
  6. package/locales/bg-BG/chat.json +7 -1
  7. package/locales/de-DE/chat.json +7 -1
  8. package/locales/en-US/chat.json +7 -1
  9. package/locales/es-ES/chat.json +7 -1
  10. package/locales/fa-IR/chat.json +7 -1
  11. package/locales/fr-FR/chat.json +7 -1
  12. package/locales/it-IT/chat.json +7 -1
  13. package/locales/ja-JP/chat.json +7 -1
  14. package/locales/ko-KR/chat.json +7 -1
  15. package/locales/nl-NL/chat.json +7 -1
  16. package/locales/pl-PL/chat.json +7 -1
  17. package/locales/pt-BR/chat.json +7 -1
  18. package/locales/ru-RU/chat.json +7 -1
  19. package/locales/tr-TR/chat.json +7 -1
  20. package/locales/vi-VN/chat.json +7 -1
  21. package/locales/zh-CN/chat.json +7 -1
  22. package/locales/zh-TW/chat.json +7 -1
  23. package/package.json +1 -1
  24. package/packages/web-crawler/src/crawler.ts +11 -1
  25. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +4 -1
  26. package/src/app/(backend)/webapi/chat/[provider]/route.ts +5 -1
  27. package/src/{features/Conversation/Messages/Assistant/Tool/Inspector/Loader.tsx → components/CircleLoader/index.tsx} +4 -3
  28. package/src/config/tools.ts +2 -0
  29. package/src/const/settings/agent.ts +6 -0
  30. package/src/features/ChatInput/ActionBar/Search/FCSearchModel.tsx +56 -0
  31. package/src/features/ChatInput/ActionBar/Search/FunctionCallingModelSelect/index.tsx +85 -0
  32. package/src/features/ChatInput/ActionBar/Search/SwitchPanel.tsx +9 -23
  33. package/src/features/Conversation/Extras/Usage/UsageDetail/ModelCard.tsx +15 -23
  34. package/src/features/Conversation/Extras/Usage/UsageDetail/pricing.ts +26 -0
  35. package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.test.ts +4 -4
  36. package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.ts +15 -10
  37. package/src/features/Conversation/Messages/Assistant/IntentUnderstanding.tsx +25 -0
  38. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/ToolTitle.tsx +1 -2
  39. package/src/features/Conversation/Messages/Assistant/index.tsx +18 -9
  40. package/src/features/ModelSwitchPanel/index.tsx +1 -4
  41. package/src/hooks/useAgentEnableSearch.ts +1 -9
  42. package/src/locales/default/chat.ts +7 -3
  43. package/src/server/routers/tools/search.ts +8 -1
  44. package/src/services/__tests__/chat.test.ts +1 -0
  45. package/src/services/chat.ts +63 -30
  46. package/src/services/session/type.ts +1 -1
  47. package/src/store/agent/slices/chat/selectors/__snapshots__/agent.test.ts.snap +4 -0
  48. package/src/store/agent/slices/chat/selectors/chatConfig.ts +5 -1
  49. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +12 -4
  50. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +113 -13
  51. package/src/store/chat/slices/aiChat/initialState.ts +2 -0
  52. package/src/store/chat/slices/aiChat/selectors.ts +8 -1
  53. package/src/store/chat/slices/message/action.ts +9 -1
  54. package/src/store/chat/slices/plugin/action.ts +6 -4
  55. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +4 -0
  56. package/src/types/agent/chatConfig.ts +6 -0
  57. package/src/types/openai/chat.ts +0 -1
@@ -1,4 +1,5 @@
1
1
  import { createStyles } from 'antd-style';
2
+ import { memo } from 'react';
2
3
 
3
4
  export const useStyles = createStyles(({ css, token }, borderWidth: number = 2.5) => ({
4
5
  background: css`
@@ -44,7 +45,7 @@ export const useStyles = createStyles(({ css, token }, borderWidth: number = 2.5
44
45
  `,
45
46
  }));
46
47
 
47
- const Loader = () => {
48
+ const CircleLoader = memo(() => {
48
49
  const { styles } = useStyles();
49
50
 
50
51
  return (
@@ -53,6 +54,6 @@ const Loader = () => {
53
54
  <div className={styles.background} />
54
55
  </div>
55
56
  );
56
- };
57
+ });
57
58
 
58
- export default Loader;
59
+ export default CircleLoader;
@@ -4,10 +4,12 @@ import { z } from 'zod';
4
4
  export const getToolsConfig = () => {
5
5
  return createEnv({
6
6
  runtimeEnv: {
7
+ CRAWLER_IMPLS: process.env.CRAWLER_IMPLS,
7
8
  SEARXNG_URL: process.env.SEARXNG_URL,
8
9
  },
9
10
 
10
11
  server: {
12
+ CRAWLER_IMPLS: z.string().optional(),
11
13
  SEARXNG_URL: z.string().url().optional(),
12
14
  },
13
15
  });
@@ -13,6 +13,11 @@ export const DEFAUTT_AGENT_TTS_CONFIG: LobeAgentTTSConfig = {
13
13
  },
14
14
  };
15
15
 
16
+ export const DEFAULT_AGENT_SEARCH_FC_MODEL = {
17
+ model: DEFAULT_MODEL,
18
+ provider: ModelProvider.OpenAI,
19
+ };
20
+
16
21
  export const DEFAULT_AGENT_CHAT_CONFIG: LobeAgentChatConfig = {
17
22
  autoCreateTopicThreshold: 2,
18
23
  displayMode: 'chat',
@@ -22,6 +27,7 @@ export const DEFAULT_AGENT_CHAT_CONFIG: LobeAgentChatConfig = {
22
27
  enableReasoning: false,
23
28
  historyCount: 8,
24
29
  reasoningBudgetToken: 1024,
30
+ searchFCModel: DEFAULT_AGENT_SEARCH_FC_MODEL,
25
31
  searchMode: 'off',
26
32
  };
27
33
 
@@ -0,0 +1,56 @@
1
+ import { createStyles } from 'antd-style';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ import InfoTooltip from '@/components/InfoTooltip';
6
+ import { useAgentStore } from '@/store/agent';
7
+ import { agentChatConfigSelectors } from '@/store/agent/slices/chat';
8
+
9
+ import FunctionCallingModelSelect from './FunctionCallingModelSelect';
10
+
11
+ const useStyles = createStyles(({ css, token }) => ({
12
+ check: css`
13
+ margin-inline-start: 12px;
14
+ font-size: 16px;
15
+ color: ${token.colorPrimary};
16
+ `,
17
+ content: css`
18
+ flex: 1;
19
+ width: 230px;
20
+ `,
21
+ description: css`
22
+ width: 200px;
23
+ font-size: 12px;
24
+ color: ${token.colorTextSecondary};
25
+ `,
26
+ title: css`
27
+ font-size: 14px;
28
+ font-weight: 500;
29
+ color: ${token.colorText};
30
+ `,
31
+ }));
32
+
33
+ const FCSearchModel = () => {
34
+ const { t } = useTranslation('chat');
35
+ const { styles } = useStyles();
36
+ const [searchFCModel, updateAgentChatConfig] = useAgentStore((s) => [
37
+ agentChatConfigSelectors.searchFCModel(s),
38
+ s.updateAgentChatConfig,
39
+ ]);
40
+ return (
41
+ <Flexbox distribution={'space-between'} gap={16} horizontal padding={8}>
42
+ <Flexbox align={'center'} gap={4} horizontal>
43
+ <Flexbox className={styles.title}>{t('search.searchModel.title')}</Flexbox>
44
+ <InfoTooltip title={t('search.searchModel.desc')} />
45
+ </Flexbox>
46
+ <FunctionCallingModelSelect
47
+ onChange={(value) => {
48
+ updateAgentChatConfig({ searchFCModel: value });
49
+ }}
50
+ value={searchFCModel}
51
+ />
52
+ </Flexbox>
53
+ );
54
+ };
55
+
56
+ export default FCSearchModel;
@@ -0,0 +1,85 @@
1
+ import { Select, SelectProps } from 'antd';
2
+ import { createStyles } from 'antd-style';
3
+ import { memo, useMemo } from 'react';
4
+
5
+ import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
6
+ import { useEnabledChatModels } from '@/hooks/useEnabledChatModels';
7
+ import { WorkingModel } from '@/types/agent';
8
+ import { EnabledProviderWithModels } from '@/types/aiProvider';
9
+
10
+ const useStyles = createStyles(({ css, prefixCls }) => ({
11
+ select: css`
12
+ &.${prefixCls}-select-dropdown .${prefixCls}-select-item-option-grouped {
13
+ padding-inline-start: 12px;
14
+ }
15
+ `,
16
+ }));
17
+
18
+ interface ModelOption {
19
+ label: any;
20
+ provider: string;
21
+ value: string;
22
+ }
23
+
24
+ interface ModelSelectProps {
25
+ onChange?: (props: WorkingModel) => void;
26
+ showAbility?: boolean;
27
+ value?: WorkingModel;
28
+ }
29
+
30
+ const ModelSelect = memo<ModelSelectProps>(({ value, onChange }) => {
31
+ const enabledList = useEnabledChatModels();
32
+
33
+ const { styles } = useStyles();
34
+
35
+ const options = useMemo<SelectProps['options']>(() => {
36
+ const getChatModels = (provider: EnabledProviderWithModels) =>
37
+ provider.children
38
+ .filter((model) => !!model.abilities.functionCall)
39
+ .map((model) => ({
40
+ label: <ModelItemRender {...model} {...model.abilities} showInfoTag={false} />,
41
+ provider: provider.id,
42
+ value: `${provider.id}/${model.id}`,
43
+ }));
44
+
45
+ if (enabledList.length === 1) {
46
+ const provider = enabledList[0];
47
+
48
+ return getChatModels(provider);
49
+ }
50
+
51
+ return enabledList
52
+ .filter((p) => !!getChatModels(p).length)
53
+ .map((provider) => {
54
+ const options = getChatModels(provider);
55
+
56
+ return {
57
+ label: (
58
+ <ProviderItemRender
59
+ logo={provider.logo}
60
+ name={provider.name}
61
+ provider={provider.id}
62
+ source={provider.source}
63
+ />
64
+ ),
65
+ options,
66
+ };
67
+ });
68
+ }, [enabledList]);
69
+
70
+ return (
71
+ <Select
72
+ onChange={(value, option) => {
73
+ const model = value.split('/').slice(1).join('/');
74
+ onChange?.({ model, provider: (option as unknown as ModelOption).provider });
75
+ }}
76
+ options={options}
77
+ popupClassName={styles.select}
78
+ popupMatchSelectWidth={false}
79
+ value={`${value?.provider}/${value?.model}`}
80
+ variant={'filled'}
81
+ />
82
+ );
83
+ });
84
+
85
+ export default ModelSelect;
@@ -12,6 +12,7 @@ import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/slices/c
12
12
  import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
13
13
  import { SearchMode } from '@/types/search';
14
14
 
15
+ import FCSearchModel from './FCSearchModel';
15
16
  import ModelBuiltinSearch from './ModelBuiltinSearch';
16
17
 
17
18
  const { Text } = Typography;
@@ -38,10 +39,6 @@ const useStyles = createStyles(({ css, token }) => ({
38
39
  font-size: 12px;
39
40
  color: ${token.colorTextSecondary};
40
41
  `,
41
- disable: css`
42
- cursor: not-allowed;
43
- opacity: 0.45;
44
- `,
45
42
  iconWrapper: css`
46
43
  display: flex;
47
44
  flex-shrink: 0;
@@ -80,8 +77,7 @@ const useStyles = createStyles(({ css, token }) => ({
80
77
  `,
81
78
  }));
82
79
 
83
- const Item = memo<NetworkOption>(({ value, description, icon, label, disable }) => {
84
- const { t } = useTranslation('chat');
80
+ const Item = memo<NetworkOption>(({ value, description, icon, label }) => {
85
81
  const { styles } = useStyles();
86
82
  const [mode, updateAgentChatConfig] = useAgentStore((s) => [
87
83
  agentChatConfigSelectors.agentSearchMode(s),
@@ -96,12 +92,12 @@ const Item = memo<NetworkOption>(({ value, description, icon, label, disable })
96
92
  key={value}
97
93
  onClick={() => updateAgentChatConfig({ searchMode: value })}
98
94
  >
99
- <Flexbox className={disable ? styles.disable : ''} gap={8} horizontal>
95
+ <Flexbox gap={8} horizontal>
100
96
  <div className={styles.iconWrapper}>{icon}</div>
101
97
  <div className={styles.content}>
102
98
  <div className={styles.title}>{label}</div>
103
99
  <Text className={styles.description} type="secondary">
104
- {disable ? t('search.mode.disable') : description}
100
+ {description}
105
101
  </Text>
106
102
  </div>
107
103
  </Flexbox>
@@ -132,34 +128,24 @@ const AINetworkSettings = memo<AINetworkSettingsProps>(() => {
132
128
  label: t('search.mode.off.title'),
133
129
  value: 'off',
134
130
  },
135
- // 等应用层联网功能做好以后再开启
136
- // {
137
- // description: t('search.mode.on.desc'),
138
- // icon: <WifiOutlined />,
139
- // label: t('search.mode.on.title'),
140
- // value: 'on',
141
- // },
142
131
  {
143
132
  description: t('search.mode.auto.desc'),
144
- disable: !supportFC,
145
133
  icon: <Icon icon={SparklesIcon} />,
146
134
  label: t('search.mode.auto.title'),
147
135
  value: 'auto',
148
136
  },
149
137
  ];
150
138
 
139
+ const showDivider = isModelHasBuiltinSearchConfig || !supportFC;
140
+
151
141
  return (
152
142
  <Flexbox gap={8}>
153
143
  {options.map((option) => (
154
144
  <Item {...option} key={option.value} />
155
145
  ))}
156
-
157
- {isModelHasBuiltinSearchConfig && (
158
- <>
159
- <Divider style={{ margin: 0, paddingInline: 12 }} />
160
- <ModelBuiltinSearch />
161
- </>
162
- )}
146
+ {showDivider && <Divider style={{ margin: 0, paddingInline: 12 }} />}
147
+ {isModelHasBuiltinSearchConfig && <ModelBuiltinSearch />}
148
+ {!supportFC && <FCSearchModel />}
163
149
  </Flexbox>
164
150
  );
165
151
  });
@@ -7,11 +7,10 @@ import { memo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
+ import { getPrice } from '@/features/Conversation/Extras/Usage/UsageDetail/pricing';
10
11
  import { useGlobalStore } from '@/store/global';
11
12
  import { systemStatusSelectors } from '@/store/global/selectors';
12
13
  import { LobeDefaultAiModelListItem } from '@/types/aiModel';
13
- import { ModelPriceCurrency } from '@/types/llm';
14
- import { formatPriceByCurrency } from '@/utils/format';
15
14
 
16
15
  export const useStyles = createStyles(({ css, token }) => {
17
16
  return {
@@ -40,19 +39,8 @@ const ModelCard = memo<ModelCardProps>(({ pricing, id, provider, displayName })
40
39
  const isShowCredit = useGlobalStore(systemStatusSelectors.isShowCredit) && !!pricing;
41
40
  const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
42
41
 
43
- const inputPrice = formatPriceByCurrency(pricing?.input, pricing?.currency as ModelPriceCurrency);
44
- const cachedInputPrice = formatPriceByCurrency(
45
- pricing?.cachedInput,
46
- pricing?.currency as ModelPriceCurrency,
47
- );
48
- const writeCacheInputPrice = formatPriceByCurrency(
49
- pricing?.writeCacheInput,
50
- pricing?.currency as ModelPriceCurrency,
51
- );
52
- const outputPrice = formatPriceByCurrency(
53
- pricing?.output,
54
- pricing?.currency as ModelPriceCurrency,
55
- );
42
+ const formatPrice = getPrice(pricing || {});
43
+
56
44
  return (
57
45
  <Flexbox gap={8}>
58
46
  <Flexbox
@@ -103,37 +91,41 @@ const ModelCard = memo<ModelCardProps>(({ pricing, id, provider, displayName })
103
91
  {pricing?.cachedInput && (
104
92
  <Tooltip
105
93
  title={t('messages.modelCard.pricing.inputCachedTokens', {
106
- amount: cachedInputPrice,
94
+ amount: formatPrice.cachedInput,
107
95
  })}
108
96
  >
109
97
  <Flexbox gap={2} horizontal>
110
98
  <Icon icon={CircleFadingArrowUp} />
111
- {cachedInputPrice}
99
+ {formatPrice.cachedInput}
112
100
  </Flexbox>
113
101
  </Tooltip>
114
102
  )}
115
103
  {pricing?.writeCacheInput && (
116
104
  <Tooltip
117
105
  title={t('messages.modelCard.pricing.writeCacheInputTokens', {
118
- amount: writeCacheInputPrice,
106
+ amount: formatPrice.writeCacheInput,
119
107
  })}
120
108
  >
121
109
  <Flexbox gap={2} horizontal>
122
110
  <Icon icon={BookUp2Icon} />
123
- {writeCacheInputPrice}
111
+ {formatPrice.writeCacheInput}
124
112
  </Flexbox>
125
113
  </Tooltip>
126
114
  )}
127
- <Tooltip title={t('messages.modelCard.pricing.inputTokens', { amount: inputPrice })}>
115
+ <Tooltip
116
+ title={t('messages.modelCard.pricing.inputTokens', { amount: formatPrice.input })}
117
+ >
128
118
  <Flexbox gap={2} horizontal>
129
119
  <Icon icon={ArrowUpFromDot} />
130
- {inputPrice}
120
+ {formatPrice.input}
131
121
  </Flexbox>
132
122
  </Tooltip>
133
- <Tooltip title={t('messages.modelCard.pricing.outputTokens', { amount: outputPrice })}>
123
+ <Tooltip
124
+ title={t('messages.modelCard.pricing.outputTokens', { amount: formatPrice.output })}
125
+ >
134
126
  <Flexbox gap={2} horizontal>
135
127
  <Icon icon={ArrowDownToDot} />
136
- {outputPrice}
128
+ {formatPrice.output}
137
129
  </Flexbox>
138
130
  </Tooltip>
139
131
  </Flexbox>
@@ -0,0 +1,26 @@
1
+ import { ChatModelPricing } from '@/types/aiModel';
2
+ import { ModelPriceCurrency } from '@/types/llm';
3
+ import { formatPriceByCurrency } from '@/utils/format';
4
+
5
+ export const getPrice = (pricing: ChatModelPricing) => {
6
+ const inputPrice = formatPriceByCurrency(pricing?.input, pricing?.currency as ModelPriceCurrency);
7
+ const cachedInputPrice = formatPriceByCurrency(
8
+ pricing?.cachedInput,
9
+ pricing?.currency as ModelPriceCurrency,
10
+ );
11
+ const writeCacheInputPrice = formatPriceByCurrency(
12
+ pricing?.writeCacheInput,
13
+ pricing?.currency as ModelPriceCurrency,
14
+ );
15
+ const outputPrice = formatPriceByCurrency(
16
+ pricing?.output,
17
+ pricing?.currency as ModelPriceCurrency,
18
+ );
19
+
20
+ return {
21
+ cachedInput: Number(cachedInputPrice),
22
+ input: Number(inputPrice),
23
+ output: Number(outputPrice),
24
+ writeCacheInput: Number(writeCacheInputPrice),
25
+ };
26
+ };
@@ -70,7 +70,7 @@ describe('getDetailsToken', () => {
70
70
  const result = getDetailsToken(usage, mockModelCard);
71
71
 
72
72
  expect(result.inputCached).toEqual({
73
- credit: 0, // 50 * 0.005 = 0.25, rounded to 0
73
+ credit: 1,
74
74
  token: 50,
75
75
  });
76
76
 
@@ -165,12 +165,12 @@ describe('getDetailsToken', () => {
165
165
  const result = getDetailsToken(usage, mockModelCard);
166
166
 
167
167
  // uncachedInput: (200 - 50) * 0.01 = 1.5 -> 2
168
- // cachedInput: 50 * 0.005 = 0.25 -> 0
168
+ // cachedInput: 50 * 0.005 = 0.25 -> 1
169
169
  // totalOutput: 300 * 0.02 = 6
170
- // totalCredit = 2 + 0 + 6 = 8
170
+ // totalCredit = 2 + 1 + 6 = 9
171
171
 
172
172
  expect(result.totalTokens).toEqual({
173
- credit: 8,
173
+ credit: 9,
174
174
  token: 500,
175
175
  });
176
176
  });
@@ -1,6 +1,8 @@
1
- import { LobeDefaultAiModelListItem } from '@/types/aiModel';
1
+ import { ChatModelPricing, LobeDefaultAiModelListItem } from '@/types/aiModel';
2
2
  import { ModelTokensUsage } from '@/types/message';
3
3
 
4
+ import { getPrice } from './pricing';
5
+
4
6
  const calcCredit = (token: number, pricing?: number) => {
5
7
  if (!pricing) return '-';
6
8
 
@@ -29,23 +31,26 @@ export const getDetailsToken = (
29
31
  ? usage?.inputCacheMissTokens
30
32
  : totalInputTokens - (inputCacheTokens || 0);
31
33
 
34
+ // Pricing
35
+ const formatPrice = getPrice(modelCard?.pricing as ChatModelPricing);
36
+
32
37
  const inputCacheMissCredit = (
33
- !!inputCacheMissTokens ? calcCredit(inputCacheMissTokens, modelCard?.pricing?.input) : 0
38
+ !!inputCacheMissTokens ? calcCredit(inputCacheMissTokens, formatPrice.input) : 0
34
39
  ) as number;
35
40
 
36
41
  const inputCachedCredit = (
37
- !!inputCacheTokens ? calcCredit(inputCacheTokens, modelCard?.pricing?.cachedInput) : 0
42
+ !!inputCacheTokens ? calcCredit(inputCacheTokens, formatPrice.cachedInput) : 0
38
43
  ) as number;
39
44
 
40
45
  const inputWriteCachedCredit = !!inputWriteCacheTokens
41
- ? (calcCredit(inputWriteCacheTokens, modelCard?.pricing?.writeCacheInput) as number)
46
+ ? (calcCredit(inputWriteCacheTokens, formatPrice.writeCacheInput) as number)
42
47
  : 0;
43
48
 
44
49
  const totalOutputCredit = (
45
- !!totalOutputTokens ? calcCredit(totalOutputTokens, modelCard?.pricing?.output) : 0
50
+ !!totalOutputTokens ? calcCredit(totalOutputTokens, formatPrice.output) : 0
46
51
  ) as number;
47
52
  const totalInputCredit = (
48
- !!totalInputTokens ? calcCredit(totalInputTokens, modelCard?.pricing?.output) : 0
53
+ !!totalInputTokens ? calcCredit(totalInputTokens, formatPrice.output) : 0
49
54
  ) as number;
50
55
 
51
56
  const totalCredit =
@@ -69,13 +74,13 @@ export const getDetailsToken = (
69
74
  : undefined,
70
75
  inputCitation: !!usage.inputCitationTokens
71
76
  ? {
72
- credit: calcCredit(usage.inputCitationTokens, modelCard?.pricing?.input),
77
+ credit: calcCredit(usage.inputCitationTokens, formatPrice.input),
73
78
  token: usage.inputCitationTokens,
74
79
  }
75
80
  : undefined,
76
81
  inputText: !!inputTextTokens
77
82
  ? {
78
- credit: calcCredit(inputTextTokens, modelCard?.pricing?.input),
83
+ credit: calcCredit(inputTextTokens, formatPrice.input),
79
84
  token: inputTextTokens,
80
85
  }
81
86
  : undefined,
@@ -89,13 +94,13 @@ export const getDetailsToken = (
89
94
  : undefined,
90
95
  outputReasoning: !!outputReasoningTokens
91
96
  ? {
92
- credit: calcCredit(outputReasoningTokens, modelCard?.pricing?.output),
97
+ credit: calcCredit(outputReasoningTokens, formatPrice.output),
93
98
  token: outputReasoningTokens,
94
99
  }
95
100
  : undefined,
96
101
  outputText: !!outputTextTokens
97
102
  ? {
98
- credit: calcCredit(outputTextTokens, modelCard?.pricing?.output),
103
+ credit: calcCredit(outputTextTokens, formatPrice.output),
99
104
  token: outputTextTokens,
100
105
  }
101
106
  : undefined,
@@ -0,0 +1,25 @@
1
+ import { createStyles } from 'antd-style';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ import CircleLoader from '@/components/CircleLoader';
6
+ import { shinyTextStylish } from '@/styles/loading';
7
+
8
+ const useStyles = createStyles(({ token }) => ({
9
+ shinyText: shinyTextStylish(token),
10
+ }));
11
+
12
+ const IntentUnderstanding = () => {
13
+ const { styles } = useStyles();
14
+ const { t } = useTranslation('chat');
15
+
16
+ return (
17
+ <Flexbox align={'center'} gap={8} horizontal>
18
+ <CircleLoader />
19
+ <Flexbox className={styles.shinyText} horizontal>
20
+ {t('intentUnderstanding.title')}
21
+ </Flexbox>
22
+ </Flexbox>
23
+ );
24
+ };
25
+ export default IntentUnderstanding;
@@ -6,6 +6,7 @@ import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
+ import Loader from '@/components/CircleLoader';
9
10
  import PluginAvatar from '@/features/PluginAvatar';
10
11
  import { useChatStore } from '@/store/chat';
11
12
  import { chatSelectors } from '@/store/chat/selectors';
@@ -14,8 +15,6 @@ import { toolSelectors } from '@/store/tool/selectors';
14
15
  import { shinyTextStylish } from '@/styles/loading';
15
16
  import { WebBrowsingManifest } from '@/tools/web-browsing';
16
17
 
17
- import Loader from './Loader';
18
-
19
18
  export const useStyles = createStyles(({ css, token }) => ({
20
19
  apiName: css`
21
20
  overflow: hidden;
@@ -8,6 +8,7 @@ import { ChatMessage } from '@/types/message';
8
8
 
9
9
  import { DefaultMessage } from '../Default';
10
10
  import FileChunks from './FileChunks';
11
+ import IntentUnderstanding from './IntentUnderstanding';
11
12
  import Reasoning from './Reasoning';
12
13
  import SearchGrounding from './SearchGrounding';
13
14
  import Tool from './Tool';
@@ -24,6 +25,8 @@ export const AssistantMessage = memo<
24
25
 
25
26
  const isReasoning = useChatStore(aiChatSelectors.isMessageInReasoning(id));
26
27
 
28
+ const isIntentUnderstanding = useChatStore(aiChatSelectors.isIntentUnderstanding(id));
29
+
27
30
  const showSearch = !!search && !!search.citations?.length;
28
31
 
29
32
  // remove \n to avoid empty content
@@ -32,6 +35,8 @@ export const AssistantMessage = memo<
32
35
  (!!props.reasoning && props.reasoning.content?.trim() !== '') ||
33
36
  (!props.reasoning && isReasoning);
34
37
 
38
+ const showFileChunks = !!chunksList && chunksList.length > 0;
39
+
35
40
  return editing ? (
36
41
  <DefaultMessage
37
42
  content={content}
@@ -44,16 +49,20 @@ export const AssistantMessage = memo<
44
49
  {showSearch && (
45
50
  <SearchGrounding citations={search?.citations} searchQueries={search?.searchQueries} />
46
51
  )}
47
- {!!chunksList && chunksList.length > 0 && <FileChunks data={chunksList} />}
52
+ {showFileChunks && <FileChunks data={chunksList} />}
48
53
  {showReasoning && <Reasoning {...props.reasoning} id={id} />}
49
- {content && (
50
- <DefaultMessage
51
- addIdOnDOM={false}
52
- content={content}
53
- id={id}
54
- isToolCallGenerating={isToolCallGenerating}
55
- {...props}
56
- />
54
+ {isIntentUnderstanding ? (
55
+ <IntentUnderstanding />
56
+ ) : (
57
+ content && (
58
+ <DefaultMessage
59
+ addIdOnDOM={false}
60
+ content={content}
61
+ id={id}
62
+ isToolCallGenerating={isToolCallGenerating}
63
+ {...props}
64
+ />
65
+ )
57
66
  )}
58
67
  {tools && (
59
68
  <Flexbox gap={8}>
@@ -115,10 +115,7 @@ const ModelSwitchPanel = memo<PropsWithChildren>(({ children }) => {
115
115
  provider={provider.id}
116
116
  source={provider.source}
117
117
  />
118
- <Link
119
- href={isDeprecatedEdition ? '/settings/llm' : `/settings/provider/${provider.id}`}
120
- prefetch={false}
121
- >
118
+ <Link href={isDeprecatedEdition ? '/settings/llm' : `/settings/provider/${provider.id}`}>
122
119
  <ActionIcon
123
120
  icon={LucideBolt}
124
121
  size={'small'}
@@ -9,19 +9,11 @@ export const useAgentEnableSearch = () => {
9
9
  agentChatConfigSelectors.agentSearchMode(s),
10
10
  ]);
11
11
 
12
- const isModelSupportToolUse = useAiInfraStore(
13
- aiModelSelectors.isModelSupportToolUse(model, provider),
14
- );
15
12
  const searchImpl = useAiInfraStore(aiModelSelectors.modelBuiltinSearchImpl(model, provider));
16
13
 
17
14
  // 只要是内置的搜索实现,一定可以联网搜索
18
15
  if (searchImpl === 'internal') return true;
19
16
 
20
17
  // 如果是关闭状态,一定不能联网搜索
21
- if (agentSearchMode === 'off') return false;
22
-
23
- // 如果是智能模式,根据是否支持 Tool Calling 判断
24
- if (agentSearchMode === 'auto') {
25
- return isModelSupportToolUse;
26
- }
18
+ return agentSearchMode !== 'off';
27
19
  };
@@ -65,6 +65,9 @@ export default {
65
65
  stop: '停止',
66
66
  warp: '换行',
67
67
  },
68
+ intentUnderstanding: {
69
+ title: '正在分析并理解意图您的意图...',
70
+ },
68
71
  knowledgeBase: {
69
72
  all: '所有内容',
70
73
  allFiles: '所有文件',
@@ -142,13 +145,11 @@ export default {
142
145
  searchQueries: '搜索关键词',
143
146
  title: '已搜索到 {{count}} 个结果',
144
147
  },
145
-
146
148
  mode: {
147
149
  auto: {
148
150
  desc: '根据对话内容智能判断是否需要搜索',
149
151
  title: '智能联网',
150
152
  },
151
- disable: '当前模型不支持函数调用,因此无法使用智能联网功能',
152
153
  off: {
153
154
  desc: '仅使用模型的基础知识,不进行网络搜索',
154
155
  title: '关闭联网',
@@ -159,7 +160,10 @@ export default {
159
160
  },
160
161
  useModelBuiltin: '使用模型内置搜索引擎',
161
162
  },
162
-
163
+ searchModel: {
164
+ desc: '当前模型不支持函数调用,因此需要搭配支持函数调用的模型才能联网搜索',
165
+ title: '搜索辅助模型',
166
+ },
163
167
  title: '联网搜索',
164
168
  },
165
169
  searchAgentPlaceholder: '搜索助手...',