@lobehub/chat 1.82.10 → 1.83.0
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/.env.desktop +1 -2
- package/.github/workflows/{release-desktop.yml → desktop-pr-build.yml} +59 -137
- package/.github/workflows/release-desktop-beta.yml +196 -0
- package/CHANGELOG.md +25 -0
- package/apps/desktop/.i18nrc.js +31 -0
- package/apps/desktop/Development.md +47 -0
- package/apps/desktop/README.md +6 -0
- package/apps/desktop/build/Icon-beta.icns +0 -0
- package/apps/desktop/build/Icon-nightly.icns +0 -0
- package/apps/desktop/build/Icon.icns +0 -0
- package/apps/desktop/build/entitlements.mac.plist +12 -0
- package/apps/desktop/build/favicon.ico +0 -0
- package/apps/desktop/build/icon-beta.png +0 -0
- package/apps/desktop/build/icon-dev.png +0 -0
- package/apps/desktop/build/icon-nightly.ico +0 -0
- package/apps/desktop/build/icon-nightly.png +0 -0
- package/apps/desktop/build/icon.ico +0 -0
- package/apps/desktop/build/icon.png +0 -0
- package/apps/desktop/dev-app-update.yml +6 -0
- package/apps/desktop/electron-builder.js +92 -0
- package/apps/desktop/electron.vite.config.ts +40 -0
- package/apps/desktop/package.json +72 -0
- package/apps/desktop/pnpm-workspace.yaml +5 -0
- package/apps/desktop/resources/error.html +136 -0
- package/apps/desktop/resources/locales/ar/common.json +32 -0
- package/apps/desktop/resources/locales/ar/dialog.json +31 -0
- package/apps/desktop/resources/locales/ar/menu.json +70 -0
- package/apps/desktop/resources/locales/bg-BG/common.json +32 -0
- package/apps/desktop/resources/locales/bg-BG/dialog.json +31 -0
- package/apps/desktop/resources/locales/bg-BG/menu.json +70 -0
- package/apps/desktop/resources/locales/de-DE/common.json +32 -0
- package/apps/desktop/resources/locales/de-DE/dialog.json +31 -0
- package/apps/desktop/resources/locales/de-DE/menu.json +70 -0
- package/apps/desktop/resources/locales/en-US/common.json +32 -0
- package/apps/desktop/resources/locales/en-US/dialog.json +31 -0
- package/apps/desktop/resources/locales/en-US/menu.json +70 -0
- package/apps/desktop/resources/locales/es-ES/common.json +32 -0
- package/apps/desktop/resources/locales/es-ES/dialog.json +31 -0
- package/apps/desktop/resources/locales/es-ES/menu.json +70 -0
- package/apps/desktop/resources/locales/fa-IR/common.json +32 -0
- package/apps/desktop/resources/locales/fa-IR/dialog.json +31 -0
- package/apps/desktop/resources/locales/fa-IR/menu.json +70 -0
- package/apps/desktop/resources/locales/fr-FR/common.json +32 -0
- package/apps/desktop/resources/locales/fr-FR/dialog.json +31 -0
- package/apps/desktop/resources/locales/fr-FR/menu.json +70 -0
- package/apps/desktop/resources/locales/it-IT/common.json +32 -0
- package/apps/desktop/resources/locales/it-IT/dialog.json +31 -0
- package/apps/desktop/resources/locales/it-IT/menu.json +70 -0
- package/apps/desktop/resources/locales/ja-JP/common.json +32 -0
- package/apps/desktop/resources/locales/ja-JP/dialog.json +31 -0
- package/apps/desktop/resources/locales/ja-JP/menu.json +70 -0
- package/apps/desktop/resources/locales/ko-KR/common.json +32 -0
- package/apps/desktop/resources/locales/ko-KR/dialog.json +31 -0
- package/apps/desktop/resources/locales/ko-KR/menu.json +70 -0
- package/apps/desktop/resources/locales/nl-NL/common.json +32 -0
- package/apps/desktop/resources/locales/nl-NL/dialog.json +31 -0
- package/apps/desktop/resources/locales/nl-NL/menu.json +70 -0
- package/apps/desktop/resources/locales/pl-PL/common.json +32 -0
- package/apps/desktop/resources/locales/pl-PL/dialog.json +31 -0
- package/apps/desktop/resources/locales/pl-PL/menu.json +70 -0
- package/apps/desktop/resources/locales/pt-BR/common.json +32 -0
- package/apps/desktop/resources/locales/pt-BR/dialog.json +31 -0
- package/apps/desktop/resources/locales/pt-BR/menu.json +70 -0
- package/apps/desktop/resources/locales/ru-RU/common.json +32 -0
- package/apps/desktop/resources/locales/ru-RU/dialog.json +31 -0
- package/apps/desktop/resources/locales/ru-RU/menu.json +70 -0
- package/apps/desktop/resources/locales/tr-TR/common.json +32 -0
- package/apps/desktop/resources/locales/tr-TR/dialog.json +31 -0
- package/apps/desktop/resources/locales/tr-TR/menu.json +70 -0
- package/apps/desktop/resources/locales/vi-VN/common.json +32 -0
- package/apps/desktop/resources/locales/vi-VN/dialog.json +31 -0
- package/apps/desktop/resources/locales/vi-VN/menu.json +70 -0
- package/apps/desktop/resources/locales/zh-CN/common.json +32 -0
- package/apps/desktop/resources/locales/zh-CN/dialog.json +31 -0
- package/apps/desktop/resources/locales/zh-CN/menu.json +70 -0
- package/apps/desktop/resources/locales/zh-TW/common.json +32 -0
- package/apps/desktop/resources/locales/zh-TW/dialog.json +31 -0
- package/apps/desktop/resources/locales/zh-TW/menu.json +70 -0
- package/apps/desktop/resources/splash.html +88 -0
- package/apps/desktop/scripts/i18nWorkflow/const.ts +18 -0
- package/apps/desktop/scripts/i18nWorkflow/genDefaultLocale.ts +35 -0
- package/apps/desktop/scripts/i18nWorkflow/genDiff.ts +57 -0
- package/apps/desktop/scripts/i18nWorkflow/index.ts +35 -0
- package/apps/desktop/scripts/i18nWorkflow/utils.ts +54 -0
- package/apps/desktop/scripts/pglite-server.ts +14 -0
- package/apps/desktop/src/common/routes.ts +78 -0
- package/apps/desktop/src/main/appBrowsers.ts +47 -0
- package/apps/desktop/src/main/const/dir.ts +29 -0
- package/apps/desktop/src/main/const/env.ts +3 -0
- package/apps/desktop/src/main/const/store.ts +22 -0
- package/apps/desktop/src/main/controllers/AuthCtr.ts +390 -0
- package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +95 -0
- package/apps/desktop/src/main/controllers/DevtoolsCtr.ts +9 -0
- package/apps/desktop/src/main/controllers/LocalFileCtr.ts +380 -0
- package/apps/desktop/src/main/controllers/MenuCtr.ts +29 -0
- package/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +335 -0
- package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +321 -0
- package/apps/desktop/src/main/controllers/ShortcutCtr.ts +19 -0
- package/apps/desktop/src/main/controllers/SystemCtr.ts +93 -0
- package/apps/desktop/src/main/controllers/UpdaterCtr.ts +43 -0
- package/apps/desktop/src/main/controllers/UploadFileCtr.ts +34 -0
- package/apps/desktop/src/main/controllers/_template.ts +9 -0
- package/apps/desktop/src/main/controllers/index.ts +58 -0
- package/apps/desktop/src/main/core/App.ts +370 -0
- package/apps/desktop/src/main/core/Browser.ts +345 -0
- package/apps/desktop/src/main/core/BrowserManager.ts +154 -0
- package/apps/desktop/src/main/core/I18nManager.ts +185 -0
- package/apps/desktop/src/main/core/IoCContainer.ts +12 -0
- package/apps/desktop/src/main/core/MenuManager.ts +64 -0
- package/apps/desktop/src/main/core/ShortcutManager.ts +173 -0
- package/apps/desktop/src/main/core/StoreManager.ts +89 -0
- package/apps/desktop/src/main/core/UpdaterManager.ts +321 -0
- package/apps/desktop/src/main/index.ts +5 -0
- package/apps/desktop/src/main/locales/default/common.ts +34 -0
- package/apps/desktop/src/main/locales/default/dialog.ts +33 -0
- package/apps/desktop/src/main/locales/default/index.ts +11 -0
- package/apps/desktop/src/main/locales/default/menu.ts +72 -0
- package/apps/desktop/src/main/locales/resources.ts +35 -0
- package/apps/desktop/src/main/menus/impls/BaseMenuPlatform.ts +10 -0
- package/apps/desktop/src/main/menus/impls/linux.ts +243 -0
- package/apps/desktop/src/main/menus/impls/macOS.ts +360 -0
- package/apps/desktop/src/main/menus/impls/windows.ts +226 -0
- package/apps/desktop/src/main/menus/index.ts +34 -0
- package/apps/desktop/src/main/menus/types.ts +28 -0
- package/apps/desktop/src/main/modules/fileSearch/impl/macOS.ts +577 -0
- package/apps/desktop/src/main/modules/fileSearch/index.ts +23 -0
- package/apps/desktop/src/main/modules/fileSearch/type.ts +27 -0
- package/apps/desktop/src/main/modules/updater/configs.ts +22 -0
- package/apps/desktop/src/main/modules/updater/utils.ts +33 -0
- package/apps/desktop/src/main/services/fileSearchSrv.ts +35 -0
- package/apps/desktop/src/main/services/fileSrv.ts +255 -0
- package/apps/desktop/src/main/services/index.ts +9 -0
- package/apps/desktop/src/main/shortcuts/config.ts +18 -0
- package/apps/desktop/src/main/shortcuts/index.ts +1 -0
- package/apps/desktop/src/main/types/fileSearch.ts +51 -0
- package/apps/desktop/src/main/types/store.ts +14 -0
- package/apps/desktop/src/main/utils/file-system.ts +15 -0
- package/apps/desktop/src/main/utils/logger.ts +44 -0
- package/apps/desktop/src/main/utils/next-electron-rsc.ts +383 -0
- package/apps/desktop/src/preload/electronApi.ts +18 -0
- package/apps/desktop/src/preload/index.ts +14 -0
- package/apps/desktop/src/preload/invoke.ts +10 -0
- package/apps/desktop/src/preload/routeInterceptor.ts +162 -0
- package/apps/desktop/tsconfig.json +21 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/events/remoteServer.ts +11 -4
- package/packages/electron-client-ipc/src/types/dataSync.ts +15 -0
- package/packages/electron-client-ipc/src/types/index.ts +2 -1
- package/packages/electron-client-ipc/src/types/proxyTRPCRequest.ts +21 -0
- package/packages/electron-server-ipc/src/const.ts +3 -3
- package/packages/electron-server-ipc/src/ipcClient.test.ts +7 -6
- package/packages/electron-server-ipc/src/ipcClient.ts +17 -8
- package/packages/electron-server-ipc/src/ipcServer.ts +7 -3
- package/scripts/electronWorkflow/setDesktopVersion.ts +60 -43
- package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +1 -1
- package/src/components/Analytics/Desktop.tsx +19 -0
- package/src/components/Analytics/index.tsx +3 -0
- package/src/database/core/db-adaptor.ts +4 -1
- package/src/database/core/electron.ts +317 -0
- package/src/{app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Mode.tsx → features/ElectronTitlebar/Connection/ConnectionMode.tsx} +24 -21
- package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/Connection/Option.tsx +3 -5
- package/src/{app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Sync.tsx → features/ElectronTitlebar/Connection/RemoteStatus.tsx} +10 -7
- package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/Connection/index.tsx +4 -4
- package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/UpdateModal.tsx +2 -1
- package/src/libs/trpc/client/async.ts +6 -0
- package/src/libs/trpc/client/edge.ts +6 -0
- package/src/libs/trpc/client/helpers/desktopRemoteRPCFetch.ts +72 -0
- package/src/libs/trpc/client/index.ts +1 -0
- package/src/libs/trpc/client/lambda.ts +10 -1
- package/src/libs/trpc/client/tools.ts +6 -0
- package/src/server/globalConfig/index.ts +0 -3
- package/src/server/modules/ElectronIPCClient/index.ts +3 -1
- package/src/server/routers/desktop/index.ts +2 -0
- package/src/server/routers/desktop/mcp.ts +47 -0
- package/src/server/routers/lambda/user.ts +38 -23
- package/src/server/routers/tools/mcp.ts +0 -6
- package/src/services/electron/remoteServer.ts +4 -4
- package/src/services/mcp.ts +17 -7
- package/src/services/upload.ts +9 -0
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +11 -2
- package/src/store/chat/slices/builtinTool/actions/localFile.ts +110 -53
- package/src/store/electron/actions/sync.ts +20 -19
- package/src/store/electron/initialState.ts +3 -3
- package/src/store/electron/selectors/sync.ts +6 -3
- package/src/store/electron/store.ts +2 -0
- package/src/store/file/slices/upload/action.ts +11 -3
- package/src/store/tool/selectors/tool.ts +10 -1
- package/src/utils/fetch/headers.ts +27 -0
- package/src/utils/fetch/index.ts +2 -0
- package/src/utils/fetch/request.ts +28 -0
- package/packages/electron-client-ipc/src/types/remoteServer.ts +0 -8
- /package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/Connection/Waiting.tsx +0 -0
- /package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/UpdateNotification.tsx +0 -0
- /package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/index.tsx +0 -0
@@ -2,6 +2,7 @@ import { createTRPCClient, httpBatchLink } from '@trpc/client';
|
|
2
2
|
import { createTRPCReact } from '@trpc/react-query';
|
3
3
|
import superjson from 'superjson';
|
4
4
|
|
5
|
+
import { isDesktop } from '@/const/version';
|
5
6
|
import { ModelProvider } from '@/libs/agent-runtime';
|
6
7
|
import type { LambdaRouter } from '@/server/routers/lambda';
|
7
8
|
|
@@ -10,7 +11,16 @@ import { ErrorResponse } from './types';
|
|
10
11
|
const links = [
|
11
12
|
httpBatchLink({
|
12
13
|
fetch: async (input, init) => {
|
14
|
+
if (isDesktop) {
|
15
|
+
const { desktopRemoteRPCFetch } = await import('./helpers/desktopRemoteRPCFetch');
|
16
|
+
|
17
|
+
const res = await desktopRemoteRPCFetch(input as string, init);
|
18
|
+
|
19
|
+
if (res) return res;
|
20
|
+
}
|
21
|
+
|
13
22
|
const response = await fetch(input, init);
|
23
|
+
|
14
24
|
if (response.ok) return response;
|
15
25
|
|
16
26
|
const errorRes: ErrorResponse = await response.clone().json();
|
@@ -20,7 +30,6 @@ const links = [
|
|
20
30
|
|
21
31
|
errorRes.forEach((item) => {
|
22
32
|
const errorData = item.error.json;
|
23
|
-
|
24
33
|
const status = errorData.data.httpStatus;
|
25
34
|
|
26
35
|
switch (status) {
|
@@ -1,11 +1,17 @@
|
|
1
1
|
import { createTRPCClient, httpBatchLink } from '@trpc/client';
|
2
2
|
import superjson from 'superjson';
|
3
3
|
|
4
|
+
import { isDesktop } from '@/const/version';
|
4
5
|
import type { ToolsRouter } from '@/server/routers/tools';
|
5
6
|
|
7
|
+
import { fetchWithDesktopRemoteRPC } from './helpers/desktopRemoteRPCFetch';
|
8
|
+
|
6
9
|
export const toolsClient = createTRPCClient<ToolsRouter>({
|
7
10
|
links: [
|
8
11
|
httpBatchLink({
|
12
|
+
fetch: isDesktop
|
13
|
+
? (input, init) => fetchWithDesktopRemoteRPC(input as string, init)
|
14
|
+
: undefined,
|
9
15
|
headers: async () => {
|
10
16
|
// dynamic import to avoid circular dependency
|
11
17
|
const { createHeaderWithAuth } = await import('@/services/_auth');
|
@@ -40,9 +40,6 @@ export const getServerGlobalConfig = async () => {
|
|
40
40
|
enabled: isDesktop ? true : undefined,
|
41
41
|
fetchOnClient: isDesktop ? false : !process.env.OLLAMA_PROXY_URL,
|
42
42
|
},
|
43
|
-
openai: {
|
44
|
-
enabled: isDesktop ? false : undefined,
|
45
|
-
},
|
46
43
|
tencentcloud: {
|
47
44
|
enabledKey: 'ENABLED_TENCENT_CLOUD',
|
48
45
|
modelListKey: 'TENCENT_CLOUD_MODEL_LIST',
|
@@ -1,5 +1,7 @@
|
|
1
1
|
import { ElectronIpcClient } from '@lobechat/electron-server-ipc';
|
2
2
|
|
3
|
+
import packageJSON from '@/../apps/desktop/package.json';
|
4
|
+
|
3
5
|
class LobeHubElectronIpcClient extends ElectronIpcClient {
|
4
6
|
// 获取数据库路径
|
5
7
|
getDatabasePath = async (): Promise<string> => {
|
@@ -33,4 +35,4 @@ class LobeHubElectronIpcClient extends ElectronIpcClient {
|
|
33
35
|
};
|
34
36
|
}
|
35
37
|
|
36
|
-
export const electronIpcClient = new LobeHubElectronIpcClient();
|
38
|
+
export const electronIpcClient = new LobeHubElectronIpcClient(packageJSON.name);
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
|
3
|
+
import { isServerMode } from '@/const/version';
|
4
|
+
import { passwordProcedure } from '@/libs/trpc/edge';
|
5
|
+
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
6
|
+
import { mcpService } from '@/server/services/mcp';
|
7
|
+
|
8
|
+
const stdioParamsSchema = z.object({
|
9
|
+
args: z.array(z.string()).optional().default([]),
|
10
|
+
command: z.string().min(1),
|
11
|
+
name: z.string().min(1),
|
12
|
+
type: z.literal('stdio').default('stdio'),
|
13
|
+
});
|
14
|
+
|
15
|
+
const mcpProcedure = isServerMode ? authedProcedure : passwordProcedure;
|
16
|
+
|
17
|
+
export const mcpRouter = router({
|
18
|
+
getStdioMcpServerManifest: mcpProcedure.input(stdioParamsSchema).query(async ({ input }) => {
|
19
|
+
return await mcpService.getStdioMcpServerManifest(input.name, input.command, input.args);
|
20
|
+
}),
|
21
|
+
|
22
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
23
|
+
// --- MCP Interaction ---
|
24
|
+
// listTools now accepts MCPClientParams directly
|
25
|
+
listTools: mcpProcedure
|
26
|
+
.input(stdioParamsSchema) // Use the unified schema
|
27
|
+
.query(async ({ input }) => {
|
28
|
+
// Pass the validated MCPClientParams to the service
|
29
|
+
return await mcpService.listTools(input);
|
30
|
+
}),
|
31
|
+
|
32
|
+
// callTool now accepts MCPClientParams, toolName, and args
|
33
|
+
callTool: mcpProcedure
|
34
|
+
.input(
|
35
|
+
z.object({
|
36
|
+
params: stdioParamsSchema, // Use the unified schema for client params
|
37
|
+
args: z.any(), // Arguments for the tool call
|
38
|
+
toolName: z.string(),
|
39
|
+
}),
|
40
|
+
)
|
41
|
+
.mutation(async ({ input }) => {
|
42
|
+
// Pass the validated params, toolName, and args to the service
|
43
|
+
const data = await mcpService.callTool(input.params, input.toolName, input.args);
|
44
|
+
|
45
|
+
return JSON.stringify(data);
|
46
|
+
}),
|
47
|
+
});
|
@@ -3,10 +3,12 @@ import { z } from 'zod';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid'; // 需要添加此导入
|
4
4
|
|
5
5
|
import { enableClerk } from '@/const/auth';
|
6
|
+
import { isDesktop } from '@/const/version';
|
6
7
|
import { MessageModel } from '@/database/models/message';
|
7
8
|
import { SessionModel } from '@/database/models/session';
|
8
9
|
import { UserModel, UserNotFoundError } from '@/database/models/user';
|
9
10
|
import { ClerkAuth } from '@/libs/clerk-auth';
|
11
|
+
import { pino } from '@/libs/logger';
|
10
12
|
import { LobeNextAuthDbAdapter } from '@/libs/next-auth/adapter';
|
11
13
|
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
12
14
|
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
|
@@ -50,33 +52,46 @@ export const userRouter = router({
|
|
50
52
|
try {
|
51
53
|
state = await ctx.userModel.getUserState(KeyVaultsGateKeeper.getUserKeyVaults);
|
52
54
|
} catch (error) {
|
53
|
-
|
54
|
-
|
55
|
-
if
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
55
|
+
// user not create yet
|
56
|
+
if (error instanceof UserNotFoundError) {
|
57
|
+
// if in clerk auth mode
|
58
|
+
if (enableClerk) {
|
59
|
+
const user = await ctx.clerkAuth.getCurrentUser();
|
60
|
+
if (user) {
|
61
|
+
const userService = new UserService();
|
62
|
+
|
63
|
+
await userService.createUser(user.id, {
|
64
|
+
created_at: user.createdAt,
|
65
|
+
email_addresses: user.emailAddresses.map((e) => ({
|
66
|
+
email_address: e.emailAddress,
|
67
|
+
id: e.id,
|
68
|
+
})),
|
69
|
+
first_name: user.firstName,
|
70
|
+
id: user.id,
|
71
|
+
image_url: user.imageUrl,
|
72
|
+
last_name: user.lastName,
|
73
|
+
phone_numbers: user.phoneNumbers.map((e) => ({
|
74
|
+
id: e.id,
|
75
|
+
phone_number: e.phoneNumber,
|
76
|
+
})),
|
77
|
+
primary_email_address_id: user.primaryEmailAddressId,
|
78
|
+
primary_phone_number_id: user.primaryPhoneNumberId,
|
79
|
+
username: user.username,
|
80
|
+
} as UserJSON);
|
81
|
+
|
82
|
+
continue;
|
83
|
+
}
|
84
|
+
}
|
76
85
|
|
86
|
+
// if in desktop mode, make sure desktop user exist
|
87
|
+
else if (isDesktop) {
|
88
|
+
await UserModel.makeSureUserExist(ctx.serverDB, ctx.userId);
|
89
|
+
pino.info('create desktop user');
|
77
90
|
continue;
|
78
91
|
}
|
79
92
|
}
|
93
|
+
|
94
|
+
console.error('getUserState:', error);
|
80
95
|
throw error;
|
81
96
|
}
|
82
97
|
}
|
@@ -35,12 +35,6 @@ const checkStdioEnvironment = (params: z.infer<typeof mcpClientParamsSchema>) =>
|
|
35
35
|
const mcpProcedure = isServerMode ? authedProcedure : passwordProcedure;
|
36
36
|
|
37
37
|
export const mcpRouter = router({
|
38
|
-
getStdioMcpServerManifest: mcpProcedure.input(stdioParamsSchema).query(async ({ input }) => {
|
39
|
-
// Stdio check can be done here or rely on the service/client layer
|
40
|
-
checkStdioEnvironment(input);
|
41
|
-
|
42
|
-
return await mcpService.getStdioMcpServerManifest(input.name, input.command, input.args);
|
43
|
-
}),
|
44
38
|
getStreamableMcpServerManifest: mcpProcedure
|
45
39
|
.input(
|
46
40
|
z.object({
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { DataSyncConfig, dispatch } from '@lobechat/electron-client-ipc';
|
2
2
|
|
3
3
|
class RemoteServerService {
|
4
4
|
/**
|
@@ -11,7 +11,7 @@ class RemoteServerService {
|
|
11
11
|
/**
|
12
12
|
* 设置远程服务器配置
|
13
13
|
*/
|
14
|
-
setRemoteServerConfig = async (config:
|
14
|
+
setRemoteServerConfig = async (config: DataSyncConfig) => {
|
15
15
|
return dispatch('setRemoteServerConfig', config);
|
16
16
|
};
|
17
17
|
|
@@ -25,8 +25,8 @@ class RemoteServerService {
|
|
25
25
|
/**
|
26
26
|
* 请求授权
|
27
27
|
*/
|
28
|
-
requestAuthorization = async (
|
29
|
-
return dispatch('requestAuthorization',
|
28
|
+
requestAuthorization = async (config: DataSyncConfig) => {
|
29
|
+
return dispatch('requestAuthorization', config);
|
30
30
|
};
|
31
31
|
|
32
32
|
/**
|
package/src/services/mcp.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
1
|
+
import { isDesktop } from '@/const/version';
|
2
|
+
import { desktopClient, toolsClient } from '@/libs/trpc/client';
|
2
3
|
import { ChatToolPayload } from '@/types/message';
|
3
4
|
|
4
5
|
class MCPService {
|
@@ -13,10 +14,20 @@ class MCPService {
|
|
13
14
|
|
14
15
|
if (!plugin) return;
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
{
|
19
|
-
|
17
|
+
const data = {
|
18
|
+
args,
|
19
|
+
params: { ...plugin.customParams?.mcp, name: identifier } as any,
|
20
|
+
toolName: apiName,
|
21
|
+
};
|
22
|
+
|
23
|
+
const isStdio = plugin?.customParams?.mcp?.type === 'stdio';
|
24
|
+
|
25
|
+
// For desktop and stdio, use the desktopClient
|
26
|
+
if (isDesktop && isStdio) {
|
27
|
+
return desktopClient.mcp.callTool.mutate(data, { signal });
|
28
|
+
}
|
29
|
+
|
30
|
+
return toolsClient.mcp.callTool.mutate(data, { signal });
|
20
31
|
}
|
21
32
|
|
22
33
|
async getStreamableMcpServerManifest(identifier: string, url: string) {
|
@@ -24,11 +35,10 @@ class MCPService {
|
|
24
35
|
}
|
25
36
|
|
26
37
|
async getStdioMcpServerManifest(identifier: string, command: string, args?: string[]) {
|
27
|
-
return
|
38
|
+
return desktopClient.mcp.getStdioMcpServerManifest.query({
|
28
39
|
args: args,
|
29
40
|
command,
|
30
41
|
name: identifier,
|
31
|
-
type: 'stdio',
|
32
42
|
});
|
33
43
|
}
|
34
44
|
}
|
package/src/services/upload.ts
CHANGED
@@ -148,6 +148,15 @@ class UploadService {
|
|
148
148
|
return result;
|
149
149
|
};
|
150
150
|
|
151
|
+
uploadToDesktop = async (file: File) => {
|
152
|
+
const fileArrayBuffer = await file.arrayBuffer();
|
153
|
+
const hash = sha256(fileArrayBuffer);
|
154
|
+
|
155
|
+
const { desktopFileAPI } = await import('@/services/electron/file');
|
156
|
+
const { metadata } = await desktopFileAPI.uploadFile(file, hash);
|
157
|
+
return metadata;
|
158
|
+
};
|
159
|
+
|
151
160
|
uploadToClientS3 = async (hash: string, file: File): Promise<FileMetadata> => {
|
152
161
|
await clientS3Storage.putObject(hash, file);
|
153
162
|
|
@@ -602,13 +602,22 @@ export const generateAIChat: StateCreator<
|
|
602
602
|
}
|
603
603
|
}
|
604
604
|
|
605
|
-
|
605
|
+
let parsedToolCalls = toolCalls;
|
606
|
+
if (parsedToolCalls && parsedToolCalls.length > 0) {
|
606
607
|
internal_toggleToolCallingStreaming(messageId, undefined);
|
608
|
+
parsedToolCalls = parsedToolCalls.map((item) => ({
|
609
|
+
...item,
|
610
|
+
function: {
|
611
|
+
...item.function,
|
612
|
+
arguments: !!item.function.arguments ? item.function.arguments : '{}',
|
613
|
+
},
|
614
|
+
}));
|
615
|
+
isFunctionCall = true;
|
607
616
|
}
|
608
617
|
|
609
618
|
// update the content after fetch result
|
610
619
|
await internal_updateMessageContent(messageId, content, {
|
611
|
-
toolCalls,
|
620
|
+
toolCalls: parsedToolCalls,
|
612
621
|
reasoning: !!reasoning ? { ...reasoning, duration } : undefined,
|
613
622
|
search: !!grounding?.citations ? grounding : undefined,
|
614
623
|
imageList: finalImages.length > 0 ? finalImages : undefined,
|
@@ -1,8 +1,11 @@
|
|
1
1
|
import {
|
2
2
|
ListLocalFileParams,
|
3
|
+
LocalMoveFilesResultItem,
|
3
4
|
LocalReadFileParams,
|
4
5
|
LocalReadFilesParams,
|
5
6
|
LocalSearchFilesParams,
|
7
|
+
MoveLocalFilesParams,
|
8
|
+
RenameLocalFileParams,
|
6
9
|
} from '@lobechat/electron-client-ipc';
|
7
10
|
import { StateCreator } from 'zustand/vanilla';
|
8
11
|
|
@@ -11,16 +14,27 @@ import { ChatStore } from '@/store/chat/store';
|
|
11
14
|
import {
|
12
15
|
LocalFileListState,
|
13
16
|
LocalFileSearchState,
|
17
|
+
LocalMoveFilesState,
|
14
18
|
LocalReadFileState,
|
15
19
|
LocalReadFilesState,
|
20
|
+
LocalRenameFileState,
|
16
21
|
} from '@/tools/local-files/type';
|
17
22
|
|
18
23
|
export interface LocalFileAction {
|
24
|
+
internal_triggerLocalFileToolCalling: <T = any>(
|
25
|
+
id: string,
|
26
|
+
callingService: () => Promise<{ content: any; state: T }>,
|
27
|
+
) => Promise<boolean>;
|
28
|
+
|
19
29
|
listLocalFiles: (id: string, params: ListLocalFileParams) => Promise<boolean>;
|
30
|
+
moveLocalFiles: (id: string, params: MoveLocalFilesParams) => Promise<boolean>;
|
20
31
|
reSearchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
|
21
32
|
readLocalFile: (id: string, params: LocalReadFileParams) => Promise<boolean>;
|
22
33
|
readLocalFiles: (id: string, params: LocalReadFilesParams) => Promise<boolean>;
|
34
|
+
renameLocalFile: (id: string, params: RenameLocalFileParams) => Promise<boolean>;
|
35
|
+
// Added rename action
|
23
36
|
searchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
|
37
|
+
|
24
38
|
toggleLocalFileLoading: (id: string, loading: boolean) => void;
|
25
39
|
}
|
26
40
|
|
@@ -30,15 +44,13 @@ export const localFileSlice: StateCreator<
|
|
30
44
|
[],
|
31
45
|
LocalFileAction
|
32
46
|
> = (set, get) => ({
|
33
|
-
|
47
|
+
internal_triggerLocalFileToolCalling: async (id, callingService) => {
|
34
48
|
get().toggleLocalFileLoading(id, true);
|
35
49
|
try {
|
36
|
-
const
|
37
|
-
|
38
|
-
await get().
|
39
|
-
await get().internal_updateMessageContent(id, JSON.stringify(data));
|
50
|
+
const { state, content } = await callingService();
|
51
|
+
await get().updatePluginState(id, state as any);
|
52
|
+
await get().internal_updateMessageContent(id, JSON.stringify(content));
|
40
53
|
} catch (error) {
|
41
|
-
console.error('Error listing local files:', error);
|
42
54
|
await get().internal_updateMessagePluginError(id, {
|
43
55
|
body: error,
|
44
56
|
message: (error as Error).message,
|
@@ -50,6 +62,41 @@ export const localFileSlice: StateCreator<
|
|
50
62
|
return true;
|
51
63
|
},
|
52
64
|
|
65
|
+
listLocalFiles: async (id, params) => {
|
66
|
+
return get().internal_triggerLocalFileToolCalling<LocalFileListState>(id, async () => {
|
67
|
+
const result = await localFileService.listLocalFiles(params);
|
68
|
+
const state: LocalFileListState = { listResults: result };
|
69
|
+
return { content: result, state };
|
70
|
+
});
|
71
|
+
},
|
72
|
+
|
73
|
+
moveLocalFiles: async (id, params) => {
|
74
|
+
return get().internal_triggerLocalFileToolCalling<LocalMoveFilesState>(id, async () => {
|
75
|
+
const results: LocalMoveFilesResultItem[] = await localFileService.moveLocalFiles(params);
|
76
|
+
|
77
|
+
// 检查所有文件是否成功移动以更新消息内容
|
78
|
+
const allSucceeded = results.every((r) => r.success);
|
79
|
+
const someFailed = results.some((r) => !r.success);
|
80
|
+
const successCount = results.filter((r) => r.success).length;
|
81
|
+
const failedCount = results.length - successCount;
|
82
|
+
|
83
|
+
let message = '';
|
84
|
+
|
85
|
+
if (allSucceeded) {
|
86
|
+
message = `Successfully moved ${results.length} item(s).`;
|
87
|
+
} else if (someFailed) {
|
88
|
+
message = `Moved ${successCount} item(s) successfully. Failed to move ${failedCount} item(s).`;
|
89
|
+
} else {
|
90
|
+
// 所有都失败了?
|
91
|
+
message = `Failed to move all ${results.length} item(s).`;
|
92
|
+
}
|
93
|
+
|
94
|
+
const state: LocalMoveFilesState = { results, successCount, totalCount: results.length };
|
95
|
+
|
96
|
+
return { content: { message, results }, state };
|
97
|
+
});
|
98
|
+
},
|
99
|
+
|
53
100
|
reSearchLocalFiles: async (id, params) => {
|
54
101
|
get().toggleLocalFileLoading(id, true);
|
55
102
|
|
@@ -59,63 +106,73 @@ export const localFileSlice: StateCreator<
|
|
59
106
|
},
|
60
107
|
|
61
108
|
readLocalFile: async (id, params) => {
|
62
|
-
get().
|
63
|
-
|
64
|
-
try {
|
109
|
+
return get().internal_triggerLocalFileToolCalling<LocalReadFileState>(id, async () => {
|
65
110
|
const result = await localFileService.readLocalFile(params);
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
} catch (error) {
|
70
|
-
console.error('Error reading local file:', error);
|
71
|
-
await get().internal_updateMessagePluginError(id, {
|
72
|
-
body: error,
|
73
|
-
message: (error as Error).message,
|
74
|
-
type: 'PluginServerError',
|
75
|
-
});
|
76
|
-
}
|
77
|
-
get().toggleLocalFileLoading(id, false);
|
78
|
-
return true;
|
111
|
+
const state: LocalReadFileState = { fileContent: result };
|
112
|
+
return { content: result, state };
|
113
|
+
});
|
79
114
|
},
|
80
115
|
|
81
116
|
readLocalFiles: async (id, params) => {
|
82
|
-
get().
|
83
|
-
|
84
|
-
try {
|
117
|
+
return get().internal_triggerLocalFileToolCalling<LocalReadFilesState>(id, async () => {
|
85
118
|
const results = await localFileService.readLocalFiles(params);
|
86
|
-
|
87
|
-
|
88
|
-
}
|
89
|
-
|
90
|
-
await get().internal_updateMessagePluginError(id, {
|
91
|
-
body: error,
|
92
|
-
message: (error as Error).message,
|
93
|
-
type: 'PluginServerError',
|
94
|
-
});
|
95
|
-
}
|
96
|
-
get().toggleLocalFileLoading(id, false);
|
119
|
+
const state: LocalReadFilesState = { filesContent: results };
|
120
|
+
return { content: results, state };
|
121
|
+
});
|
122
|
+
},
|
97
123
|
|
98
|
-
|
124
|
+
renameLocalFile: async (id, params) => {
|
125
|
+
return get().internal_triggerLocalFileToolCalling<LocalRenameFileState>(id, async () => {
|
126
|
+
const { path: currentPath, newName } = params;
|
127
|
+
|
128
|
+
// Basic validation for newName (can be done here or backend, maybe better in backend)
|
129
|
+
if (
|
130
|
+
!newName ||
|
131
|
+
newName.includes('/') ||
|
132
|
+
newName.includes('\\') ||
|
133
|
+
newName === '.' ||
|
134
|
+
newName === '..' ||
|
135
|
+
/["*/:<>?\\|]/.test(newName)
|
136
|
+
) {
|
137
|
+
throw new Error(
|
138
|
+
'Invalid new name provided. It cannot be empty, contain path separators, or invalid characters.',
|
139
|
+
);
|
140
|
+
}
|
141
|
+
|
142
|
+
const result = await localFileService.renameLocalFile({ newName, path: currentPath }); // Call the specific service
|
143
|
+
|
144
|
+
let state: LocalRenameFileState;
|
145
|
+
let content: { message: string; success: boolean };
|
146
|
+
|
147
|
+
if (result.success) {
|
148
|
+
state = { newPath: result.newPath!, oldPath: currentPath, success: true };
|
149
|
+
// Simplified message
|
150
|
+
content = {
|
151
|
+
message: `Successfully renamed file ${currentPath} to ${newName}.`,
|
152
|
+
success: true,
|
153
|
+
};
|
154
|
+
} else {
|
155
|
+
const errorMessage = result.error;
|
156
|
+
state = {
|
157
|
+
error: errorMessage,
|
158
|
+
newPath: '',
|
159
|
+
oldPath: params.path,
|
160
|
+
success: false,
|
161
|
+
};
|
162
|
+
content = { message: errorMessage, success: false };
|
163
|
+
}
|
164
|
+
return { content, state };
|
165
|
+
});
|
99
166
|
},
|
100
167
|
|
101
168
|
searchLocalFiles: async (id, params) => {
|
102
|
-
get().
|
103
|
-
|
104
|
-
const
|
105
|
-
|
106
|
-
|
107
|
-
} catch (error) {
|
108
|
-
console.error('Error searching local files:', error);
|
109
|
-
await get().internal_updateMessagePluginError(id, {
|
110
|
-
body: error,
|
111
|
-
message: (error as Error).message,
|
112
|
-
type: 'PluginServerError',
|
113
|
-
});
|
114
|
-
}
|
115
|
-
get().toggleLocalFileLoading(id, false);
|
116
|
-
|
117
|
-
return true;
|
169
|
+
return get().internal_triggerLocalFileToolCalling<LocalFileSearchState>(id, async () => {
|
170
|
+
const result = await localFileService.searchLocalFiles(params);
|
171
|
+
const state: LocalFileSearchState = { searchResults: result };
|
172
|
+
return { content: result, state };
|
173
|
+
});
|
118
174
|
},
|
175
|
+
|
119
176
|
toggleLocalFileLoading: (id, loading) => {
|
120
177
|
// Assuming a loading state structure similar to searchLoading
|
121
178
|
set(
|
@@ -1,8 +1,8 @@
|
|
1
|
-
import {
|
1
|
+
import { DataSyncConfig } from '@lobechat/electron-client-ipc';
|
2
|
+
import isEqual from 'fast-deep-equal';
|
2
3
|
import useSWR, { SWRResponse, mutate } from 'swr';
|
3
4
|
import type { StateCreator } from 'zustand/vanilla';
|
4
5
|
|
5
|
-
import { INBOX_SESSION_ID } from '@/const/session';
|
6
6
|
import { remoteServerService } from '@/services/electron/remoteServer';
|
7
7
|
|
8
8
|
import { initialState } from '../initialState';
|
@@ -12,11 +12,12 @@ import type { ElectronStore } from '../store';
|
|
12
12
|
* 设置操作
|
13
13
|
*/
|
14
14
|
export interface ElectronRemoteServerAction {
|
15
|
-
connectRemoteServer: (params:
|
15
|
+
connectRemoteServer: (params: DataSyncConfig) => Promise<void>;
|
16
16
|
disconnectRemoteServer: () => Promise<void>;
|
17
17
|
refreshServerConfig: () => Promise<void>;
|
18
18
|
refreshUserData: () => Promise<void>;
|
19
|
-
|
19
|
+
useDataSyncConfig: () => SWRResponse;
|
20
|
+
useRefreshDataWhenActive: (active?: boolean) => SWRResponse;
|
20
21
|
}
|
21
22
|
|
22
23
|
const REMOTE_SERVER_CONFIG_KEY = 'electron:getRemoteServerConfig';
|
@@ -28,7 +29,7 @@ export const remoteSyncSlice: StateCreator<
|
|
28
29
|
ElectronRemoteServerAction
|
29
30
|
> = (set, get) => ({
|
30
31
|
connectRemoteServer: async (values) => {
|
31
|
-
if (!values.
|
32
|
+
if (!values.remoteServerUrl) return;
|
32
33
|
|
33
34
|
set({ isConnectingServer: true });
|
34
35
|
try {
|
@@ -36,12 +37,12 @@ export const remoteSyncSlice: StateCreator<
|
|
36
37
|
const config = await remoteServerService.getRemoteServerConfig();
|
37
38
|
|
38
39
|
// 如果已经激活,需要先清除
|
39
|
-
if (config
|
40
|
-
await remoteServerService.
|
40
|
+
if (!isEqual(config, values)) {
|
41
|
+
await remoteServerService.setRemoteServerConfig({ ...values, active: false });
|
41
42
|
}
|
42
43
|
|
43
44
|
// 请求授权
|
44
|
-
const result = await remoteServerService.requestAuthorization(values
|
45
|
+
const result = await remoteServerService.requestAuthorization(values);
|
45
46
|
|
46
47
|
if (!result.success) {
|
47
48
|
console.error('请求授权失败:', result.error);
|
@@ -65,9 +66,9 @@ export const remoteSyncSlice: StateCreator<
|
|
65
66
|
disconnectRemoteServer: async () => {
|
66
67
|
set({ isConnectingServer: false });
|
67
68
|
try {
|
68
|
-
await remoteServerService.
|
69
|
+
await remoteServerService.setRemoteServerConfig({ active: false, storageMode: 'local' });
|
69
70
|
// 更新表单URL为空
|
70
|
-
set({
|
71
|
+
set({ dataSyncConfig: initialState.dataSyncConfig });
|
71
72
|
// 刷新状态
|
72
73
|
await get().refreshServerConfig();
|
73
74
|
} catch (error) {
|
@@ -93,11 +94,10 @@ export const remoteSyncSlice: StateCreator<
|
|
93
94
|
await getChatStoreState().refreshMessages();
|
94
95
|
await getChatStoreState().refreshTopic();
|
95
96
|
await getUserStoreState().refreshUserState();
|
96
|
-
getSessionStoreState().switchSession(INBOX_SESSION_ID);
|
97
97
|
},
|
98
98
|
|
99
|
-
|
100
|
-
useSWR<
|
99
|
+
useDataSyncConfig: () =>
|
100
|
+
useSWR<DataSyncConfig>(
|
101
101
|
REMOTE_SERVER_CONFIG_KEY,
|
102
102
|
async () => {
|
103
103
|
try {
|
@@ -109,13 +109,14 @@ export const remoteSyncSlice: StateCreator<
|
|
109
109
|
},
|
110
110
|
{
|
111
111
|
onSuccess: (data) => {
|
112
|
-
|
113
|
-
set({ isInitRemoteServerConfig: true, remoteServerConfig: data });
|
114
|
-
|
115
|
-
if (data.active) {
|
116
|
-
get().refreshUserData();
|
117
|
-
}
|
112
|
+
set({ dataSyncConfig: data, isInitRemoteServerConfig: true });
|
118
113
|
},
|
119
114
|
},
|
120
115
|
),
|
116
|
+
useRefreshDataWhenActive: (active) =>
|
117
|
+
useSWR(['refreshDataWhenActive', active], async () => {
|
118
|
+
if (!active) return;
|
119
|
+
|
120
|
+
await get().refreshUserData();
|
121
|
+
}),
|
121
122
|
});
|