@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.
- package/CHANGELOG.md +58 -0
- package/changelog/v1.json +21 -0
- package/locales/ar/models.json +108 -13
- package/locales/bg-BG/models.json +126 -15
- package/locales/de-DE/models.json +3 -0
- package/locales/en-US/models.json +3 -0
- package/locales/es-ES/models.json +3 -0
- package/locales/fa-IR/models.json +108 -13
- package/locales/fr-FR/models.json +108 -13
- package/locales/it-IT/models.json +3 -0
- package/locales/ja-JP/models.json +108 -13
- package/locales/ko-KR/models.json +108 -13
- package/locales/nl-NL/models.json +3 -0
- package/locales/pl-PL/models.json +108 -13
- package/locales/pt-BR/models.json +3 -0
- package/locales/ru-RU/models.json +126 -15
- package/locales/tr-TR/models.json +108 -13
- package/locales/vi-VN/models.json +3 -0
- package/locales/zh-TW/models.json +3 -0
- package/package.json +1 -1
- package/packages/model-bank/src/aiModels/cometapi.ts +8 -8
- package/packages/model-bank/src/aiModels/fal.ts +2 -2
- package/packages/model-bank/src/aiModels/nebius.ts +1 -1
- package/packages/model-bank/src/aiModels/newapi.ts +3 -3
- package/packages/model-bank/src/aiModels/openai.ts +4 -4
- package/packages/model-bank/src/aiModels/qwen.ts +4 -4
- package/packages/model-bank/src/aiModels/stepfun.ts +1 -1
- package/packages/model-bank/src/aiModels/vercelaigateway.ts +1 -1
- package/packages/model-bank/src/aiModels/volcengine.ts +3 -3
- package/packages/model-bank/src/aiModels/zhipu.ts +1 -1
- package/packages/model-bank/src/types/aiModel.ts +8 -8
- package/src/app/[variants]/(main)/layouts/desktop/SideBar/TopActions.tsx +5 -6
- package/src/app/[variants]/desktopRouter.config.tsx +5 -0
- package/src/app/[variants]/mobileRouter.config.tsx +5 -0
- package/src/features/KnowledgeManager/Home/index.tsx +1 -1
- package/src/hooks/usePinnedAgentState.ts +22 -15
- package/src/layout/GlobalProvider/StoreInitialization.tsx +5 -0
- package/src/server/globalConfig/genServerAiProviderConfig.ts +3 -3
- package/src/server/globalConfig/parseFilesConfig.ts +1 -1
- package/src/server/globalConfig/parseSystemAgent.ts +4 -4
- package/src/store/session/slices/session/action.ts +23 -0
- package/src/store/session/slices/session/initialState.ts +6 -0
- package/src/store/urlHydration/action.ts +56 -0
- package/src/store/urlHydration/index.ts +1 -0
- package/src/store/urlHydration/initialState.ts +12 -0
- 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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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', //
|
|
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
|
-
*
|
|
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(
|
|
107
|
+
navigate(`/knowledge/${documentId}`);
|
|
108
108
|
};
|
|
109
109
|
|
|
110
110
|
return (
|
|
@@ -1,21 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useSessionStore } from '@/store/session';
|
|
4
|
+
import { useUrlHydrationStore } from '@/store/urlHydration';
|
|
4
5
|
|
|
5
6
|
export const usePinnedAgentState = () => {
|
|
6
|
-
const
|
|
7
|
-
|
|
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
|
|
11
|
-
() =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 [
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
+
);
|