@lobehub/chat 1.109.0 → 1.110.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/.cursor/rules/i18n.mdc +1 -2
- package/CHANGELOG.md +59 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/apps/desktop/electron-builder.js +22 -0
- package/apps/desktop/src/main/controllers/McpInstallCtr.ts +153 -0
- package/apps/desktop/src/main/controllers/index.ts +19 -0
- package/apps/desktop/src/main/core/App.ts +46 -0
- package/apps/desktop/src/main/core/infrastructure/IoCContainer.ts +4 -0
- package/apps/desktop/src/main/core/infrastructure/ProtocolManager.ts +256 -0
- package/apps/desktop/src/main/types/protocol.ts +60 -0
- package/apps/desktop/src/main/utils/__tests__/protocol.test.ts +203 -0
- package/apps/desktop/src/main/utils/protocol.ts +210 -0
- package/changelog/v1.json +21 -0
- package/locales/ar/models.json +6 -0
- package/locales/ar/plugin.json +196 -136
- package/locales/ar/providers.json +3 -0
- package/locales/bg-BG/models.json +6 -0
- package/locales/bg-BG/plugin.json +204 -144
- package/locales/bg-BG/providers.json +3 -0
- package/locales/de-DE/models.json +6 -0
- package/locales/de-DE/plugin.json +176 -116
- package/locales/de-DE/providers.json +3 -0
- package/locales/en-US/models.json +6 -0
- package/locales/en-US/plugin.json +192 -132
- package/locales/en-US/providers.json +3 -0
- package/locales/es-ES/models.json +6 -0
- package/locales/es-ES/plugin.json +203 -143
- package/locales/es-ES/providers.json +3 -0
- package/locales/fa-IR/models.json +6 -0
- package/locales/fa-IR/plugin.json +155 -95
- package/locales/fa-IR/providers.json +3 -0
- package/locales/fr-FR/models.json +6 -0
- package/locales/fr-FR/plugin.json +161 -101
- package/locales/fr-FR/providers.json +3 -0
- package/locales/it-IT/models.json +6 -0
- package/locales/it-IT/plugin.json +193 -133
- package/locales/it-IT/providers.json +3 -0
- package/locales/ja-JP/models.json +6 -0
- package/locales/ja-JP/plugin.json +195 -135
- package/locales/ja-JP/providers.json +3 -0
- package/locales/ko-KR/models.json +6 -0
- package/locales/ko-KR/plugin.json +163 -103
- package/locales/ko-KR/providers.json +3 -0
- package/locales/nl-NL/models.json +6 -0
- package/locales/nl-NL/plugin.json +211 -151
- package/locales/nl-NL/providers.json +3 -0
- package/locales/pl-PL/models.json +6 -0
- package/locales/pl-PL/plugin.json +171 -111
- package/locales/pl-PL/providers.json +3 -0
- package/locales/pt-BR/models.json +6 -0
- package/locales/pt-BR/plugin.json +180 -120
- package/locales/pt-BR/providers.json +3 -0
- package/locales/ru-RU/models.json +6 -0
- package/locales/ru-RU/plugin.json +191 -131
- package/locales/ru-RU/providers.json +3 -0
- package/locales/tr-TR/models.json +6 -0
- package/locales/tr-TR/plugin.json +187 -127
- package/locales/tr-TR/providers.json +3 -0
- package/locales/vi-VN/models.json +6 -0
- package/locales/vi-VN/plugin.json +152 -92
- package/locales/vi-VN/providers.json +3 -0
- package/locales/zh-CN/models.json +6 -0
- package/locales/zh-CN/plugin.json +60 -0
- package/locales/zh-CN/providers.json +3 -0
- package/locales/zh-TW/models.json +6 -0
- package/locales/zh-TW/plugin.json +157 -97
- package/locales/zh-TW/providers.json +3 -0
- package/package.json +2 -1
- package/packages/electron-client-ipc/src/events/index.ts +5 -2
- package/packages/electron-client-ipc/src/events/protocol.ts +29 -0
- package/packages/electron-client-ipc/src/types/index.ts +1 -0
- package/packages/electron-client-ipc/src/types/mcpInstall.ts +19 -0
- package/packages/types/src/plugins/mcp.ts +38 -1
- package/packages/types/src/plugins/protocol.ts +166 -0
- package/src/app/[variants]/(main)/chat/_layout/Desktop/index.tsx +4 -1
- package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/ActionButton/index.tsx +1 -2
- package/src/components/KeyValueEditor/index.tsx +4 -2
- package/src/config/aiModels/aihubmix.ts +465 -30
- package/src/config/aiModels/anthropic.ts +27 -1
- package/src/config/aiModels/groq.ts +40 -4
- package/src/config/aiModels/qwen.ts +24 -2
- package/src/features/MCP/MCPInstallProgress/index.tsx +1 -1
- package/src/features/PluginDevModal/MCPManifestForm/index.tsx +30 -36
- package/src/features/PluginStore/McpList/List/Item.tsx +1 -1
- package/src/features/ProtocolUrlHandler/InstallPlugin/ConfigDisplay.tsx +211 -0
- package/src/features/ProtocolUrlHandler/InstallPlugin/CustomPluginInstallModal.tsx +228 -0
- package/src/features/ProtocolUrlHandler/InstallPlugin/OfficialPluginInstallModal/Detail.tsx +44 -0
- package/src/features/ProtocolUrlHandler/InstallPlugin/OfficialPluginInstallModal/index.tsx +105 -0
- package/src/features/ProtocolUrlHandler/InstallPlugin/index.tsx +55 -0
- package/src/features/ProtocolUrlHandler/InstallPlugin/types.ts +45 -0
- package/src/features/ProtocolUrlHandler/index.tsx +30 -0
- package/src/libs/model-runtime/anthropic/index.ts +15 -2
- package/src/libs/model-runtime/utils/modelParse.ts +2 -2
- package/src/libs/model-runtime/utils/streams/ollama.test.ts +97 -51
- package/src/libs/model-runtime/utils/streams/ollama.ts +4 -0
- package/src/locales/default/plugin.ts +60 -0
- package/src/store/tool/slices/mcpStore/action.ts +127 -1
- package/src/store/tool/slices/mcpStore/initialState.ts +8 -13
- package/src/store/tool/slices/mcpStore/selectors.ts +13 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { memo, useState } from 'react';
|
4
|
+
import { Flexbox } from 'react-layout-kit';
|
5
|
+
|
6
|
+
import MCPInstallProgress from '@/features/MCP/MCPInstallProgress';
|
7
|
+
import Deployment from '@/features/MCPPluginDetail/Deployment';
|
8
|
+
import { DetailContextConfig, DetailProvider } from '@/features/MCPPluginDetail/DetailProvider';
|
9
|
+
import Header from '@/features/MCPPluginDetail/Header';
|
10
|
+
import Nav from '@/features/MCPPluginDetail/Nav';
|
11
|
+
import Overview from '@/features/MCPPluginDetail/Overview';
|
12
|
+
import Schema from '@/features/MCPPluginDetail/Schema';
|
13
|
+
import Score from '@/features/MCPPluginDetail/Score';
|
14
|
+
import { McpNavKey } from '@/types/discover';
|
15
|
+
|
16
|
+
interface OfficialDetailProps {
|
17
|
+
data: DetailContextConfig;
|
18
|
+
identifier: string;
|
19
|
+
}
|
20
|
+
|
21
|
+
const OfficialDetail = memo<OfficialDetailProps>(({ data, identifier }) => {
|
22
|
+
const [activeTab, setActiveTab] = useState(McpNavKey.Overview);
|
23
|
+
|
24
|
+
return (
|
25
|
+
<DetailProvider config={data}>
|
26
|
+
<Flexbox gap={16}>
|
27
|
+
<Header inModal />
|
28
|
+
<MCPInstallProgress identifier={identifier} />
|
29
|
+
|
30
|
+
<Nav activeTab={activeTab as McpNavKey} inModal noSettings setActiveTab={setActiveTab} />
|
31
|
+
<Flexbox gap={24}>
|
32
|
+
{activeTab === McpNavKey.Overview && <Overview inModal />}
|
33
|
+
{activeTab === McpNavKey.Deployment && <Deployment />}
|
34
|
+
{activeTab === McpNavKey.Schema && <Schema />}
|
35
|
+
{activeTab === McpNavKey.Score && <Score />}
|
36
|
+
</Flexbox>
|
37
|
+
</Flexbox>
|
38
|
+
</DetailProvider>
|
39
|
+
);
|
40
|
+
});
|
41
|
+
|
42
|
+
OfficialDetail.displayName = 'OfficialDetail';
|
43
|
+
|
44
|
+
export default OfficialDetail;
|
@@ -0,0 +1,105 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { Block, Modal, Text } from '@lobehub/ui';
|
4
|
+
import { App } from 'antd';
|
5
|
+
import { memo, useCallback, useState } from 'react';
|
6
|
+
import { useTranslation } from 'react-i18next';
|
7
|
+
|
8
|
+
import DetailLoading from '@/features/PluginStore/McpList/Detail/Loading';
|
9
|
+
import { useAgentStore } from '@/store/agent';
|
10
|
+
import { useDiscoverStore } from '@/store/discover';
|
11
|
+
import { useToolStore } from '@/store/tool';
|
12
|
+
import { pluginSelectors } from '@/store/tool/slices/plugin/selectors';
|
13
|
+
|
14
|
+
import { McpInstallRequest } from '../types';
|
15
|
+
import OfficialDetail from './Detail';
|
16
|
+
|
17
|
+
interface OfficialPluginInstallModalProps {
|
18
|
+
installRequest: McpInstallRequest | null;
|
19
|
+
onComplete: () => void;
|
20
|
+
}
|
21
|
+
|
22
|
+
const OfficialPluginInstallModal = memo<OfficialPluginInstallModalProps>(
|
23
|
+
({ installRequest, onComplete }) => {
|
24
|
+
const { message } = App.useApp();
|
25
|
+
const { t } = useTranslation(['plugin', 'common']);
|
26
|
+
const [loading, setLoading] = useState(false);
|
27
|
+
|
28
|
+
// 获取 MCP 插件详情
|
29
|
+
const useMcpDetail = useDiscoverStore((s) => s.useFetchMcpDetail);
|
30
|
+
const identifier = installRequest?.pluginId || '';
|
31
|
+
|
32
|
+
const [installed, installMCPPlugin] = useToolStore((s) => [
|
33
|
+
pluginSelectors.isPluginInstalled(identifier!)(s),
|
34
|
+
|
35
|
+
s.installMCPPlugin,
|
36
|
+
]);
|
37
|
+
const togglePlugin = useAgentStore((s) => s.togglePlugin);
|
38
|
+
|
39
|
+
const { data, isLoading } = useMcpDetail({ identifier });
|
40
|
+
|
41
|
+
const handleConfirm = useCallback(async () => {
|
42
|
+
if (!installRequest || !data) return;
|
43
|
+
|
44
|
+
setLoading(true);
|
45
|
+
try {
|
46
|
+
setLoading(true);
|
47
|
+
await installMCPPlugin(identifier);
|
48
|
+
await togglePlugin(identifier);
|
49
|
+
setLoading(false);
|
50
|
+
|
51
|
+
message.success(t('protocolInstall.messages.installSuccess', { name: data.name }));
|
52
|
+
onComplete();
|
53
|
+
} catch (error) {
|
54
|
+
console.error('Official plugin installation error:', error);
|
55
|
+
message.error(t('protocolInstall.messages.installError'));
|
56
|
+
setLoading(false);
|
57
|
+
}
|
58
|
+
}, [installRequest, data]);
|
59
|
+
|
60
|
+
if (!installRequest) return null;
|
61
|
+
|
62
|
+
// 渲染内容
|
63
|
+
const renderContent = () => {
|
64
|
+
// 如果正在加载,显示骨架屏
|
65
|
+
if (isLoading || !identifier) {
|
66
|
+
return <DetailLoading />;
|
67
|
+
}
|
68
|
+
|
69
|
+
// 如果加载失败或没有数据,显示错误信息
|
70
|
+
if (!data) {
|
71
|
+
return (
|
72
|
+
<Block>
|
73
|
+
<Text type="danger">{t('protocolInstall.messages.manifestError')}</Text>
|
74
|
+
</Block>
|
75
|
+
);
|
76
|
+
}
|
77
|
+
|
78
|
+
return <OfficialDetail data={data} identifier={identifier} />;
|
79
|
+
};
|
80
|
+
|
81
|
+
return (
|
82
|
+
<Modal
|
83
|
+
confirmLoading={loading}
|
84
|
+
okButtonProps={{
|
85
|
+
disabled: installed || isLoading,
|
86
|
+
type: installed ? 'default' : 'primary',
|
87
|
+
}}
|
88
|
+
okText={
|
89
|
+
installed ? t('protocolInstall.actions.installed') : t('protocolInstall.actions.install')
|
90
|
+
}
|
91
|
+
onCancel={onComplete}
|
92
|
+
onOk={handleConfirm}
|
93
|
+
open
|
94
|
+
title={t('protocolInstall.official.title')}
|
95
|
+
width={800}
|
96
|
+
>
|
97
|
+
{renderContent()}
|
98
|
+
</Modal>
|
99
|
+
);
|
100
|
+
},
|
101
|
+
);
|
102
|
+
|
103
|
+
OfficialPluginInstallModal.displayName = 'OfficialPluginInstallModal';
|
104
|
+
|
105
|
+
export default OfficialPluginInstallModal;
|
@@ -0,0 +1,55 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { memo } from 'react';
|
4
|
+
|
5
|
+
import CustomPluginInstallModal from './CustomPluginInstallModal';
|
6
|
+
import OfficialPluginInstallModal from './OfficialPluginInstallModal';
|
7
|
+
import { McpInstallRequest, PluginSource } from './types';
|
8
|
+
|
9
|
+
interface PluginInstallConfirmModalProps {
|
10
|
+
installRequest: McpInstallRequest | null;
|
11
|
+
onComplete: () => void;
|
12
|
+
}
|
13
|
+
|
14
|
+
/**
|
15
|
+
* 根据安装请求的来源确定插件类型
|
16
|
+
*/
|
17
|
+
const getPluginSource = (request: McpInstallRequest): PluginSource => {
|
18
|
+
const { marketId } = request;
|
19
|
+
|
20
|
+
// 官方 LobeHub 插件
|
21
|
+
if (marketId === 'lobehub') {
|
22
|
+
return PluginSource.OFFICIAL;
|
23
|
+
}
|
24
|
+
|
25
|
+
// 第三方市场插件(包括可信和不可信的)
|
26
|
+
if (marketId && marketId !== 'lobehub') {
|
27
|
+
return PluginSource.MARKETPLACE;
|
28
|
+
}
|
29
|
+
|
30
|
+
// 自定义插件(没有 marketId)
|
31
|
+
return PluginSource.CUSTOM;
|
32
|
+
};
|
33
|
+
|
34
|
+
const PluginInstallConfirmModal = memo<PluginInstallConfirmModalProps>(
|
35
|
+
({ installRequest, onComplete }) => {
|
36
|
+
if (!installRequest) return null;
|
37
|
+
|
38
|
+
const pluginSource = getPluginSource(installRequest);
|
39
|
+
|
40
|
+
if (pluginSource === PluginSource.OFFICIAL)
|
41
|
+
return <OfficialPluginInstallModal installRequest={installRequest} onComplete={onComplete} />;
|
42
|
+
|
43
|
+
return (
|
44
|
+
<CustomPluginInstallModal
|
45
|
+
installRequest={installRequest}
|
46
|
+
isMarketplace={pluginSource === PluginSource.MARKETPLACE}
|
47
|
+
onComplete={onComplete}
|
48
|
+
/>
|
49
|
+
);
|
50
|
+
},
|
51
|
+
);
|
52
|
+
|
53
|
+
PluginInstallConfirmModal.displayName = 'PluginInstallConfirmModal';
|
54
|
+
|
55
|
+
export default PluginInstallConfirmModal;
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import { McpInstallSchema } from '@lobechat/electron-client-ipc';
|
2
|
+
|
3
|
+
export enum PluginSource {
|
4
|
+
CUSTOM = 'custom',
|
5
|
+
MARKETPLACE = 'marketplace',
|
6
|
+
OFFICIAL = 'official',
|
7
|
+
}
|
8
|
+
|
9
|
+
export interface McpInstallRequest {
|
10
|
+
marketId?: string;
|
11
|
+
pluginId: string;
|
12
|
+
schema?: McpInstallSchema;
|
13
|
+
source: string;
|
14
|
+
}
|
15
|
+
|
16
|
+
export interface BaseContentProps {
|
17
|
+
installRequest: McpInstallRequest;
|
18
|
+
}
|
19
|
+
|
20
|
+
export interface ModalConfig {
|
21
|
+
okText: string;
|
22
|
+
title: string;
|
23
|
+
width?: number;
|
24
|
+
}
|
25
|
+
|
26
|
+
// 可信的第三方市场列表
|
27
|
+
export const TRUSTED_MARKETPLACES = {
|
28
|
+
higress: {
|
29
|
+
description: 'Enterprise-grade MCP plugins for cloud-native applications',
|
30
|
+
name: 'Higress Marketplace',
|
31
|
+
website: 'https://higress.ai',
|
32
|
+
},
|
33
|
+
mcprouter: {
|
34
|
+
description: 'Community-driven MCP plugin marketplace',
|
35
|
+
name: 'MCPRouter',
|
36
|
+
website: 'https://mcprouter.com',
|
37
|
+
},
|
38
|
+
smithery: {
|
39
|
+
description: 'Professional MCP plugins and tools',
|
40
|
+
name: 'Smithery',
|
41
|
+
website: 'https://smithery.ai',
|
42
|
+
},
|
43
|
+
} as const;
|
44
|
+
|
45
|
+
export type TrustedMarketplaceId = keyof typeof TRUSTED_MARKETPLACES;
|
@@ -0,0 +1,30 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useWatchBroadcast } from '@lobechat/electron-client-ipc';
|
4
|
+
import { useCallback, useState } from 'react';
|
5
|
+
|
6
|
+
import { McpInstallRequest } from '@/features/ProtocolUrlHandler/InstallPlugin/types';
|
7
|
+
|
8
|
+
import PluginInstallConfirmModal from './InstallPlugin';
|
9
|
+
|
10
|
+
const ProtocolUrlHandler = () => {
|
11
|
+
const [installRequest, setInstallRequest] = useState<McpInstallRequest | null>(null);
|
12
|
+
|
13
|
+
const handleMcpInstallRequest = useCallback(
|
14
|
+
(data: { marketId?: string; pluginId: string; schema: any }) => {
|
15
|
+
// 将原始数据传递给子组件处理
|
16
|
+
setInstallRequest(data as McpInstallRequest);
|
17
|
+
},
|
18
|
+
[],
|
19
|
+
);
|
20
|
+
|
21
|
+
const handleComplete = useCallback(() => {
|
22
|
+
setInstallRequest(null);
|
23
|
+
}, []);
|
24
|
+
|
25
|
+
useWatchBroadcast('mcpInstallRequest', handleMcpInstallRequest);
|
26
|
+
|
27
|
+
return <PluginInstallConfirmModal installRequest={installRequest} onComplete={handleComplete} />;
|
28
|
+
};
|
29
|
+
|
30
|
+
export default ProtocolUrlHandler;
|
@@ -27,6 +27,9 @@ type anthropicTools = Anthropic.Tool | Anthropic.WebSearchTool20250305;
|
|
27
27
|
|
28
28
|
const modelsWithSmallContextWindow = new Set(['claude-3-opus-20240229', 'claude-3-haiku-20240307']);
|
29
29
|
|
30
|
+
// Opus 4.1 models that don't allow both temperature and top_p parameters
|
31
|
+
const opus41Models = new Set(['claude-opus-4-1', 'claude-opus-4-1-20250805']);
|
32
|
+
|
30
33
|
const DEFAULT_BASE_URL = 'https://api.anthropic.com';
|
31
34
|
|
32
35
|
interface AnthropicAIParams extends ClientOptions {
|
@@ -189,6 +192,10 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
|
|
189
192
|
} satisfies Anthropic.MessageCreateParams;
|
190
193
|
}
|
191
194
|
|
195
|
+
// For Opus 4.1 models, we can only set either temperature OR top_p, not both
|
196
|
+
const isOpus41Model = opus41Models.has(model);
|
197
|
+
const shouldSetTemperature = payload.temperature !== undefined;
|
198
|
+
|
192
199
|
return {
|
193
200
|
// claude 3 series model hax max output token of 4096, 3.x series has 8192
|
194
201
|
// https://docs.anthropic.com/en/docs/about-claude/models/all-models#:~:text=200K-,Max%20output,-Normal%3A
|
@@ -196,9 +203,15 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
|
|
196
203
|
messages: postMessages,
|
197
204
|
model,
|
198
205
|
system: systemPrompts,
|
199
|
-
|
206
|
+
// For Opus 4.1 models: prefer temperature over top_p if both are provided
|
207
|
+
temperature: isOpus41Model
|
208
|
+
? (shouldSetTemperature ? temperature / 2 : undefined)
|
209
|
+
: (payload.temperature !== undefined ? temperature / 2 : undefined),
|
200
210
|
tools: postTools,
|
201
|
-
top_p
|
211
|
+
// For Opus 4.1 models: only set top_p if temperature is not set
|
212
|
+
top_p: isOpus41Model
|
213
|
+
? (shouldSetTemperature ? undefined : top_p)
|
214
|
+
: top_p,
|
202
215
|
} satisfies Anthropic.MessageCreateParams;
|
203
216
|
}
|
204
217
|
|
@@ -30,8 +30,8 @@ export const MODEL_LIST_CONFIGS = {
|
|
30
30
|
},
|
31
31
|
openai: {
|
32
32
|
excludeKeywords: ['audio'],
|
33
|
-
functionCallKeywords: ['4o', '4.1', 'o3', 'o4'],
|
34
|
-
reasoningKeywords: ['o1', 'o3', 'o4'],
|
33
|
+
functionCallKeywords: ['4o', '4.1', 'o3', 'o4', 'oss'],
|
34
|
+
reasoningKeywords: ['o1', 'o3', 'o4', 'oss'],
|
35
35
|
visionKeywords: ['4o', '4.1', 'o4'],
|
36
36
|
},
|
37
37
|
qwen: {
|
@@ -7,63 +7,109 @@ import { OllamaStream } from './ollama';
|
|
7
7
|
|
8
8
|
describe('OllamaStream', () => {
|
9
9
|
describe('should transform Ollama stream to protocol stream', () => {
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
const messages = [
|
14
|
-
'<think>',
|
15
|
-
'这是一个思考过程',
|
16
|
-
',需要仔细分析问题。',
|
17
|
-
'</think>',
|
18
|
-
'根据分析,我的答案是:',
|
19
|
-
'这是最终答案。',
|
20
|
-
];
|
10
|
+
describe('reasoning', () => {
|
11
|
+
it('reasoning with thinking tag', async () => {
|
12
|
+
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('2');
|
21
13
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
14
|
+
const messages = [
|
15
|
+
'<think>',
|
16
|
+
'这是一个思考过程',
|
17
|
+
',需要仔细分析问题。',
|
18
|
+
'</think>',
|
19
|
+
'根据分析,我的答案是:',
|
20
|
+
'这是最终答案。',
|
21
|
+
];
|
22
|
+
|
23
|
+
const mockOllamaStream = new ReadableStream<ChatResponse>({
|
24
|
+
start(controller) {
|
25
|
+
messages.forEach((content) => {
|
26
|
+
controller.enqueue({ message: { content }, done: false } as ChatResponse);
|
27
|
+
});
|
28
|
+
controller.enqueue({ message: { content: '' }, done: true } as ChatResponse);
|
29
|
+
controller.close();
|
30
|
+
},
|
31
|
+
});
|
32
|
+
|
33
|
+
const protocolStream = OllamaStream(mockOllamaStream);
|
34
|
+
|
35
|
+
const decoder = new TextDecoder();
|
36
|
+
const chunks = [];
|
37
|
+
|
38
|
+
// @ts-ignore
|
39
|
+
for await (const chunk of protocolStream) {
|
40
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
41
|
+
}
|
42
|
+
|
43
|
+
expect(chunks).toEqual(
|
44
|
+
[
|
45
|
+
'id: chat_2',
|
46
|
+
'event: reasoning',
|
47
|
+
`data: ""\n`,
|
48
|
+
'id: chat_2',
|
49
|
+
'event: reasoning',
|
50
|
+
`data: "这是一个思考过程"\n`,
|
51
|
+
'id: chat_2',
|
52
|
+
'event: reasoning',
|
53
|
+
`data: ",需要仔细分析问题。"\n`,
|
54
|
+
'id: chat_2',
|
55
|
+
'event: text',
|
56
|
+
`data: ""\n`,
|
57
|
+
'id: chat_2',
|
58
|
+
'event: text',
|
59
|
+
`data: "根据分析,我的答案是:"\n`,
|
60
|
+
'id: chat_2',
|
61
|
+
'event: text',
|
62
|
+
`data: "这是最终答案。"\n`,
|
63
|
+
'id: chat_2',
|
64
|
+
'event: stop',
|
65
|
+
`data: "finished"\n`,
|
66
|
+
].map((line) => `${line}\n`),
|
67
|
+
);
|
30
68
|
});
|
31
69
|
|
32
|
-
|
70
|
+
it('thinking field', async () => {
|
71
|
+
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
|
33
72
|
|
34
|
-
|
35
|
-
|
73
|
+
const mockOllamaStream = new ReadableStream<ChatResponse>({
|
74
|
+
start(controller) {
|
75
|
+
controller.enqueue({ message: { thinking: 'Hello' }, done: false } as ChatResponse);
|
76
|
+
controller.enqueue({ message: { thinking: ' world!' }, done: false } as ChatResponse);
|
77
|
+
controller.enqueue({ message: { thinking: '' }, done: true } as ChatResponse);
|
36
78
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
}
|
79
|
+
controller.close();
|
80
|
+
},
|
81
|
+
});
|
41
82
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
'
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
83
|
+
const onStartMock = vi.fn();
|
84
|
+
const onTextMock = vi.fn();
|
85
|
+
const onCompletionMock = vi.fn();
|
86
|
+
|
87
|
+
const protocolStream = OllamaStream(mockOllamaStream, {
|
88
|
+
onStart: onStartMock,
|
89
|
+
onText: onTextMock,
|
90
|
+
onCompletion: onCompletionMock,
|
91
|
+
});
|
92
|
+
|
93
|
+
const decoder = new TextDecoder();
|
94
|
+
const chunks = [];
|
95
|
+
|
96
|
+
// @ts-ignore
|
97
|
+
for await (const chunk of protocolStream) {
|
98
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
99
|
+
}
|
100
|
+
|
101
|
+
expect(chunks).toEqual([
|
102
|
+
'id: chat_1\n',
|
103
|
+
'event: reasoning\n',
|
104
|
+
`data: "Hello"\n\n`,
|
105
|
+
'id: chat_1\n',
|
106
|
+
'event: reasoning\n',
|
107
|
+
`data: " world!"\n\n`,
|
108
|
+
'id: chat_1\n',
|
109
|
+
'event: stop\n',
|
110
|
+
`data: "finished"\n\n`,
|
111
|
+
]);
|
112
|
+
});
|
67
113
|
});
|
68
114
|
|
69
115
|
it('text', async () => {
|
@@ -17,6 +17,10 @@ const transformOllamaStream = (chunk: ChatResponse, stack: StreamContext): Strea
|
|
17
17
|
return { data: 'finished', id: stack.id, type: 'stop' };
|
18
18
|
}
|
19
19
|
|
20
|
+
if (chunk.message.thinking) {
|
21
|
+
return { data: chunk.message.thinking, id: stack.id, type: 'reasoning' };
|
22
|
+
}
|
23
|
+
|
20
24
|
if (chunk.message.tool_calls && chunk.message.tool_calls.length > 0) {
|
21
25
|
return {
|
22
26
|
data: chunk.message.tool_calls.map((value, index) => ({
|
@@ -308,6 +308,66 @@ export default {
|
|
308
308
|
skipDependencies: '跳过检查',
|
309
309
|
},
|
310
310
|
pluginList: '插件列表',
|
311
|
+
protocolInstall: {
|
312
|
+
actions: {
|
313
|
+
install: '安装',
|
314
|
+
installAnyway: '仍要安装',
|
315
|
+
installed: '已安装',
|
316
|
+
},
|
317
|
+
config: {
|
318
|
+
args: '参数',
|
319
|
+
command: '命令',
|
320
|
+
env: '环境变量',
|
321
|
+
headers: '请求头',
|
322
|
+
title: '配置信息',
|
323
|
+
type: {
|
324
|
+
http: '类型: HTTP',
|
325
|
+
label: '类型',
|
326
|
+
stdio: '类型: Stdio',
|
327
|
+
},
|
328
|
+
url: '服务地址',
|
329
|
+
},
|
330
|
+
custom: {
|
331
|
+
badge: '自定义插件',
|
332
|
+
security: {
|
333
|
+
description: '此插件未经过官方验证,安装可能存在安全风险!请确保您信任插件来源。',
|
334
|
+
title: '⚠️ 安全风险提示',
|
335
|
+
},
|
336
|
+
title: '安装自定义插件',
|
337
|
+
},
|
338
|
+
marketplace: {
|
339
|
+
title: '安装第三方插件',
|
340
|
+
trustedBy: '由 {{name}} 提供',
|
341
|
+
unverified: {
|
342
|
+
title: '未经验证的第三方插件',
|
343
|
+
warning: '此插件来自未验证的第三方市场,安装前请确认您信任该来源。',
|
344
|
+
},
|
345
|
+
verified: '已验证',
|
346
|
+
},
|
347
|
+
messages: {
|
348
|
+
connectionTestFailed: '连接测试失败',
|
349
|
+
installError: '插件安装失败,请重试',
|
350
|
+
installSuccess: '插件 {{name}} 安装成功!',
|
351
|
+
manifestError: '获取插件详情失败,请检查网络连接后重试',
|
352
|
+
manifestNotFound: '未能获取插件描述文件',
|
353
|
+
},
|
354
|
+
meta: {
|
355
|
+
author: '作者',
|
356
|
+
homepage: '主页',
|
357
|
+
identifier: '标识符',
|
358
|
+
source: '来源',
|
359
|
+
version: '版本',
|
360
|
+
},
|
361
|
+
official: {
|
362
|
+
badge: 'LobeHub 官方插件',
|
363
|
+
description: '此插件由 LobeHub 官方开发和维护,经过严格的安全审核,可放心使用。',
|
364
|
+
loadingMessage: '正在获取插件详情...',
|
365
|
+
loadingTitle: '加载中',
|
366
|
+
title: '安装官方插件',
|
367
|
+
},
|
368
|
+
title: '安装 MCP 插件',
|
369
|
+
warning: '⚠️ 请确认您信任此插件的来源,恶意插件可能会危害您的系统安全。',
|
370
|
+
},
|
311
371
|
search: {
|
312
372
|
apiName: {
|
313
373
|
crawlMultiPages: '读取多个页面内容',
|