@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 +25 -0
- package/changelog/v1.json +9 -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/hooks/usePinnedAgentState.ts +22 -15
- package/src/layout/GlobalProvider/StoreInitialization.tsx +5 -0
- 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
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
|
+
[](#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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
24
|
-
//
|
|
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 },
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AIChatModelCard } from '../types/aiModel';
|
|
2
2
|
|
|
3
|
-
// NewAPI Router Provider -
|
|
4
|
-
//
|
|
3
|
+
// NewAPI Router Provider - Aggregates multiple AI services
|
|
4
|
+
// Models are fetched dynamically, not predefined
|
|
5
5
|
const newapiChatModels: AIChatModelCard[] = [
|
|
6
|
-
// NewAPI
|
|
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
|
|
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
|
-
//
|
|
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}
|
|
@@ -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),
|
|
@@ -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
|
+
);
|