@lobehub/chat 1.141.0 → 1.141.2

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,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.141.2](https://github.com/lobehub/lobe-chat/compare/v1.141.1...v1.141.2)
6
+
7
+ <sup>Released on **2025-10-21**</sup>
8
+
9
+ #### 💄 Styles
10
+
11
+ - **settings**: Broadcast locale changes and update switchLocale action.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Styles
19
+
20
+ - **settings**: Broadcast locale changes and update switchLocale action, closes [#9620](https://github.com/lobehub/lobe-chat/issues/9620) ([0eb02ca](https://github.com/lobehub/lobe-chat/commit/0eb02ca))
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
+
30
+ ### [Version 1.141.1](https://github.com/lobehub/lobe-chat/compare/v1.141.0...v1.141.1)
31
+
32
+ <sup>Released on **2025-10-21**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Refactor context engine.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Code refactoring
44
+
45
+ - **misc**: Refactor context engine, closes [#9821](https://github.com/lobehub/lobe-chat/issues/9821) ([e99f12f](https://github.com/lobehub/lobe-chat/commit/e99f12f))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 1.141.0](https://github.com/lobehub/lobe-chat/compare/v1.140.0...v1.141.0)
6
56
 
7
57
  <sup>Released on **2025-10-21**</sup>
@@ -77,6 +77,7 @@ export default class SystemController extends ControllerModule {
77
77
 
78
78
  // 更新i18n实例的语言
79
79
  await this.app.i18n.changeLanguage(locale === 'auto' ? app.getLocale() : locale);
80
+ this.app.browserManager.broadcastToAllWindows('localeChanged', { locale });
80
81
 
81
82
  return { success: true };
82
83
  }
package/changelog/v1.json CHANGED
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2025-10-21",
5
+ "version": "1.141.2"
6
+ },
7
+ {
8
+ "children": {
9
+ "improvements": [
10
+ "Refactor context engine."
11
+ ]
12
+ },
13
+ "date": "2025-10-21",
14
+ "version": "1.141.1"
15
+ },
2
16
  {
3
17
  "children": {
4
18
  "features": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.141.0",
3
+ "version": "1.141.2",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot 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",
@@ -252,7 +252,7 @@
252
252
  "pino": "^9.13.1",
253
253
  "plaiceholder": "^3.0.0",
254
254
  "polished": "^4.3.1",
255
- "posthog-js": "^1.275.1",
255
+ "posthog-js": "~1.278.0",
256
256
  "pure-rand": "^7.0.1",
257
257
  "pwa-install-handler": "^2.6.3",
258
258
  "query-string": "^9.3.1",
@@ -324,7 +324,6 @@
324
324
  "@types/json-schema": "^7.0.15",
325
325
  "@types/lodash": "^4.17.20",
326
326
  "@types/lodash-es": "^4.17.12",
327
- "@types/marked": "^6.0.0",
328
327
  "@types/node": "^22.18.9",
329
328
  "@types/numeral": "^2.0.5",
330
329
  "@types/oidc-provider": "^9.5.0",
@@ -36,19 +36,9 @@ describe('ContextEngine', () => {
36
36
  });
37
37
 
38
38
  const createInitialContext = (): {
39
- initialState: any;
40
- maxTokens: number;
41
39
  messages: any[];
42
- model: string;
43
40
  } => ({
44
- initialState: {
45
- messages: [],
46
- model: 'test-model',
47
- provider: 'test-provider',
48
- },
49
- maxTokens: 4000,
50
41
  messages: [{ content: 'test', role: 'user' }],
51
- model: 'test-model',
52
42
  });
53
43
 
54
44
  describe('constructor', () => {
@@ -206,8 +196,7 @@ describe('ContextEngine', () => {
206
196
  const processor = createMockProcessor('p1');
207
197
  const engine = new ContextEngine({ pipeline: [processor] });
208
198
 
209
- const input = { ...createInitialContext(), messages: undefined };
210
- const result = await engine.process(input);
199
+ const result = await engine.process({ messages: [] });
211
200
 
212
201
  expect(result.messages).toEqual([]);
213
202
  });
@@ -216,19 +205,14 @@ describe('ContextEngine', () => {
216
205
  const processor: ContextProcessor = {
217
206
  name: 'test',
218
207
  process: vi.fn(async (context) => {
219
- expect(context.metadata.maxTokens).toBe(4000);
220
- expect(context.metadata.model).toBe('test-model');
221
- expect(context.metadata.customKey).toBe('customValue');
208
+ expect(context.metadata).toBeDefined();
222
209
  return context;
223
210
  }),
224
211
  };
225
212
 
226
213
  const engine = new ContextEngine({ pipeline: [processor] });
227
214
 
228
- await engine.process({
229
- ...createInitialContext(),
230
- metadata: { customKey: 'customValue' },
231
- });
215
+ await engine.process(createInitialContext());
232
216
  });
233
217
 
234
218
  it('should track execution stats', async () => {
@@ -278,12 +262,7 @@ describe('ContextEngine', () => {
278
262
  pipeline: [processor1, processor2],
279
263
  });
280
264
 
281
- const input = {
282
- ...createInitialContext(),
283
- };
284
- input.initialState = { ...input.initialState, messages: [] };
285
-
286
- const result = await engine.process(input);
265
+ const result = await engine.process(createInitialContext());
287
266
 
288
267
  expect(result.isAborted).toBe(true);
289
268
  expect(result.stats.processedCount).toBe(1);
@@ -333,16 +312,17 @@ describe('ContextEngine', () => {
333
312
  });
334
313
 
335
314
  it('should preserve initial state', async () => {
315
+ const testContext = createInitialContext();
336
316
  const processor: ContextProcessor = {
337
317
  name: 'test',
338
318
  process: vi.fn(async (context) => {
339
- expect(context.initialState).toEqual(createInitialContext().initialState);
319
+ expect(context.initialState.messages).toEqual(testContext.messages);
340
320
  return context;
341
321
  }),
342
322
  };
343
323
 
344
324
  const engine = new ContextEngine({ pipeline: [processor] });
345
- await engine.process(createInitialContext());
325
+ await engine.process(testContext);
346
326
  });
347
327
  });
348
328
 
@@ -1,12 +1,6 @@
1
1
  import debug from 'debug';
2
2
 
3
- import type {
4
- AgentState,
5
- ContextProcessor,
6
- PipelineContext,
7
- PipelineResult,
8
- ProcessorOptions,
9
- } from './types';
3
+ import type { ContextProcessor, PipelineContext, PipelineResult, ProcessorOptions } from './types';
10
4
  import { PipelineError } from './types';
11
5
 
12
6
  const log = debug('context-engine:ContextEngine');
@@ -70,26 +64,16 @@ export class ContextEngine {
70
64
  /**
71
65
  * Execute pipeline processing
72
66
  */
73
- async process(input: {
74
- initialState: AgentState;
75
- maxTokens: number;
76
- messages?: Array<any>;
77
- metadata?: Record<string, any>;
78
- model: string;
79
- }): Promise<PipelineResult> {
67
+ async process(input: { messages: Array<any> }): Promise<PipelineResult> {
80
68
  const startTime = Date.now();
81
69
  const processorDurations: Record<string, number> = {};
82
70
 
83
71
  // Create initial pipeline context
84
72
  let context: PipelineContext = {
85
- initialState: input.initialState,
73
+ initialState: { messages: input.messages },
86
74
  isAborted: false,
87
- messages: Array.isArray(input.messages) ? [...input.messages] : [],
88
- metadata: {
89
- maxTokens: input.maxTokens,
90
- model: input.model,
91
- ...input.metadata,
92
- },
75
+ messages: [...input.messages],
76
+ metadata: {},
93
77
  };
94
78
 
95
79
  log('Starting pipeline processing');
@@ -60,9 +60,9 @@ export interface PipelineContext {
60
60
  /** 当前 token 估算值 */
61
61
  currentTokenCount?: number;
62
62
  /** 最大 token 限制 */
63
- maxTokens: number;
63
+ maxTokens?: number;
64
64
  /** 模型标识 */
65
- model: string;
65
+ model?: string;
66
66
  };
67
67
  }
68
68
 
@@ -18,6 +18,7 @@ export interface SystemDispatchEvents {
18
18
  }
19
19
 
20
20
  export interface SystemBroadcastEvents {
21
+ localeChanged: (data: { locale: string }) => void;
21
22
  systemThemeChanged: (data: { themeMode: ThemeAppearance }) => void;
22
23
  themeChanged: (data: { themeMode: ThemeMode }) => void;
23
24
  }
@@ -28,7 +28,7 @@ const TitleTags = memo(() => {
28
28
  agentSelectors.isAgentConfigLoading(s),
29
29
  ]);
30
30
 
31
- const plugins = useAgentStore(agentSelectors.currentAgentPlugins, isEqual);
31
+ const plugins = useAgentStore(agentSelectors.displayableAgentPlugins, isEqual);
32
32
  const enabledKnowledge = useAgentStore(agentSelectors.currentEnabledKnowledge, isEqual);
33
33
  const enableHistoryCount = useAgentStore(agentChatConfigSelectors.enableHistoryCount);
34
34
 
@@ -17,6 +17,7 @@ import { useServerConfigStore } from '@/store/serverConfig';
17
17
  import { serverConfigSelectors } from '@/store/serverConfig/selectors';
18
18
  import { useUserStore } from '@/store/user';
19
19
  import { settingsSelectors } from '@/store/user/selectors';
20
+ import { LocaleMode } from '@/types/locale';
20
21
 
21
22
  const Common = memo(() => {
22
23
  const { t } = useTranslation('setting');
@@ -33,6 +34,10 @@ const Common = memo(() => {
33
34
  ]);
34
35
  const [loading, setLoading] = useState(false);
35
36
 
37
+ const handleLangChange = (value: LocaleMode) => {
38
+ switchLocale(value);
39
+ };
40
+
36
41
  if (!(isStatusInit && isUserStateInit))
37
42
  return <Skeleton active paragraph={{ rows: 5 }} title={false} />;
38
43
 
@@ -75,7 +80,7 @@ const Common = memo(() => {
75
80
  children: (
76
81
  <Select
77
82
  defaultValue={language}
78
- onChange={switchLocale}
83
+ onChange={handleLangChange}
79
84
  options={[{ label: t('settingCommon.lang.autoMode'), value: 'auto' }, ...localeOptions]}
80
85
  />
81
86
  ),
@@ -142,4 +147,4 @@ const Common = memo(() => {
142
147
  );
143
148
  });
144
149
 
145
- export default Common;
150
+ export default Common;
@@ -29,7 +29,7 @@ const Preview = memo<PreviewProps>(
29
29
  ({ title, withBackground, withFooter, message, previewId = 'preview' }) => {
30
30
  const [model, plugins] = useAgentStore((s) => [
31
31
  agentSelectors.currentAgentModel(s),
32
- agentSelectors.currentAgentPlugins(s),
32
+ agentSelectors.displayableAgentPlugins(s),
33
33
  ]);
34
34
 
35
35
  const [isInbox, description, avatar, backgroundColor] = useSessionStore((s) => [
@@ -15,7 +15,10 @@ export const useWatchThemeUpdate = () => {
15
15
  s.appState.isMac,
16
16
  ],
17
17
  );
18
- const switchThemeMode = useGlobalStore((s) => s.switchThemeMode);
18
+ const [switchThemeMode, switchLocale] = useGlobalStore((s) => [
19
+ s.switchThemeMode,
20
+ s.switchLocale,
21
+ ]);
19
22
 
20
23
  const theme = useTheme();
21
24
 
@@ -23,6 +26,10 @@ export const useWatchThemeUpdate = () => {
23
26
  switchThemeMode(themeMode, { skipBroadcast: true });
24
27
  });
25
28
 
29
+ useWatchBroadcast('localeChanged', ({ locale }) => {
30
+ switchLocale(locale as any, { skipBroadcast: true });
31
+ });
32
+
26
33
  useWatchBroadcast('systemThemeChanged', ({ themeMode }) => {
27
34
  updateElectronAppState({ systemAppearance: themeMode });
28
35
  });
@@ -22,7 +22,7 @@ const Preview = memo<FieldType & { title?: string }>(
22
22
  ({ title, withSystemRole, withBackground, withFooter }) => {
23
23
  const [model, plugins, systemRole] = useAgentStore((s) => [
24
24
  agentSelectors.currentAgentModel(s),
25
- agentSelectors.currentAgentPlugins(s),
25
+ agentSelectors.displayableAgentPlugins(s),
26
26
  agentSelectors.currentAgentSystemRole(s),
27
27
  ]);
28
28
  const [isInbox, description, avatar, backgroundColor] = useSessionStore((s) => [
@@ -6,12 +6,13 @@ import type { PluginEnableChecker } from '@lobechat/context-engine';
6
6
  import { ChatCompletionTool, WorkingModel } from '@lobechat/types';
7
7
  import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
8
8
 
9
- import { getSearchConfig } from '@/helpers/getSearchConfig';
10
9
  import { getToolStoreState } from '@/store/tool';
11
10
  import { pluginSelectors } from '@/store/tool/selectors';
12
11
  import { WebBrowsingManifest } from '@/tools/web-browsing';
13
12
 
13
+ import { getSearchConfig } from '../getSearchConfig';
14
14
  import { isCanUseFC } from '../isCanUseFC';
15
+ import { shouldEnableTool } from '../toolFilters';
15
16
 
16
17
  /**
17
18
  * Tools engine configuration options
@@ -58,6 +59,11 @@ export const createChatToolsEngine = (workingModel: WorkingModel) =>
58
59
  defaultToolIds: [WebBrowsingManifest.identifier],
59
60
  // Create search-aware enableChecker for this request
60
61
  enableChecker: ({ pluginId }) => {
62
+ // Check platform-specific constraints (e.g., LocalSystem desktop-only)
63
+ if (!shouldEnableTool(pluginId)) {
64
+ return false;
65
+ }
66
+
61
67
  // For WebBrowsingManifest, apply search logic
62
68
  if (pluginId === WebBrowsingManifest.identifier) {
63
69
  const searchConfig = getSearchConfig(workingModel.model, workingModel.provider);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Shared tool filtering logic used across both runtime (ToolsEngine)
3
+ * and display layer (selectors)
4
+ */
5
+ import { isDesktop } from '@lobechat/const';
6
+
7
+ import { LocalSystemManifest } from '@/tools/local-system';
8
+
9
+ /**
10
+ * Check if a tool should be enabled based on platform-specific constraints
11
+ * @param toolId - The tool identifier to check
12
+ * @returns true if the tool should be enabled, false otherwise
13
+ */
14
+ export const shouldEnableTool = (toolId: string): boolean => {
15
+ // Filter LocalSystem tool in non-desktop environment
16
+ if (toolId === LocalSystemManifest.identifier) {
17
+ return isDesktop;
18
+ }
19
+
20
+ // Add more platform-specific filters here as needed
21
+ // if (toolId === SomeOtherPlatformSpecificTool.identifier) {
22
+ // return someCondition;
23
+ // }
24
+
25
+ return true;
26
+ };
27
+
28
+ /**
29
+ * Filter tool IDs based on platform constraints
30
+ * @param toolIds - Array of tool identifiers to filter
31
+ * @returns Filtered array of tool identifiers
32
+ */
33
+ export const filterToolIds = (toolIds: string[]): string[] => {
34
+ return toolIds.filter(shouldEnableTool);
35
+ };
@@ -1,6 +1,5 @@
1
1
  import { INBOX_GUIDE_SYSTEMROLE, INBOX_SESSION_ID, isDesktop, isServerMode } from '@lobechat/const';
2
2
  import {
3
- type AgentState,
4
3
  ContextEngine,
5
4
  HistorySummaryProvider,
6
5
  HistoryTruncateProcessor,
@@ -122,14 +121,7 @@ export const contextEngineering = async ({
122
121
  ],
123
122
  });
124
123
 
125
- const initialState: AgentState = { messages, model, provider, systemRole, tools };
126
-
127
- const result = await pipeline.process({
128
- initialState,
129
- maxTokens: 10_000_000,
130
- messages,
131
- model,
132
- });
124
+ const result = await pipeline.process({ messages });
133
125
 
134
126
  return result.messages;
135
127
  };
@@ -1,16 +1,16 @@
1
- import { VoiceList } from '@lobehub/tts';
2
-
3
- import { INBOX_SESSION_ID } from '@/const/session';
4
1
  import {
5
2
  DEFAULT_AGENT_CONFIG,
6
3
  DEFAULT_MODEL,
7
4
  DEFAULT_PROVIDER,
8
5
  DEFAUTT_AGENT_TTS_CONFIG,
9
- } from '@/const/settings';
6
+ INBOX_SESSION_ID,
7
+ } from '@lobechat/const';
8
+ import { KnowledgeItem, KnowledgeType, LobeAgentConfig, LobeAgentTTSConfig } from '@lobechat/types';
9
+ import { VoiceList } from '@lobehub/tts';
10
+
10
11
  import { DEFAULT_OPENING_QUESTIONS } from '@/features/AgentSetting/store/selectors';
12
+ import { filterToolIds } from '@/helpers/toolFilters';
11
13
  import { AgentStoreState } from '@/store/agent/initialState';
12
- import { LobeAgentConfig, LobeAgentTTSConfig } from '@/types/agent';
13
- import { KnowledgeItem, KnowledgeType } from '@/types/knowledgeBase';
14
14
  import { merge } from '@/utils/merge';
15
15
 
16
16
  const isInboxSession = (s: AgentStoreState) => s.activeId === INBOX_SESSION_ID;
@@ -68,6 +68,15 @@ const currentAgentPlugins = (s: AgentStoreState) => {
68
68
  return config?.plugins || [];
69
69
  };
70
70
 
71
+ /**
72
+ * Get displayable agent plugins by filtering out platform-specific tools
73
+ * that shouldn't be shown in the current environment
74
+ */
75
+ const displayableAgentPlugins = (s: AgentStoreState) => {
76
+ const plugins = currentAgentPlugins(s);
77
+ return filterToolIds(plugins);
78
+ };
79
+
71
80
  const currentAgentKnowledgeBases = (s: AgentStoreState) => {
72
81
  const config = currentAgentConfig(s);
73
82
 
@@ -172,6 +181,7 @@ export const agentSelectors = {
172
181
  currentAgentTTSVoice,
173
182
  currentEnabledKnowledge,
174
183
  currentKnowledgeIds,
184
+ displayableAgentPlugins,
175
185
  getAgentConfigByAgentId,
176
186
  getAgentConfigById,
177
187
  hasEnabledKnowledge,
@@ -22,7 +22,7 @@ const n = setNamespace('g');
22
22
  export interface GlobalGeneralAction {
23
23
  openSessionInNewWindow: (sessionId: string) => Promise<void>;
24
24
  openTopicInNewWindow: (sessionId: string, topicId: string) => Promise<void>;
25
- switchLocale: (locale: LocaleMode) => void;
25
+ switchLocale: (locale: LocaleMode, params?: { skipBroadcast?: boolean }) => void;
26
26
  switchThemeMode: (themeMode: ThemeMode, params?: { skipBroadcast?: boolean }) => void;
27
27
  updateSystemStatus: (status: Partial<SystemStatus>, action?: any) => void;
28
28
  useCheckLatestVersion: (enabledCheck?: boolean) => SWRResponse<string>;
@@ -79,12 +79,12 @@ export const generalActionSlice: StateCreator<
79
79
  }
80
80
  },
81
81
 
82
- switchLocale: (locale) => {
82
+ switchLocale: (locale, { skipBroadcast } = {}) => {
83
83
  get().updateSystemStatus({ language: locale });
84
84
 
85
85
  switchLang(locale);
86
86
 
87
- if (isDesktop) {
87
+ if (isDesktop && !skipBroadcast) {
88
88
  (async () => {
89
89
  try {
90
90
  const { dispatch } = await import('@lobechat/electron-client-ipc');
@@ -1,5 +1,7 @@
1
+ import { LobeToolMeta } from '@lobechat/types';
2
+
3
+ import { shouldEnableTool } from '@/helpers/toolFilters';
1
4
  import { DalleManifest } from '@/tools/dalle';
2
- import { LobeToolMeta } from '@/types/tool/tool';
3
5
 
4
6
  import type { ToolStoreState } from '../../initialState';
5
7
 
@@ -7,10 +9,18 @@ const metaList =
7
9
  (showDalle?: boolean) =>
8
10
  (s: ToolStoreState): LobeToolMeta[] =>
9
11
  s.builtinTools
10
- .filter(
11
- (item) =>
12
- !item.hidden && (!showDalle ? item.identifier !== DalleManifest.identifier : true),
13
- )
12
+ .filter((item) => {
13
+ // Filter hidden tools
14
+ if (item.hidden) return false;
15
+
16
+ // Filter Dalle if not enabled
17
+ if (!showDalle && item.identifier === DalleManifest.identifier) return false;
18
+
19
+ // Filter platform-specific tools (e.g., LocalSystem desktop-only)
20
+ if (!shouldEnableTool(item.identifier)) return false;
21
+
22
+ return true;
23
+ })
14
24
  .map((t) => ({
15
25
  author: 'LobeHub',
16
26
  identifier: t.identifier,