@lobehub/lobehub 2.0.0-next.190 → 2.0.0-next.192
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 +50 -0
- package/changelog/v1.json +18 -0
- package/e2e/docs/testing-tips.md +30 -0
- package/e2e/src/steps/discover/smoke.steps.ts +11 -33
- package/locales/zh-CN/auth.json +1 -1
- package/package.json +1 -2
- package/packages/const/src/utils/merge.test.ts +679 -0
- package/packages/context-engine/src/processors/__tests__/AgentCouncilFlatten.test.ts +0 -20
- package/packages/context-engine/src/processors/__tests__/MessageContent.test.ts +5 -23
- package/packages/context-engine/src/providers/SystemRoleInjector.ts +0 -1
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentCouncil/simple.json +4 -19
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentCouncil/with-supervisor-reply.json +4 -23
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentGroup/speak-different-agent.json +3 -13
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistant-chain-with-followup.json +3 -8
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistantGroup/assistant-with-tools.json +21 -17
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistantGroup/tools-with-branches.json +15 -15
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/active-index-1.json +3 -13
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-branch.json +2 -9
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-group-branches.json +0 -11
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-user-branch.json +5 -15
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/conversation.json +4 -14
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/multi-assistant-group.json +0 -13
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/nested.json +2 -15
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/simple.json +4 -9
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/with-tools.json +8 -17
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/linear-conversation.json +3 -7
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/simple.json +1 -7
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/with-summary.json +10 -11
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/agentCouncil/simple.json +2 -32
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/agentCouncil/with-supervisor-reply.json +8 -46
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/agentGroup/speak-different-agent.json +5 -24
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistant-chain-with-followup.json +5 -13
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistantGroup/assistant-with-tools.json +6 -16
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistantGroup/tools-with-branches.json +6 -17
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/active-index-1.json +4 -18
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-branch.json +4 -16
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-group-branches.json +0 -19
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-user-branch.json +8 -24
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/conversation.json +7 -23
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/multi-assistant-group.json +0 -15
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/nested.json +4 -25
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/simple.json +2 -13
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/with-tools.json +4 -20
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/linear-conversation.json +4 -12
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/tasks/simple.json +2 -14
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/tasks/with-summary.json +20 -22
- package/packages/conversation-flow/src/__tests__/indexing.test.ts +0 -35
- package/packages/conversation-flow/src/__tests__/structuring.test.ts +0 -41
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +0 -4
- package/packages/conversation-flow/src/transformation/__tests__/BranchResolver.test.ts +0 -10
- package/packages/conversation-flow/src/transformation/__tests__/ContextTreeBuilder.test.ts +0 -19
- package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +0 -37
- package/packages/conversation-flow/src/transformation/__tests__/MessageCollector.test.ts +0 -12
- package/packages/conversation-flow/src/transformation/__tests__/MessageTransformer.test.ts +0 -2
- package/packages/database/package.json +1 -1
- package/packages/database/src/client/db.test.ts +19 -144
- package/packages/database/src/client/db.ts +26 -234
- package/packages/database/src/models/__tests__/_util.ts +19 -3
- package/packages/database/src/models/message.ts +0 -1
- package/packages/prompts/src/prompts/chatMessages/index.test.ts +0 -1
- package/packages/prompts/src/prompts/groupChat/__snapshots__/index.test.ts.snap +0 -21
- package/packages/prompts/src/prompts/groupChat/index.test.ts +0 -3
- package/packages/types/src/message/ui/chat.ts +0 -2
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/ModelItem.tsx +8 -7
- package/src/features/Conversation/store/slices/data/action.test.ts +0 -14
- package/src/features/Conversation/store/slices/data/reducer.test.ts +0 -21
- package/src/features/Conversation/store/slices/data/reducer.ts +1 -1
- package/src/features/Conversation/store/slices/message/action/crud.test.ts +0 -10
- package/src/libs/next/config/define-config.ts +1 -1
- package/src/server/modules/Mecha/ContextEngineering/__tests__/serverMessagesEngine.test.ts +3 -5
- package/src/server/routers/lambda/__tests__/message.test.ts +1 -2
- package/src/server/services/agentRuntime/AgentRuntimeService.ts +109 -109
- package/src/server/services/agentRuntime/types.ts +8 -8
- package/src/server/services/doc/index.tsx +2 -2
- package/src/server/services/generation/index.ts +2 -2
- package/src/server/services/message/index.ts +3 -3
- package/src/server/services/usage/index.ts +4 -4
- package/src/services/chat/chat.test.ts +0 -6
- package/src/services/chat/mecha/contextEngineering.test.ts +3 -29
- package/src/services/chat/mecha/modelParamsResolver.test.ts +803 -0
- package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +0 -2
- package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +0 -6
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockMessages.ts +0 -3
- package/src/store/chat/agents/createAgentExecutors.ts +4 -4
- package/src/store/chat/slices/aiAgent/actions/__tests__/agentGroup.test.ts +0 -2
- package/src/store/chat/slices/aiAgent/actions/__tests__/runAgent.test.ts +0 -3
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +0 -1
- package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +0 -4
- package/src/store/chat/slices/message/reducer.test.ts +0 -5
- package/src/store/chat/slices/message/reducer.ts +1 -1
- package/src/store/chat/slices/message/selectors/displayMessage.test.ts +0 -13
- package/src/store/chat/slices/message/selectors/displayMessage.ts +3 -34
- package/src/store/chat/slices/portal/selectors.test.ts +0 -7
- package/src/store/chat/slices/thread/action.test.ts +0 -1
- package/src/store/chat/slices/translate/action.test.ts +0 -1
- package/src/store/tool/slices/oldStore/action.test.ts +0 -1
- package/packages/database/src/client/pglite.ts +0 -17
- package/packages/database/src/client/pglite.worker.ts +0 -25
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import { DatabaseManager } from './db';
|
|
5
|
-
|
|
6
|
-
// Mock 所有外部依赖
|
|
7
4
|
vi.mock('@electric-sql/pglite', () => ({
|
|
8
|
-
|
|
9
|
-
IdbFs: vi.fn(),
|
|
10
|
-
PGlite: vi.fn(),
|
|
11
|
-
MemoryFS: vi.fn(),
|
|
5
|
+
PGlite: vi.fn(() => ({})),
|
|
12
6
|
}));
|
|
13
7
|
|
|
14
8
|
vi.mock('@electric-sql/pglite/vector', () => ({
|
|
15
|
-
default: vi.fn(),
|
|
16
9
|
vector: vi.fn(),
|
|
17
10
|
}));
|
|
18
11
|
|
|
@@ -24,154 +17,36 @@ vi.mock('drizzle-orm/pglite', () => ({
|
|
|
24
17
|
})),
|
|
25
18
|
}));
|
|
26
19
|
|
|
27
|
-
let manager: DatabaseManager;
|
|
28
|
-
let progressEvents: ClientDBLoadingProgress[] = [];
|
|
29
|
-
let stateChanges: DatabaseLoadingState[] = [];
|
|
30
|
-
|
|
31
|
-
let callbacks = {
|
|
32
|
-
onProgress: vi.fn((progress: ClientDBLoadingProgress) => {
|
|
33
|
-
progressEvents.push(progress);
|
|
34
|
-
}),
|
|
35
|
-
onStateChange: vi.fn((state: DatabaseLoadingState) => {
|
|
36
|
-
stateChanges.push(state);
|
|
37
|
-
}),
|
|
38
|
-
};
|
|
39
|
-
|
|
40
20
|
beforeEach(() => {
|
|
41
21
|
vi.clearAllMocks();
|
|
42
|
-
|
|
43
|
-
stateChanges = [];
|
|
44
|
-
|
|
45
|
-
callbacks = {
|
|
46
|
-
onProgress: vi.fn((progress: ClientDBLoadingProgress) => {
|
|
47
|
-
progressEvents.push(progress);
|
|
48
|
-
}),
|
|
49
|
-
onStateChange: vi.fn((state: DatabaseLoadingState) => {
|
|
50
|
-
stateChanges.push(state);
|
|
51
|
-
}),
|
|
52
|
-
};
|
|
53
|
-
// @ts-expect-error
|
|
54
|
-
DatabaseManager['instance'] = undefined;
|
|
55
|
-
manager = DatabaseManager.getInstance();
|
|
22
|
+
vi.resetModules();
|
|
56
23
|
});
|
|
57
24
|
|
|
58
25
|
describe('DatabaseManager', () => {
|
|
59
|
-
describe('
|
|
60
|
-
it(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
DatabaseLoadingState.Initializing,
|
|
68
|
-
DatabaseLoadingState.LoadingDependencies,
|
|
69
|
-
DatabaseLoadingState.LoadingWasm,
|
|
70
|
-
DatabaseLoadingState.Migrating,
|
|
71
|
-
DatabaseLoadingState.Finished,
|
|
72
|
-
DatabaseLoadingState.Ready,
|
|
73
|
-
]);
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
timeout: 15000,
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
it('should report dependencies loading progress', async () => {
|
|
81
|
-
await manager.initialize(callbacks);
|
|
82
|
-
|
|
83
|
-
// 验证依赖加载进度回调
|
|
84
|
-
const dependencyProgress = progressEvents.filter((e) => e.phase === 'dependencies');
|
|
85
|
-
expect(dependencyProgress.length).toBeGreaterThan(0);
|
|
86
|
-
expect(dependencyProgress[dependencyProgress.length - 1]).toEqual(
|
|
87
|
-
expect.objectContaining({
|
|
88
|
-
phase: 'dependencies',
|
|
89
|
-
progress: 100,
|
|
90
|
-
costTime: expect.any(Number),
|
|
91
|
-
}),
|
|
92
|
-
);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should report WASM loading progress', async () => {
|
|
96
|
-
await manager.initialize(callbacks);
|
|
97
|
-
|
|
98
|
-
// 验证 WASM 加载进度回调
|
|
99
|
-
const wasmProgress = progressEvents.filter((e) => e.phase === 'wasm');
|
|
100
|
-
// expect(wasmProgress.length).toBeGreaterThan(0);
|
|
101
|
-
expect(wasmProgress[wasmProgress.length - 1]).toEqual(
|
|
102
|
-
expect.objectContaining({
|
|
103
|
-
phase: 'wasm',
|
|
104
|
-
progress: 100,
|
|
105
|
-
costTime: expect.any(Number),
|
|
106
|
-
}),
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should handle initialization errors', async () => {
|
|
111
|
-
// 模拟加载失败
|
|
112
|
-
vi.spyOn(global, 'fetch').mockRejectedValueOnce(new Error('Network error'));
|
|
113
|
-
|
|
114
|
-
await expect(manager.initialize(callbacks)).rejects.toThrow();
|
|
115
|
-
expect(stateChanges).toContain(DatabaseLoadingState.Error);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('should only initialize once when called multiple times', async () => {
|
|
119
|
-
const firstInit = manager.initialize(callbacks);
|
|
120
|
-
const secondInit = manager.initialize(callbacks);
|
|
121
|
-
|
|
122
|
-
await Promise.all([firstInit, secondInit]);
|
|
123
|
-
|
|
124
|
-
// 验证回调只被调用一次
|
|
125
|
-
const readyStateCount = stateChanges.filter(
|
|
126
|
-
(state) => state === DatabaseLoadingState.Ready,
|
|
127
|
-
).length;
|
|
128
|
-
expect(readyStateCount).toBe(1);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('Progress Calculation', () => {
|
|
133
|
-
it('should report progress between 0 and 100', async () => {
|
|
134
|
-
await manager.initialize(callbacks);
|
|
135
|
-
|
|
136
|
-
// 验证所有进度值都在有效范围内
|
|
137
|
-
progressEvents.forEach((event) => {
|
|
138
|
-
expect(event.progress).toBeGreaterThanOrEqual(0);
|
|
139
|
-
expect(event.progress).toBeLessThanOrEqual(100);
|
|
26
|
+
describe('initializeDB', () => {
|
|
27
|
+
it('should initialize database with PGlite', async () => {
|
|
28
|
+
const { initializeDB } = await import('./db');
|
|
29
|
+
await initializeDB();
|
|
30
|
+
|
|
31
|
+
expect(PGlite).toHaveBeenCalledWith('idb://lobechat', {
|
|
32
|
+
extensions: { vector: expect.any(Function) },
|
|
33
|
+
relaxedDurability: true,
|
|
140
34
|
});
|
|
141
35
|
});
|
|
142
36
|
|
|
143
|
-
it('should
|
|
144
|
-
await
|
|
145
|
-
|
|
146
|
-
// 验证最终进度回调包含耗时信息
|
|
147
|
-
const finalProgress = progressEvents[progressEvents.length - 1];
|
|
148
|
-
expect(finalProgress.costTime).toBeGreaterThan(0);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe('Error Handling', () => {
|
|
153
|
-
it('should handle missing callbacks gracefully', async () => {
|
|
154
|
-
// 测试没有提供回调的情况
|
|
155
|
-
await expect(manager.initialize()).resolves.toBeDefined();
|
|
156
|
-
});
|
|
37
|
+
it('should only initialize once when called multiple times', async () => {
|
|
38
|
+
const { initializeDB } = await import('./db');
|
|
39
|
+
await Promise.all([initializeDB(), initializeDB()]);
|
|
157
40
|
|
|
158
|
-
|
|
159
|
-
// 只提供部分回调
|
|
160
|
-
await expect(manager.initialize({ onProgress: callbacks.onProgress })).resolves.toBeDefined();
|
|
161
|
-
await expect(
|
|
162
|
-
manager.initialize({ onStateChange: callbacks.onStateChange }),
|
|
163
|
-
).resolves.toBeDefined();
|
|
41
|
+
expect(PGlite).toHaveBeenCalledTimes(1);
|
|
164
42
|
});
|
|
165
43
|
});
|
|
166
44
|
|
|
167
|
-
describe('
|
|
168
|
-
it('should throw error when accessing database before initialization', () => {
|
|
169
|
-
expect(() => manager.db).toThrow('Database not initialized');
|
|
170
|
-
});
|
|
171
|
-
|
|
45
|
+
describe('clientDB proxy', () => {
|
|
172
46
|
it('should provide access to database after initialization', async () => {
|
|
173
|
-
await
|
|
174
|
-
|
|
47
|
+
const { clientDB, initializeDB } = await import('./db');
|
|
48
|
+
await initializeDB();
|
|
49
|
+
expect(clientDB).toBeDefined();
|
|
175
50
|
});
|
|
176
51
|
});
|
|
177
52
|
});
|
|
@@ -1,53 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
DatabaseLoadingState,
|
|
4
|
-
type MigrationSQL,
|
|
5
|
-
type MigrationTableItem,
|
|
6
|
-
} from '@lobechat/types';
|
|
1
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
2
|
+
import { vector } from '@electric-sql/pglite/vector';
|
|
7
3
|
import { sql } from 'drizzle-orm';
|
|
8
4
|
import { PgliteDatabase, drizzle } from 'drizzle-orm/pglite';
|
|
9
5
|
import { Md5 } from 'ts-md5';
|
|
10
6
|
|
|
11
|
-
import { sleep } from '@/utils/sleep';
|
|
12
|
-
|
|
13
7
|
import migrations from '../core/migrations.json';
|
|
14
8
|
import { DrizzleMigrationModel } from '../models/drizzleMigration';
|
|
15
9
|
import * as schema from '../schemas';
|
|
16
10
|
|
|
17
11
|
const pgliteSchemaHashCache = 'LOBE_CHAT_PGLITE_SCHEMA_HASH';
|
|
18
|
-
|
|
19
12
|
const DB_NAME = 'lobechat';
|
|
20
|
-
type DrizzleInstance = PgliteDatabase<typeof schema>;
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
error: Error;
|
|
24
|
-
migrationTableItems: MigrationTableItem[];
|
|
25
|
-
migrationsSQL: MigrationSQL[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface DatabaseLoadingCallbacks {
|
|
29
|
-
onError?: (error: onErrorState) => void;
|
|
30
|
-
onProgress?: (progress: ClientDBLoadingProgress) => void;
|
|
31
|
-
onStateChange?: (state: DatabaseLoadingState) => void;
|
|
32
|
-
}
|
|
14
|
+
type DrizzleInstance = PgliteDatabase<typeof schema>;
|
|
33
15
|
|
|
34
|
-
|
|
16
|
+
class DatabaseManager {
|
|
35
17
|
private static instance: DatabaseManager;
|
|
36
18
|
private dbInstance: DrizzleInstance | null = null;
|
|
37
19
|
private initPromise: Promise<DrizzleInstance> | null = null;
|
|
38
|
-
private callbacks?: DatabaseLoadingCallbacks;
|
|
39
20
|
private isLocalDBSchemaSynced = false;
|
|
40
21
|
|
|
41
|
-
// CDN configuration
|
|
42
|
-
private static WASM_CDN_URL =
|
|
43
|
-
'https://registry.npmmirror.com/@electric-sql/pglite/0.2.17/files/dist/postgres.wasm';
|
|
44
|
-
|
|
45
|
-
private static FSBUNDLER_CDN_URL =
|
|
46
|
-
'https://registry.npmmirror.com/@electric-sql/pglite/0.2.17/files/dist/postgres.data';
|
|
47
|
-
|
|
48
|
-
private static VECTOR_CDN_URL =
|
|
49
|
-
'https://registry.npmmirror.com/@electric-sql/pglite/0.2.17/files/dist/vector.tar.gz';
|
|
50
|
-
|
|
51
22
|
private constructor() {}
|
|
52
23
|
|
|
53
24
|
static getInstance() {
|
|
@@ -57,108 +28,8 @@ export class DatabaseManager {
|
|
|
57
28
|
return DatabaseManager.instance;
|
|
58
29
|
}
|
|
59
30
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const start = Date.now();
|
|
63
|
-
this.callbacks?.onStateChange?.(DatabaseLoadingState.LoadingWasm);
|
|
64
|
-
|
|
65
|
-
const response = await fetch(DatabaseManager.WASM_CDN_URL);
|
|
66
|
-
|
|
67
|
-
const contentLength = Number(response.headers.get('Content-Length')) || 0;
|
|
68
|
-
const reader = response.body?.getReader();
|
|
69
|
-
|
|
70
|
-
if (!reader) throw new Error('Failed to start WASM download');
|
|
71
|
-
|
|
72
|
-
let receivedLength = 0;
|
|
73
|
-
const chunks: Uint8Array[] = [];
|
|
74
|
-
|
|
75
|
-
// Read data stream
|
|
76
|
-
// eslint-disable-next-line no-constant-condition
|
|
77
|
-
while (true) {
|
|
78
|
-
const { done, value } = await reader.read();
|
|
79
|
-
|
|
80
|
-
if (done) break;
|
|
81
|
-
|
|
82
|
-
chunks.push(value);
|
|
83
|
-
receivedLength += value.length;
|
|
84
|
-
|
|
85
|
-
// Calculate and report progress
|
|
86
|
-
const progress = Math.min(Math.round((receivedLength / contentLength) * 100), 100);
|
|
87
|
-
this.callbacks?.onProgress?.({
|
|
88
|
-
phase: 'wasm',
|
|
89
|
-
progress,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Merge data chunks
|
|
94
|
-
const wasmBytes = new Uint8Array(receivedLength);
|
|
95
|
-
let position = 0;
|
|
96
|
-
for (const chunk of chunks) {
|
|
97
|
-
wasmBytes.set(chunk, position);
|
|
98
|
-
position += chunk.length;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
this.callbacks?.onProgress?.({
|
|
102
|
-
costTime: Date.now() - start,
|
|
103
|
-
phase: 'wasm',
|
|
104
|
-
progress: 100,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// Compile WASM module
|
|
108
|
-
return WebAssembly.compile(wasmBytes);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private fetchFsBundle = async () => {
|
|
112
|
-
const res = await fetch(DatabaseManager.FSBUNDLER_CDN_URL);
|
|
113
|
-
|
|
114
|
-
return await res.blob();
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Asynchronously load PGlite related dependencies
|
|
118
|
-
private async loadDependencies() {
|
|
119
|
-
const start = Date.now();
|
|
120
|
-
this.callbacks?.onStateChange?.(DatabaseLoadingState.LoadingDependencies);
|
|
121
|
-
|
|
122
|
-
const imports = [
|
|
123
|
-
import('@electric-sql/pglite').then((m) => ({
|
|
124
|
-
IdbFs: m.IdbFs,
|
|
125
|
-
MemoryFS: m.MemoryFS,
|
|
126
|
-
PGlite: m.PGlite,
|
|
127
|
-
})),
|
|
128
|
-
import('@electric-sql/pglite/vector'),
|
|
129
|
-
this.fetchFsBundle(),
|
|
130
|
-
];
|
|
131
|
-
|
|
132
|
-
let loaded = 0;
|
|
133
|
-
const results = await Promise.all(
|
|
134
|
-
imports.map(async (importPromise) => {
|
|
135
|
-
const result = await importPromise;
|
|
136
|
-
loaded += 1;
|
|
137
|
-
|
|
138
|
-
// Calculate loading progress
|
|
139
|
-
this.callbacks?.onProgress?.({
|
|
140
|
-
phase: 'dependencies',
|
|
141
|
-
progress: Math.min(Math.round((loaded / imports.length) * 100), 100),
|
|
142
|
-
});
|
|
143
|
-
return result;
|
|
144
|
-
}),
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
this.callbacks?.onProgress?.({
|
|
148
|
-
costTime: Date.now() - start,
|
|
149
|
-
phase: 'dependencies',
|
|
150
|
-
progress: 100,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// @ts-ignore
|
|
154
|
-
const [{ PGlite, IdbFs, MemoryFS }, { vector }, fsBundle] = results;
|
|
155
|
-
|
|
156
|
-
return { IdbFs, MemoryFS, PGlite, fsBundle, vector };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Database migration method
|
|
160
|
-
private async migrate(skipMultiRun = false): Promise<DrizzleInstance> {
|
|
161
|
-
if (this.isLocalDBSchemaSynced && skipMultiRun) return this.db;
|
|
31
|
+
private async migrate(): Promise<DrizzleInstance> {
|
|
32
|
+
if (this.isLocalDBSchemaSynced) return this.db;
|
|
162
33
|
|
|
163
34
|
let hash: string | undefined;
|
|
164
35
|
if (typeof localStorage !== 'undefined') {
|
|
@@ -179,17 +50,13 @@ export class DatabaseManager {
|
|
|
179
50
|
}
|
|
180
51
|
} catch (error) {
|
|
181
52
|
console.warn('Error checking table existence, proceeding with migration', error);
|
|
182
|
-
// If query fails, continue migration to ensure safety
|
|
183
53
|
}
|
|
184
54
|
}
|
|
185
55
|
}
|
|
186
56
|
|
|
187
57
|
const start = Date.now();
|
|
188
58
|
try {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// refs: https://github.com/drizzle-team/drizzle-orm/discussions/2532
|
|
192
|
-
// @ts-expect-error
|
|
59
|
+
// @ts-expect-error - migrate internal API
|
|
193
60
|
await this.db.dialect.migrate(migrations, this.db.session, {});
|
|
194
61
|
|
|
195
62
|
if (typeof localStorage !== 'undefined' && hash) {
|
|
@@ -197,7 +64,6 @@ export class DatabaseManager {
|
|
|
197
64
|
}
|
|
198
65
|
|
|
199
66
|
this.isLocalDBSchemaSynced = true;
|
|
200
|
-
|
|
201
67
|
console.info(`🗂 Migration success, take ${Date.now() - start}ms`);
|
|
202
68
|
} catch (cause) {
|
|
203
69
|
console.error('❌ Local database schema migration failed', cause);
|
|
@@ -207,95 +73,32 @@ export class DatabaseManager {
|
|
|
207
73
|
return this.db;
|
|
208
74
|
}
|
|
209
75
|
|
|
210
|
-
|
|
211
|
-
async initialize(callbacks?: DatabaseLoadingCallbacks): Promise<DrizzleInstance> {
|
|
76
|
+
async initialize(): Promise<DrizzleInstance> {
|
|
212
77
|
if (this.initPromise) return this.initPromise;
|
|
213
78
|
|
|
214
|
-
this.callbacks = callbacks;
|
|
215
|
-
|
|
216
79
|
this.initPromise = (async () => {
|
|
217
|
-
|
|
218
|
-
if (this.dbInstance) return this.dbInstance;
|
|
219
|
-
|
|
220
|
-
const time = Date.now();
|
|
221
|
-
// Initialize database
|
|
222
|
-
this.callbacks?.onStateChange?.(DatabaseLoadingState.Initializing);
|
|
223
|
-
|
|
224
|
-
// Load dependencies
|
|
225
|
-
const { fsBundle, PGlite, MemoryFS, IdbFs, vector } = await this.loadDependencies();
|
|
226
|
-
|
|
227
|
-
// Load and compile WASM module
|
|
228
|
-
const wasmModule = await this.loadWasmModule();
|
|
229
|
-
|
|
230
|
-
const { initPgliteWorker } = await import('./pglite');
|
|
231
|
-
|
|
232
|
-
let db: typeof PGlite;
|
|
233
|
-
|
|
234
|
-
// make db as web worker if worker is available
|
|
235
|
-
// https://github.com/lobehub/lobe-chat/issues/5785
|
|
236
|
-
if (typeof Worker !== 'undefined' && typeof navigator.locks !== 'undefined') {
|
|
237
|
-
db = await initPgliteWorker({
|
|
238
|
-
dbName: DB_NAME,
|
|
239
|
-
fsBundle: fsBundle as Blob,
|
|
240
|
-
vectorBundlePath: DatabaseManager.VECTOR_CDN_URL,
|
|
241
|
-
wasmModule,
|
|
242
|
-
});
|
|
243
|
-
} else {
|
|
244
|
-
// in edge runtime or test runtime, we don't have worker
|
|
245
|
-
db = new PGlite({
|
|
246
|
-
extensions: { vector },
|
|
247
|
-
fs: typeof window === 'undefined' ? new MemoryFS(DB_NAME) : new IdbFs(DB_NAME),
|
|
248
|
-
relaxedDurability: true,
|
|
249
|
-
wasmModule,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
this.dbInstance = drizzle({ client: db, schema });
|
|
80
|
+
if (this.dbInstance) return this.dbInstance;
|
|
254
81
|
|
|
255
|
-
|
|
82
|
+
const time = Date.now();
|
|
256
83
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
84
|
+
// 直接使用 pglite,自动处理 wasm 加载
|
|
85
|
+
const pglite = new PGlite(`idb://${DB_NAME}`, {
|
|
86
|
+
extensions: { vector },
|
|
87
|
+
relaxedDurability: true,
|
|
88
|
+
});
|
|
261
89
|
|
|
262
|
-
|
|
90
|
+
this.dbInstance = drizzle({ client: pglite, schema });
|
|
263
91
|
|
|
264
|
-
|
|
265
|
-
} catch (e) {
|
|
266
|
-
this.initPromise = null;
|
|
267
|
-
this.callbacks?.onStateChange?.(DatabaseLoadingState.Error);
|
|
268
|
-
const error = e as Error;
|
|
92
|
+
await this.migrate();
|
|
269
93
|
|
|
270
|
-
|
|
271
|
-
let migrationsTableData: MigrationTableItem[] = [];
|
|
272
|
-
try {
|
|
273
|
-
// Attempt to query migration table
|
|
274
|
-
const drizzleMigration = new DrizzleMigrationModel(this.db as any);
|
|
275
|
-
migrationsTableData = await drizzleMigration.getMigrationList();
|
|
276
|
-
} catch (queryError) {
|
|
277
|
-
console.error('Failed to query migrations table:', queryError);
|
|
278
|
-
}
|
|
94
|
+
console.log(`✅ Database initialized in ${Date.now() - time}ms`);
|
|
279
95
|
|
|
280
|
-
|
|
281
|
-
error: {
|
|
282
|
-
message: error.message,
|
|
283
|
-
name: error.name,
|
|
284
|
-
stack: error.stack,
|
|
285
|
-
},
|
|
286
|
-
migrationTableItems: migrationsTableData,
|
|
287
|
-
migrationsSQL: migrations,
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
console.error(error);
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
96
|
+
return this.dbInstance;
|
|
293
97
|
})();
|
|
294
98
|
|
|
295
99
|
return this.initPromise;
|
|
296
100
|
}
|
|
297
101
|
|
|
298
|
-
// Get database instance
|
|
299
102
|
get db(): DrizzleInstance {
|
|
300
103
|
if (!this.dbInstance) {
|
|
301
104
|
throw new Error('Database not initialized. Please call initialize() first.');
|
|
@@ -303,7 +106,6 @@ export class DatabaseManager {
|
|
|
303
106
|
return this.dbInstance;
|
|
304
107
|
}
|
|
305
108
|
|
|
306
|
-
// Create proxy object
|
|
307
109
|
createProxy(): DrizzleInstance {
|
|
308
110
|
return new Proxy({} as DrizzleInstance, {
|
|
309
111
|
get: (target, prop) => {
|
|
@@ -313,7 +115,7 @@ export class DatabaseManager {
|
|
|
313
115
|
}
|
|
314
116
|
|
|
315
117
|
async resetDatabase(): Promise<void> {
|
|
316
|
-
// 1. Close existing PGlite connection
|
|
118
|
+
// 1. Close existing PGlite connection
|
|
317
119
|
if (this.dbInstance) {
|
|
318
120
|
try {
|
|
319
121
|
// @ts-ignore
|
|
@@ -321,31 +123,28 @@ export class DatabaseManager {
|
|
|
321
123
|
console.log('PGlite instance closed successfully.');
|
|
322
124
|
} catch (e) {
|
|
323
125
|
console.error('Error closing PGlite instance:', e);
|
|
324
|
-
// Even if closing fails, continue with deletion attempt; IndexedDB onblocked or onerror will handle subsequent issues
|
|
325
126
|
}
|
|
326
127
|
}
|
|
327
128
|
|
|
328
129
|
// 2. Reset database instance and initialization state
|
|
329
130
|
this.dbInstance = null;
|
|
330
131
|
this.initPromise = null;
|
|
331
|
-
this.isLocalDBSchemaSynced = false;
|
|
132
|
+
this.isLocalDBSchemaSynced = false;
|
|
332
133
|
|
|
333
134
|
// 3. Delete IndexedDB database
|
|
334
135
|
return new Promise<void>((resolve, reject) => {
|
|
335
|
-
// Check if IndexedDB is available
|
|
336
136
|
if (typeof indexedDB === 'undefined') {
|
|
337
137
|
console.warn('IndexedDB is not available, cannot delete database');
|
|
338
|
-
resolve();
|
|
138
|
+
resolve();
|
|
339
139
|
return;
|
|
340
140
|
}
|
|
341
141
|
|
|
342
|
-
const dbName = `/pglite/${DB_NAME}`;
|
|
142
|
+
const dbName = `/pglite/${DB_NAME}`;
|
|
343
143
|
const request = indexedDB.deleteDatabase(dbName);
|
|
344
144
|
|
|
345
145
|
request.onsuccess = () => {
|
|
346
146
|
console.log(`✅ Database '${dbName}' reset successfully`);
|
|
347
147
|
|
|
348
|
-
// Clear locally stored schema hash
|
|
349
148
|
if (typeof localStorage !== 'undefined') {
|
|
350
149
|
localStorage.removeItem(pgliteSchemaHashCache);
|
|
351
150
|
}
|
|
@@ -365,14 +164,10 @@ export class DatabaseManager {
|
|
|
365
164
|
};
|
|
366
165
|
|
|
367
166
|
request.onblocked = (event) => {
|
|
368
|
-
|
|
369
|
-
console.warn(
|
|
370
|
-
`Deletion of database '${dbName}' is blocked. This usually means other connections (e.g., in other tabs) are still open. Event:`,
|
|
371
|
-
event,
|
|
372
|
-
);
|
|
167
|
+
console.warn(`Deletion of database '${dbName}' is blocked.`, event);
|
|
373
168
|
reject(
|
|
374
169
|
new Error(
|
|
375
|
-
`Failed to reset database '${dbName}' because it is blocked by other open connections
|
|
170
|
+
`Failed to reset database '${dbName}' because it is blocked by other open connections.`,
|
|
376
171
|
),
|
|
377
172
|
);
|
|
378
173
|
};
|
|
@@ -383,12 +178,9 @@ export class DatabaseManager {
|
|
|
383
178
|
// Export singleton
|
|
384
179
|
const dbManager = DatabaseManager.getInstance();
|
|
385
180
|
|
|
386
|
-
// Keep original clientDB export unchanged
|
|
387
181
|
export const clientDB = dbManager.createProxy();
|
|
388
182
|
|
|
389
|
-
|
|
390
|
-
export const initializeDB = (callbacks?: DatabaseLoadingCallbacks) =>
|
|
391
|
-
dbManager.initialize(callbacks);
|
|
183
|
+
export const initializeDB = () => dbManager.initialize();
|
|
392
184
|
|
|
393
185
|
export const resetClientDatabase = async () => {
|
|
394
186
|
await dbManager.resetDatabase();
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
2
|
+
import { vector } from '@electric-sql/pglite/vector';
|
|
3
|
+
import { drizzle } from 'drizzle-orm/pglite';
|
|
4
|
+
|
|
5
|
+
import migrations from '../../core/migrations.json';
|
|
6
|
+
import * as schema from '../../schemas';
|
|
2
7
|
import { LobeChatDatabase } from '../../type';
|
|
3
8
|
|
|
4
9
|
const isServerDBMode = process.env.TEST_SERVER_DB === '1';
|
|
5
10
|
|
|
11
|
+
let testClientDB: ReturnType<typeof drizzle<typeof schema>> | null = null;
|
|
12
|
+
|
|
6
13
|
export const getTestDB = async () => {
|
|
7
14
|
if (isServerDBMode) {
|
|
8
15
|
const { getTestDBInstance } = await import('../../core/dbForTest');
|
|
9
16
|
return await getTestDBInstance();
|
|
10
17
|
}
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
if (testClientDB) return testClientDB as unknown as LobeChatDatabase;
|
|
20
|
+
|
|
21
|
+
// 直接使用 pglite 内置资源,不需要从 CDN 下载
|
|
22
|
+
const pglite = new PGlite({ extensions: { vector } });
|
|
23
|
+
|
|
24
|
+
testClientDB = drizzle({ client: pglite, schema });
|
|
25
|
+
|
|
26
|
+
// @ts-expect-error - migrate internal API
|
|
27
|
+
await testClientDB.dialect.migrate(migrations, testClientDB.session, {});
|
|
28
|
+
|
|
29
|
+
return testClientDB as unknown as LobeChatDatabase;
|
|
14
30
|
};
|
|
@@ -485,7 +485,6 @@ export class MessageModel {
|
|
|
485
485
|
.filter((relation) => relation.messageId === item.id)
|
|
486
486
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
487
487
|
.map<ChatImageItem>(({ id, url, name }) => ({ alt: name!, id, url })),
|
|
488
|
-
meta: {},
|
|
489
488
|
|
|
490
489
|
model,
|
|
491
490
|
|