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

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.108](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.107...v2.0.0-next.108)
6
+
7
+ <sup>Released on **2025-11-24**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fixed the pinned session not work.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fixed the pinned session not work, closes [#10323](https://github.com/lobehub/lobe-chat/issues/10323) ([224f999](https://github.com/lobehub/lobe-chat/commit/224f999))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.107](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.106...v2.0.0-next.107)
6
31
 
7
32
  <sup>Released on **2025-11-23**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fixed the pinned session not work."
6
+ ]
7
+ },
8
+ "date": "2025-11-24",
9
+ "version": "2.0.0-next.108"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.107",
3
+ "version": "2.0.0-next.108",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -1,8 +1,8 @@
1
1
  import { AIChatModelCard } from '../types/aiModel';
2
2
 
3
- // src/config/modelProviders/cometapi.ts 为准的 CometAPI 模型清单
3
+ // CometAPI model list based on src/config/modelProviders/cometapi.ts
4
4
  const cometapiChatModels: AIChatModelCard[] = [
5
- // GPT-5 系列
5
+ // GPT-5 series
6
6
  {
7
7
  abilities: { functionCall: true, vision: true },
8
8
  contextWindowTokens: 400_000,
@@ -46,7 +46,7 @@ const cometapiChatModels: AIChatModelCard[] = [
46
46
  type: 'chat',
47
47
  },
48
48
 
49
- // GPT-4.1 / 4o 系列
49
+ // GPT-4.1 / 4o series
50
50
  {
51
51
  abilities: { functionCall: true, vision: true },
52
52
  contextWindowTokens: 1_047_576,
@@ -103,7 +103,7 @@ const cometapiChatModels: AIChatModelCard[] = [
103
103
  type: 'chat',
104
104
  },
105
105
 
106
- // OpenAI o 系列
106
+ // OpenAI o series
107
107
  {
108
108
  abilities: { vision: true },
109
109
  contextWindowTokens: 200_000,
@@ -141,7 +141,7 @@ const cometapiChatModels: AIChatModelCard[] = [
141
141
  type: 'chat',
142
142
  },
143
143
 
144
- // Anthropic Claude 系列
144
+ // Anthropic Claude series
145
145
  {
146
146
  abilities: { functionCall: true, vision: true },
147
147
  contextWindowTokens: 200_000,
@@ -212,7 +212,7 @@ const cometapiChatModels: AIChatModelCard[] = [
212
212
  type: 'chat',
213
213
  },
214
214
 
215
- // Google Gemini 系列
215
+ // Google Gemini series
216
216
  {
217
217
  abilities: { functionCall: true, vision: true },
218
218
  contextWindowTokens: 1_114_112,
@@ -259,7 +259,7 @@ const cometapiChatModels: AIChatModelCard[] = [
259
259
  type: 'chat',
260
260
  },
261
261
 
262
- // xAI Grok 系列
262
+ // xAI Grok series
263
263
  {
264
264
  abilities: { functionCall: true, vision: true },
265
265
  contextWindowTokens: 131_072,
@@ -291,7 +291,7 @@ const cometapiChatModels: AIChatModelCard[] = [
291
291
  type: 'chat',
292
292
  },
293
293
 
294
- // DeepSeek 系列
294
+ // DeepSeek series
295
295
  {
296
296
  abilities: { functionCall: true, vision: true },
297
297
  contextWindowTokens: 128_000,
@@ -20,8 +20,8 @@ export const fluxKreaParamsSchema: ModelParamsSchema = {
20
20
 
21
21
  export const qwenImageParamsSchema: ModelParamsSchema = {
22
22
  cfg: { default: 2.5, max: 20, min: 0, step: 0.1 },
23
- // 实测 fal 宽高 最大就支持到 1536
24
- // 默认值取自 https://chat.qwen.ai/ 官网的默认值
23
+ // Tested: fal width/height max support up to 1536
24
+ // Default values from https://chat.qwen.ai/ official website
25
25
  height: { default: 1328, max: 1536, min: 512, step: 1 },
26
26
  prompt: { default: '' },
27
27
  seed: { default: null },
@@ -644,7 +644,7 @@ const nebiusChatModels: AIChatModelCard[] = [
644
644
  },
645
645
  ];
646
646
 
647
- // 下述模型待验证
647
+ // Models below are pending verification
648
648
 
649
649
  // export const nebiusImageModels: AIImageModelCard[] = [
650
650
  // {
@@ -1,9 +1,9 @@
1
1
  import { AIChatModelCard } from '../types/aiModel';
2
2
 
3
- // NewAPI Router Provider - 聚合多个 AI 服务
4
- // 模型通过动态获取,不预定义具体模型
3
+ // NewAPI Router Provider - Aggregates multiple AI services
4
+ // Models are fetched dynamically, not predefined
5
5
  const newapiChatModels: AIChatModelCard[] = [
6
- // NewAPI 作为路由提供商,模型列表通过 API 动态获取
6
+ // NewAPI as router provider, model list fetched dynamically via API
7
7
  ];
8
8
 
9
9
  export const allModels = [...newapiChatModels];
@@ -1074,7 +1074,7 @@ export const openaiEmbeddingModels: AIEmbeddingModelCard[] = [
1074
1074
  },
1075
1075
  ];
1076
1076
 
1077
- // 语音合成模型
1077
+ // Text-to-speech models
1078
1078
  export const openaiTTSModels: AITTSModelCard[] = [
1079
1079
  {
1080
1080
  description: '最新的文本转语音模型,针对实时场景优化速度',
@@ -1109,7 +1109,7 @@ export const openaiTTSModels: AITTSModelCard[] = [
1109
1109
  },
1110
1110
  ];
1111
1111
 
1112
- // 语音识别模型
1112
+ // Speech recognition models
1113
1113
  export const openaiSTTModels: AISTTModelCard[] = [
1114
1114
  {
1115
1115
  description: '通用语音识别模型,支持多语言语音识别、语音翻译和语言识别。',
@@ -1161,7 +1161,7 @@ export const openaiSTTModels: AISTTModelCard[] = [
1161
1161
  },
1162
1162
  ];
1163
1163
 
1164
- // 图像生成模型
1164
+ // Image generation models
1165
1165
  export const openaiImageModels: AIImageModelCard[] = [
1166
1166
  // https://platform.openai.com/docs/models/gpt-image-1
1167
1167
  {
@@ -1276,7 +1276,7 @@ export const openaiImageModels: AIImageModelCard[] = [
1276
1276
  },
1277
1277
  ];
1278
1278
 
1279
- // GPT-4o GPT-4o-mini 实时模型
1279
+ // GPT-4o and GPT-4o-mini realtime models
1280
1280
  export const openaiRealtimeModels: AIRealtimeModelCard[] = [
1281
1281
  {
1282
1282
  abilities: {
@@ -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}
@@ -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),
@@ -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
+ );