@lobehub/lobehub 2.0.7 → 2.0.8
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/CHANGELOG.md +25 -0
- package/changelog/v2.json +9 -0
- package/package.json +1 -1
- package/packages/model-runtime/src/index.ts +1 -1
- package/packages/model-runtime/src/runtimeMap.ts +1 -1
- package/src/app/[variants]/(mobile)/(home)/features/SessionListContent/Inbox/index.tsx +10 -20
- package/src/server/services/memory/userMemory/extract.ts +45 -34
- package/src/server/services/memory/userMemory/persona/__tests__/service.test.ts +2 -4
- package/src/server/services/memory/userMemory/persona/service.ts +18 -32
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 2.0.8](https://github.com/lobehub/lobe-chat/compare/v2.0.7...v2.0.8)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-28**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Fix inbox agent in mobile.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Fix inbox agent in mobile, closes [#11929](https://github.com/lobehub/lobe-chat/issues/11929) ([42f5c0b](https://github.com/lobehub/lobe-chat/commit/42f5c0b))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
### [Version 2.0.7](https://github.com/lobehub/lobe-chat/compare/v2.0.6...v2.0.7)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2026-01-28**</sup>
|
package/changelog/v2.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -18,6 +18,7 @@ export { LobeComfyUI } from './providers/comfyui';
|
|
|
18
18
|
export { LobeDeepSeekAI } from './providers/deepseek';
|
|
19
19
|
export { LobeGoogleAI } from './providers/google';
|
|
20
20
|
export { LobeGroq } from './providers/groq';
|
|
21
|
+
export { LobeHubAI } from './providers/lobehub';
|
|
21
22
|
export { LobeMinimaxAI } from './providers/minimax';
|
|
22
23
|
export { LobeMistralAI } from './providers/mistral';
|
|
23
24
|
export { LobeMoonshotAI } from './providers/moonshot';
|
|
@@ -36,7 +37,6 @@ export { LobeXiaomiMiMoAI } from './providers/xiaomimimo';
|
|
|
36
37
|
export { LobeZenMuxAI } from './providers/zenmux';
|
|
37
38
|
export { LobeZeroOneAI } from './providers/zeroone';
|
|
38
39
|
export { LobeZhipuAI } from './providers/zhipu';
|
|
39
|
-
export { LobeHubAI } from './providers/lobehub';
|
|
40
40
|
export * from './types';
|
|
41
41
|
export * from './types/error';
|
|
42
42
|
export { consumeStreamUntilDone } from './utils/consumeStream';
|
|
@@ -88,7 +88,6 @@ export const providerRuntimeMap = {
|
|
|
88
88
|
deepseek: LobeDeepSeekAI,
|
|
89
89
|
fal: LobeFalAI,
|
|
90
90
|
fireworksai: LobeFireworksAI,
|
|
91
|
-
lobehub: LobeHubAI,
|
|
92
91
|
giteeai: LobeGiteeAI,
|
|
93
92
|
github: LobeGithubAI,
|
|
94
93
|
google: LobeGoogleAI,
|
|
@@ -100,6 +99,7 @@ export const providerRuntimeMap = {
|
|
|
100
99
|
internlm: LobeInternLMAI,
|
|
101
100
|
jina: LobeJinaAI,
|
|
102
101
|
lmstudio: LobeLMStudioAI,
|
|
102
|
+
lobehub: LobeHubAI,
|
|
103
103
|
minimax: LobeMinimaxAI,
|
|
104
104
|
mistral: LobeMistralAI,
|
|
105
105
|
modelscope: LobeModelScopeAI,
|
|
@@ -2,45 +2,35 @@ import { memo } from 'react';
|
|
|
2
2
|
import { Link } from 'react-router-dom';
|
|
3
3
|
|
|
4
4
|
import { DEFAULT_INBOX_AVATAR } from '@/const/meta';
|
|
5
|
-
import { INBOX_SESSION_ID } from '@/const/session';
|
|
6
5
|
import { SESSION_CHAT_URL } from '@/const/url';
|
|
7
6
|
import { useNavigateToAgent } from '@/hooks/useNavigateToAgent';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { useAgentStore } from '@/store/agent';
|
|
8
|
+
import { builtinAgentSelectors } from '@/store/agent/selectors';
|
|
10
9
|
import { useServerConfigStore } from '@/store/serverConfig';
|
|
11
10
|
import { useSessionStore } from '@/store/session';
|
|
11
|
+
import { sessionSelectors } from '@/store/session/selectors';
|
|
12
12
|
|
|
13
13
|
import ListItem from '../ListItem';
|
|
14
14
|
|
|
15
15
|
const Inbox = memo(() => {
|
|
16
16
|
const mobile = useServerConfigStore((s) => s.isMobile);
|
|
17
|
-
const
|
|
17
|
+
const isInboxActive = useSessionStore(sessionSelectors.isInboxSession);
|
|
18
18
|
const navigateToAgent = useNavigateToAgent();
|
|
19
|
-
|
|
20
|
-
const openNewTopicOrSaveTopic = useChatStore((s) => s.openNewTopicOrSaveTopic);
|
|
19
|
+
const inboxAgentId = useAgentStore(builtinAgentSelectors.inboxAgentId);
|
|
21
20
|
|
|
22
21
|
return (
|
|
23
22
|
<Link
|
|
24
23
|
aria-label={'Lobe AI'}
|
|
25
|
-
onClick={
|
|
24
|
+
onClick={(e) => {
|
|
26
25
|
e.preventDefault();
|
|
27
|
-
|
|
28
|
-
// If user tap the inbox again, open a new topic.
|
|
29
|
-
// Only for desktop.
|
|
30
|
-
const inboxMessages = chatSelectors.inboxActiveTopicMessages(getChatStoreState());
|
|
31
|
-
if (inboxMessages.length > 0) {
|
|
32
|
-
await openNewTopicOrSaveTopic();
|
|
33
|
-
}
|
|
34
|
-
} else {
|
|
35
|
-
navigateToAgent(INBOX_SESSION_ID);
|
|
36
|
-
}
|
|
26
|
+
navigateToAgent(inboxAgentId);
|
|
37
27
|
}}
|
|
38
|
-
to={SESSION_CHAT_URL(
|
|
28
|
+
to={SESSION_CHAT_URL(inboxAgentId, mobile)}
|
|
39
29
|
>
|
|
40
30
|
<ListItem
|
|
41
|
-
active={
|
|
31
|
+
active={isInboxActive}
|
|
42
32
|
avatar={DEFAULT_INBOX_AVATAR}
|
|
43
|
-
key={
|
|
33
|
+
key={'inbox'}
|
|
44
34
|
styles={{
|
|
45
35
|
container: {
|
|
46
36
|
gap: 12,
|
|
@@ -227,7 +227,7 @@ export interface TopicBatchWorkflowPayload extends MemoryExtractionPayloadInput
|
|
|
227
227
|
userId: string;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
type ProviderKeyVaultMap = Record<
|
|
230
|
+
export type ProviderKeyVaultMap = Record<
|
|
231
231
|
string,
|
|
232
232
|
AiProviderRuntimeState['runtimeConfig'][string]['keyVaults'] | undefined
|
|
233
233
|
>;
|
|
@@ -296,16 +296,16 @@ const maskSecret = (value?: string) => {
|
|
|
296
296
|
return `${value.slice(0, 6)}***${value.slice(-4)}`;
|
|
297
297
|
};
|
|
298
298
|
|
|
299
|
-
type ProviderCredential = { apiKey?: string; baseURL?: string };
|
|
299
|
+
export type ProviderCredential = { apiKey?: string; baseURL?: string };
|
|
300
300
|
|
|
301
|
-
type RuntimeResolveOptions = {
|
|
301
|
+
export type RuntimeResolveOptions = {
|
|
302
302
|
fallback?: ProviderCredential;
|
|
303
303
|
preferred?: {
|
|
304
304
|
providerIds?: string[];
|
|
305
305
|
};
|
|
306
306
|
};
|
|
307
307
|
|
|
308
|
-
const resolveRuntimeAgentConfig = (
|
|
308
|
+
export const resolveRuntimeAgentConfig = (
|
|
309
309
|
agent: MemoryAgentConfig,
|
|
310
310
|
keyVaults?: ProviderKeyVaultMap,
|
|
311
311
|
options?: RuntimeResolveOptions,
|
|
@@ -323,34 +323,63 @@ const resolveRuntimeAgentConfig = (
|
|
|
323
323
|
);
|
|
324
324
|
|
|
325
325
|
for (const provider of providerOrder) {
|
|
326
|
+
if (provider === 'lobehub') {
|
|
327
|
+
debugRuntimeInit(agent, {
|
|
328
|
+
provider,
|
|
329
|
+
source: 'user-vault' as const,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
return ModelRuntime.initializeWithProvider(provider, {});
|
|
333
|
+
}
|
|
334
|
+
|
|
326
335
|
const { apiKey: userApiKey, baseURL: userBaseURL } = extractCredentialsFromVault(
|
|
327
336
|
keyVaults?.[provider],
|
|
328
337
|
);
|
|
329
|
-
if (!userApiKey)
|
|
338
|
+
if (!userApiKey) {
|
|
339
|
+
console.warn(
|
|
340
|
+
`[memory-extraction] skipping provider ${provider} due to missing API key in user vault`,
|
|
341
|
+
);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
debugRuntimeInit(agent, {
|
|
346
|
+
apiKey: userApiKey,
|
|
347
|
+
baseURL: userBaseURL,
|
|
348
|
+
provider,
|
|
349
|
+
source: 'user-vault' as const,
|
|
350
|
+
});
|
|
330
351
|
|
|
331
352
|
// Only use the user baseURL if we are also using their API key; otherwise fall back entirely
|
|
332
353
|
// to system config to avoid mixing credentials.
|
|
333
|
-
return {
|
|
354
|
+
return ModelRuntime.initializeWithProvider(provider, {
|
|
334
355
|
apiKey: userApiKey,
|
|
335
|
-
baseURL: userBaseURL
|
|
336
|
-
|
|
337
|
-
source: 'user-keyvault' as const,
|
|
338
|
-
};
|
|
356
|
+
baseURL: userBaseURL,
|
|
357
|
+
});
|
|
339
358
|
}
|
|
340
359
|
|
|
341
|
-
|
|
360
|
+
debugRuntimeInit(agent, {
|
|
342
361
|
apiKey: agent.apiKey || options?.fallback?.apiKey,
|
|
343
362
|
baseURL: agent.baseURL || options?.fallback?.baseURL,
|
|
344
363
|
provider: agent.provider || 'openai',
|
|
345
364
|
source: 'system-config' as const,
|
|
346
|
-
};
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
return ModelRuntime.initializeWithProvider(agent.provider || 'openai', {
|
|
368
|
+
apiKey: agent.apiKey || options?.fallback?.apiKey,
|
|
369
|
+
baseURL: agent.baseURL || options?.fallback?.baseURL,
|
|
370
|
+
});
|
|
347
371
|
};
|
|
348
372
|
|
|
349
373
|
const logRuntime = debug('lobe-server:memory:user-memory:runtime');
|
|
350
374
|
|
|
351
375
|
const debugRuntimeInit = (
|
|
352
376
|
agent: MemoryAgentConfig,
|
|
353
|
-
resolved:
|
|
377
|
+
resolved: {
|
|
378
|
+
apiKey?: string;
|
|
379
|
+
baseURL?: string;
|
|
380
|
+
provider: string;
|
|
381
|
+
source: 'user-vault' | 'system-config';
|
|
382
|
+
},
|
|
354
383
|
) => {
|
|
355
384
|
if (!logRuntime.enabled) return;
|
|
356
385
|
logRuntime('init runtime', {
|
|
@@ -363,24 +392,6 @@ const debugRuntimeInit = (
|
|
|
363
392
|
});
|
|
364
393
|
};
|
|
365
394
|
|
|
366
|
-
const initRuntimeForAgent = async (
|
|
367
|
-
agent: MemoryAgentConfig,
|
|
368
|
-
keyVaults?: ProviderKeyVaultMap,
|
|
369
|
-
options?: RuntimeResolveOptions,
|
|
370
|
-
) => {
|
|
371
|
-
const resolved = resolveRuntimeAgentConfig(agent, keyVaults, options);
|
|
372
|
-
debugRuntimeInit(agent, resolved);
|
|
373
|
-
|
|
374
|
-
if (!resolved.apiKey) {
|
|
375
|
-
throw new Error(`Missing API key for ${resolved.provider} memory extraction runtime`);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return ModelRuntime.initializeWithProvider(resolved.provider, {
|
|
379
|
-
apiKey: resolved.apiKey,
|
|
380
|
-
baseURL: resolved.baseURL,
|
|
381
|
-
});
|
|
382
|
-
};
|
|
383
|
-
|
|
384
395
|
const isTopicExtracted = (metadata?: ChatTopicMetadata | null): boolean => {
|
|
385
396
|
const extractStatus = metadata?.userMemoryExtractStatus;
|
|
386
397
|
if (extractStatus) return extractStatus === 'completed';
|
|
@@ -1957,17 +1968,17 @@ export class MemoryExtractionExecutor {
|
|
|
1957
1968
|
};
|
|
1958
1969
|
|
|
1959
1970
|
const runtimes: RuntimeBundle = {
|
|
1960
|
-
embeddings: await
|
|
1971
|
+
embeddings: await resolveRuntimeAgentConfig(
|
|
1961
1972
|
{ ...this.privateConfig.embedding },
|
|
1962
1973
|
keyVaults,
|
|
1963
1974
|
embeddingOptions,
|
|
1964
1975
|
),
|
|
1965
|
-
gatekeeper: await
|
|
1976
|
+
gatekeeper: await resolveRuntimeAgentConfig(
|
|
1966
1977
|
{ ...this.privateConfig.agentGateKeeper },
|
|
1967
1978
|
keyVaults,
|
|
1968
1979
|
gatekeeperOptions,
|
|
1969
1980
|
),
|
|
1970
|
-
layerExtractor: await
|
|
1981
|
+
layerExtractor: await resolveRuntimeAgentConfig(
|
|
1971
1982
|
{ ...this.privateConfig.agentLayerExtractor },
|
|
1972
1983
|
keyVaults,
|
|
1973
1984
|
layerExtractorOptions,
|
|
@@ -75,10 +75,8 @@ vi.mock('@lobechat/memory-user-memory', () => ({
|
|
|
75
75
|
})),
|
|
76
76
|
}));
|
|
77
77
|
|
|
78
|
-
vi.mock('
|
|
79
|
-
|
|
80
|
-
initializeWithProvider: vi.fn().mockResolvedValue({}),
|
|
81
|
-
},
|
|
78
|
+
vi.mock('@/server/services/memory/userMemory/extract', () => ({
|
|
79
|
+
resolveRuntimeAgentConfig: vi.fn().mockResolvedValue({}),
|
|
82
80
|
}));
|
|
83
81
|
|
|
84
82
|
let db: LobeChatDatabase;
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
type UserPersonaExtractionResult,
|
|
10
10
|
UserPersonaExtractor,
|
|
11
11
|
} from '@lobechat/memory-user-memory';
|
|
12
|
-
import { ModelRuntime } from '@lobechat/model-runtime';
|
|
13
12
|
import { desc, eq } from 'drizzle-orm';
|
|
14
13
|
|
|
15
14
|
import { UserMemoryModel } from '@/database/models/userMemory';
|
|
@@ -21,26 +20,14 @@ import {
|
|
|
21
20
|
parseMemoryExtractionConfig,
|
|
22
21
|
} from '@/server/globalConfig/parseMemoryExtractionConfig';
|
|
23
22
|
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
|
23
|
+
import {
|
|
24
|
+
type ProviderKeyVaultMap,
|
|
25
|
+
type RuntimeResolveOptions,
|
|
26
|
+
resolveRuntimeAgentConfig,
|
|
27
|
+
} from '@/server/services/memory/userMemory/extract';
|
|
24
28
|
import { LayersEnum } from '@/types/userMemory';
|
|
25
29
|
import { trimBasedOnBatchProbe } from '@/utils/chunkers';
|
|
26
30
|
|
|
27
|
-
const extractCredentialsFromVault = (
|
|
28
|
-
vault?: Record<string, unknown>,
|
|
29
|
-
): { apiKey?: string; baseURL?: string } => {
|
|
30
|
-
if (!vault || typeof vault !== 'object') return {};
|
|
31
|
-
|
|
32
|
-
const apiKey =
|
|
33
|
-
'apiKey' in vault && typeof (vault as any).apiKey === 'string'
|
|
34
|
-
? (vault as any).apiKey
|
|
35
|
-
: undefined;
|
|
36
|
-
const baseURL =
|
|
37
|
-
'baseURL' in vault && typeof (vault as any).baseURL === 'string'
|
|
38
|
-
? (vault as any).baseURL
|
|
39
|
-
: undefined;
|
|
40
|
-
|
|
41
|
-
return { apiKey, baseURL };
|
|
42
|
-
};
|
|
43
|
-
|
|
44
31
|
interface UserPersonaAgentPayload {
|
|
45
32
|
existingPersona?: string | null;
|
|
46
33
|
language?: string;
|
|
@@ -79,28 +66,27 @@ export class UserPersonaService {
|
|
|
79
66
|
const runtimeState = await aiInfraRepos.getAiProviderRuntimeState(
|
|
80
67
|
KeyVaultsGateKeeper.getUserKeyVaults,
|
|
81
68
|
);
|
|
82
|
-
|
|
83
69
|
const providerId = await AiInfraRepos.tryMatchingProviderFrom(runtimeState, {
|
|
84
70
|
fallbackProvider: this.agentConfig.provider,
|
|
85
71
|
label: 'persona writer',
|
|
86
72
|
modelId: this.agentConfig.model,
|
|
87
73
|
});
|
|
88
74
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
75
|
+
const keyVaults: ProviderKeyVaultMap = Object.entries(runtimeState.runtimeConfig || {}).reduce(
|
|
76
|
+
(acc, [provider, config]) => {
|
|
77
|
+
acc[provider.toLowerCase()] = config?.keyVaults;
|
|
78
|
+
return acc;
|
|
79
|
+
},
|
|
80
|
+
{} as ProviderKeyVaultMap,
|
|
92
81
|
);
|
|
93
82
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
apiKey,
|
|
102
|
-
baseURL,
|
|
103
|
-
});
|
|
83
|
+
const runtime = await resolveRuntimeAgentConfig({ ...this.agentConfig }, keyVaults, {
|
|
84
|
+
fallback: {
|
|
85
|
+
apiKey: this.agentConfig.apiKey,
|
|
86
|
+
baseURL: this.agentConfig.baseURL,
|
|
87
|
+
},
|
|
88
|
+
preferred: { providerIds: [providerId] },
|
|
89
|
+
} satisfies RuntimeResolveOptions);
|
|
104
90
|
|
|
105
91
|
const personaModel = new UserPersonaModel(this.db, payload.userId);
|
|
106
92
|
const lastDocument = await personaModel.getLatestPersonaDocument();
|