diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0007b550060..cbfa71bcf7d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,74 @@
# Changelog
+### [Version 1.141.1](https://github.com/lobehub/lobe-chat/compare/v1.141.0...v1.141.1)
+
+Released on **2025-10-21**
+
+#### ♻ Code Refactoring
+
+- **misc**: Refactor context engine.
+
+
+
+
+Improvements and Fixes
+
+#### Code refactoring
+
+- **misc**: Refactor context engine, closes [#9821](https://github.com/lobehub/lobe-chat/issues/9821) ([e99f12f](https://github.com/lobehub/lobe-chat/commit/e99f12f))
+
+
+
+
+
+[](#readme-top)
+
+
+
+## [Version 1.141.0](https://github.com/lobehub/lobe-chat/compare/v1.140.0...v1.141.0)
+
+Released on **2025-10-21**
+
+#### ✨ Features
+
+- **misc**: Add PDF export functionality to share modal.
+
+#### 🐛 Bug Fixes
+
+- **misc**: Ignore abort signal errors in TRPC client, slove when pwa user info have code cannot be viewed in full.
+
+#### 💄 Styles
+
+- **misc**: Add knowledge base mansory layout \[LOB-496], improve rich text link display.
+
+
+
+
+Improvements and Fixes
+
+#### What's improved
+
+- **misc**: Add PDF export functionality to share modal, closes [#9300](https://github.com/lobehub/lobe-chat/issues/9300) [#9299](https://github.com/lobehub/lobe-chat/issues/9299) ([2b7761c](https://github.com/lobehub/lobe-chat/commit/2b7761c))
+
+#### What's fixed
+
+- **misc**: Ignore abort signal errors in TRPC client, closes [#9809](https://github.com/lobehub/lobe-chat/issues/9809) [#9401](https://github.com/lobehub/lobe-chat/issues/9401) ([7f7dcfb](https://github.com/lobehub/lobe-chat/commit/7f7dcfb))
+- **misc**: Slove when pwa user info have code cannot be viewed in full, closes [#9817](https://github.com/lobehub/lobe-chat/issues/9817) ([6734a47](https://github.com/lobehub/lobe-chat/commit/6734a47))
+
+#### Styles
+
+- **misc**: Add knowledge base mansory layout \[LOB-496], closes [#9722](https://github.com/lobehub/lobe-chat/issues/9722) ([69f21da](https://github.com/lobehub/lobe-chat/commit/69f21da))
+- **misc**: Improve rich text link display, closes [#9816](https://github.com/lobehub/lobe-chat/issues/9816) ([af33543](https://github.com/lobehub/lobe-chat/commit/af33543))
+
+
+
+
+
+[](#readme-top)
+
+
+
## [Version 1.140.0](https://github.com/lobehub/lobe-chat/compare/v1.139.5...v1.140.0)
Released on **2025-10-21**
diff --git a/changelog/v1.json b/changelog/v1.json
index 9f3e43ea1a0..d7ee3aa1913 100644
--- a/changelog/v1.json
+++ b/changelog/v1.json
@@ -1,4 +1,24 @@
[
+ {
+ "children": {
+ "improvements": ["Refactor context engine."]
+ },
+ "date": "2025-10-21",
+ "version": "1.141.1"
+ },
+ {
+ "children": {
+ "features": ["Add PDF export functionality to share modal."],
+ "fixes": [
+ "Ignore abort signal errors in TRPC client, slove when pwa user info have code cannot be viewed in full."
+ ],
+ "improvements": [
+ "Add knowledge base mansory layout [LOB-496], improve rich text link display."
+ ]
+ },
+ "date": "2025-10-21",
+ "version": "1.141.0"
+ },
{
"children": {
"features": ["Add ComfyUI integration Phase1(RFC-128)."]
diff --git a/package.json b/package.json
index 2a1b4e0f82f..ab41c4ce5dd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@lobehub/chat",
- "version": "1.140.0",
+ "version": "1.141.1",
"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.",
"keywords": [
"framework",
@@ -168,13 +168,13 @@
"@lobehub/icons": "^2.42.0",
"@lobehub/market-sdk": "^0.22.7",
"@lobehub/tts": "^2.0.1",
- "@react-pdf/renderer": "^4.3.0",
"@lobehub/ui": "^2.13.2",
"@modelcontextprotocol/sdk": "^1.20.0",
"@neondatabase/serverless": "^1.0.2",
"@next/third-parties": "^15.5.4",
"@opentelemetry/exporter-jaeger": "^2.1.0",
"@opentelemetry/winston-transport": "^0.17.0",
+ "@react-pdf/renderer": "^4.3.0",
"@react-spring/web": "^9.7.5",
"@saintno/comfyui-sdk": "^0.2.48",
"@serwist/next": "^9.2.1",
@@ -399,4 +399,4 @@
"mdast-util-gfm-autolink-literal": "2.0.0"
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/context-engine/src/__tests__/pipeline.test.ts b/packages/context-engine/src/__tests__/pipeline.test.ts
index ab2330d1d1d..71eac67f3da 100644
--- a/packages/context-engine/src/__tests__/pipeline.test.ts
+++ b/packages/context-engine/src/__tests__/pipeline.test.ts
@@ -36,19 +36,9 @@ describe('ContextEngine', () => {
});
const createInitialContext = (): {
- initialState: any;
- maxTokens: number;
messages: any[];
- model: string;
} => ({
- initialState: {
- messages: [],
- model: 'test-model',
- provider: 'test-provider',
- },
- maxTokens: 4000,
messages: [{ content: 'test', role: 'user' }],
- model: 'test-model',
});
describe('constructor', () => {
@@ -206,8 +196,7 @@ describe('ContextEngine', () => {
const processor = createMockProcessor('p1');
const engine = new ContextEngine({ pipeline: [processor] });
- const input = { ...createInitialContext(), messages: undefined };
- const result = await engine.process(input);
+ const result = await engine.process({ messages: [] });
expect(result.messages).toEqual([]);
});
@@ -216,19 +205,14 @@ describe('ContextEngine', () => {
const processor: ContextProcessor = {
name: 'test',
process: vi.fn(async (context) => {
- expect(context.metadata.maxTokens).toBe(4000);
- expect(context.metadata.model).toBe('test-model');
- expect(context.metadata.customKey).toBe('customValue');
+ expect(context.metadata).toBeDefined();
return context;
}),
};
const engine = new ContextEngine({ pipeline: [processor] });
- await engine.process({
- ...createInitialContext(),
- metadata: { customKey: 'customValue' },
- });
+ await engine.process(createInitialContext());
});
it('should track execution stats', async () => {
@@ -278,12 +262,7 @@ describe('ContextEngine', () => {
pipeline: [processor1, processor2],
});
- const input = {
- ...createInitialContext(),
- };
- input.initialState = { ...input.initialState, messages: [] };
-
- const result = await engine.process(input);
+ const result = await engine.process(createInitialContext());
expect(result.isAborted).toBe(true);
expect(result.stats.processedCount).toBe(1);
@@ -333,16 +312,17 @@ describe('ContextEngine', () => {
});
it('should preserve initial state', async () => {
+ const testContext = createInitialContext();
const processor: ContextProcessor = {
name: 'test',
process: vi.fn(async (context) => {
- expect(context.initialState).toEqual(createInitialContext().initialState);
+ expect(context.initialState.messages).toEqual(testContext.messages);
return context;
}),
};
const engine = new ContextEngine({ pipeline: [processor] });
- await engine.process(createInitialContext());
+ await engine.process(testContext);
});
});
diff --git a/packages/context-engine/src/pipeline.ts b/packages/context-engine/src/pipeline.ts
index d629f124923..a020866840c 100644
--- a/packages/context-engine/src/pipeline.ts
+++ b/packages/context-engine/src/pipeline.ts
@@ -1,12 +1,6 @@
import debug from 'debug';
-import type {
- AgentState,
- ContextProcessor,
- PipelineContext,
- PipelineResult,
- ProcessorOptions,
-} from './types';
+import type { ContextProcessor, PipelineContext, PipelineResult, ProcessorOptions } from './types';
import { PipelineError } from './types';
const log = debug('context-engine:ContextEngine');
@@ -70,26 +64,16 @@ export class ContextEngine {
/**
* Execute pipeline processing
*/
- async process(input: {
- initialState: AgentState;
- maxTokens: number;
- messages?: Array;
- metadata?: Record;
- model: string;
- }): Promise {
+ async process(input: { messages: Array }): Promise {
const startTime = Date.now();
const processorDurations: Record = {};
// Create initial pipeline context
let context: PipelineContext = {
- initialState: input.initialState,
+ initialState: { messages: input.messages },
isAborted: false,
- messages: Array.isArray(input.messages) ? [...input.messages] : [],
- metadata: {
- maxTokens: input.maxTokens,
- model: input.model,
- ...input.metadata,
- },
+ messages: [...input.messages],
+ metadata: {},
};
log('Starting pipeline processing');
diff --git a/packages/context-engine/src/types.ts b/packages/context-engine/src/types.ts
index 95e757e3ef4..373e8590cee 100644
--- a/packages/context-engine/src/types.ts
+++ b/packages/context-engine/src/types.ts
@@ -60,9 +60,9 @@ export interface PipelineContext {
/** 当前 token 估算值 */
currentTokenCount?: number;
/** 最大 token 限制 */
- maxTokens: number;
+ maxTokens?: number;
/** 模型标识 */
- model: string;
+ model?: string;
};
}
diff --git a/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Tags/index.tsx b/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Tags/index.tsx
index c2d8f535d2b..72c98c85212 100644
--- a/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Tags/index.tsx
+++ b/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Tags/index.tsx
@@ -28,7 +28,7 @@ const TitleTags = memo(() => {
agentSelectors.isAgentConfigLoading(s),
]);
- const plugins = useAgentStore(agentSelectors.currentAgentPlugins, isEqual);
+ const plugins = useAgentStore(agentSelectors.displayableAgentPlugins, isEqual);
const enabledKnowledge = useAgentStore(agentSelectors.currentEnabledKnowledge, isEqual);
const enableHistoryCount = useAgentStore(agentChatConfigSelectors.enableHistoryCount);
diff --git a/src/features/Conversation/components/ShareMessageModal/ShareImage/Preview.tsx b/src/features/Conversation/components/ShareMessageModal/ShareImage/Preview.tsx
index 238413d3ab1..2d98f02f4ba 100644
--- a/src/features/Conversation/components/ShareMessageModal/ShareImage/Preview.tsx
+++ b/src/features/Conversation/components/ShareMessageModal/ShareImage/Preview.tsx
@@ -29,7 +29,7 @@ const Preview = memo(
({ title, withBackground, withFooter, message, previewId = 'preview' }) => {
const [model, plugins] = useAgentStore((s) => [
agentSelectors.currentAgentModel(s),
- agentSelectors.currentAgentPlugins(s),
+ agentSelectors.displayableAgentPlugins(s),
]);
const [isInbox, description, avatar, backgroundColor] = useSessionStore((s) => [
diff --git a/src/features/ShareModal/ShareImage/Preview.tsx b/src/features/ShareModal/ShareImage/Preview.tsx
index 9a9f6b7317b..977325c01e2 100644
--- a/src/features/ShareModal/ShareImage/Preview.tsx
+++ b/src/features/ShareModal/ShareImage/Preview.tsx
@@ -22,7 +22,7 @@ const Preview = memo(
({ title, withSystemRole, withBackground, withFooter }) => {
const [model, plugins, systemRole] = useAgentStore((s) => [
agentSelectors.currentAgentModel(s),
- agentSelectors.currentAgentPlugins(s),
+ agentSelectors.displayableAgentPlugins(s),
agentSelectors.currentAgentSystemRole(s),
]);
const [isInbox, description, avatar, backgroundColor] = useSessionStore((s) => [
diff --git a/src/helpers/toolEngineering/index.ts b/src/helpers/toolEngineering/index.ts
index abfb349f4fa..339936679ad 100644
--- a/src/helpers/toolEngineering/index.ts
+++ b/src/helpers/toolEngineering/index.ts
@@ -6,12 +6,13 @@ import type { PluginEnableChecker } from '@lobechat/context-engine';
import { ChatCompletionTool, WorkingModel } from '@lobechat/types';
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
-import { getSearchConfig } from '@/helpers/getSearchConfig';
import { getToolStoreState } from '@/store/tool';
import { pluginSelectors } from '@/store/tool/selectors';
import { WebBrowsingManifest } from '@/tools/web-browsing';
+import { getSearchConfig } from '../getSearchConfig';
import { isCanUseFC } from '../isCanUseFC';
+import { shouldEnableTool } from '../toolFilters';
/**
* Tools engine configuration options
@@ -58,6 +59,11 @@ export const createChatToolsEngine = (workingModel: WorkingModel) =>
defaultToolIds: [WebBrowsingManifest.identifier],
// Create search-aware enableChecker for this request
enableChecker: ({ pluginId }) => {
+ // Check platform-specific constraints (e.g., LocalSystem desktop-only)
+ if (!shouldEnableTool(pluginId)) {
+ return false;
+ }
+
// For WebBrowsingManifest, apply search logic
if (pluginId === WebBrowsingManifest.identifier) {
const searchConfig = getSearchConfig(workingModel.model, workingModel.provider);
diff --git a/src/helpers/toolFilters.ts b/src/helpers/toolFilters.ts
new file mode 100644
index 00000000000..9d4a531a898
--- /dev/null
+++ b/src/helpers/toolFilters.ts
@@ -0,0 +1,35 @@
+/**
+ * Shared tool filtering logic used across both runtime (ToolsEngine)
+ * and display layer (selectors)
+ */
+import { isDesktop } from '@lobechat/const';
+
+import { LocalSystemManifest } from '@/tools/local-system';
+
+/**
+ * Check if a tool should be enabled based on platform-specific constraints
+ * @param toolId - The tool identifier to check
+ * @returns true if the tool should be enabled, false otherwise
+ */
+export const shouldEnableTool = (toolId: string): boolean => {
+ // Filter LocalSystem tool in non-desktop environment
+ if (toolId === LocalSystemManifest.identifier) {
+ return isDesktop;
+ }
+
+ // Add more platform-specific filters here as needed
+ // if (toolId === SomeOtherPlatformSpecificTool.identifier) {
+ // return someCondition;
+ // }
+
+ return true;
+};
+
+/**
+ * Filter tool IDs based on platform constraints
+ * @param toolIds - Array of tool identifiers to filter
+ * @returns Filtered array of tool identifiers
+ */
+export const filterToolIds = (toolIds: string[]): string[] => {
+ return toolIds.filter(shouldEnableTool);
+};
diff --git a/src/locales/default/chat.ts b/src/locales/default/chat.ts
index 2376a959356..7df75ec5486 100644
--- a/src/locales/default/chat.ts
+++ b/src/locales/default/chat.ts
@@ -334,10 +334,10 @@ export default {
shareModal: {
copy: '复制',
download: '下载截图',
+ downloadError: '下载失败',
downloadFile: '下载文件',
downloadPdf: '下载 PDF',
downloadSuccess: '下载成功',
- downloadError: '下载失败',
exportPdf: '导出为 PDF',
exportTitle: '默认标题',
generatePdf: '生成 PDF',
diff --git a/src/locales/default/components.ts b/src/locales/default/components.ts
index 37fa74b5e36..77a4d441a14 100644
--- a/src/locales/default/components.ts
+++ b/src/locales/default/components.ts
@@ -35,10 +35,6 @@ export default {
config: {
showFilesInKnowledgeBase: '显示知识库中内容',
},
- view: {
- list: '列表视图',
- masonry: '网格视图',
- },
emptyStatus: {
actions: {
file: '上传文件',
@@ -57,6 +53,10 @@ export default {
fileCount: '共 {{count}} 项',
selectedCount: '已选 {{count}} 项',
},
+ view: {
+ list: '列表视图',
+ masonry: '网格视图',
+ },
},
FileParsingStatus: {
chunks: {
diff --git a/src/services/chat/contextEngineering.ts b/src/services/chat/contextEngineering.ts
index c2f34c3313d..fab4459cb2b 100644
--- a/src/services/chat/contextEngineering.ts
+++ b/src/services/chat/contextEngineering.ts
@@ -1,6 +1,5 @@
import { INBOX_GUIDE_SYSTEMROLE, INBOX_SESSION_ID, isDesktop, isServerMode } from '@lobechat/const';
import {
- type AgentState,
ContextEngine,
HistorySummaryProvider,
HistoryTruncateProcessor,
@@ -122,14 +121,7 @@ export const contextEngineering = async ({
],
});
- const initialState: AgentState = { messages, model, provider, systemRole, tools };
-
- const result = await pipeline.process({
- initialState,
- maxTokens: 10_000_000,
- messages,
- model,
- });
+ const result = await pipeline.process({ messages });
return result.messages;
};
diff --git a/src/store/agent/slices/chat/selectors/agent.ts b/src/store/agent/slices/chat/selectors/agent.ts
index 968e3bfe217..892354c7d6c 100644
--- a/src/store/agent/slices/chat/selectors/agent.ts
+++ b/src/store/agent/slices/chat/selectors/agent.ts
@@ -1,16 +1,16 @@
-import { VoiceList } from '@lobehub/tts';
-
-import { INBOX_SESSION_ID } from '@/const/session';
import {
DEFAULT_AGENT_CONFIG,
DEFAULT_MODEL,
DEFAULT_PROVIDER,
DEFAUTT_AGENT_TTS_CONFIG,
-} from '@/const/settings';
+ INBOX_SESSION_ID,
+} from '@lobechat/const';
+import { KnowledgeItem, KnowledgeType, LobeAgentConfig, LobeAgentTTSConfig } from '@lobechat/types';
+import { VoiceList } from '@lobehub/tts';
+
import { DEFAULT_OPENING_QUESTIONS } from '@/features/AgentSetting/store/selectors';
+import { filterToolIds } from '@/helpers/toolFilters';
import { AgentStoreState } from '@/store/agent/initialState';
-import { LobeAgentConfig, LobeAgentTTSConfig } from '@/types/agent';
-import { KnowledgeItem, KnowledgeType } from '@/types/knowledgeBase';
import { merge } from '@/utils/merge';
const isInboxSession = (s: AgentStoreState) => s.activeId === INBOX_SESSION_ID;
@@ -68,6 +68,15 @@ const currentAgentPlugins = (s: AgentStoreState) => {
return config?.plugins || [];
};
+/**
+ * Get displayable agent plugins by filtering out platform-specific tools
+ * that shouldn't be shown in the current environment
+ */
+const displayableAgentPlugins = (s: AgentStoreState) => {
+ const plugins = currentAgentPlugins(s);
+ return filterToolIds(plugins);
+};
+
const currentAgentKnowledgeBases = (s: AgentStoreState) => {
const config = currentAgentConfig(s);
@@ -172,6 +181,7 @@ export const agentSelectors = {
currentAgentTTSVoice,
currentEnabledKnowledge,
currentKnowledgeIds,
+ displayableAgentPlugins,
getAgentConfigByAgentId,
getAgentConfigById,
hasEnabledKnowledge,
diff --git a/src/store/tool/slices/builtin/selectors.ts b/src/store/tool/slices/builtin/selectors.ts
index 0194417e519..6aaffcc472b 100644
--- a/src/store/tool/slices/builtin/selectors.ts
+++ b/src/store/tool/slices/builtin/selectors.ts
@@ -1,5 +1,7 @@
+import { LobeToolMeta } from '@lobechat/types';
+
+import { shouldEnableTool } from '@/helpers/toolFilters';
import { DalleManifest } from '@/tools/dalle';
-import { LobeToolMeta } from '@/types/tool/tool';
import type { ToolStoreState } from '../../initialState';
@@ -7,10 +9,18 @@ const metaList =
(showDalle?: boolean) =>
(s: ToolStoreState): LobeToolMeta[] =>
s.builtinTools
- .filter(
- (item) =>
- !item.hidden && (!showDalle ? item.identifier !== DalleManifest.identifier : true),
- )
+ .filter((item) => {
+ // Filter hidden tools
+ if (item.hidden) return false;
+
+ // Filter Dalle if not enabled
+ if (!showDalle && item.identifier === DalleManifest.identifier) return false;
+
+ // Filter platform-specific tools (e.g., LocalSystem desktop-only)
+ if (!shouldEnableTool(item.identifier)) return false;
+
+ return true;
+ })
.map((t) => ({
author: 'LobeHub',
identifier: t.identifier,