@lobehub/chat 1.82.0 → 1.82.1

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.
Files changed (96) hide show
  1. package/.cursor/rules/desktop-local-tools-implement.mdc +80 -0
  2. package/.env.desktop +2 -1
  3. package/.github/scripts/pr-comment.js +4 -9
  4. package/CHANGELOG.md +25 -0
  5. package/changelog/v1.json +9 -0
  6. package/locales/ar/electron.json +38 -2
  7. package/locales/ar/plugin.json +31 -31
  8. package/locales/bg-BG/electron.json +38 -2
  9. package/locales/bg-BG/plugin.json +31 -31
  10. package/locales/de-DE/electron.json +38 -2
  11. package/locales/de-DE/plugin.json +3 -8
  12. package/locales/en-US/electron.json +38 -2
  13. package/locales/en-US/plugin.json +3 -8
  14. package/locales/es-ES/electron.json +38 -2
  15. package/locales/es-ES/plugin.json +31 -31
  16. package/locales/fa-IR/electron.json +38 -2
  17. package/locales/fa-IR/plugin.json +31 -31
  18. package/locales/fr-FR/electron.json +38 -2
  19. package/locales/fr-FR/plugin.json +31 -31
  20. package/locales/it-IT/electron.json +38 -2
  21. package/locales/it-IT/plugin.json +31 -31
  22. package/locales/ja-JP/electron.json +38 -2
  23. package/locales/ja-JP/plugin.json +31 -31
  24. package/locales/ko-KR/electron.json +38 -2
  25. package/locales/ko-KR/plugin.json +3 -8
  26. package/locales/nl-NL/electron.json +38 -2
  27. package/locales/nl-NL/plugin.json +31 -31
  28. package/locales/pl-PL/electron.json +38 -2
  29. package/locales/pl-PL/plugin.json +3 -8
  30. package/locales/pt-BR/electron.json +38 -2
  31. package/locales/pt-BR/plugin.json +31 -31
  32. package/locales/ru-RU/electron.json +38 -2
  33. package/locales/ru-RU/plugin.json +31 -31
  34. package/locales/tr-TR/electron.json +38 -2
  35. package/locales/tr-TR/plugin.json +31 -31
  36. package/locales/vi-VN/electron.json +38 -2
  37. package/locales/vi-VN/plugin.json +3 -8
  38. package/locales/zh-CN/electron.json +38 -2
  39. package/locales/zh-CN/plugin.json +14 -9
  40. package/locales/zh-TW/electron.json +38 -2
  41. package/locales/zh-TW/plugin.json +31 -31
  42. package/package.json +1 -1
  43. package/packages/electron-client-ipc/src/events/update.ts +3 -3
  44. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Mode.tsx +222 -0
  45. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Option.tsx +104 -0
  46. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Sync.tsx +42 -0
  47. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Waiting.tsx +203 -0
  48. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/index.tsx +57 -0
  49. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/UpdateModal.tsx +242 -0
  50. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/UpdateNotification.tsx +193 -0
  51. package/src/app/[variants]/(main)/_layout/Desktop/{Titlebar.tsx → ElectronTitlebar/index.tsx} +15 -1
  52. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/BottomActions.tsx +3 -2
  53. package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +1 -1
  54. package/src/app/[variants]/layout.tsx +2 -1
  55. package/src/features/Conversation/components/MarkdownElements/LocalFile/Render/LocalFile.tsx +65 -0
  56. package/src/features/Conversation/components/MarkdownElements/LocalFile/Render/index.tsx +29 -0
  57. package/src/features/Conversation/components/MarkdownElements/LocalFile/index.ts +16 -0
  58. package/src/features/Conversation/components/MarkdownElements/index.ts +7 -1
  59. package/src/features/Conversation/components/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +260 -0
  60. package/src/features/Conversation/components/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +204 -0
  61. package/src/features/Conversation/components/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +133 -0
  62. package/src/features/Conversation/components/MarkdownElements/type.ts +5 -1
  63. package/src/features/PluginDevModal/MCPManifestForm/ArgsInput.tsx +20 -0
  64. package/src/features/PluginDevModal/MCPManifestForm/MCPTypeSelect.tsx +176 -0
  65. package/src/features/PluginDevModal/{MCPManifestForm.tsx → MCPManifestForm/index.tsx} +92 -30
  66. package/src/libs/mcp/__tests__/__snapshots__/index.test.ts.snap +0 -56
  67. package/src/locales/default/electron.ts +38 -2
  68. package/src/locales/default/plugin.ts +14 -7
  69. package/src/server/modules/ElectronIPCClient/index.ts +36 -0
  70. package/src/server/routers/lambda/session.ts +2 -6
  71. package/src/server/routers/tools/mcp.ts +6 -0
  72. package/src/server/services/file/impls/index.ts +9 -1
  73. package/src/server/services/file/impls/local.test.ts +299 -0
  74. package/src/server/services/file/impls/local.ts +183 -0
  75. package/src/server/services/mcp/index.ts +19 -0
  76. package/src/services/aiModel/index.ts +5 -1
  77. package/src/services/aiProvider/index.ts +5 -1
  78. package/src/services/electron/autoUpdate.ts +4 -0
  79. package/src/services/file/index.ts +5 -1
  80. package/src/services/mcp.ts +13 -2
  81. package/src/services/message/index.ts +5 -1
  82. package/src/services/plugin/index.ts +5 -1
  83. package/src/services/session/index.ts +5 -1
  84. package/src/services/tableViewer/desktop.ts +15 -0
  85. package/src/services/tableViewer/index.ts +4 -1
  86. package/src/services/thread/index.ts +5 -1
  87. package/src/services/topic/index.ts +5 -1
  88. package/src/services/user/index.ts +5 -1
  89. package/src/store/electron/actions/app.ts +59 -0
  90. package/src/store/electron/actions/sync.ts +5 -1
  91. package/src/store/electron/initialState.ts +3 -1
  92. package/src/store/electron/store.ts +6 -1
  93. package/src/store/tool/slices/customPlugin/action.ts +16 -4
  94. package/src/utils/client/GlobalAgentContextManager.ts +85 -0
  95. package/src/utils/promptTemplate.test.ts +78 -0
  96. package/src/utils/promptTemplate.ts +17 -0
@@ -1,3 +1,5 @@
1
+ import { isDesktop } from '@/const/version';
2
+
1
3
  import { ClientService as DeprecatedService } from './_deprecated';
2
4
  import { ClientService } from './client';
3
5
  import { ServerService } from './server';
@@ -6,6 +8,8 @@ const clientService =
6
8
  process.env.NEXT_PUBLIC_CLIENT_DB === 'pglite' ? new ClientService() : new DeprecatedService();
7
9
 
8
10
  export const userService =
9
- process.env.NEXT_PUBLIC_SERVICE_MODE === 'server' ? new ServerService() : clientService;
11
+ process.env.NEXT_PUBLIC_SERVICE_MODE === 'server' || isDesktop
12
+ ? new ServerService()
13
+ : clientService;
10
14
 
11
15
  export const userClientService = clientService;
@@ -0,0 +1,59 @@
1
+ import { ElectronAppState } from '@lobechat/electron-client-ipc';
2
+ import { SWRResponse } from 'swr';
3
+ import { StateCreator } from 'zustand/vanilla';
4
+
5
+ import { useOnlyFetchOnceSWR } from '@/libs/swr';
6
+ // Import for type usage
7
+ import { electronSystemService } from '@/services/electron/system';
8
+ import { globalAgentContextManager } from '@/utils/client/GlobalAgentContextManager';
9
+
10
+ import { ElectronStore } from '../store';
11
+
12
+ // Import the new service
13
+
14
+ // ======== State ======== //
15
+
16
+ // Note: Actual state is defined in initialState.ts and ElectronState interface
17
+
18
+ // ======== Action Interface ======== //
19
+
20
+ export interface ElectronAppAction {
21
+ /**
22
+ * Initializes the basic Electron application state, including system info and special paths.
23
+ * Should be called once when the application starts.
24
+ */
25
+ useInitElectronAppState: () => SWRResponse<ElectronAppState>;
26
+ }
27
+
28
+ // ======== Action Implementation ======== //
29
+
30
+ export const createElectronAppSlice: StateCreator<
31
+ ElectronStore,
32
+ [['zustand/devtools', never]],
33
+ [],
34
+ ElectronAppAction
35
+ > = (set) => ({
36
+ useInitElectronAppState: () =>
37
+ useOnlyFetchOnceSWR<ElectronAppState>(
38
+ 'initElectronAppState',
39
+ async () => electronSystemService.getAppState(),
40
+ {
41
+ onSuccess: (result) => {
42
+ set({ appState: result }, false, 'initElectronAppState');
43
+
44
+ // Update the global agent context manager with relevant paths
45
+ // We typically only need paths in the agent context for now.
46
+ globalAgentContextManager.updateContext({
47
+ desktopPath: result.userPath!.desktop,
48
+ documentsPath: result.userPath!.documents,
49
+ downloadsPath: result.userPath!.downloads,
50
+ homePath: result.userPath!.home,
51
+ musicPath: result.userPath!.music,
52
+ picturesPath: result.userPath!.pictures,
53
+ userDataPath: result.userPath!.userData,
54
+ videosPath: result.userPath!.videos,
55
+ });
56
+ },
57
+ },
58
+ ),
59
+ });
@@ -109,8 +109,12 @@ export const remoteSyncSlice: StateCreator<
109
109
  },
110
110
  {
111
111
  onSuccess: (data) => {
112
+ console.log('remote server config:', data);
112
113
  set({ isInitRemoteServerConfig: true, remoteServerConfig: data });
113
- get().refreshUserData();
114
+
115
+ if (data.active) {
116
+ get().refreshUserData();
117
+ }
114
118
  },
115
119
  },
116
120
  ),
@@ -1,8 +1,9 @@
1
- import { RemoteServerConfig } from '@lobechat/electron-client-ipc';
1
+ import { ElectronAppState, RemoteServerConfig } from '@lobechat/electron-client-ipc';
2
2
 
3
3
  export type RemoteServerError = 'CONFIG_ERROR' | 'AUTH_ERROR' | 'DISCONNECT_ERROR';
4
4
 
5
5
  export interface ElectronState {
6
+ appState: ElectronAppState;
6
7
  isConnectingServer?: boolean;
7
8
  isInitRemoteServerConfig: boolean;
8
9
  isSyncActive?: boolean;
@@ -11,6 +12,7 @@ export interface ElectronState {
11
12
  }
12
13
 
13
14
  export const initialState: ElectronState = {
15
+ appState: {},
14
16
  isConnectingServer: false,
15
17
  isInitRemoteServerConfig: false,
16
18
  isSyncActive: false,
@@ -3,12 +3,16 @@ import { createWithEqualityFn } from 'zustand/traditional';
3
3
  import { StateCreator } from 'zustand/vanilla';
4
4
 
5
5
  import { createDevtools } from '../middleware/createDevtools';
6
+ import { type ElectronAppAction, createElectronAppSlice } from './actions/app';
6
7
  import { type ElectronRemoteServerAction, remoteSyncSlice } from './actions/sync';
7
8
  import { type ElectronState, initialState } from './initialState';
8
9
 
9
10
  // =============== 聚合 createStoreFn ============ //
10
11
 
11
- export interface ElectronStore extends ElectronState, ElectronRemoteServerAction {
12
+ export interface ElectronStore
13
+ extends ElectronState,
14
+ ElectronRemoteServerAction,
15
+ ElectronAppAction {
12
16
  /* empty */
13
17
  }
14
18
 
@@ -17,6 +21,7 @@ const createStore: StateCreator<ElectronStore, [['zustand/devtools', never]]> =
17
21
  ) => ({
18
22
  ...initialState,
19
23
  ...remoteSyncSlice(...parameters),
24
+ ...createElectronAppSlice(...parameters),
20
25
  });
21
26
 
22
27
  // =============== 实装 useStore ============ //
@@ -1,8 +1,10 @@
1
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
1
2
  import { t } from 'i18next';
2
3
  import { merge } from 'lodash-es';
3
4
  import { StateCreator } from 'zustand/vanilla';
4
5
 
5
6
  import { notification } from '@/components/AntdStaticMethods';
7
+ import { mcpService } from '@/services/mcp';
6
8
  import { pluginService } from '@/services/plugin';
7
9
  import { toolService } from '@/services/tool';
8
10
  import { pluginHelpers } from '@/store/tool/helpers';
@@ -40,12 +42,22 @@ export const createCustomPluginSlice: StateCreator<
40
42
  if (!plugin) return;
41
43
 
42
44
  const { refreshPlugins, updateInstallLoadingState } = get();
45
+
43
46
  try {
44
47
  updateInstallLoadingState(id, true);
45
- const manifest = await toolService.getToolManifest(
46
- plugin.customParams?.manifestUrl,
47
- plugin.customParams?.useProxy,
48
- );
48
+ let manifest: LobeChatPluginManifest;
49
+ // mean this is a mcp plugin
50
+ if (!!plugin.customParams?.mcp) {
51
+ const url = plugin.customParams?.mcp?.url;
52
+ if (!url) return;
53
+
54
+ manifest = await mcpService.getStreamableMcpServerManifest(plugin.identifier, url);
55
+ } else {
56
+ manifest = await toolService.getToolManifest(
57
+ plugin.customParams?.manifestUrl,
58
+ plugin.customParams?.useProxy,
59
+ );
60
+ }
49
61
  updateInstallLoadingState(id, false);
50
62
 
51
63
  await pluginService.updatePluginManifest(id, manifest);
@@ -0,0 +1,85 @@
1
+ export interface LobeGlobalAgentContext {
2
+ // Other potential context
3
+ currentTime?: string;
4
+
5
+ // App's data directory
6
+ // Paths commonly used by agents
7
+ desktopPath?: string;
8
+ documentsPath?: string;
9
+ downloadsPath?: string;
10
+ homePath?: string;
11
+ musicPath?: string;
12
+ picturesPath?: string; // User's home directory
13
+ userDataPath?: string;
14
+
15
+ videosPath?: string;
16
+ // Add other global context properties needed by agents here
17
+ }
18
+
19
+ // Augment the Window interface to include our global context
20
+ declare global {
21
+ interface Window {
22
+ __LOBE_GLOBAL_AGENT_CONTEXT__?: LobeGlobalAgentContext;
23
+ }
24
+ }
25
+
26
+ const CONTEXT_KEY = '__LOBE_GLOBAL_AGENT_CONTEXT__';
27
+
28
+ class GlobalAgentContextManager {
29
+ private get context(): LobeGlobalAgentContext {
30
+ if (typeof window === 'undefined') return {};
31
+ if (!window[CONTEXT_KEY]) {
32
+ window[CONTEXT_KEY] = {};
33
+ }
34
+ return window[CONTEXT_KEY]!;
35
+ }
36
+
37
+ private set context(value: LobeGlobalAgentContext) {
38
+ if (typeof window === 'undefined') return;
39
+ window[CONTEXT_KEY] = value;
40
+ }
41
+
42
+ /**
43
+ * Retrieves the current global agent context.
44
+ */
45
+ public getContext(): LobeGlobalAgentContext {
46
+ return this.context;
47
+ }
48
+
49
+ /**
50
+ * Updates the global agent context by merging updates.
51
+ * This is typically called by the Electron store initializer.
52
+ * @param updates - Partial context updates to merge.
53
+ */
54
+ public updateContext(updates: Partial<LobeGlobalAgentContext>): void {
55
+ this.context = { ...this.context, ...updates };
56
+ }
57
+
58
+ /**
59
+ * Sets the entire global agent context, replacing the existing one.
60
+ * @param context - The new context object.
61
+ */
62
+ public setContext(context: LobeGlobalAgentContext): void {
63
+ this.context = context;
64
+ }
65
+
66
+ /**
67
+ * Fills a template string using the current global agent context.
68
+ * Replaces {{key}} placeholders with values from the context.
69
+ * @param template - The template string with placeholders.
70
+ * @returns The filled template string.
71
+ */
72
+ public fillTemplate(template?: string): string {
73
+ const ctx = this.getContext();
74
+ if (!template) return '';
75
+
76
+ // Updated to use replaceAll for potentially multiple occurrences
77
+ return template.replaceAll(/{{([^}]+)}}/g, (match, key) => {
78
+ const trimmedKey = key.trim() as keyof LobeGlobalAgentContext;
79
+ return ctx[trimmedKey] !== undefined ? String(ctx[trimmedKey]) : '[N/A]';
80
+ });
81
+ }
82
+ }
83
+
84
+ // Export a singleton instance for global use
85
+ export const globalAgentContextManager = new GlobalAgentContextManager();
@@ -0,0 +1,78 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { hydrationPrompt } from './promptTemplate';
4
+
5
+ describe('hydrationPrompt', () => {
6
+ it('should replace basic variables', () => {
7
+ const prompt = 'Hello {{name}}!';
8
+ const context = { name: 'World' };
9
+ expect(hydrationPrompt(prompt, context)).toBe('Hello World!');
10
+ });
11
+
12
+ it('should replace missing variables with an empty string', () => {
13
+ const prompt = 'Hello {{name}}! Your age is {{age}}.';
14
+ const context = { name: 'World' };
15
+ expect(hydrationPrompt(prompt, context)).toBe('Hello World! Your age is .');
16
+ });
17
+
18
+ it('should replace nested variables', () => {
19
+ const prompt = 'User: {{user.name}}, Role: {{user.role.name}}';
20
+ const context = { user: { name: 'Alice', role: { name: 'Admin' } } };
21
+ expect(hydrationPrompt(prompt, context)).toBe('User: Alice, Role: Admin');
22
+ });
23
+
24
+ it('should handle missing nested variables gracefully', () => {
25
+ const prompt = 'User: {{user.name}}, City: {{user.address.city}}';
26
+ const context = { user: { name: 'Bob' } };
27
+ expect(hydrationPrompt(prompt, context)).toBe('User: Bob, City: ');
28
+ });
29
+
30
+ it('should handle multiple variables, some missing', () => {
31
+ const prompt = '{{greeting}} {{user.name}}. Welcome to {{place}}. Your id is {{id}}';
32
+ const context = { greeting: 'Hi', user: { name: 'Charlie' } };
33
+ expect(hydrationPrompt(prompt, context)).toBe('Hi Charlie. Welcome to . Your id is ');
34
+ });
35
+
36
+ it('should handle empty context', () => {
37
+ const prompt = 'Hello {{name}}!';
38
+ const context = {};
39
+ expect(hydrationPrompt(prompt, context)).toBe('Hello !');
40
+ });
41
+
42
+ it('should handle empty prompt string', () => {
43
+ const prompt = '';
44
+ const context = { name: 'World' };
45
+ expect(hydrationPrompt(prompt, context)).toBe('');
46
+ });
47
+
48
+ it('should handle prompt with no variables', () => {
49
+ const prompt = 'This is a plain string.';
50
+ const context = { name: 'World' };
51
+ expect(hydrationPrompt(prompt, context)).toBe('This is a plain string.');
52
+ });
53
+
54
+ it('should handle different data types in context', () => {
55
+ const prompt = 'Count: {{count}}, Active: {{isActive}}, User: {{user}}';
56
+ const context = { count: 123, isActive: true, user: null };
57
+ // Note: null becomes "null" when converted to string
58
+ expect(hydrationPrompt(prompt, context)).toBe('Count: 123, Active: true, User: null');
59
+ });
60
+
61
+ it('should handle keys with leading/trailing whitespace', () => {
62
+ const prompt = 'Value: {{ spacedKey }}';
63
+ const context = { spacedKey: 'Trimmed' };
64
+ expect(hydrationPrompt(prompt, context)).toBe('Value: Trimmed');
65
+ });
66
+
67
+ it('should replace variables with undefined value with an empty string', () => {
68
+ const prompt = 'Name: {{name}}, Age: {{age}}';
69
+ const context = { name: 'Defined', age: undefined };
70
+ expect(hydrationPrompt(prompt, context)).toBe('Name: Defined, Age: ');
71
+ });
72
+
73
+ it('should handle complex nested structures and missing parts', () => {
74
+ const prompt = 'Data: {{a.b.c}}, Missing: {{x.y.z}}, Partial: {{a.b.d}}';
75
+ const context = { a: { b: { c: 'Found' } } };
76
+ expect(hydrationPrompt(prompt, context)).toBe('Data: Found, Missing: , Partial: ');
77
+ });
78
+ });
@@ -0,0 +1,17 @@
1
+ import { get } from 'lodash-es';
2
+
3
+ export const hydrationPrompt = (prompt: string, context: any) => {
4
+ const regex = /{{([\S\s]+?)}}/g;
5
+
6
+ // Use String.prototype.replace with a replacer function
7
+ return prompt.replaceAll(regex, (match, key) => {
8
+ const trimmedKey = key.trim();
9
+
10
+ // Safely get the value from the context, including nested paths
11
+ const value = get(context, trimmedKey);
12
+
13
+ // If the value exists (is not undefined), convert it to string and return.
14
+ // Otherwise, return an empty string to replace the placeholder.
15
+ return value !== undefined ? String(value) : '';
16
+ });
17
+ };