@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.
- package/.cursor/rules/desktop-local-tools-implement.mdc +80 -0
- package/.env.desktop +2 -1
- package/.github/scripts/pr-comment.js +4 -9
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/electron.json +38 -2
- package/locales/ar/plugin.json +31 -31
- package/locales/bg-BG/electron.json +38 -2
- package/locales/bg-BG/plugin.json +31 -31
- package/locales/de-DE/electron.json +38 -2
- package/locales/de-DE/plugin.json +3 -8
- package/locales/en-US/electron.json +38 -2
- package/locales/en-US/plugin.json +3 -8
- package/locales/es-ES/electron.json +38 -2
- package/locales/es-ES/plugin.json +31 -31
- package/locales/fa-IR/electron.json +38 -2
- package/locales/fa-IR/plugin.json +31 -31
- package/locales/fr-FR/electron.json +38 -2
- package/locales/fr-FR/plugin.json +31 -31
- package/locales/it-IT/electron.json +38 -2
- package/locales/it-IT/plugin.json +31 -31
- package/locales/ja-JP/electron.json +38 -2
- package/locales/ja-JP/plugin.json +31 -31
- package/locales/ko-KR/electron.json +38 -2
- package/locales/ko-KR/plugin.json +3 -8
- package/locales/nl-NL/electron.json +38 -2
- package/locales/nl-NL/plugin.json +31 -31
- package/locales/pl-PL/electron.json +38 -2
- package/locales/pl-PL/plugin.json +3 -8
- package/locales/pt-BR/electron.json +38 -2
- package/locales/pt-BR/plugin.json +31 -31
- package/locales/ru-RU/electron.json +38 -2
- package/locales/ru-RU/plugin.json +31 -31
- package/locales/tr-TR/electron.json +38 -2
- package/locales/tr-TR/plugin.json +31 -31
- package/locales/vi-VN/electron.json +38 -2
- package/locales/vi-VN/plugin.json +3 -8
- package/locales/zh-CN/electron.json +38 -2
- package/locales/zh-CN/plugin.json +14 -9
- package/locales/zh-TW/electron.json +38 -2
- package/locales/zh-TW/plugin.json +31 -31
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/events/update.ts +3 -3
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Mode.tsx +222 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Option.tsx +104 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Sync.tsx +42 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Waiting.tsx +203 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/index.tsx +57 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/UpdateModal.tsx +242 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/UpdateNotification.tsx +193 -0
- package/src/app/[variants]/(main)/_layout/Desktop/{Titlebar.tsx → ElectronTitlebar/index.tsx} +15 -1
- package/src/app/[variants]/(main)/_layout/Desktop/SideBar/BottomActions.tsx +3 -2
- package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +1 -1
- package/src/app/[variants]/layout.tsx +2 -1
- package/src/features/Conversation/components/MarkdownElements/LocalFile/Render/LocalFile.tsx +65 -0
- package/src/features/Conversation/components/MarkdownElements/LocalFile/Render/index.tsx +29 -0
- package/src/features/Conversation/components/MarkdownElements/LocalFile/index.ts +16 -0
- package/src/features/Conversation/components/MarkdownElements/index.ts +7 -1
- package/src/features/Conversation/components/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +260 -0
- package/src/features/Conversation/components/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +204 -0
- package/src/features/Conversation/components/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +133 -0
- package/src/features/Conversation/components/MarkdownElements/type.ts +5 -1
- package/src/features/PluginDevModal/MCPManifestForm/ArgsInput.tsx +20 -0
- package/src/features/PluginDevModal/MCPManifestForm/MCPTypeSelect.tsx +176 -0
- package/src/features/PluginDevModal/{MCPManifestForm.tsx → MCPManifestForm/index.tsx} +92 -30
- package/src/libs/mcp/__tests__/__snapshots__/index.test.ts.snap +0 -56
- package/src/locales/default/electron.ts +38 -2
- package/src/locales/default/plugin.ts +14 -7
- package/src/server/modules/ElectronIPCClient/index.ts +36 -0
- package/src/server/routers/lambda/session.ts +2 -6
- package/src/server/routers/tools/mcp.ts +6 -0
- package/src/server/services/file/impls/index.ts +9 -1
- package/src/server/services/file/impls/local.test.ts +299 -0
- package/src/server/services/file/impls/local.ts +183 -0
- package/src/server/services/mcp/index.ts +19 -0
- package/src/services/aiModel/index.ts +5 -1
- package/src/services/aiProvider/index.ts +5 -1
- package/src/services/electron/autoUpdate.ts +4 -0
- package/src/services/file/index.ts +5 -1
- package/src/services/mcp.ts +13 -2
- package/src/services/message/index.ts +5 -1
- package/src/services/plugin/index.ts +5 -1
- package/src/services/session/index.ts +5 -1
- package/src/services/tableViewer/desktop.ts +15 -0
- package/src/services/tableViewer/index.ts +4 -1
- package/src/services/thread/index.ts +5 -1
- package/src/services/topic/index.ts +5 -1
- package/src/services/user/index.ts +5 -1
- package/src/store/electron/actions/app.ts +59 -0
- package/src/store/electron/actions/sync.ts +5 -1
- package/src/store/electron/initialState.ts +3 -1
- package/src/store/electron/store.ts +6 -1
- package/src/store/tool/slices/customPlugin/action.ts +16 -4
- package/src/utils/client/GlobalAgentContextManager.ts +85 -0
- package/src/utils/promptTemplate.test.ts +78 -0
- package/src/utils/promptTemplate.ts +17 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
2
|
+
import { Typography } from 'antd';
|
3
|
+
import { createStyles } from 'antd-style';
|
4
|
+
import { CheckIcon, RouterIcon, TerminalIcon } from 'lucide-react';
|
5
|
+
import React, { memo } from 'react';
|
6
|
+
import { useTranslation } from 'react-i18next';
|
7
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
8
|
+
|
9
|
+
import { isDesktop } from '@/const/version';
|
10
|
+
|
11
|
+
// Define styles using antd-style (moved from MCPManifestForm)
|
12
|
+
const useStyles = createStyles(({ token, css }) => ({
|
13
|
+
active: css`
|
14
|
+
border-color: ${token.colorPrimary};
|
15
|
+
|
16
|
+
&:hover {
|
17
|
+
border-color: ${token.colorPrimary};
|
18
|
+
}
|
19
|
+
`,
|
20
|
+
cardDescription: css`
|
21
|
+
font-size: ${token.fontSizeSM}px;
|
22
|
+
color: ${token.colorTextDescription};
|
23
|
+
`,
|
24
|
+
cardTitle: css`
|
25
|
+
font-weight: bold;
|
26
|
+
`,
|
27
|
+
checkIcon: css`
|
28
|
+
position: absolute;
|
29
|
+
inset-block-start: 12px;
|
30
|
+
inset-inline-end: 12px;
|
31
|
+
|
32
|
+
display: flex;
|
33
|
+
align-items: center;
|
34
|
+
justify-content: center;
|
35
|
+
|
36
|
+
width: 20px;
|
37
|
+
height: 20px;
|
38
|
+
border-radius: 50%;
|
39
|
+
|
40
|
+
color: ${token.colorBgContainer};
|
41
|
+
|
42
|
+
background-color: ${token.colorPrimary};
|
43
|
+
`,
|
44
|
+
container: css`
|
45
|
+
cursor: pointer;
|
46
|
+
|
47
|
+
position: relative;
|
48
|
+
|
49
|
+
width: 100%;
|
50
|
+
padding-block: 12px;
|
51
|
+
padding-inline: 16px;
|
52
|
+
border: 1px solid ${token.colorBorder};
|
53
|
+
border-radius: ${token.borderRadiusLG}px;
|
54
|
+
|
55
|
+
background-color: ${token.colorBgContainer};
|
56
|
+
|
57
|
+
transition:
|
58
|
+
border-color 0.3s ${token.motionEaseInOut},
|
59
|
+
box-shadow 0.3s ${token.motionEaseInOut};
|
60
|
+
|
61
|
+
&:hover {
|
62
|
+
border-color: ${token.colorPrimaryHover};
|
63
|
+
}
|
64
|
+
`,
|
65
|
+
disabled: css`
|
66
|
+
cursor: not-allowed;
|
67
|
+
border-color: ${token.colorBorder};
|
68
|
+
opacity: 0.6;
|
69
|
+
background-color: ${token.colorBgContainerDisabled};
|
70
|
+
|
71
|
+
&:hover {
|
72
|
+
border-color: ${token.colorBorder};
|
73
|
+
}
|
74
|
+
`,
|
75
|
+
featureIcon: css`
|
76
|
+
color: ${token.colorTextSecondary};
|
77
|
+
`,
|
78
|
+
featureItem: css`
|
79
|
+
display: flex;
|
80
|
+
gap: 8px;
|
81
|
+
align-items: center;
|
82
|
+
`,
|
83
|
+
featureText: css`
|
84
|
+
font-size: ${token.fontSizeSM}px;
|
85
|
+
color: ${token.colorTextSecondary};
|
86
|
+
`,
|
87
|
+
}));
|
88
|
+
|
89
|
+
// Helper component for feature list items (moved from MCPManifestForm)
|
90
|
+
const FeatureItem = memo(({ children }: { children: React.ReactNode }) => {
|
91
|
+
const { styles } = useStyles();
|
92
|
+
return (
|
93
|
+
<div className={styles.featureItem}>
|
94
|
+
<Center className={styles.featureIcon}>
|
95
|
+
<CheckIcon size={16} />
|
96
|
+
</Center>
|
97
|
+
<div className={styles.featureText}>{children}</div>
|
98
|
+
</div>
|
99
|
+
);
|
100
|
+
});
|
101
|
+
|
102
|
+
interface MCPTypeSelectProps {
|
103
|
+
onChange?: (value: string) => void;
|
104
|
+
value?: string;
|
105
|
+
}
|
106
|
+
|
107
|
+
const MCPTypeSelect = ({ value, onChange }: MCPTypeSelectProps) => {
|
108
|
+
const { t } = useTranslation('plugin');
|
109
|
+
const { styles, cx } = useStyles();
|
110
|
+
|
111
|
+
const handleSelect = (type: string) => {
|
112
|
+
onChange?.(type);
|
113
|
+
};
|
114
|
+
|
115
|
+
const data = [
|
116
|
+
{
|
117
|
+
description: t('dev.mcp.type.httpShortDesc'),
|
118
|
+
features: [t('dev.mcp.type.httpFeature1'), t('dev.mcp.type.httpFeature2')],
|
119
|
+
icon: RouterIcon,
|
120
|
+
label: 'Streamable HTTP',
|
121
|
+
value: 'http',
|
122
|
+
},
|
123
|
+
{
|
124
|
+
description: t('dev.mcp.type.stdioShortDesc'),
|
125
|
+
features: [t('dev.mcp.type.stdioFeature1'), t('dev.mcp.type.stdioFeature2')],
|
126
|
+
icon: TerminalIcon,
|
127
|
+
label: 'STDIO',
|
128
|
+
value: 'stdio',
|
129
|
+
},
|
130
|
+
];
|
131
|
+
|
132
|
+
return (
|
133
|
+
<Flexbox gap={16} horizontal width={'100%'}>
|
134
|
+
{data.map(({ label, description, features, value: itemValue, icon }) => {
|
135
|
+
const isActive = value === itemValue;
|
136
|
+
const disabled = itemValue === 'stdio' && !isDesktop;
|
137
|
+
return (
|
138
|
+
<Flexbox
|
139
|
+
className={cx(styles.container, isActive && styles.active, disabled && styles.disabled)}
|
140
|
+
gap={12}
|
141
|
+
key={itemValue}
|
142
|
+
onClick={disabled ? undefined : () => handleSelect(itemValue)}
|
143
|
+
style={{ flex: 1 }} // Make cards take equal width
|
144
|
+
>
|
145
|
+
<Center className={styles.checkIcon} style={{ opacity: isActive ? 1 : 0 }}>
|
146
|
+
<CheckIcon size={14} />
|
147
|
+
</Center>
|
148
|
+
|
149
|
+
<Flexbox align={'flex-start'} gap={12} horizontal>
|
150
|
+
<Center height={22}>
|
151
|
+
<Icon icon={icon} style={{ fontSize: 16 }} />
|
152
|
+
</Center>
|
153
|
+
<Flexbox>
|
154
|
+
<div className={styles.cardTitle}>{label}</div>
|
155
|
+
<div className={styles.cardDescription}>{description}</div>
|
156
|
+
</Flexbox>
|
157
|
+
</Flexbox>
|
158
|
+
<Flexbox gap={8}>
|
159
|
+
{features.map((feature) => (
|
160
|
+
<FeatureItem key={feature}>{feature}</FeatureItem>
|
161
|
+
))}
|
162
|
+
</Flexbox>
|
163
|
+
{disabled && (
|
164
|
+
<Typography.Text style={{ fontSize: 12, marginTop: 8 }} type="warning">
|
165
|
+
{t('dev.mcp.type.stdioNotAvailable')}
|
166
|
+
</Typography.Text>
|
167
|
+
)}
|
168
|
+
</Flexbox>
|
169
|
+
);
|
170
|
+
})}
|
171
|
+
{/* Streamable HTTP Card */}
|
172
|
+
</Flexbox>
|
173
|
+
);
|
174
|
+
};
|
175
|
+
|
176
|
+
export default MCPTypeSelect;
|
@@ -1,23 +1,52 @@
|
|
1
|
+
import {
|
2
|
+
SiBun,
|
3
|
+
SiDocker,
|
4
|
+
SiNodedotjs,
|
5
|
+
SiNpm,
|
6
|
+
SiPnpm,
|
7
|
+
SiPython,
|
8
|
+
} from '@icons-pack/react-simple-icons';
|
1
9
|
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
2
10
|
import { ActionIcon, FormItem } from '@lobehub/ui';
|
3
|
-
import { Form, FormInstance, Input
|
11
|
+
import { AutoComplete, Form, FormInstance, Input } from 'antd';
|
4
12
|
import { FileCode, RotateCwIcon } from 'lucide-react';
|
5
|
-
import { useState } from 'react';
|
13
|
+
import { FC, useState } from 'react';
|
6
14
|
import { useTranslation } from 'react-i18next';
|
7
15
|
import { Flexbox } from 'react-layout-kit';
|
8
16
|
|
9
17
|
import ManifestPreviewer from '@/components/ManifestPreviewer';
|
10
|
-
import { isDesktop } from '@/const/version';
|
11
18
|
import { mcpService } from '@/services/mcp';
|
12
19
|
import { useToolStore } from '@/store/tool';
|
13
20
|
import { pluginSelectors } from '@/store/tool/selectors';
|
14
21
|
import { PluginInstallError } from '@/types/tool/plugin';
|
15
22
|
|
23
|
+
import ArgsInput from './ArgsInput';
|
24
|
+
import MCPTypeSelect from './MCPTypeSelect';
|
25
|
+
|
16
26
|
interface MCPManifestFormProps {
|
17
27
|
form: FormInstance;
|
18
28
|
isEditMode?: boolean;
|
19
29
|
}
|
20
30
|
|
31
|
+
// 定义预设的命令选项
|
32
|
+
const STDIO_COMMAND_OPTIONS: {
|
33
|
+
// 假设图标是 React 函数组件
|
34
|
+
color?: string;
|
35
|
+
icon?: FC<{ color?: string; size?: number }>;
|
36
|
+
value: string;
|
37
|
+
}[] = [
|
38
|
+
{ color: '#CB3837', icon: SiNpm, value: 'npx' },
|
39
|
+
{ color: '#CB3837', icon: SiNpm, value: 'npm' },
|
40
|
+
{ color: '#F69220', icon: SiPnpm, value: 'pnpm' },
|
41
|
+
{ color: '#F69220', icon: SiPnpm, value: 'pnpx' },
|
42
|
+
{ color: '#339933', icon: SiNodedotjs, value: 'node' },
|
43
|
+
{ color: '#efe2d2', icon: SiBun, value: 'bun' },
|
44
|
+
{ color: '#efe2d2', icon: SiBun, value: 'bunx' },
|
45
|
+
{ color: '#DE5FE9', icon: SiPython, value: 'uv' },
|
46
|
+
{ color: '#3776AB', icon: SiPython, value: 'python' },
|
47
|
+
{ color: '#2496ED', icon: SiDocker, value: 'docker' },
|
48
|
+
];
|
49
|
+
|
21
50
|
const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
22
51
|
const { t } = useTranslation('plugin');
|
23
52
|
const mcpType = Form.useWatch(['customParams', 'mcp', 'type'], form);
|
@@ -25,9 +54,18 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
|
25
54
|
const pluginIds = useToolStore(pluginSelectors.storeAndInstallPluginsIdList);
|
26
55
|
|
27
56
|
const HTTP_URL_KEY = ['customParams', 'mcp', 'url'];
|
57
|
+
const STDIO_COMMAND = ['customParams', 'mcp', 'command'];
|
58
|
+
const STDIO_ARGS = ['customParams', 'mcp', 'args'];
|
28
59
|
return (
|
29
60
|
<Form form={form} layout={'vertical'}>
|
30
|
-
<Flexbox
|
61
|
+
<Flexbox>
|
62
|
+
<Form.Item
|
63
|
+
label={t('dev.mcp.type.title')}
|
64
|
+
name={['customParams', 'mcp', 'type']}
|
65
|
+
rules={[{ required: true }]}
|
66
|
+
>
|
67
|
+
<MCPTypeSelect />
|
68
|
+
</Form.Item>
|
31
69
|
<Form.Item
|
32
70
|
extra={t('dev.mcp.identifier.desc')}
|
33
71
|
label={t('dev.mcp.identifier.label')}
|
@@ -38,7 +76,6 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
|
38
76
|
message: t('dev.mcp.identifier.invalid'),
|
39
77
|
pattern: /^[\w-]+$/,
|
40
78
|
},
|
41
|
-
// 编辑模式下,不进行重复校验
|
42
79
|
isEditMode
|
43
80
|
? {}
|
44
81
|
: {
|
@@ -46,7 +83,6 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
|
46
83
|
validator: async () => {
|
47
84
|
const id = form.getFieldValue('identifier');
|
48
85
|
if (!id) return true;
|
49
|
-
|
50
86
|
if (pluginIds.includes(id)) {
|
51
87
|
throw new Error('Duplicate');
|
52
88
|
}
|
@@ -57,21 +93,6 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
|
57
93
|
<Input placeholder={t('dev.mcp.identifier.placeholder')} />
|
58
94
|
</Form.Item>
|
59
95
|
|
60
|
-
<Form.Item
|
61
|
-
extra={t('dev.mcp.type.desc')}
|
62
|
-
initialValue={'http'}
|
63
|
-
label={t('dev.mcp.type.label')}
|
64
|
-
name={['customParams', 'mcp', 'type']}
|
65
|
-
rules={[{ required: true }]}
|
66
|
-
>
|
67
|
-
<Radio.Group>
|
68
|
-
<Radio value={'http'}>Streamable HTTP</Radio>
|
69
|
-
<Radio disabled={!isDesktop} value={'stdio'}>
|
70
|
-
STDIO
|
71
|
-
</Radio>
|
72
|
-
</Radio.Group>
|
73
|
-
</Form.Item>
|
74
|
-
|
75
96
|
{mcpType === 'http' && (
|
76
97
|
<Form.Item
|
77
98
|
extra={
|
@@ -97,14 +118,12 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
|
97
118
|
{
|
98
119
|
validator: async (_, value) => {
|
99
120
|
if (!value) return true;
|
100
|
-
|
101
121
|
try {
|
102
122
|
const data = await mcpService.getStreamableMcpServerManifest(
|
103
123
|
form.getFieldValue('identifier'),
|
104
124
|
value,
|
105
125
|
);
|
106
126
|
setManifest(data);
|
107
|
-
|
108
127
|
form.setFieldsValue({ identifier: data.identifier, manifest: data });
|
109
128
|
} catch (error) {
|
110
129
|
const err = error as PluginInstallError;
|
@@ -136,21 +155,64 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
|
136
155
|
<Form.Item
|
137
156
|
extra={t('dev.mcp.command.desc')}
|
138
157
|
label={t('dev.mcp.command.label')}
|
139
|
-
name={
|
158
|
+
name={STDIO_COMMAND}
|
140
159
|
rules={[{ required: true }]}
|
141
160
|
>
|
142
|
-
<
|
161
|
+
<AutoComplete
|
162
|
+
options={STDIO_COMMAND_OPTIONS.map(({ value, icon: Icon, color }) => ({
|
163
|
+
label: (
|
164
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
165
|
+
{Icon && <Icon color={color} size={16} />}
|
166
|
+
{value}
|
167
|
+
</Flexbox>
|
168
|
+
),
|
169
|
+
value: value,
|
170
|
+
}))}
|
171
|
+
placeholder={t('dev.mcp.command.placeholder')}
|
172
|
+
/>
|
143
173
|
</Form.Item>
|
144
174
|
<Form.Item
|
145
175
|
extra={t('dev.mcp.args.desc')}
|
176
|
+
hasFeedback
|
146
177
|
label={t('dev.mcp.args.label')}
|
147
|
-
name={
|
148
|
-
|
178
|
+
name={STDIO_ARGS}
|
179
|
+
rules={[
|
180
|
+
{ required: true },
|
181
|
+
{
|
182
|
+
validator: async (_, value) => {
|
183
|
+
if (!value) return true;
|
184
|
+
const name = form.getFieldValue('identifier');
|
185
|
+
|
186
|
+
if (!name) throw new Error('Please input mcp server name');
|
187
|
+
try {
|
188
|
+
const data = await mcpService.getStdioMcpServerManifest(
|
189
|
+
name,
|
190
|
+
form.getFieldValue(STDIO_COMMAND),
|
191
|
+
value,
|
192
|
+
);
|
193
|
+
setManifest(data);
|
194
|
+
form.setFieldsValue({ identifier: data.identifier, manifest: data });
|
195
|
+
} catch (error) {
|
196
|
+
const err = error as PluginInstallError;
|
197
|
+
throw t(`error.${err.message}`, { error: err.cause! });
|
198
|
+
}
|
199
|
+
},
|
200
|
+
},
|
201
|
+
]}
|
149
202
|
>
|
150
|
-
<
|
151
|
-
mode="tags"
|
203
|
+
<ArgsInput
|
152
204
|
placeholder={t('dev.mcp.args.placeholder')}
|
153
|
-
|
205
|
+
suffix={
|
206
|
+
<ActionIcon
|
207
|
+
icon={RotateCwIcon}
|
208
|
+
onClick={(e) => {
|
209
|
+
e.stopPropagation();
|
210
|
+
form.validateFields([STDIO_ARGS]);
|
211
|
+
}}
|
212
|
+
size={'small'}
|
213
|
+
title={t('dev.meta.manifest.refresh')}
|
214
|
+
/>
|
215
|
+
}
|
154
216
|
/>
|
155
217
|
</Form.Item>
|
156
218
|
</>
|
@@ -1,61 +1,5 @@
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
2
2
|
|
3
|
-
exports[`MCPClient > Stdio Transport (using SDK Mock Server) > should list tools via stdio 1`] = `
|
4
|
-
[
|
5
|
-
{
|
6
|
-
"description": "Echoes back a message with 'Hello' prefix",
|
7
|
-
"inputSchema": {
|
8
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
9
|
-
"additionalProperties": false,
|
10
|
-
"properties": {
|
11
|
-
"message": {
|
12
|
-
"description": "The message to echo",
|
13
|
-
"type": "string",
|
14
|
-
},
|
15
|
-
},
|
16
|
-
"required": [
|
17
|
-
"message",
|
18
|
-
],
|
19
|
-
"type": "object",
|
20
|
-
},
|
21
|
-
"name": "echo",
|
22
|
-
},
|
23
|
-
{
|
24
|
-
"description": "Lists all available tools and methods",
|
25
|
-
"inputSchema": {
|
26
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
27
|
-
"additionalProperties": false,
|
28
|
-
"properties": {},
|
29
|
-
"type": "object",
|
30
|
-
},
|
31
|
-
"name": "debug",
|
32
|
-
},
|
33
|
-
{
|
34
|
-
"description": "Adds two numbers",
|
35
|
-
"inputSchema": {
|
36
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
37
|
-
"additionalProperties": false,
|
38
|
-
"properties": {
|
39
|
-
"a": {
|
40
|
-
"description": "The first number",
|
41
|
-
"type": "number",
|
42
|
-
},
|
43
|
-
"b": {
|
44
|
-
"description": "The second number",
|
45
|
-
"type": "number",
|
46
|
-
},
|
47
|
-
},
|
48
|
-
"required": [
|
49
|
-
"a",
|
50
|
-
"b",
|
51
|
-
],
|
52
|
-
"type": "object",
|
53
|
-
},
|
54
|
-
"name": "add",
|
55
|
-
},
|
56
|
-
]
|
57
|
-
`;
|
58
|
-
|
59
3
|
exports[`MCPClient > Stdio Transport > should list tools via stdio 1`] = `
|
60
4
|
[
|
61
5
|
{
|
@@ -17,18 +17,54 @@ const electron = {
|
|
17
17
|
statusDisconnected: '未连接',
|
18
18
|
urlRequired: '请输入服务器地址',
|
19
19
|
},
|
20
|
+
sync: {
|
21
|
+
continue: '继续',
|
22
|
+
inCloud: '当前使用云端同步',
|
23
|
+
inLocalStorage: '当前使用本地存储',
|
24
|
+
isIniting: '正在初始化...',
|
25
|
+
lobehubCloud: {
|
26
|
+
description: '官方提供的云版本',
|
27
|
+
title: 'LobeHub Cloud',
|
28
|
+
},
|
29
|
+
local: {
|
30
|
+
description: '使用本地数据库,完全离线可用',
|
31
|
+
title: '本地数据库',
|
32
|
+
},
|
33
|
+
mode: {
|
34
|
+
cloudSync: '云端同步',
|
35
|
+
localStorage: '本地存储',
|
36
|
+
title: '选择你的连接模式',
|
37
|
+
useSelfHosted: '使用自托管实例?',
|
38
|
+
},
|
39
|
+
selfHosted: {
|
40
|
+
description: '自行部署的社区版本',
|
41
|
+
title: '自托管实例',
|
42
|
+
},
|
43
|
+
},
|
20
44
|
updater: {
|
45
|
+
checkingUpdate: '检查新版本',
|
46
|
+
checkingUpdateDesc: '正在获取版本信息...',
|
47
|
+
downloadNewVersion: '下载新版本',
|
21
48
|
downloadingUpdate: '正在下载更新',
|
22
49
|
downloadingUpdateDesc: '更新正在下载中,请稍候...',
|
50
|
+
installLater: '下次启动时更新',
|
51
|
+
isLatestVersion: '当前已是最新版本',
|
52
|
+
isLatestVersionDesc: '非常棒,使用的版本 {{version}} 已是最前沿的版本。',
|
23
53
|
later: '稍后更新',
|
24
54
|
newVersionAvailable: '新版本可用',
|
25
55
|
newVersionAvailableDesc: '发现新版本 {{version}},是否立即下载?',
|
26
|
-
restartAndInstall: '
|
56
|
+
restartAndInstall: '安装更新并重启',
|
27
57
|
updateError: '更新错误',
|
28
58
|
updateReady: '更新已就绪',
|
29
|
-
updateReadyDesc: '
|
59
|
+
updateReadyDesc: '新版本 {{version}} 已下载完成,重启应用后即可完成安装。',
|
30
60
|
upgradeNow: '立即更新',
|
31
61
|
},
|
62
|
+
waitingOAuth: {
|
63
|
+
cancel: '取消',
|
64
|
+
description: '浏览器已打开授权页面,请在浏览器中完成授权',
|
65
|
+
helpText: '如果浏览器没有自动打开,请点击取消后重新尝试',
|
66
|
+
title: '等待授权连接',
|
67
|
+
},
|
32
68
|
};
|
33
69
|
|
34
70
|
export default electron;
|
@@ -47,15 +47,14 @@ export default {
|
|
47
47
|
},
|
48
48
|
mcp: {
|
49
49
|
args: {
|
50
|
-
desc: '传递给 STDIO
|
50
|
+
desc: '传递给 STDIO 命令的参数列表,一般在这里输入 MCP 服务器名称',
|
51
51
|
label: '命令参数',
|
52
|
-
placeholder: '
|
53
|
-
tooltip: '输入参数后按回车或使用逗号/空格分隔',
|
52
|
+
placeholder: '例如:mcp-hello-world',
|
54
53
|
},
|
55
54
|
command: {
|
56
|
-
desc: '用于启动 MCP STDIO
|
55
|
+
desc: '用于启动 MCP STDIO Server 的可执行文件或脚本',
|
57
56
|
label: '命令',
|
58
|
-
placeholder: '例如:
|
57
|
+
placeholder: '例如:npx / uv / docker 等',
|
59
58
|
},
|
60
59
|
endpoint: {
|
61
60
|
desc: '输入你的 MCP Streamable HTTP Server 的地址',
|
@@ -69,11 +68,19 @@ export default {
|
|
69
68
|
},
|
70
69
|
type: {
|
71
70
|
desc: '选择 MCP 插件的通信方式,网页版只支持 Streamable HTTP',
|
71
|
+
httpFeature1: '兼容网页版与桌面端',
|
72
|
+
httpFeature2: '连接远程 MCP 服务端, 无需额外安装配置',
|
73
|
+
httpShortDesc: '基于流式 HTTP 的通信协议',
|
72
74
|
label: 'MCP 插件类型',
|
75
|
+
stdioFeature1: '更低的通信延迟, 适合本地执行',
|
76
|
+
stdioFeature2: '通过系统标准输入输出通信,需在本地安装运行 MCP 服务端',
|
77
|
+
stdioNotAvailable: 'STDIO 模式仅在桌面版可用',
|
78
|
+
stdioShortDesc: '基于标准输入输出的通信协议',
|
79
|
+
title: 'MCP 插件类型',
|
73
80
|
},
|
74
81
|
url: {
|
75
|
-
desc: '输入你的 MCP HTTP
|
76
|
-
label: 'HTTP Endpoint URL',
|
82
|
+
desc: '输入你的 MCP Server Streamable HTTP 地址,不会以 /sse 结尾',
|
83
|
+
label: 'Streamable HTTP Endpoint URL',
|
77
84
|
},
|
78
85
|
},
|
79
86
|
meta: {
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import { ElectronIpcClient } from '@lobechat/electron-server-ipc';
|
2
|
+
|
3
|
+
class LobeHubElectronIpcClient extends ElectronIpcClient {
|
4
|
+
// 获取数据库路径
|
5
|
+
getDatabasePath = async (): Promise<string> => {
|
6
|
+
return this.sendRequest<string>('getDatabasePath');
|
7
|
+
};
|
8
|
+
|
9
|
+
// 获取用户数据路径
|
10
|
+
getUserDataPath = async (): Promise<string> => {
|
11
|
+
return this.sendRequest<string>('getUserDataPath');
|
12
|
+
};
|
13
|
+
|
14
|
+
getDatabaseSchemaHash = async () => {
|
15
|
+
return this.sendRequest<string>('setDatabaseSchemaHash');
|
16
|
+
};
|
17
|
+
|
18
|
+
setDatabaseSchemaHash = async (hash: string | undefined) => {
|
19
|
+
if (!hash) return;
|
20
|
+
|
21
|
+
return this.sendRequest('setDatabaseSchemaHash', hash);
|
22
|
+
};
|
23
|
+
|
24
|
+
getFilePathById = async (id: string) => {
|
25
|
+
return this.sendRequest<string>('getStaticFilePath', id);
|
26
|
+
};
|
27
|
+
|
28
|
+
deleteFiles = async (paths: string[]) => {
|
29
|
+
return this.sendRequest<{ errors?: { message: string; path: string }[]; success: boolean }>(
|
30
|
+
'deleteFiles',
|
31
|
+
paths,
|
32
|
+
);
|
33
|
+
};
|
34
|
+
}
|
35
|
+
|
36
|
+
export const electronIpcClient = new LobeHubElectronIpcClient();
|
@@ -97,14 +97,10 @@ export const sessionRouter = router({
|
|
97
97
|
}),
|
98
98
|
|
99
99
|
getGroupedSessions: publicProcedure.query(async ({ ctx }): Promise<ChatSessionList> => {
|
100
|
-
if (!ctx.userId)
|
101
|
-
return {
|
102
|
-
sessionGroups: [],
|
103
|
-
sessions: [],
|
104
|
-
};
|
100
|
+
if (!ctx.userId) return { sessionGroups: [], sessions: [] };
|
105
101
|
|
106
102
|
const serverDB = await getServerDB();
|
107
|
-
const sessionModel = new SessionModel(serverDB, ctx.userId);
|
103
|
+
const sessionModel = new SessionModel(serverDB, ctx.userId!);
|
108
104
|
|
109
105
|
return sessionModel.queryWithGroups();
|
110
106
|
}),
|
@@ -35,6 +35,12 @@ 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
|
+
}),
|
38
44
|
getStreamableMcpServerManifest: mcpProcedure
|
39
45
|
.input(
|
40
46
|
z.object({
|
@@ -1,11 +1,19 @@
|
|
1
|
+
import { isDesktop } from '@/const/version';
|
2
|
+
|
3
|
+
import { DesktopLocalFileImpl } from './local';
|
1
4
|
import { S3StaticFileImpl } from './s3';
|
2
5
|
import { FileServiceImpl } from './type';
|
3
6
|
|
4
7
|
/**
|
5
8
|
* 创建文件服务模块
|
9
|
+
* 根据环境自动选择使用S3或桌面本地文件实现
|
6
10
|
*/
|
7
11
|
export const createFileServiceModule = (): FileServiceImpl => {
|
8
|
-
//
|
12
|
+
// 如果在桌面应用环境,使用本地文件实现
|
13
|
+
if (isDesktop) {
|
14
|
+
return new DesktopLocalFileImpl();
|
15
|
+
}
|
16
|
+
|
9
17
|
return new S3StaticFileImpl();
|
10
18
|
};
|
11
19
|
|