@lobehub/lobehub 2.1.3 → 2.1.5
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.example +0 -3
- package/.env.example.development +0 -3
- package/CHANGELOG.md +59 -0
- package/changelog/v2.json +18 -0
- package/docker-compose/deploy/.env.example +3 -1
- package/docker-compose/deploy/.env.zh-CN.example +4 -1
- package/docker-compose/local/.env.example +0 -1
- package/docker-compose/local/.env.zh-CN.example +0 -1
- package/docker-compose/local/grafana/.env.example +0 -1
- package/docker-compose/local/grafana/.env.zh-CN.example +0 -1
- package/docker-compose/local/logto/docker-compose.yml +0 -1
- package/docker-compose/local/zitadel/.env.example +1 -2
- package/docker-compose/local/zitadel/.env.zh-CN.example +1 -2
- package/docker-compose/production/grafana/.env.example +0 -1
- package/docker-compose/production/grafana/.env.zh-CN.example +0 -1
- package/docker-compose/production/logto/.env.example +0 -2
- package/docker-compose/production/logto/.env.zh-CN.example +0 -2
- package/docker-compose/production/zitadel/.env.example +0 -2
- package/docker-compose/production/zitadel/.env.zh-CN.example +0 -2
- package/docker-compose/setup.sh +16 -2
- package/docs/development/basic/folder-structure.mdx +23 -14
- package/docs/development/basic/folder-structure.zh-CN.mdx +23 -14
- package/docs/development/basic/work-with-server-side-database.mdx +0 -1
- package/docs/development/basic/work-with-server-side-database.zh-CN.mdx +0 -1
- package/docs/development/start.mdx +19 -12
- package/docs/development/start.zh-CN.mdx +19 -12
- package/docs/self-hosting/advanced/s3/cloudflare-r2.mdx +0 -5
- package/docs/self-hosting/advanced/s3/cloudflare-r2.zh-CN.mdx +0 -5
- package/docs/self-hosting/advanced/s3/rustfs.mdx +0 -2
- package/docs/self-hosting/advanced/s3/rustfs.zh-CN.mdx +0 -2
- package/docs/self-hosting/advanced/s3/tencent-cloud.mdx +0 -1
- package/docs/self-hosting/advanced/s3/tencent-cloud.zh-CN.mdx +0 -2
- package/docs/self-hosting/advanced/s3.mdx +0 -9
- package/docs/self-hosting/advanced/s3.zh-CN.mdx +0 -8
- package/docs/self-hosting/environment-variables/s3.mdx +0 -7
- package/docs/self-hosting/environment-variables/s3.zh-CN.mdx +0 -7
- package/docs/self-hosting/platform/docker-compose.mdx +0 -1
- package/docs/self-hosting/platform/docker-compose.zh-CN.mdx +0 -1
- package/docs/self-hosting/platform/docker.mdx +5 -3
- package/docs/self-hosting/platform/docker.zh-CN.mdx +5 -4
- package/docs/self-hosting/platform/dokploy.mdx +0 -2
- package/docs/self-hosting/platform/dokploy.zh-CN.mdx +0 -2
- package/docs/self-hosting/platform/vercel.mdx +0 -7
- package/docs/self-hosting/platform/vercel.zh-CN.mdx +0 -7
- package/e2e/src/steps/home/sidebarAgent.steps.ts +56 -24
- package/locales/ar/authError.json +1 -0
- package/locales/ar/models.json +25 -22
- package/locales/ar/providers.json +0 -1
- package/locales/ar/setting.json +16 -0
- package/locales/bg-BG/authError.json +1 -0
- package/locales/bg-BG/models.json +18 -21
- package/locales/bg-BG/providers.json +0 -1
- package/locales/bg-BG/setting.json +16 -0
- package/locales/de-DE/authError.json +1 -0
- package/locales/de-DE/models.json +20 -20
- package/locales/de-DE/providers.json +0 -1
- package/locales/de-DE/setting.json +16 -0
- package/locales/en-US/models.json +22 -22
- package/locales/en-US/providers.json +0 -1
- package/locales/es-ES/authError.json +1 -0
- package/locales/es-ES/models.json +84 -20
- package/locales/es-ES/providers.json +0 -1
- package/locales/es-ES/setting.json +16 -0
- package/locales/fa-IR/authError.json +1 -0
- package/locales/fa-IR/models.json +43 -20
- package/locales/fa-IR/providers.json +0 -1
- package/locales/fa-IR/setting.json +16 -0
- package/locales/fr-FR/authError.json +1 -0
- package/locales/fr-FR/models.json +19 -21
- package/locales/fr-FR/providers.json +0 -1
- package/locales/fr-FR/setting.json +16 -0
- package/locales/it-IT/authError.json +1 -0
- package/locales/it-IT/models.json +17 -19
- package/locales/it-IT/providers.json +0 -1
- package/locales/it-IT/setting.json +16 -0
- package/locales/ja-JP/authError.json +1 -0
- package/locales/ja-JP/models.json +43 -22
- package/locales/ja-JP/providers.json +0 -1
- package/locales/ja-JP/setting.json +16 -0
- package/locales/ko-KR/authError.json +1 -0
- package/locales/ko-KR/models.json +41 -20
- package/locales/ko-KR/providers.json +0 -1
- package/locales/ko-KR/setting.json +16 -0
- package/locales/nl-NL/authError.json +1 -0
- package/locales/nl-NL/models.json +48 -20
- package/locales/nl-NL/providers.json +0 -1
- package/locales/nl-NL/setting.json +16 -0
- package/locales/pl-PL/authError.json +1 -0
- package/locales/pl-PL/models.json +19 -22
- package/locales/pl-PL/providers.json +0 -1
- package/locales/pl-PL/setting.json +16 -0
- package/locales/pt-BR/authError.json +1 -0
- package/locales/pt-BR/models.json +21 -21
- package/locales/pt-BR/providers.json +0 -1
- package/locales/pt-BR/setting.json +16 -0
- package/locales/ru-RU/authError.json +1 -0
- package/locales/ru-RU/models.json +23 -20
- package/locales/ru-RU/providers.json +0 -1
- package/locales/ru-RU/setting.json +16 -0
- package/locales/tr-TR/authError.json +1 -0
- package/locales/tr-TR/models.json +37 -20
- package/locales/tr-TR/providers.json +0 -1
- package/locales/tr-TR/setting.json +16 -0
- package/locales/vi-VN/authError.json +1 -0
- package/locales/vi-VN/models.json +15 -19
- package/locales/vi-VN/providers.json +0 -1
- package/locales/vi-VN/setting.json +16 -0
- package/locales/zh-CN/models.json +20 -20
- package/locales/zh-CN/providers.json +0 -1
- package/locales/zh-TW/authError.json +1 -0
- package/locales/zh-TW/models.json +20 -20
- package/locales/zh-TW/providers.json +0 -1
- package/locales/zh-TW/setting.json +16 -0
- package/package.json +1 -1
- package/packages/model-bank/src/aiModels/google.ts +0 -19
- package/packages/model-bank/src/aiModels/moonshot.ts +56 -5
- package/packages/model-bank/src/aiModels/ollamacloud.ts +14 -0
- package/packages/model-bank/src/aiModels/openrouter.ts +0 -14
- package/packages/model-bank/src/aiModels/qwen.ts +105 -4
- package/packages/model-bank/src/aiModels/siliconcloud.ts +39 -0
- package/packages/model-bank/src/aiModels/wenxin.ts +0 -99
- package/packages/model-runtime/src/core/contextBuilders/openai.test.ts +24 -0
- package/packages/model-runtime/src/core/contextBuilders/openai.ts +22 -5
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +10 -3
- package/packages/model-runtime/src/core/streams/google/google-ai.test.ts +54 -13
- package/packages/model-runtime/src/core/streams/google/index.ts +1 -4
- package/packages/model-runtime/src/providers/moonshot/index.ts +24 -2
- package/packages/model-runtime/src/providers/qwen/index.ts +16 -15
- package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +28 -5
- package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +32 -9
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +16 -3
- package/src/features/ProfileEditor/AgentTool.tsx +10 -3
- package/src/server/routers/lambda/__tests__/integration/aiAgent/execAgent.integration.test.ts +3 -2
|
@@ -73,6 +73,7 @@ export interface OpenAICompatibleFactoryOptions<T extends Record<string, any> =
|
|
|
73
73
|
baseURL?: string;
|
|
74
74
|
chatCompletion?: {
|
|
75
75
|
excludeUsage?: boolean;
|
|
76
|
+
forceImageBase64?: boolean;
|
|
76
77
|
handleError?: (
|
|
77
78
|
error: any,
|
|
78
79
|
options: ConstructorOptions<T>,
|
|
@@ -387,7 +388,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
387
388
|
this.baseURL = targetBaseURL;
|
|
388
389
|
}
|
|
389
390
|
|
|
390
|
-
const messages = await convertOpenAIMessages(postPayload.messages
|
|
391
|
+
const messages = await convertOpenAIMessages(postPayload.messages, {
|
|
392
|
+
forceImageBase64: chatCompletion?.forceImageBase64,
|
|
393
|
+
});
|
|
391
394
|
|
|
392
395
|
let response: Stream<OpenAI.Chat.Completions.ChatCompletionChunk>;
|
|
393
396
|
|
|
@@ -877,7 +880,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
877
880
|
delete res.frequency_penalty;
|
|
878
881
|
delete res.presence_penalty;
|
|
879
882
|
|
|
880
|
-
const input = await convertOpenAIResponseInputs(messages as any
|
|
883
|
+
const input = await convertOpenAIResponseInputs(messages as any, {
|
|
884
|
+
forceImageBase64: chatCompletion?.forceImageBase64,
|
|
885
|
+
});
|
|
881
886
|
|
|
882
887
|
const isStreaming = payload.stream !== false;
|
|
883
888
|
log(
|
|
@@ -1006,7 +1011,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
1006
1011
|
|
|
1007
1012
|
if (shouldUseResponses) {
|
|
1008
1013
|
log('calling responses.create for tool calling');
|
|
1009
|
-
const input = await convertOpenAIResponseInputs(messages as any
|
|
1014
|
+
const input = await convertOpenAIResponseInputs(messages as any, {
|
|
1015
|
+
forceImageBase64: chatCompletion?.forceImageBase64,
|
|
1016
|
+
});
|
|
1010
1017
|
|
|
1011
1018
|
const res = await this.client.responses.create(
|
|
1012
1019
|
{
|
|
@@ -251,16 +251,16 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
251
251
|
expect(chunks).toEqual(
|
|
252
252
|
[
|
|
253
253
|
'id: chat_1',
|
|
254
|
-
'event:
|
|
255
|
-
'data:
|
|
254
|
+
'event: text',
|
|
255
|
+
'data: "234"\n',
|
|
256
256
|
|
|
257
257
|
'id: chat_1',
|
|
258
258
|
'event: text',
|
|
259
259
|
'data: ""\n',
|
|
260
260
|
|
|
261
261
|
'id: chat_1',
|
|
262
|
-
'event:
|
|
263
|
-
`data:
|
|
262
|
+
'event: text',
|
|
263
|
+
`data: "567890\\n"\n`,
|
|
264
264
|
// stop
|
|
265
265
|
'id: chat_1',
|
|
266
266
|
'event: stop',
|
|
@@ -384,12 +384,12 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
384
384
|
`data: {"content":"**Finalizing Interpretation**\\n\\n","inReasoning":true,"partType":"text"}\n`,
|
|
385
385
|
|
|
386
386
|
'id: chat_1',
|
|
387
|
-
'event:
|
|
388
|
-
|
|
387
|
+
'event: text',
|
|
388
|
+
'data: "简单来说,"\n',
|
|
389
389
|
|
|
390
390
|
'id: chat_1',
|
|
391
|
-
'event:
|
|
392
|
-
|
|
391
|
+
'event: text',
|
|
392
|
+
'data: "文本内容。"\n',
|
|
393
393
|
// stop
|
|
394
394
|
'id: chat_1',
|
|
395
395
|
'event: stop',
|
|
@@ -471,12 +471,12 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
471
471
|
expect(chunks).toEqual(
|
|
472
472
|
[
|
|
473
473
|
'id: chat_1',
|
|
474
|
-
'event:
|
|
475
|
-
'data:
|
|
474
|
+
'event: text',
|
|
475
|
+
'data: "234"\n',
|
|
476
476
|
|
|
477
477
|
'id: chat_1',
|
|
478
|
-
'event:
|
|
479
|
-
|
|
478
|
+
'event: text',
|
|
479
|
+
'data: "567890\\n"\n',
|
|
480
480
|
// stop
|
|
481
481
|
'id: chat_1',
|
|
482
482
|
'event: stop',
|
|
@@ -1103,7 +1103,7 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
1103
1103
|
content: {
|
|
1104
1104
|
parts: [
|
|
1105
1105
|
{
|
|
1106
|
-
text:
|
|
1106
|
+
text: "**Planning the Solution**\n\nI'm solidifying my plan...",
|
|
1107
1107
|
thought: true,
|
|
1108
1108
|
},
|
|
1109
1109
|
],
|
|
@@ -1901,5 +1901,46 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
1901
1901
|
].map((i) => i + '\n'),
|
|
1902
1902
|
);
|
|
1903
1903
|
});
|
|
1904
|
+
|
|
1905
|
+
it('should NOT use multimodal processing if only thoughtsTokenCount is present in metadata but no thought parts', async () => {
|
|
1906
|
+
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
|
|
1907
|
+
|
|
1908
|
+
const data = [
|
|
1909
|
+
{
|
|
1910
|
+
candidates: [
|
|
1911
|
+
{
|
|
1912
|
+
content: {
|
|
1913
|
+
parts: [{ text: 'Hello world' }],
|
|
1914
|
+
role: 'model',
|
|
1915
|
+
},
|
|
1916
|
+
index: 0,
|
|
1917
|
+
},
|
|
1918
|
+
],
|
|
1919
|
+
usageMetadata: {
|
|
1920
|
+
promptTokenCount: 10,
|
|
1921
|
+
candidatesTokenCount: 2,
|
|
1922
|
+
totalTokenCount: 17,
|
|
1923
|
+
thoughtsTokenCount: 5,
|
|
1924
|
+
},
|
|
1925
|
+
modelVersion: 'gemini-2.5-flash',
|
|
1926
|
+
},
|
|
1927
|
+
];
|
|
1928
|
+
|
|
1929
|
+
const mockGoogleStream = new ReadableStream({
|
|
1930
|
+
start(controller) {
|
|
1931
|
+
data.forEach((item) => {
|
|
1932
|
+
controller.enqueue(item);
|
|
1933
|
+
});
|
|
1934
|
+
controller.close();
|
|
1935
|
+
},
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
const protocolStream = GoogleGenerativeAIStream(mockGoogleStream);
|
|
1939
|
+
const chunks = await decodeStreamChunks(protocolStream);
|
|
1940
|
+
|
|
1941
|
+
// Should use 'text' event, not 'content_part'
|
|
1942
|
+
expect(chunks).toContain('event: text\n');
|
|
1943
|
+
expect(chunks).not.toContain('event: content_part\n');
|
|
1944
|
+
});
|
|
1904
1945
|
});
|
|
1905
1946
|
});
|
|
@@ -120,7 +120,6 @@ const transformGoogleGenerativeAIStream = (
|
|
|
120
120
|
const hasReasoningParts = parts.some((p: any) => p.thought === true);
|
|
121
121
|
const hasImageParts = parts.some((p: any) => p.inlineData);
|
|
122
122
|
const hasThoughtSignature = parts.some((p: any) => p.thoughtSignature);
|
|
123
|
-
const hasThoughtsInMetadata = (usageMetadata as any)?.thoughtsTokenCount > 0;
|
|
124
123
|
|
|
125
124
|
// Check model version to determine if new format should be used
|
|
126
125
|
const modelVersion = (chunk as any).modelVersion || '';
|
|
@@ -144,8 +143,7 @@ const transformGoogleGenerativeAIStream = (
|
|
|
144
143
|
// 1. There are reasoning parts in current chunk (thought: true)
|
|
145
144
|
// 2. There are multiple parts with images (multimodal content)
|
|
146
145
|
// 3. There are thoughtSignature in parts (reasoning metadata attached to content)
|
|
147
|
-
// 4.
|
|
148
|
-
// 5. This is Gemini 3 model with image generation (always use new format for consistency)
|
|
146
|
+
// 4. This is Gemini 3 model with image generation (always use new format for consistency)
|
|
149
147
|
// BUT NOT for:
|
|
150
148
|
// - The legacy single-image scenario
|
|
151
149
|
// - Grounding metadata scenario (uses legacy text + grounding events)
|
|
@@ -153,7 +151,6 @@ const transformGoogleGenerativeAIStream = (
|
|
|
153
151
|
(hasReasoningParts ||
|
|
154
152
|
(hasImageParts && parts.length > 1) ||
|
|
155
153
|
hasThoughtSignature ||
|
|
156
|
-
hasThoughtsInMetadata ||
|
|
157
154
|
isGemini3Model) &&
|
|
158
155
|
!isSingleImageWithFinish &&
|
|
159
156
|
!hasGroundingMetadata;
|
|
@@ -15,8 +15,9 @@ export interface MoonshotModelCard {
|
|
|
15
15
|
export const params = {
|
|
16
16
|
baseURL: 'https://api.moonshot.cn/v1',
|
|
17
17
|
chatCompletion: {
|
|
18
|
+
forceImageBase64: true,
|
|
18
19
|
handlePayload: (payload: ChatStreamPayload) => {
|
|
19
|
-
const { enabledSearch, messages, temperature, tools, ...rest } = payload;
|
|
20
|
+
const { enabledSearch, messages, model, temperature, thinking, tools, ...rest } = payload;
|
|
20
21
|
|
|
21
22
|
const filteredMessages = messages.map((message: any) => {
|
|
22
23
|
let normalizedMessage = message;
|
|
@@ -51,12 +52,33 @@ export const params = {
|
|
|
51
52
|
]
|
|
52
53
|
: tools;
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
const isK25Model = model === 'kimi-k2.5';
|
|
56
|
+
|
|
57
|
+
if (isK25Model) {
|
|
58
|
+
const thinkingParam =
|
|
59
|
+
thinking?.type === 'disabled' ? { type: 'disabled' } : { type: 'enabled' };
|
|
60
|
+
const isThinkingEnabled = thinkingParam.type === 'enabled';
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...rest,
|
|
64
|
+
frequency_penalty: 0,
|
|
65
|
+
messages: filteredMessages,
|
|
66
|
+
model,
|
|
67
|
+
presence_penalty: 0,
|
|
68
|
+
temperature: isThinkingEnabled ? 1 : 0.6,
|
|
69
|
+
thinking: thinkingParam,
|
|
70
|
+
tools: moonshotTools,
|
|
71
|
+
top_p: 0.95,
|
|
72
|
+
} as any;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Resolve parameters with normalization for non-K2.5 models
|
|
55
76
|
const resolvedParams = resolveParameters({ temperature }, { normalizeTemperature: true });
|
|
56
77
|
|
|
57
78
|
return {
|
|
58
79
|
...rest,
|
|
59
80
|
messages: filteredMessages,
|
|
81
|
+
model,
|
|
60
82
|
temperature: resolvedParams.temperature,
|
|
61
83
|
tools: moonshotTools,
|
|
62
84
|
} as any;
|
|
@@ -47,24 +47,25 @@ export const LobeQwenAI = createOpenAICompatibleRuntime({
|
|
|
47
47
|
...rest,
|
|
48
48
|
...(model.includes('-thinking')
|
|
49
49
|
? {
|
|
50
|
-
|
|
50
|
+
enable_thinking: true,
|
|
51
|
+
thinking_budget:
|
|
52
|
+
thinking?.budget_tokens === 0 ? 0 : thinking?.budget_tokens || undefined,
|
|
53
|
+
}
|
|
54
|
+
: [
|
|
55
|
+
'qwen3',
|
|
56
|
+
'qwen-turbo',
|
|
57
|
+
'qwen-plus',
|
|
58
|
+
'qwen-flash',
|
|
59
|
+
'deepseek-v3.1',
|
|
60
|
+
'deepseek-v3.2',
|
|
61
|
+
'glm',
|
|
62
|
+
'kimi-k2.5',
|
|
63
|
+
].some((keyword) => model.toLowerCase().includes(keyword))
|
|
64
|
+
? {
|
|
65
|
+
enable_thinking: thinking !== undefined ? thinking.type === 'enabled' : false,
|
|
51
66
|
thinking_budget:
|
|
52
67
|
thinking?.budget_tokens === 0 ? 0 : thinking?.budget_tokens || undefined,
|
|
53
68
|
}
|
|
54
|
-
: [
|
|
55
|
-
'qwen3',
|
|
56
|
-
'qwen-turbo',
|
|
57
|
-
'qwen-plus',
|
|
58
|
-
'qwen-flash',
|
|
59
|
-
'deepseek-v3.1',
|
|
60
|
-
'deepseek-v3.2',
|
|
61
|
-
'glm',
|
|
62
|
-
].some((keyword) => model.toLowerCase().includes(keyword))
|
|
63
|
-
? {
|
|
64
|
-
enable_thinking: thinking !== undefined ? thinking.type === 'enabled' : false,
|
|
65
|
-
thinking_budget:
|
|
66
|
-
thinking?.budget_tokens === 0 ? 0 : thinking?.budget_tokens || undefined,
|
|
67
|
-
}
|
|
68
69
|
: {}),
|
|
69
70
|
frequency_penalty: undefined,
|
|
70
71
|
model,
|
|
@@ -15,6 +15,11 @@ const POLL_INTERVAL_MS = 1000; // 每秒轮询一次
|
|
|
15
15
|
const POLL_TIMEOUT_MS = 15_000; // 15 秒超时
|
|
16
16
|
|
|
17
17
|
interface KlavisServerItemProps {
|
|
18
|
+
/**
|
|
19
|
+
* Optional agent ID to use instead of currentAgentConfig
|
|
20
|
+
* Used in group profile to specify which member's plugins to toggle
|
|
21
|
+
*/
|
|
22
|
+
agentId?: string;
|
|
18
23
|
/**
|
|
19
24
|
* Identifier used for storage (e.g., 'google-calendar')
|
|
20
25
|
*/
|
|
@@ -28,7 +33,7 @@ interface KlavisServerItemProps {
|
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
const KlavisServerItem = memo<KlavisServerItemProps>(
|
|
31
|
-
({ identifier, label, server, serverName }) => {
|
|
36
|
+
({ identifier, label, server, serverName, agentId }) => {
|
|
32
37
|
const { t } = useTranslation('setting');
|
|
33
38
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
34
39
|
const [isToggling, setIsToggling] = useState(false);
|
|
@@ -43,6 +48,10 @@ const KlavisServerItem = memo<KlavisServerItemProps>(
|
|
|
43
48
|
const createKlavisServer = useToolStore((s) => s.createKlavisServer);
|
|
44
49
|
const refreshKlavisServerTools = useToolStore((s) => s.refreshKlavisServerTools);
|
|
45
50
|
|
|
51
|
+
// Get effective agent ID (agentId prop or current active agent)
|
|
52
|
+
const activeAgentId = useAgentStore((s) => s.activeAgentId);
|
|
53
|
+
const effectiveAgentId = agentId || activeAgentId || '';
|
|
54
|
+
|
|
46
55
|
// 清理所有定时器
|
|
47
56
|
const cleanup = useCallback(() => {
|
|
48
57
|
if (windowCheckIntervalRef.current) {
|
|
@@ -162,10 +171,24 @@ const KlavisServerItem = memo<KlavisServerItemProps>(
|
|
|
162
171
|
|
|
163
172
|
// Get plugin ID for this server (使用 identifier 作为 pluginId)
|
|
164
173
|
const pluginId = server ? server.identifier : '';
|
|
165
|
-
const
|
|
166
|
-
agentSelectors.
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
const plugins =
|
|
175
|
+
useAgentStore(agentSelectors.getAgentConfigById(effectiveAgentId))?.plugins || [];
|
|
176
|
+
const checked = plugins.includes(pluginId);
|
|
177
|
+
const updateAgentConfigById = useAgentStore((s) => s.updateAgentConfigById);
|
|
178
|
+
|
|
179
|
+
// Toggle plugin for the effective agent
|
|
180
|
+
const togglePlugin = useCallback(
|
|
181
|
+
async (pluginIdToToggle: string) => {
|
|
182
|
+
if (!effectiveAgentId) return;
|
|
183
|
+
const currentPlugins = plugins;
|
|
184
|
+
const hasPlugin = currentPlugins.includes(pluginIdToToggle);
|
|
185
|
+
const newPlugins = hasPlugin
|
|
186
|
+
? currentPlugins.filter((id) => id !== pluginIdToToggle)
|
|
187
|
+
: [...currentPlugins, pluginIdToToggle];
|
|
188
|
+
await updateAgentConfigById(effectiveAgentId, { plugins: newPlugins });
|
|
189
|
+
},
|
|
190
|
+
[effectiveAgentId, plugins, updateAgentConfigById],
|
|
191
|
+
);
|
|
169
192
|
|
|
170
193
|
const handleConnect = async () => {
|
|
171
194
|
if (!userId) {
|
|
@@ -13,6 +13,11 @@ const POLL_INTERVAL_MS = 1000;
|
|
|
13
13
|
const POLL_TIMEOUT_MS = 15_000;
|
|
14
14
|
|
|
15
15
|
interface LobehubSkillServerItemProps {
|
|
16
|
+
/**
|
|
17
|
+
* Optional agent ID to use instead of currentAgentConfig
|
|
18
|
+
* Used in group profile to specify which member's plugins to toggle
|
|
19
|
+
*/
|
|
20
|
+
agentId?: string;
|
|
16
21
|
/**
|
|
17
22
|
* Display label for the provider
|
|
18
23
|
*/
|
|
@@ -23,7 +28,7 @@ interface LobehubSkillServerItemProps {
|
|
|
23
28
|
provider: string;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
const LobehubSkillServerItem = memo<LobehubSkillServerItemProps>(({ provider, label }) => {
|
|
31
|
+
const LobehubSkillServerItem = memo<LobehubSkillServerItemProps>(({ provider, label, agentId }) => {
|
|
27
32
|
const { t } = useTranslation('setting');
|
|
28
33
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
29
34
|
const [isToggling, setIsToggling] = useState(false);
|
|
@@ -38,6 +43,10 @@ const LobehubSkillServerItem = memo<LobehubSkillServerItemProps>(({ provider, la
|
|
|
38
43
|
const checkStatus = useToolStore((s) => s.checkLobehubSkillStatus);
|
|
39
44
|
const getAuthorizeUrl = useToolStore((s) => s.getLobehubSkillAuthorizeUrl);
|
|
40
45
|
|
|
46
|
+
// Get effective agent ID (agentId prop or current active agent)
|
|
47
|
+
const activeAgentId = useAgentStore((s) => s.activeAgentId);
|
|
48
|
+
const effectiveAgentId = agentId || activeAgentId || '';
|
|
49
|
+
|
|
41
50
|
const cleanup = useCallback(() => {
|
|
42
51
|
if (windowCheckIntervalRef.current) {
|
|
43
52
|
clearInterval(windowCheckIntervalRef.current);
|
|
@@ -129,10 +138,23 @@ const LobehubSkillServerItem = memo<LobehubSkillServerItemProps>(({ provider, la
|
|
|
129
138
|
);
|
|
130
139
|
|
|
131
140
|
const pluginId = server ? server.identifier : '';
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
const plugins = useAgentStore(agentSelectors.getAgentConfigById(effectiveAgentId))?.plugins || [];
|
|
142
|
+
const checked = plugins.includes(pluginId);
|
|
143
|
+
const updateAgentConfigById = useAgentStore((s) => s.updateAgentConfigById);
|
|
144
|
+
|
|
145
|
+
// Toggle plugin for the effective agent
|
|
146
|
+
const togglePlugin = useCallback(
|
|
147
|
+
async (pluginIdToToggle: string) => {
|
|
148
|
+
if (!effectiveAgentId) return;
|
|
149
|
+
const currentPlugins = plugins;
|
|
150
|
+
const hasPlugin = currentPlugins.includes(pluginIdToToggle);
|
|
151
|
+
const newPlugins = hasPlugin
|
|
152
|
+
? currentPlugins.filter((id) => id !== pluginIdToToggle)
|
|
153
|
+
: [...currentPlugins, pluginIdToToggle];
|
|
154
|
+
await updateAgentConfigById(effectiveAgentId, { plugins: newPlugins });
|
|
155
|
+
},
|
|
156
|
+
[effectiveAgentId, plugins, updateAgentConfigById],
|
|
157
|
+
);
|
|
136
158
|
|
|
137
159
|
// Listen for OAuth success message from popup window
|
|
138
160
|
useEffect(() => {
|
|
@@ -156,9 +178,10 @@ const LobehubSkillServerItem = memo<LobehubSkillServerItemProps>(({ provider, la
|
|
|
156
178
|
.lobehubSkillServers?.find((s) => s.identifier === provider);
|
|
157
179
|
if (latestServer?.status === LobehubSkillStatus.CONNECTED) {
|
|
158
180
|
const newPluginId = latestServer.identifier;
|
|
159
|
-
const
|
|
160
|
-
.
|
|
161
|
-
|
|
181
|
+
const currentAgentPlugins =
|
|
182
|
+
agentSelectors.getAgentConfigById(effectiveAgentId)(useAgentStore.getState())
|
|
183
|
+
?.plugins || [];
|
|
184
|
+
const isAlreadyEnabled = currentAgentPlugins.includes(newPluginId);
|
|
162
185
|
if (!isAlreadyEnabled) {
|
|
163
186
|
console.log('[LobehubSkill] Auto-enabling plugin:', newPluginId);
|
|
164
187
|
togglePlugin(newPluginId);
|
|
@@ -169,7 +192,7 @@ const LobehubSkillServerItem = memo<LobehubSkillServerItemProps>(({ provider, la
|
|
|
169
192
|
|
|
170
193
|
window.addEventListener('message', handleMessage);
|
|
171
194
|
return () => window.removeEventListener('message', handleMessage);
|
|
172
|
-
}, [provider, cleanup, checkStatus, togglePlugin]);
|
|
195
|
+
}, [provider, cleanup, checkStatus, togglePlugin, effectiveAgentId]);
|
|
173
196
|
|
|
174
197
|
const handleConnect = async () => {
|
|
175
198
|
// 只有已连接状态才阻止重新连接
|
|
@@ -176,6 +176,7 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
|
|
|
176
176
|
key: type.identifier,
|
|
177
177
|
label: (
|
|
178
178
|
<KlavisServerItem
|
|
179
|
+
agentId={agentId}
|
|
179
180
|
identifier={type.identifier}
|
|
180
181
|
label={type.label}
|
|
181
182
|
server={getServerByName(type.identifier)}
|
|
@@ -184,7 +185,7 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
|
|
|
184
185
|
),
|
|
185
186
|
}))
|
|
186
187
|
: [],
|
|
187
|
-
[isKlavisEnabledInEnv, allKlavisServers, installedKlavisIds, recommendedKlavisIds],
|
|
188
|
+
[isKlavisEnabledInEnv, allKlavisServers, installedKlavisIds, recommendedKlavisIds, agentId],
|
|
188
189
|
);
|
|
189
190
|
|
|
190
191
|
// LobeHub Skill Provider 列表项 - 只展示已安装或推荐的
|
|
@@ -197,10 +198,22 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
|
|
|
197
198
|
).map((provider) => ({
|
|
198
199
|
icon: <LobehubSkillIcon icon={provider.icon} label={provider.label} />,
|
|
199
200
|
key: provider.id, // 使用 provider.id 作为 key,与 pluginId 保持一致
|
|
200
|
-
label:
|
|
201
|
+
label: (
|
|
202
|
+
<LobehubSkillServerItem
|
|
203
|
+
agentId={agentId}
|
|
204
|
+
label={provider.label}
|
|
205
|
+
provider={provider.id}
|
|
206
|
+
/>
|
|
207
|
+
),
|
|
201
208
|
}))
|
|
202
209
|
: [],
|
|
203
|
-
[
|
|
210
|
+
[
|
|
211
|
+
isLobehubSkillEnabled,
|
|
212
|
+
allLobehubSkillServers,
|
|
213
|
+
installedLobehubIds,
|
|
214
|
+
recommendedLobehubIds,
|
|
215
|
+
agentId,
|
|
216
|
+
],
|
|
204
217
|
);
|
|
205
218
|
|
|
206
219
|
// Builtin 工具列表项(不包含 Klavis 和 LobeHub Skill)
|
|
@@ -271,6 +271,7 @@ const AgentTool = memo<AgentToolProps>(
|
|
|
271
271
|
key: type.identifier,
|
|
272
272
|
label: (
|
|
273
273
|
<KlavisServerItem
|
|
274
|
+
agentId={effectiveAgentId}
|
|
274
275
|
identifier={type.identifier}
|
|
275
276
|
label={type.label}
|
|
276
277
|
server={getServerByName(type.identifier)}
|
|
@@ -279,7 +280,7 @@ const AgentTool = memo<AgentToolProps>(
|
|
|
279
280
|
),
|
|
280
281
|
}))
|
|
281
282
|
: [],
|
|
282
|
-
[isKlavisEnabledInEnv, allKlavisServers],
|
|
283
|
+
[isKlavisEnabledInEnv, allKlavisServers, effectiveAgentId],
|
|
283
284
|
);
|
|
284
285
|
|
|
285
286
|
// LobeHub Skill Provider 列表项
|
|
@@ -289,10 +290,16 @@ const AgentTool = memo<AgentToolProps>(
|
|
|
289
290
|
? LOBEHUB_SKILL_PROVIDERS.map((provider) => ({
|
|
290
291
|
icon: <LobehubSkillIcon icon={provider.icon} label={provider.label} />,
|
|
291
292
|
key: provider.id, // 使用 provider.id 作为 key,与 pluginId 保持一致
|
|
292
|
-
label:
|
|
293
|
+
label: (
|
|
294
|
+
<LobehubSkillServerItem
|
|
295
|
+
agentId={effectiveAgentId}
|
|
296
|
+
label={provider.label}
|
|
297
|
+
provider={provider.id}
|
|
298
|
+
/>
|
|
299
|
+
),
|
|
293
300
|
}))
|
|
294
301
|
: [],
|
|
295
|
-
[isLobehubSkillEnabled, allLobehubSkillServers],
|
|
302
|
+
[isLobehubSkillEnabled, allLobehubSkillServers, effectiveAgentId],
|
|
296
303
|
);
|
|
297
304
|
|
|
298
305
|
// Handle plugin remove via Tag close - use byId actions
|
package/src/server/routers/lambda/__tests__/integration/aiAgent/execAgent.integration.test.ts
CHANGED
|
@@ -232,14 +232,15 @@ describe('execAgent', () => {
|
|
|
232
232
|
})
|
|
233
233
|
.returning();
|
|
234
234
|
|
|
235
|
-
const
|
|
235
|
+
const threadResult = (await serverDB
|
|
236
236
|
.insert(threads)
|
|
237
237
|
.values({
|
|
238
238
|
topicId: topic.id,
|
|
239
239
|
type: 'standalone',
|
|
240
240
|
userId,
|
|
241
241
|
})
|
|
242
|
-
.returning();
|
|
242
|
+
.returning()) as { id: string }[];
|
|
243
|
+
const thread = threadResult[0];
|
|
243
244
|
|
|
244
245
|
const caller = aiAgentRouter.createCaller(createTestContext());
|
|
245
246
|
|