@lobehub/lobehub 2.0.0-next.107 → 2.0.0-next.109

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 (46) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/changelog/v1.json +21 -0
  3. package/locales/ar/models.json +108 -13
  4. package/locales/bg-BG/models.json +126 -15
  5. package/locales/de-DE/models.json +3 -0
  6. package/locales/en-US/models.json +3 -0
  7. package/locales/es-ES/models.json +3 -0
  8. package/locales/fa-IR/models.json +108 -13
  9. package/locales/fr-FR/models.json +108 -13
  10. package/locales/it-IT/models.json +3 -0
  11. package/locales/ja-JP/models.json +108 -13
  12. package/locales/ko-KR/models.json +108 -13
  13. package/locales/nl-NL/models.json +3 -0
  14. package/locales/pl-PL/models.json +108 -13
  15. package/locales/pt-BR/models.json +3 -0
  16. package/locales/ru-RU/models.json +126 -15
  17. package/locales/tr-TR/models.json +108 -13
  18. package/locales/vi-VN/models.json +3 -0
  19. package/locales/zh-TW/models.json +3 -0
  20. package/package.json +1 -1
  21. package/packages/model-bank/src/aiModels/cometapi.ts +8 -8
  22. package/packages/model-bank/src/aiModels/fal.ts +2 -2
  23. package/packages/model-bank/src/aiModels/nebius.ts +1 -1
  24. package/packages/model-bank/src/aiModels/newapi.ts +3 -3
  25. package/packages/model-bank/src/aiModels/openai.ts +4 -4
  26. package/packages/model-bank/src/aiModels/qwen.ts +4 -4
  27. package/packages/model-bank/src/aiModels/stepfun.ts +1 -1
  28. package/packages/model-bank/src/aiModels/vercelaigateway.ts +1 -1
  29. package/packages/model-bank/src/aiModels/volcengine.ts +3 -3
  30. package/packages/model-bank/src/aiModels/zhipu.ts +1 -1
  31. package/packages/model-bank/src/types/aiModel.ts +8 -8
  32. package/src/app/[variants]/(main)/layouts/desktop/SideBar/TopActions.tsx +5 -6
  33. package/src/app/[variants]/desktopRouter.config.tsx +5 -0
  34. package/src/app/[variants]/mobileRouter.config.tsx +5 -0
  35. package/src/features/KnowledgeManager/Home/index.tsx +1 -1
  36. package/src/hooks/usePinnedAgentState.ts +22 -15
  37. package/src/layout/GlobalProvider/StoreInitialization.tsx +5 -0
  38. package/src/server/globalConfig/genServerAiProviderConfig.ts +3 -3
  39. package/src/server/globalConfig/parseFilesConfig.ts +1 -1
  40. package/src/server/globalConfig/parseSystemAgent.ts +4 -4
  41. package/src/store/session/slices/session/action.ts +23 -0
  42. package/src/store/session/slices/session/initialState.ts +6 -0
  43. package/src/store/urlHydration/action.ts +56 -0
  44. package/src/store/urlHydration/index.ts +1 -0
  45. package/src/store/urlHydration/initialState.ts +12 -0
  46. package/src/store/urlHydration/store.ts +28 -0
@@ -310,7 +310,7 @@ const qwenChatModels: AIChatModelCard[] = [
310
310
  functionCall: true,
311
311
  },
312
312
  config: {
313
- deploymentName: 'qwen3-coder-plus', // 支持上下文缓存
313
+ deploymentName: 'qwen3-coder-plus', // Supports context caching
314
314
  },
315
315
  contextWindowTokens: 1_000_000,
316
316
  description:
@@ -373,7 +373,7 @@ const qwenChatModels: AIChatModelCard[] = [
373
373
  functionCall: true,
374
374
  },
375
375
  config: {
376
- deploymentName: 'qwen3-coder-flash', // 支持上下文缓存
376
+ deploymentName: 'qwen3-coder-flash', // Supports context caching
377
377
  },
378
378
  contextWindowTokens: 1_000_000,
379
379
  description:
@@ -1052,7 +1052,7 @@ const qwenChatModels: AIChatModelCard[] = [
1052
1052
  search: true,
1053
1053
  },
1054
1054
  config: {
1055
- deploymentName: 'qwen3-max', // 其支持上下文缓存
1055
+ deploymentName: 'qwen3-max', // Supports context caching
1056
1056
  },
1057
1057
  contextWindowTokens: 262_144,
1058
1058
  description:
@@ -1119,7 +1119,7 @@ const qwenChatModels: AIChatModelCard[] = [
1119
1119
  search: true,
1120
1120
  },
1121
1121
  config: {
1122
- deploymentName: 'qwen3-max-preview', // 其支持上下文缓存
1122
+ deploymentName: 'qwen3-max-preview', // Supports context caching
1123
1123
  },
1124
1124
  contextWindowTokens: 262_144,
1125
1125
  description: '通义千问系列效果最好的模型,适合复杂、多步骤的任务。预览版已支持思考。',
@@ -31,7 +31,7 @@ const stepfunChatModels: AIChatModelCard[] = [
31
31
  strategy: 'tiered',
32
32
  tiers: [
33
33
  { rate: 4, upTo: 0.004 },
34
- { rate: 8, upTo: 'infinity' }, // 仍与文档有出入
34
+ { rate: 8, upTo: 'infinity' }, // Still differs from documentation
35
35
  ],
36
36
  unit: 'millionTokens',
37
37
  },
@@ -1,6 +1,6 @@
1
1
  import { AIChatModelCard, AIEmbeddingModelCard } from '../types/aiModel';
2
2
 
3
- // 根据 Vercel AI Gateway 提供的模型列表,按 SOTA、大模型、小模型排序
3
+ // Model list provided by Vercel AI Gateway, sorted by SOTA, large models, small models
4
4
  const vercelAIGatewayChatModels: AIChatModelCard[] = [
5
5
  {
6
6
  abilities: {
@@ -790,7 +790,7 @@ const doubaoChatModels: AIChatModelCard[] = [
790
790
  const volcengineImageModels: AIImageModelCard[] = [
791
791
  {
792
792
  /*
793
- // TODO: AIImageModelCard 不支持 config.deploymentName
793
+ // TODO: AIImageModelCard does not support config.deploymentName
794
794
  config: {
795
795
  deploymentName: 'doubao-seedream-3-0-t2i-250415',
796
796
  },
@@ -824,7 +824,7 @@ const volcengineImageModels: AIImageModelCard[] = [
824
824
  },
825
825
  {
826
826
  /*
827
- // TODO: AIImageModelCard 不支持 config.deploymentName
827
+ // TODO: AIImageModelCard does not support config.deploymentName
828
828
  config: {
829
829
  deploymentName: 'doubao-seedream-3-0-t2i-250415',
830
830
  },
@@ -857,7 +857,7 @@ const volcengineImageModels: AIImageModelCard[] = [
857
857
  releasedAt: '2025-04-15',
858
858
  type: 'image',
859
859
  },
860
- // Note: Doubao 图生图模型与文生图模型公用一个 Endpoint,当前如果存在 imageUrl 会切换至 edit endpoint
860
+ // Note: Doubao image-to-image and text-to-image models share the same Endpoint, currently switches to edit endpoint if imageUrl exists
861
861
  {
862
862
  // config: {
863
863
  // deploymentName: 'doubao-seededit-3-0-i2i-250628',
@@ -680,7 +680,7 @@ const zhipuChatModels: AIChatModelCard[] = [
680
680
  contextWindowTokens: 131_072,
681
681
  description: 'GLM-4-0520 是最新模型版本,专为高度复杂和多样化任务设计,表现卓越。',
682
682
  displayName: 'GLM-4-0520',
683
- id: 'glm-4-0520', // 弃用时间 2025年12月30日
683
+ id: 'glm-4-0520', // Deprecation date: December 30, 2025
684
684
  pricing: {
685
685
  currency: 'CNY',
686
686
  units: [
@@ -70,34 +70,34 @@ const AiModelAbilitiesSchema = z.object({
70
70
  vision: z.boolean().optional(),
71
71
  });
72
72
 
73
- // 语言模型的设置参数
73
+ // Language model configuration parameters
74
74
  export interface LLMParams {
75
75
  /**
76
- * 控制生成文本中的惩罚系数,用于减少重复性
76
+ * Controls the penalty coefficient in generated text to reduce repetition
77
77
  * @default 0
78
78
  */
79
79
  frequency_penalty?: number;
80
80
  /**
81
- * 生成文本的最大长度
81
+ * Maximum length of generated text
82
82
  */
83
83
  max_tokens?: number;
84
84
  /**
85
- * 控制生成文本中的惩罚系数,用于减少主题的变化
85
+ * Controls the penalty coefficient in generated text to reduce topic variation
86
86
  * @default 0
87
87
  */
88
88
  presence_penalty?: number;
89
89
  /**
90
- * 生成文本的随机度量,用于控制文本的创造性和多样性
90
+ * Random measure for generated text to control creativity and diversity
91
91
  * @default 1
92
92
  */
93
93
  reasoning_effort?: string;
94
94
  /**
95
- * 生成文本的随机度量,用于控制文本的创造性和多样性
95
+ * Random measure for generated text to control creativity and diversity
96
96
  * @default 1
97
97
  */
98
98
  temperature?: number;
99
99
  /**
100
- * 控制生成文本中最高概率的单个 token
100
+ * Controls the single token with highest probability in generated text
101
101
  * @default 1
102
102
  */
103
103
  top_p?: number;
@@ -248,7 +248,7 @@ export type ExtendParamsType =
248
248
  export interface AiModelSettings {
249
249
  extendParams?: ExtendParamsType[];
250
250
  /**
251
- * 模型层实现搜索的方式
251
+ * How the model layer implements search
252
252
  */
253
253
  searchImpl?: ModelSearchImplementType;
254
254
  searchProvider?: string;
@@ -2,11 +2,12 @@ import { ActionIcon, ActionIconProps, Hotkey } from '@lobehub/ui';
2
2
  import { Compass, FolderClosed, MessageSquare, Palette } from 'lucide-react';
3
3
  import { memo, useMemo, useTransition } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { useNavigate } from 'react-router-dom';
6
5
  import { Flexbox } from 'react-layout-kit';
6
+ import { useNavigate } from 'react-router-dom';
7
7
 
8
8
  import { INBOX_SESSION_ID } from '@/const/session';
9
9
  import { SESSION_CHAT_URL } from '@/const/url';
10
+ import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
10
11
  import { useGlobalStore } from '@/store/global';
11
12
  import { SidebarTabKey } from '@/store/global/initialState';
12
13
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
@@ -31,11 +32,8 @@ const TopActions = memo<TopActionProps>(({ tab, isPinned }) => {
31
32
  const { t } = useTranslation('common');
32
33
  const navigate = useNavigate();
33
34
  const [, startTransition] = useTransition();
34
-
35
- const [switchBackToChat, isMobile] = useGlobalStore((s) => [
36
- s.switchBackToChat,
37
- s.isMobile,
38
- ]);
35
+ const [, { unpinAgent }] = usePinnedAgentState();
36
+ const [switchBackToChat, isMobile] = useGlobalStore((s) => [s.switchBackToChat, s.isMobile]);
39
37
  const { showMarket, enableKnowledgeBase, showAiImage } =
40
38
  useServerConfigStore(featureFlagsSelectors);
41
39
  const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.NavigateToChat));
@@ -69,6 +67,7 @@ const TopActions = memo<TopActionProps>(({ tab, isPinned }) => {
69
67
  e.preventDefault();
70
68
  startTransition(() => {
71
69
  switchBackToChat(activeSessionId);
70
+ unpinAgent();
72
71
  });
73
72
  }}
74
73
  size={ICON_SIZE}
@@ -385,6 +385,11 @@ export const createDesktopRouter = (locale: Locales) =>
385
385
  element: <KnowledgeHome />,
386
386
  index: true,
387
387
  },
388
+ {
389
+ element: <KnowledgeHome />,
390
+ loader: idLoader,
391
+ path: ':id',
392
+ },
388
393
  {
389
394
  element: <KnowledgeBasesList />,
390
395
  path: 'bases',
@@ -402,6 +402,11 @@ export const createMobileRouter = (locale: Locales) =>
402
402
  element: <KnowledgeHome />,
403
403
  index: true,
404
404
  },
405
+ {
406
+ element: <KnowledgeHome />,
407
+ loader: idLoader,
408
+ path: ':id',
409
+ },
405
410
  {
406
411
  element: <KnowledgeBasesList />,
407
412
  path: 'bases',
@@ -104,7 +104,7 @@ const Home = memo<HomeProps>(({ knowledgeBaseId, onOpenFile }) => {
104
104
  const handleDocumentClick = (documentId: string) => {
105
105
  // Navigate to the document in the explorer
106
106
  // The KnowledgeHomePage will automatically set category to 'documents' when it detects the id param
107
- navigate(`/${documentId}`);
107
+ navigate(`/knowledge/${documentId}`);
108
108
  };
109
109
 
110
110
  return (
@@ -1,21 +1,28 @@
1
- import { useMemo } from 'react';
1
+ 'use client';
2
2
 
3
- import { parseAsBoolean, useQueryParam } from './useQueryParam';
3
+ import { useSessionStore } from '@/store/session';
4
+ import { useUrlHydrationStore } from '@/store/urlHydration';
4
5
 
5
6
  export const usePinnedAgentState = () => {
6
- const [isPinned, setIsPinned] = useQueryParam('pinned', parseAsBoolean.withDefault(false), {
7
- clearOnDefault: true,
8
- });
7
+ const isPinned = useSessionStore((s) => s.isAgentPinned);
8
+ const setAgentPinned = useSessionStore((s) => s.setAgentPinned);
9
+ const toggleAgentPinned = useSessionStore((s) => s.toggleAgentPinned);
10
+ const syncToUrl = useUrlHydrationStore((s) => s.syncAgentPinnedToUrl);
9
11
 
10
- const actions = useMemo(
11
- () => ({
12
- pinAgent: () => setIsPinned(true),
13
- setIsPinned,
14
- togglePinAgent: () => setIsPinned((prev) => !prev),
15
- unpinAgent: () => setIsPinned(false),
16
- }),
17
- [setIsPinned],
18
- );
12
+ const withSync = <T extends (...args: any[]) => void>(fn: T) => {
13
+ return (...args: Parameters<T>) => {
14
+ fn(...args);
15
+ syncToUrl();
16
+ };
17
+ };
19
18
 
20
- return [isPinned, actions] as const;
19
+ return [
20
+ isPinned,
21
+ {
22
+ pinAgent: withSync(() => setAgentPinned(true)),
23
+ setIsPinned: withSync(setAgentPinned),
24
+ togglePinAgent: withSync(toggleAgentPinned),
25
+ unpinAgent: withSync(() => setAgentPinned(false)),
26
+ },
27
+ ] as const;
21
28
  };
@@ -12,6 +12,7 @@ import { useAiInfraStore } from '@/store/aiInfra';
12
12
  import { useGlobalStore } from '@/store/global';
13
13
  import { useServerConfigStore } from '@/store/serverConfig';
14
14
  import { serverConfigSelectors } from '@/store/serverConfig/selectors';
15
+ import { useUrlHydrationStore } from '@/store/urlHydration';
15
16
  import { useUserStore } from '@/store/user';
16
17
  import { authSelectors } from '@/store/user/selectors';
17
18
 
@@ -19,6 +20,10 @@ const StoreInitialization = memo(() => {
19
20
  // prefetch error ns to avoid don't show error content correctly
20
21
  useTranslation('error');
21
22
 
23
+ // Initialize from URL (one-time)
24
+ const initAgentPinnedFromUrl = useUrlHydrationStore((s) => s.initAgentPinnedFromUrl);
25
+ initAgentPinnedFromUrl();
26
+
22
27
  const router = useRouter();
23
28
  const [isLogin, isSignedIn, useInitUserState] = useUserStore((s) => [
24
29
  authSelectors.isLogin(s),
@@ -18,7 +18,7 @@ export const genServerAiProvidersConfig = async (
18
18
  ) => {
19
19
  const llmConfig = getLLMConfig() as Record<string, any>;
20
20
 
21
- // 并发处理所有 providers
21
+ // Process all providers concurrently
22
22
  const providerConfigs = await Promise.all(
23
23
  Object.values(ModelProvider).map(async (provider) => {
24
24
  const providerUpperCase = provider.toUpperCase();
@@ -33,7 +33,7 @@ export const genServerAiProvidersConfig = async (
33
33
  const modelString =
34
34
  process.env[providerConfig.modelListKey ?? `${providerUpperCase}_MODEL_LIST`];
35
35
 
36
- // 并发处理 extractEnabledModels transformToAiModelList
36
+ // Process extractEnabledModels and transformToAiModelList concurrently
37
37
  const [enabledModels, serverModelLists] = await Promise.all([
38
38
  extractEnabledModels(provider, modelString, providerConfig.withDeploymentName || false),
39
39
  transformToAiModelList({
@@ -61,7 +61,7 @@ export const genServerAiProvidersConfig = async (
61
61
  }),
62
62
  );
63
63
 
64
- // 将结果转换为对象
64
+ // Convert the results to an object
65
65
  const config = {} as Record<string, ProviderConfig>;
66
66
  for (const { provider, config: providerConfig } of providerConfigs) {
67
67
  config[provider] = providerConfig;
@@ -12,7 +12,7 @@ export const parseFilesConfig = (envString: string = ''): SystemEmbeddingConfig
12
12
  if (!envString) return DEFAULT_FILES_CONFIG;
13
13
  const config: FilesConfig = {} as any;
14
14
 
15
- // 处理全角逗号和多余空格
15
+ // Handle full-width commas and extra spaces
16
16
  let envValue = envString.replaceAll(',', ',').trim();
17
17
 
18
18
  const pairs = envValue.split(',');
@@ -10,12 +10,12 @@ export const parseSystemAgent = (envString: string = ''): Partial<UserSystemAgen
10
10
 
11
11
  const config: Partial<UserSystemAgentConfig> = {};
12
12
 
13
- // 处理全角逗号和多余空格
13
+ // Handle full-width commas and extra spaces
14
14
  let envValue = envString.replaceAll(',', ',').trim();
15
15
 
16
16
  const pairs = envValue.split(',');
17
17
 
18
- // 用于存储默认设置,如果有 default=provider/model 的情况
18
+ // Store default settings if there is a default=provider/model case
19
19
  let defaultSetting: { model: string; provider: string } | undefined;
20
20
 
21
21
  for (const pair of pairs) {
@@ -29,7 +29,7 @@ export const parseSystemAgent = (envString: string = ''): Partial<UserSystemAgen
29
29
  throw new Error('Missing model or provider value');
30
30
  }
31
31
 
32
- // 如果是 default 键,保存默认设置
32
+ // If it's the default key, save the default settings
33
33
  if (key === 'default') {
34
34
  defaultSetting = {
35
35
  model: model.trim(),
@@ -50,7 +50,7 @@ export const parseSystemAgent = (envString: string = ''): Partial<UserSystemAgen
50
50
  }
51
51
  }
52
52
 
53
- // 如果有默认设置,应用到所有未设置的系统智能体
53
+ // If there are default settings, apply them to all unconfigured system agents
54
54
  if (defaultSetting) {
55
55
  for (const key of protectedKeys) {
56
56
  if (!config[key as keyof UserSystemAgentConfig]) {
@@ -76,6 +76,15 @@ export interface SessionAction {
76
76
  */
77
77
  removeSession: (id: string) => Promise<void>;
78
78
 
79
+ /**
80
+ * Set the agent panel pinned state
81
+ */
82
+ setAgentPinned: (pinned: boolean | ((prev: boolean) => boolean)) => void;
83
+ /**
84
+ * Toggle the agent panel pinned state
85
+ */
86
+ toggleAgentPinned: () => void;
87
+
79
88
  updateSearchKeywords: (keywords: string) => void;
80
89
 
81
90
  useFetchSessions: (
@@ -187,12 +196,26 @@ export const createSessionSlice: StateCreator<
187
196
  }
188
197
  },
189
198
 
199
+ setAgentPinned: (value) => {
200
+ set(
201
+ (state) => ({
202
+ isAgentPinned: typeof value === 'function' ? value(state.isAgentPinned) : value,
203
+ }),
204
+ false,
205
+ n('setAgentPinned'),
206
+ );
207
+ },
208
+
190
209
  switchSession: (sessionId) => {
191
210
  if (get().activeId === sessionId) return;
192
211
 
193
212
  set({ activeId: sessionId }, false, n(`activeSession/${sessionId}`));
194
213
  },
195
214
 
215
+ toggleAgentPinned: () => {
216
+ set((state) => ({ isAgentPinned: !state.isAgentPinned }), false, n('toggleAgentPinned'));
217
+ },
218
+
196
219
  triggerSessionUpdate: async (id) => {
197
220
  await get().internal_updateSession(id, { updatedAt: new Date() });
198
221
  },
@@ -7,6 +7,11 @@ export interface SessionState {
7
7
  */
8
8
  activeId: string;
9
9
  defaultSessions: LobeSessions;
10
+ /**
11
+ * @title Whether the agent panel is pinned
12
+ * @description Controls the agent panel pinning state in the UI layout
13
+ */
14
+ isAgentPinned: boolean;
10
15
  isSearching: boolean;
11
16
  isSessionsFirstFetchFinished: boolean;
12
17
  pinnedSessions: LobeSessions;
@@ -22,6 +27,7 @@ export interface SessionState {
22
27
  export const initialSessionState: SessionState = {
23
28
  activeId: 'inbox',
24
29
  defaultSessions: [],
30
+ isAgentPinned: false,
25
31
  isSearching: false,
26
32
  isSessionsFirstFetchFinished: false,
27
33
  pinnedSessions: [],
@@ -0,0 +1,56 @@
1
+ import { StateCreator } from 'zustand/vanilla';
2
+
3
+ import { useSessionStore } from '@/store/session';
4
+
5
+ import type { UrlHydrationStore } from './store';
6
+
7
+ export interface UrlHydrationAction {
8
+ /**
9
+ * Initialize store state from URL (one-time on app load)
10
+ */
11
+ initAgentPinnedFromUrl: () => void;
12
+
13
+ /**
14
+ * Sync agent pinned state to URL (call after state change)
15
+ */
16
+ syncAgentPinnedToUrl: () => void;
17
+ }
18
+
19
+ export const urlHydrationAction: StateCreator<
20
+ UrlHydrationStore,
21
+ [['zustand/devtools', never]],
22
+ [],
23
+ UrlHydrationAction
24
+ > = (set, get) => ({
25
+ initAgentPinnedFromUrl: () => {
26
+ if (get().isAgentPinnedInitialized) return;
27
+
28
+ if (typeof window !== 'undefined') {
29
+ const params = new URLSearchParams(window.location.search);
30
+ const pinnedParam = params.get('pinned');
31
+
32
+ console.log('pinnedParam', pinnedParam);
33
+
34
+ if (pinnedParam === 'true') {
35
+ useSessionStore.setState({ isAgentPinned: true });
36
+ }
37
+
38
+ set({ isAgentPinnedInitialized: true });
39
+ }
40
+ },
41
+
42
+ syncAgentPinnedToUrl: () => {
43
+ if (typeof window === 'undefined') return;
44
+
45
+ const isAgentPinned = useSessionStore.getState().isAgentPinned;
46
+ const url = new URL(window.location.href);
47
+
48
+ if (isAgentPinned) {
49
+ url.searchParams.set('pinned', 'true');
50
+ } else {
51
+ url.searchParams.delete('pinned');
52
+ }
53
+
54
+ window.history.replaceState(null, '', `${url.pathname}${url.search}`);
55
+ },
56
+ });
@@ -0,0 +1 @@
1
+ export { type UrlHydrationStore,useUrlHydrationStore } from './store';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * URL Hydration Store State
3
+ *
4
+ * Tracks initialization status to ensure one-time URL reading.
5
+ */
6
+ export interface UrlHydrationState {
7
+ isAgentPinnedInitialized: boolean;
8
+ }
9
+
10
+ export const initialState: UrlHydrationState = {
11
+ isAgentPinnedInitialized: false,
12
+ };
@@ -0,0 +1,28 @@
1
+ import { subscribeWithSelector } from 'zustand/middleware';
2
+ import { shallow } from 'zustand/shallow';
3
+ import { createWithEqualityFn } from 'zustand/traditional';
4
+ import { StateCreator } from 'zustand/vanilla';
5
+
6
+ import { createDevtools } from '../middleware/createDevtools';
7
+ import { type UrlHydrationAction, urlHydrationAction } from './action';
8
+ import { type UrlHydrationState, initialState } from './initialState';
9
+
10
+ // =============== 聚合 createStoreFn ============ //
11
+
12
+ export interface UrlHydrationStore extends UrlHydrationState, UrlHydrationAction {}
13
+
14
+ const createStore: StateCreator<UrlHydrationStore, [['zustand/devtools', never]]> = (
15
+ ...parameters
16
+ ) => ({
17
+ ...initialState,
18
+ ...urlHydrationAction(...parameters),
19
+ });
20
+
21
+ // =============== 实装 useStore ============ //
22
+
23
+ const devtools = createDevtools('urlHydration');
24
+
25
+ export const useUrlHydrationStore = createWithEqualityFn<UrlHydrationStore>()(
26
+ subscribeWithSelector(devtools(createStore)),
27
+ shallow,
28
+ );