libre-webui 0.2.4
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/LICENSE +201 -0
- package/README.md +204 -0
- package/backend/dist/db.d.ts +19 -0
- package/backend/dist/db.d.ts.map +1 -0
- package/backend/dist/db.js +355 -0
- package/backend/dist/db.js.map +1 -0
- package/backend/dist/env.d.ts +2 -0
- package/backend/dist/env.d.ts.map +1 -0
- package/backend/dist/env.js +22 -0
- package/backend/dist/env.js.map +1 -0
- package/backend/dist/index.d.ts +4 -0
- package/backend/dist/index.d.ts.map +1 -0
- package/backend/dist/index.js +751 -0
- package/backend/dist/index.js.map +1 -0
- package/backend/dist/middleware/auth.d.ts +18 -0
- package/backend/dist/middleware/auth.d.ts.map +1 -0
- package/backend/dist/middleware/auth.js +98 -0
- package/backend/dist/middleware/auth.js.map +1 -0
- package/backend/dist/middleware/index.d.ts +5 -0
- package/backend/dist/middleware/index.d.ts.map +1 -0
- package/backend/dist/middleware/index.js +62 -0
- package/backend/dist/middleware/index.js.map +1 -0
- package/backend/dist/models/personaModel.d.ts +37 -0
- package/backend/dist/models/personaModel.d.ts.map +1 -0
- package/backend/dist/models/personaModel.js +269 -0
- package/backend/dist/models/personaModel.js.map +1 -0
- package/backend/dist/models/userModel.d.ts +86 -0
- package/backend/dist/models/userModel.d.ts.map +1 -0
- package/backend/dist/models/userModel.js +212 -0
- package/backend/dist/models/userModel.js.map +1 -0
- package/backend/dist/routes/auth.d.ts +3 -0
- package/backend/dist/routes/auth.d.ts.map +1 -0
- package/backend/dist/routes/auth.js +389 -0
- package/backend/dist/routes/auth.js.map +1 -0
- package/backend/dist/routes/chat.d.ts +3 -0
- package/backend/dist/routes/chat.d.ts.map +1 -0
- package/backend/dist/routes/chat.js +767 -0
- package/backend/dist/routes/chat.js.map +1 -0
- package/backend/dist/routes/documents.d.ts +3 -0
- package/backend/dist/routes/documents.d.ts.map +1 -0
- package/backend/dist/routes/documents.js +244 -0
- package/backend/dist/routes/documents.js.map +1 -0
- package/backend/dist/routes/ollama.d.ts +3 -0
- package/backend/dist/routes/ollama.d.ts.map +1 -0
- package/backend/dist/routes/ollama.js +549 -0
- package/backend/dist/routes/ollama.js.map +1 -0
- package/backend/dist/routes/personas.d.ts +3 -0
- package/backend/dist/routes/personas.d.ts.map +1 -0
- package/backend/dist/routes/personas.js +505 -0
- package/backend/dist/routes/personas.js.map +1 -0
- package/backend/dist/routes/plugins.d.ts +3 -0
- package/backend/dist/routes/plugins.d.ts.map +1 -0
- package/backend/dist/routes/plugins.js +417 -0
- package/backend/dist/routes/plugins.js.map +1 -0
- package/backend/dist/routes/preferences.d.ts +3 -0
- package/backend/dist/routes/preferences.d.ts.map +1 -0
- package/backend/dist/routes/preferences.js +303 -0
- package/backend/dist/routes/preferences.js.map +1 -0
- package/backend/dist/routes/tts.d.ts +3 -0
- package/backend/dist/routes/tts.d.ts.map +1 -0
- package/backend/dist/routes/tts.js +304 -0
- package/backend/dist/routes/tts.js.map +1 -0
- package/backend/dist/routes/users.d.ts +3 -0
- package/backend/dist/routes/users.d.ts.map +1 -0
- package/backend/dist/routes/users.js +246 -0
- package/backend/dist/routes/users.js.map +1 -0
- package/backend/dist/services/authService.d.ts +51 -0
- package/backend/dist/services/authService.d.ts.map +1 -0
- package/backend/dist/services/authService.js +153 -0
- package/backend/dist/services/authService.js.map +1 -0
- package/backend/dist/services/chatService.d.ts +52 -0
- package/backend/dist/services/chatService.d.ts.map +1 -0
- package/backend/dist/services/chatService.js +645 -0
- package/backend/dist/services/chatService.js.map +1 -0
- package/backend/dist/services/documentService.d.ts +34 -0
- package/backend/dist/services/documentService.d.ts.map +1 -0
- package/backend/dist/services/documentService.js +428 -0
- package/backend/dist/services/documentService.js.map +1 -0
- package/backend/dist/services/encryptionService.d.ts +62 -0
- package/backend/dist/services/encryptionService.d.ts.map +1 -0
- package/backend/dist/services/encryptionService.js +284 -0
- package/backend/dist/services/encryptionService.js.map +1 -0
- package/backend/dist/services/memoryService.d.ts +140 -0
- package/backend/dist/services/memoryService.d.ts.map +1 -0
- package/backend/dist/services/memoryService.js +867 -0
- package/backend/dist/services/memoryService.js.map +1 -0
- package/backend/dist/services/mutationEngineService.d.ts +49 -0
- package/backend/dist/services/mutationEngineService.d.ts.map +1 -0
- package/backend/dist/services/mutationEngineService.js +432 -0
- package/backend/dist/services/mutationEngineService.js.map +1 -0
- package/backend/dist/services/ollamaService.d.ts +55 -0
- package/backend/dist/services/ollamaService.d.ts.map +1 -0
- package/backend/dist/services/ollamaService.js +450 -0
- package/backend/dist/services/ollamaService.js.map +1 -0
- package/backend/dist/services/personaService.d.ts +67 -0
- package/backend/dist/services/personaService.d.ts.map +1 -0
- package/backend/dist/services/personaService.js +373 -0
- package/backend/dist/services/personaService.js.map +1 -0
- package/backend/dist/services/pluginService.d.ts +42 -0
- package/backend/dist/services/pluginService.d.ts.map +1 -0
- package/backend/dist/services/pluginService.js +961 -0
- package/backend/dist/services/pluginService.js.map +1 -0
- package/backend/dist/services/preferencesService.d.ts +35 -0
- package/backend/dist/services/preferencesService.d.ts.map +1 -0
- package/backend/dist/services/preferencesService.js +255 -0
- package/backend/dist/services/preferencesService.js.map +1 -0
- package/backend/dist/services/simpleGitHubOAuth.d.ts +48 -0
- package/backend/dist/services/simpleGitHubOAuth.d.ts.map +1 -0
- package/backend/dist/services/simpleGitHubOAuth.js +203 -0
- package/backend/dist/services/simpleGitHubOAuth.js.map +1 -0
- package/backend/dist/services/simpleHuggingFaceOAuth.d.ts +43 -0
- package/backend/dist/services/simpleHuggingFaceOAuth.d.ts.map +1 -0
- package/backend/dist/services/simpleHuggingFaceOAuth.js +159 -0
- package/backend/dist/services/simpleHuggingFaceOAuth.js.map +1 -0
- package/backend/dist/services/userService.d.ts +1 -0
- package/backend/dist/services/userService.d.ts.map +1 -0
- package/backend/dist/services/userService.js +18 -0
- package/backend/dist/services/userService.js.map +1 -0
- package/backend/dist/storage.d.ts +55 -0
- package/backend/dist/storage.d.ts.map +1 -0
- package/backend/dist/storage.js +741 -0
- package/backend/dist/storage.js.map +1 -0
- package/backend/dist/test-encryption.d.ts +2 -0
- package/backend/dist/test-encryption.d.ts.map +1 -0
- package/backend/dist/test-encryption.js +64 -0
- package/backend/dist/test-encryption.js.map +1 -0
- package/backend/dist/types/index.d.ts +523 -0
- package/backend/dist/types/index.d.ts.map +1 -0
- package/backend/dist/types/index.js +31 -0
- package/backend/dist/types/index.js.map +1 -0
- package/backend/dist/utils/generationUtils.d.ts +10 -0
- package/backend/dist/utils/generationUtils.d.ts.map +1 -0
- package/backend/dist/utils/generationUtils.js +49 -0
- package/backend/dist/utils/generationUtils.js.map +1 -0
- package/backend/dist/utils/hash.d.ts +29 -0
- package/backend/dist/utils/hash.d.ts.map +1 -0
- package/backend/dist/utils/hash.js +73 -0
- package/backend/dist/utils/hash.js.map +1 -0
- package/backend/dist/utils/jwt.d.ts +37 -0
- package/backend/dist/utils/jwt.d.ts.map +1 -0
- package/backend/dist/utils/jwt.js +86 -0
- package/backend/dist/utils/jwt.js.map +1 -0
- package/bin/cli.js +150 -0
- package/electron/main.js +322 -0
- package/frontend/dist/_redirects +1 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/frontend/dist/assets/index-CRQkB7Wz.js +3 -0
- package/frontend/dist/css/index-B1OjddR-.css +1 -0
- package/frontend/dist/favicon-dark.png +0 -0
- package/frontend/dist/favicon-light.png +0 -0
- package/frontend/dist/index.html +23 -0
- package/frontend/dist/js/ArtifactContainer-c_oi7XMs.js +23 -0
- package/frontend/dist/js/ArtifactDemoPage-CdfwJVXu.js +98 -0
- package/frontend/dist/js/ChatPage-CyotkmS0.js +281 -0
- package/frontend/dist/js/ModelsPage-DNaziPHc.js +2 -0
- package/frontend/dist/js/PersonasPage-DcnbJf8Q.js +13 -0
- package/frontend/dist/js/UserManagementPage-DtTf92dS.js +1 -0
- package/frontend/dist/js/markdown-vendor-D-79K2xZ.js +22 -0
- package/frontend/dist/js/react-vendor-N--QU9DW.js +8 -0
- package/frontend/dist/js/router-vendor-B-t91v39.js +3 -0
- package/frontend/dist/js/ui-vendor-VxSCY_bv.js +177 -0
- package/frontend/dist/js/utils-vendor-DNzxLBGx.js +6 -0
- package/frontend/dist/logo-dark.png +0 -0
- package/frontend/dist/logo-light.png +0 -0
- package/frontend/dist/logo.svg +14 -0
- package/package.json +128 -0
- package/plugins/anthropic.json +25 -0
- package/plugins/elevenlabs.json +58 -0
- package/plugins/gemini.json +57 -0
- package/plugins/github.json +23 -0
- package/plugins/groq.json +25 -0
- package/plugins/mistral.json +73 -0
- package/plugins/openai-tts.json +38 -0
- package/plugins/openai.json +132 -0
- package/plugins/openrouter.json +353 -0
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Libre WebUI
|
|
3
|
+
* Copyright (C) 2025 Kroonen AI, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at:
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
21
|
+
import bcrypt from 'bcrypt';
|
|
22
|
+
import getDatabase, { isDatabaseInitialized } from './db.js';
|
|
23
|
+
import { encryptionService } from './services/encryptionService.js';
|
|
24
|
+
// Get __dirname equivalent for ES modules
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = path.dirname(__filename);
|
|
27
|
+
class StorageService {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.useSQLite = false;
|
|
30
|
+
this.sessionsFile = path.join(__dirname, '..', 'sessions.json');
|
|
31
|
+
this.preferencesFile = path.join(__dirname, '..', 'preferences.json');
|
|
32
|
+
this.documentsFile = path.join(__dirname, '..', 'documents.json');
|
|
33
|
+
this.documentChunksFile = path.join(__dirname, '..', 'document-chunks.json');
|
|
34
|
+
// Check if SQLite should be used
|
|
35
|
+
this.useSQLite = isDatabaseInitialized();
|
|
36
|
+
console.log(`Storage mode: ${this.useSQLite ? 'SQLite' : 'JSON'}`);
|
|
37
|
+
}
|
|
38
|
+
// =================================
|
|
39
|
+
// USER MANAGEMENT
|
|
40
|
+
// =================================
|
|
41
|
+
async createUser(username, email, password, role = 'user') {
|
|
42
|
+
const userId = uuidv4();
|
|
43
|
+
const passwordHash = await bcrypt.hash(password, 12);
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
const user = {
|
|
46
|
+
id: userId,
|
|
47
|
+
username,
|
|
48
|
+
email,
|
|
49
|
+
password_hash: passwordHash,
|
|
50
|
+
role,
|
|
51
|
+
created_at: now,
|
|
52
|
+
updated_at: now,
|
|
53
|
+
};
|
|
54
|
+
if (this.useSQLite) {
|
|
55
|
+
const db = getDatabase();
|
|
56
|
+
const stmt = db.prepare(`
|
|
57
|
+
INSERT INTO users (id, username, email, password_hash, role, created_at, updated_at)
|
|
58
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
59
|
+
`);
|
|
60
|
+
// Encrypt sensitive user data
|
|
61
|
+
const encryptedEmail = user.email
|
|
62
|
+
? encryptionService.encrypt(user.email)
|
|
63
|
+
: null;
|
|
64
|
+
stmt.run(user.id, user.username, encryptedEmail, user.password_hash, user.role, user.created_at, user.updated_at);
|
|
65
|
+
}
|
|
66
|
+
return user;
|
|
67
|
+
}
|
|
68
|
+
getUser(userId) {
|
|
69
|
+
if (this.useSQLite) {
|
|
70
|
+
const db = getDatabase();
|
|
71
|
+
const stmt = db.prepare('SELECT * FROM users WHERE id = ?');
|
|
72
|
+
const user = stmt.get(userId);
|
|
73
|
+
if (user && user.email) {
|
|
74
|
+
// Decrypt email
|
|
75
|
+
user.email = encryptionService.decrypt(user.email);
|
|
76
|
+
}
|
|
77
|
+
return user;
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
getUserByUsername(username) {
|
|
82
|
+
if (this.useSQLite) {
|
|
83
|
+
const db = getDatabase();
|
|
84
|
+
const stmt = db.prepare('SELECT * FROM users WHERE username = ?');
|
|
85
|
+
const user = stmt.get(username);
|
|
86
|
+
if (user && user.email) {
|
|
87
|
+
// Decrypt email
|
|
88
|
+
user.email = encryptionService.decrypt(user.email);
|
|
89
|
+
}
|
|
90
|
+
return user;
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
// =================================
|
|
95
|
+
// SESSION MANAGEMENT
|
|
96
|
+
// =================================
|
|
97
|
+
getAllSessions(userId = 'default') {
|
|
98
|
+
if (this.useSQLite) {
|
|
99
|
+
const db = getDatabase();
|
|
100
|
+
// Get sessions
|
|
101
|
+
const sessionsStmt = db.prepare(`
|
|
102
|
+
SELECT * FROM sessions WHERE user_id = ? ORDER BY updated_at DESC
|
|
103
|
+
`);
|
|
104
|
+
const sessions = sessionsStmt.all(userId);
|
|
105
|
+
// Get ALL messages for each session (including branches for side-by-side display)
|
|
106
|
+
const messagesStmt = db.prepare(`
|
|
107
|
+
SELECT * FROM session_messages
|
|
108
|
+
WHERE session_id = ?
|
|
109
|
+
ORDER BY message_index ASC, branch_index ASC
|
|
110
|
+
`);
|
|
111
|
+
// Get sibling counts for branching (count all variants for each parent)
|
|
112
|
+
const siblingCountStmt = db.prepare(`
|
|
113
|
+
SELECT parent_id, COUNT(*) as count FROM session_messages
|
|
114
|
+
WHERE session_id = ? AND parent_id IS NOT NULL
|
|
115
|
+
GROUP BY parent_id
|
|
116
|
+
`);
|
|
117
|
+
return sessions.map(session => {
|
|
118
|
+
const messages = messagesStmt.all(session.id);
|
|
119
|
+
const siblingCounts = siblingCountStmt.all(session.id);
|
|
120
|
+
// Create a map for quick lookup of sibling counts
|
|
121
|
+
const siblingCountMap = new Map();
|
|
122
|
+
for (const sc of siblingCounts) {
|
|
123
|
+
// Add 1 to include the original message in the count
|
|
124
|
+
siblingCountMap.set(sc.parent_id, sc.count + 1);
|
|
125
|
+
}
|
|
126
|
+
// Decrypt session data
|
|
127
|
+
const decryptedTitle = encryptionService.decrypt(session.title);
|
|
128
|
+
return {
|
|
129
|
+
id: session.id,
|
|
130
|
+
title: decryptedTitle,
|
|
131
|
+
model: session.model,
|
|
132
|
+
personaId: session.persona_id || undefined,
|
|
133
|
+
createdAt: session.created_at,
|
|
134
|
+
updatedAt: session.updated_at,
|
|
135
|
+
messages: messages.map(msg => {
|
|
136
|
+
// Decrypt message data
|
|
137
|
+
const decryptedContent = encryptionService.decrypt(msg.content);
|
|
138
|
+
const decryptedImages = msg.images
|
|
139
|
+
? JSON.parse(encryptionService.decrypt(msg.images))
|
|
140
|
+
: undefined;
|
|
141
|
+
const decryptedStatistics = msg.statistics
|
|
142
|
+
? JSON.parse(encryptionService.decrypt(msg.statistics))
|
|
143
|
+
: undefined;
|
|
144
|
+
const decryptedArtifacts = msg.artifacts
|
|
145
|
+
? JSON.parse(encryptionService.decrypt(msg.artifacts))
|
|
146
|
+
: undefined;
|
|
147
|
+
// Calculate sibling count: if this message has variants, count them
|
|
148
|
+
// A message has siblings if it's a parent (has variants) or is a variant itself
|
|
149
|
+
const parentId = msg.parent_id || msg.id;
|
|
150
|
+
const siblingCount = siblingCountMap.get(parentId) || 1;
|
|
151
|
+
return {
|
|
152
|
+
id: msg.id,
|
|
153
|
+
role: msg.role,
|
|
154
|
+
content: decryptedContent,
|
|
155
|
+
timestamp: msg.timestamp,
|
|
156
|
+
model: msg.model,
|
|
157
|
+
images: decryptedImages,
|
|
158
|
+
statistics: decryptedStatistics,
|
|
159
|
+
artifacts: decryptedArtifacts,
|
|
160
|
+
parentId: msg.parent_id,
|
|
161
|
+
branchIndex: msg.branch_index ?? 0,
|
|
162
|
+
isActive: msg.is_active !== 0,
|
|
163
|
+
siblingCount: siblingCount > 1 ? siblingCount : undefined,
|
|
164
|
+
};
|
|
165
|
+
}),
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Fallback to JSON
|
|
171
|
+
try {
|
|
172
|
+
if (fs.existsSync(this.sessionsFile)) {
|
|
173
|
+
const data = fs.readFileSync(this.sessionsFile, 'utf8');
|
|
174
|
+
return JSON.parse(data);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error('Failed to load sessions from JSON:', error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
getSession(sessionId, userId = 'default') {
|
|
184
|
+
if (this.useSQLite) {
|
|
185
|
+
const db = getDatabase();
|
|
186
|
+
// Get session
|
|
187
|
+
const sessionStmt = db.prepare(`
|
|
188
|
+
SELECT * FROM sessions WHERE id = ? AND user_id = ?
|
|
189
|
+
`);
|
|
190
|
+
const session = sessionStmt.get(sessionId, userId);
|
|
191
|
+
if (!session)
|
|
192
|
+
return undefined;
|
|
193
|
+
// Get ALL messages (including branches for side-by-side display)
|
|
194
|
+
const messagesStmt = db.prepare(`
|
|
195
|
+
SELECT * FROM session_messages
|
|
196
|
+
WHERE session_id = ?
|
|
197
|
+
ORDER BY message_index ASC, branch_index ASC
|
|
198
|
+
`);
|
|
199
|
+
const messages = messagesStmt.all(sessionId);
|
|
200
|
+
// Get sibling counts for branching
|
|
201
|
+
const siblingCountStmt = db.prepare(`
|
|
202
|
+
SELECT parent_id, COUNT(*) as count FROM session_messages
|
|
203
|
+
WHERE session_id = ? AND parent_id IS NOT NULL
|
|
204
|
+
GROUP BY parent_id
|
|
205
|
+
`);
|
|
206
|
+
const siblingCounts = siblingCountStmt.all(sessionId);
|
|
207
|
+
// Create a map for quick lookup of sibling counts
|
|
208
|
+
const siblingCountMap = new Map();
|
|
209
|
+
for (const sc of siblingCounts) {
|
|
210
|
+
siblingCountMap.set(sc.parent_id, sc.count + 1);
|
|
211
|
+
}
|
|
212
|
+
// Decrypt session data
|
|
213
|
+
const decryptedTitle = encryptionService.decrypt(session.title);
|
|
214
|
+
return {
|
|
215
|
+
id: session.id,
|
|
216
|
+
title: decryptedTitle,
|
|
217
|
+
model: session.model,
|
|
218
|
+
createdAt: session.created_at,
|
|
219
|
+
updatedAt: session.updated_at,
|
|
220
|
+
messages: messages.map(msg => {
|
|
221
|
+
// Decrypt message data
|
|
222
|
+
const decryptedContent = encryptionService.decrypt(msg.content);
|
|
223
|
+
const decryptedImages = msg.images
|
|
224
|
+
? JSON.parse(encryptionService.decrypt(msg.images))
|
|
225
|
+
: undefined;
|
|
226
|
+
const decryptedStatistics = msg.statistics
|
|
227
|
+
? JSON.parse(encryptionService.decrypt(msg.statistics))
|
|
228
|
+
: undefined;
|
|
229
|
+
const decryptedArtifacts = msg.artifacts
|
|
230
|
+
? JSON.parse(encryptionService.decrypt(msg.artifacts))
|
|
231
|
+
: undefined;
|
|
232
|
+
const parentId = msg.parent_id || msg.id;
|
|
233
|
+
const siblingCount = siblingCountMap.get(parentId) || 1;
|
|
234
|
+
return {
|
|
235
|
+
id: msg.id,
|
|
236
|
+
role: msg.role,
|
|
237
|
+
content: decryptedContent,
|
|
238
|
+
timestamp: msg.timestamp,
|
|
239
|
+
model: msg.model,
|
|
240
|
+
images: decryptedImages,
|
|
241
|
+
statistics: decryptedStatistics,
|
|
242
|
+
artifacts: decryptedArtifacts,
|
|
243
|
+
parentId: msg.parent_id,
|
|
244
|
+
branchIndex: msg.branch_index ?? 0,
|
|
245
|
+
isActive: msg.is_active !== 0,
|
|
246
|
+
siblingCount: siblingCount > 1 ? siblingCount : undefined,
|
|
247
|
+
};
|
|
248
|
+
}),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
// Fallback to JSON
|
|
253
|
+
const sessions = this.getAllSessions();
|
|
254
|
+
return sessions.find(s => s.id === sessionId);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
saveSession(session, userId = 'default') {
|
|
258
|
+
if (this.useSQLite) {
|
|
259
|
+
const db = getDatabase();
|
|
260
|
+
// Use transaction for consistency
|
|
261
|
+
const transaction = db.transaction((session) => {
|
|
262
|
+
// Insert or update session
|
|
263
|
+
const sessionStmt = db.prepare(`
|
|
264
|
+
INSERT OR REPLACE INTO sessions (id, user_id, title, model, persona_id, created_at, updated_at)
|
|
265
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
266
|
+
`);
|
|
267
|
+
// Encrypt sensitive session data
|
|
268
|
+
const encryptedTitle = encryptionService.encrypt(session.title);
|
|
269
|
+
sessionStmt.run(session.id, userId, encryptedTitle, session.model, session.personaId || null, session.createdAt, session.updatedAt);
|
|
270
|
+
// Delete existing messages
|
|
271
|
+
const deleteMessagesStmt = db.prepare('DELETE FROM session_messages WHERE session_id = ?');
|
|
272
|
+
deleteMessagesStmt.run(session.id);
|
|
273
|
+
// Insert messages
|
|
274
|
+
if (session.messages && session.messages.length > 0) {
|
|
275
|
+
const insertMessageStmt = db.prepare(`
|
|
276
|
+
INSERT INTO session_messages (id, session_id, role, content, timestamp, message_index, model, images, statistics, artifacts, parent_id, branch_index, is_active)
|
|
277
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
278
|
+
`);
|
|
279
|
+
session.messages.forEach((message, index) => {
|
|
280
|
+
// Encrypt sensitive data before storing
|
|
281
|
+
const encryptedContent = encryptionService.encrypt(message.content);
|
|
282
|
+
const encryptedImages = message.images
|
|
283
|
+
? encryptionService.encrypt(JSON.stringify(message.images))
|
|
284
|
+
: null;
|
|
285
|
+
const encryptedStatistics = message.statistics
|
|
286
|
+
? encryptionService.encrypt(JSON.stringify(message.statistics))
|
|
287
|
+
: null;
|
|
288
|
+
const encryptedArtifacts = message.artifacts
|
|
289
|
+
? encryptionService.encrypt(JSON.stringify(message.artifacts))
|
|
290
|
+
: null;
|
|
291
|
+
// Use the message's own ID if it has one, otherwise generate a new one
|
|
292
|
+
const messageId = message.id || uuidv4();
|
|
293
|
+
insertMessageStmt.run(messageId, session.id, message.role, encryptedContent, message.timestamp, index, message.model || null, encryptedImages, encryptedStatistics, encryptedArtifacts, message.parentId || null, message.branchIndex ?? 0, message.isActive !== false ? 1 : 0);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
transaction(session);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
// Fallback to JSON
|
|
301
|
+
try {
|
|
302
|
+
const sessions = this.getAllSessions();
|
|
303
|
+
const existingIndex = sessions.findIndex(s => s.id === session.id);
|
|
304
|
+
if (existingIndex >= 0) {
|
|
305
|
+
sessions[existingIndex] = session;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
sessions.push(session);
|
|
309
|
+
}
|
|
310
|
+
fs.writeFileSync(this.sessionsFile, JSON.stringify(sessions, null, 2));
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
console.error('Failed to save session to JSON:', error);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
deleteSession(sessionId, userId = 'default') {
|
|
318
|
+
if (this.useSQLite) {
|
|
319
|
+
const db = getDatabase();
|
|
320
|
+
const stmt = db.prepare('DELETE FROM sessions WHERE id = ? AND user_id = ?');
|
|
321
|
+
const result = stmt.run(sessionId, userId);
|
|
322
|
+
return result.changes > 0;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
// Fallback to JSON
|
|
326
|
+
try {
|
|
327
|
+
const sessions = this.getAllSessions();
|
|
328
|
+
const filteredSessions = sessions.filter(s => s.id !== sessionId);
|
|
329
|
+
if (filteredSessions.length !== sessions.length) {
|
|
330
|
+
fs.writeFileSync(this.sessionsFile, JSON.stringify(filteredSessions, null, 2));
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
console.error('Failed to delete session from JSON:', error);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
clearAllSessions(userId = 'default') {
|
|
341
|
+
if (this.useSQLite) {
|
|
342
|
+
const db = getDatabase();
|
|
343
|
+
const stmt = db.prepare('DELETE FROM sessions WHERE user_id = ?');
|
|
344
|
+
const result = stmt.run(userId);
|
|
345
|
+
return result.changes;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
// Fallback to JSON
|
|
349
|
+
try {
|
|
350
|
+
const currentSessions = this.getAllSessions();
|
|
351
|
+
const deletedCount = currentSessions.length;
|
|
352
|
+
fs.writeFileSync(this.sessionsFile, JSON.stringify([], null, 2));
|
|
353
|
+
return deletedCount;
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
console.error('Failed to clear all sessions from JSON:', error);
|
|
357
|
+
return 0;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// =================================
|
|
362
|
+
// PREFERENCES MANAGEMENT
|
|
363
|
+
// =================================
|
|
364
|
+
/**
|
|
365
|
+
* Safely decrypt and parse a preference value with proper error handling
|
|
366
|
+
*/
|
|
367
|
+
safeDecryptPreference(key, value) {
|
|
368
|
+
try {
|
|
369
|
+
// Decrypt the preference value
|
|
370
|
+
const decryptedValue = encryptionService.decrypt(value);
|
|
371
|
+
try {
|
|
372
|
+
// Parse the decrypted value
|
|
373
|
+
return JSON.parse(decryptedValue);
|
|
374
|
+
}
|
|
375
|
+
catch (parseError) {
|
|
376
|
+
console.error(`Parsing error for preference key "${key}":`, parseError);
|
|
377
|
+
// Fallback to raw decrypted value
|
|
378
|
+
return decryptedValue;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch (decryptError) {
|
|
382
|
+
console.error(`Decryption error for preference key "${key}":`, decryptError);
|
|
383
|
+
// Fallback to raw value (assuming unencrypted legacy data)
|
|
384
|
+
try {
|
|
385
|
+
return JSON.parse(value);
|
|
386
|
+
}
|
|
387
|
+
catch (parseError) {
|
|
388
|
+
console.error(`Parsing error for raw preference key "${key}":`, parseError);
|
|
389
|
+
return value;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
getPreferences(userId) {
|
|
394
|
+
if (this.useSQLite) {
|
|
395
|
+
const db = getDatabase();
|
|
396
|
+
// If no userId provided, get the first user (single-user mode)
|
|
397
|
+
if (!userId) {
|
|
398
|
+
const firstUser = db.prepare('SELECT id FROM users LIMIT 1').get();
|
|
399
|
+
if (firstUser) {
|
|
400
|
+
userId = firstUser.id;
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
return null; // No users found, return null
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const stmt = db.prepare('SELECT key, value FROM user_preferences WHERE user_id = ?');
|
|
407
|
+
const rows = stmt.all(userId);
|
|
408
|
+
if (rows.length === 0)
|
|
409
|
+
return null;
|
|
410
|
+
const preferences = {};
|
|
411
|
+
rows.forEach(row => {
|
|
412
|
+
preferences[row.key] = this.safeDecryptPreference(row.key, row.value);
|
|
413
|
+
});
|
|
414
|
+
return preferences;
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
// Fallback to JSON
|
|
418
|
+
try {
|
|
419
|
+
if (fs.existsSync(this.preferencesFile)) {
|
|
420
|
+
const data = fs.readFileSync(this.preferencesFile, 'utf8');
|
|
421
|
+
return JSON.parse(data);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
console.error('Failed to load preferences from JSON:', error);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
savePreferences(preferences, userId) {
|
|
431
|
+
if (this.useSQLite) {
|
|
432
|
+
const db = getDatabase();
|
|
433
|
+
const now = Date.now();
|
|
434
|
+
// If no userId provided, get the first user (single-user mode)
|
|
435
|
+
if (!userId) {
|
|
436
|
+
const firstUser = db.prepare('SELECT id FROM users LIMIT 1').get();
|
|
437
|
+
if (firstUser) {
|
|
438
|
+
userId = firstUser.id;
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
throw new Error('No users found in database');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const transaction = db.transaction((preferences) => {
|
|
445
|
+
// Delete existing preferences for this user
|
|
446
|
+
const deleteStmt = db.prepare('DELETE FROM user_preferences WHERE user_id = ?');
|
|
447
|
+
deleteStmt.run(userId);
|
|
448
|
+
// Insert new preferences
|
|
449
|
+
const insertStmt = db.prepare(`
|
|
450
|
+
INSERT INTO user_preferences (id, user_id, key, value, created_at, updated_at)
|
|
451
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
452
|
+
`);
|
|
453
|
+
Object.entries(preferences).forEach(([key, value]) => {
|
|
454
|
+
// Skip undefined values - they would cause NOT NULL constraint errors
|
|
455
|
+
if (value === undefined) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
// Encrypt the preference value before storing
|
|
459
|
+
const encryptedValue = encryptionService.encrypt(JSON.stringify(value));
|
|
460
|
+
insertStmt.run(uuidv4(), userId, key, encryptedValue, now, now);
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
transaction(preferences);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
// Fallback to JSON
|
|
467
|
+
try {
|
|
468
|
+
fs.writeFileSync(this.preferencesFile, JSON.stringify(preferences, null, 2));
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
console.error('Failed to save preferences to JSON:', error);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// =================================
|
|
476
|
+
// DOCUMENT MANAGEMENT
|
|
477
|
+
// =================================
|
|
478
|
+
getAllDocuments(userId = 'default') {
|
|
479
|
+
if (this.useSQLite) {
|
|
480
|
+
const db = getDatabase();
|
|
481
|
+
const stmt = db.prepare(`
|
|
482
|
+
SELECT * FROM documents WHERE user_id = ? ORDER BY uploaded_at DESC
|
|
483
|
+
`);
|
|
484
|
+
const rows = stmt.all(userId);
|
|
485
|
+
return rows.map(row => {
|
|
486
|
+
// Decrypt document data
|
|
487
|
+
const decryptedTitle = row.title
|
|
488
|
+
? encryptionService.decrypt(row.title)
|
|
489
|
+
: undefined;
|
|
490
|
+
const decryptedContent = row.content
|
|
491
|
+
? encryptionService.decrypt(row.content)
|
|
492
|
+
: undefined;
|
|
493
|
+
const decryptedMetadata = row.metadata
|
|
494
|
+
? JSON.parse(encryptionService.decrypt(row.metadata))
|
|
495
|
+
: undefined;
|
|
496
|
+
return {
|
|
497
|
+
id: row.id,
|
|
498
|
+
filename: row.filename,
|
|
499
|
+
title: decryptedTitle,
|
|
500
|
+
content: decryptedContent,
|
|
501
|
+
fileType: row.file_type,
|
|
502
|
+
size: row.size,
|
|
503
|
+
sessionId: row.session_id,
|
|
504
|
+
uploadedAt: row.uploaded_at,
|
|
505
|
+
createdAt: row.created_at,
|
|
506
|
+
metadata: decryptedMetadata,
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
// Fallback to JSON
|
|
512
|
+
try {
|
|
513
|
+
if (fs.existsSync(this.documentsFile)) {
|
|
514
|
+
const data = fs.readFileSync(this.documentsFile, 'utf8');
|
|
515
|
+
return JSON.parse(data);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
console.error('Failed to load documents from JSON:', error);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
getDocument(documentId, userId = 'default') {
|
|
525
|
+
if (this.useSQLite) {
|
|
526
|
+
const db = getDatabase();
|
|
527
|
+
const stmt = db.prepare('SELECT * FROM documents WHERE id = ? AND user_id = ?');
|
|
528
|
+
const row = stmt.get(documentId, userId);
|
|
529
|
+
if (!row)
|
|
530
|
+
return undefined;
|
|
531
|
+
// Decrypt document data
|
|
532
|
+
const decryptedTitle = row.title
|
|
533
|
+
? encryptionService.decrypt(row.title)
|
|
534
|
+
: undefined;
|
|
535
|
+
const decryptedContent = row.content
|
|
536
|
+
? encryptionService.decrypt(row.content)
|
|
537
|
+
: undefined;
|
|
538
|
+
const decryptedMetadata = row.metadata
|
|
539
|
+
? JSON.parse(encryptionService.decrypt(row.metadata))
|
|
540
|
+
: undefined;
|
|
541
|
+
return {
|
|
542
|
+
id: row.id,
|
|
543
|
+
filename: row.filename,
|
|
544
|
+
title: decryptedTitle,
|
|
545
|
+
content: decryptedContent,
|
|
546
|
+
fileType: row.file_type,
|
|
547
|
+
size: row.size,
|
|
548
|
+
sessionId: row.session_id,
|
|
549
|
+
uploadedAt: row.uploaded_at,
|
|
550
|
+
createdAt: row.created_at,
|
|
551
|
+
metadata: decryptedMetadata,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
// Fallback to JSON
|
|
556
|
+
const documents = this.getAllDocuments();
|
|
557
|
+
return documents.find(d => d.id === documentId);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
saveDocument(document, userId = 'default') {
|
|
561
|
+
if (this.useSQLite) {
|
|
562
|
+
const db = getDatabase();
|
|
563
|
+
const now = Date.now();
|
|
564
|
+
const stmt = db.prepare(`
|
|
565
|
+
INSERT OR REPLACE INTO documents
|
|
566
|
+
(id, user_id, filename, title, content, metadata, uploaded_at, created_at, updated_at)
|
|
567
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
568
|
+
`);
|
|
569
|
+
// Encrypt sensitive document data
|
|
570
|
+
const encryptedTitle = document.title
|
|
571
|
+
? encryptionService.encrypt(document.title)
|
|
572
|
+
: null;
|
|
573
|
+
const encryptedContent = document.content
|
|
574
|
+
? encryptionService.encrypt(document.content)
|
|
575
|
+
: null;
|
|
576
|
+
const encryptedMetadata = document.metadata
|
|
577
|
+
? encryptionService.encrypt(JSON.stringify(document.metadata))
|
|
578
|
+
: null;
|
|
579
|
+
stmt.run(document.id, userId, document.filename, encryptedTitle, encryptedContent, encryptedMetadata, document.uploadedAt, document.createdAt || now, now);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
// Fallback to JSON
|
|
583
|
+
try {
|
|
584
|
+
const documents = this.getAllDocuments();
|
|
585
|
+
const existingIndex = documents.findIndex(d => d.id === document.id);
|
|
586
|
+
if (existingIndex >= 0) {
|
|
587
|
+
documents[existingIndex] = document;
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
documents.push(document);
|
|
591
|
+
}
|
|
592
|
+
fs.writeFileSync(this.documentsFile, JSON.stringify(documents, null, 2));
|
|
593
|
+
}
|
|
594
|
+
catch (error) {
|
|
595
|
+
console.error('Failed to save document to JSON:', error);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
deleteDocument(documentId, userId = 'default') {
|
|
600
|
+
if (this.useSQLite) {
|
|
601
|
+
const db = getDatabase();
|
|
602
|
+
const stmt = db.prepare('DELETE FROM documents WHERE id = ? AND user_id = ?');
|
|
603
|
+
const result = stmt.run(documentId, userId);
|
|
604
|
+
return result.changes > 0;
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
// Fallback to JSON
|
|
608
|
+
try {
|
|
609
|
+
const documents = this.getAllDocuments();
|
|
610
|
+
const filteredDocuments = documents.filter(d => d.id !== documentId);
|
|
611
|
+
if (filteredDocuments.length !== documents.length) {
|
|
612
|
+
fs.writeFileSync(this.documentsFile, JSON.stringify(filteredDocuments, null, 2));
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
catch (error) {
|
|
617
|
+
console.error('Failed to delete document from JSON:', error);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
// =================================
|
|
623
|
+
// DOCUMENT CHUNKS MANAGEMENT
|
|
624
|
+
// =================================
|
|
625
|
+
getDocumentChunks(documentId) {
|
|
626
|
+
if (this.useSQLite) {
|
|
627
|
+
const db = getDatabase();
|
|
628
|
+
const stmt = db.prepare(`
|
|
629
|
+
SELECT * FROM document_chunks WHERE document_id = ? ORDER BY chunk_index ASC
|
|
630
|
+
`);
|
|
631
|
+
const rows = stmt.all(documentId);
|
|
632
|
+
return rows.map(row => {
|
|
633
|
+
// Decrypt document chunk data
|
|
634
|
+
const decryptedContent = encryptionService.decrypt(row.content);
|
|
635
|
+
const decryptedEmbedding = row.embedding
|
|
636
|
+
? JSON.parse(encryptionService.decrypt(row.embedding))
|
|
637
|
+
: undefined;
|
|
638
|
+
const decryptedMetadata = row.metadata
|
|
639
|
+
? JSON.parse(encryptionService.decrypt(row.metadata))
|
|
640
|
+
: undefined;
|
|
641
|
+
return {
|
|
642
|
+
id: row.id,
|
|
643
|
+
documentId: row.document_id,
|
|
644
|
+
content: decryptedContent,
|
|
645
|
+
embedding: decryptedEmbedding,
|
|
646
|
+
chunkIndex: row.chunk_index,
|
|
647
|
+
startChar: row.start_char,
|
|
648
|
+
endChar: row.end_char,
|
|
649
|
+
metadata: decryptedMetadata,
|
|
650
|
+
};
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
// Fallback to JSON
|
|
655
|
+
try {
|
|
656
|
+
if (fs.existsSync(this.documentChunksFile)) {
|
|
657
|
+
const data = fs.readFileSync(this.documentChunksFile, 'utf8');
|
|
658
|
+
const chunksData = JSON.parse(data);
|
|
659
|
+
return chunksData[documentId] || [];
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch (error) {
|
|
663
|
+
console.error('Failed to load document chunks from JSON:', error);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return [];
|
|
667
|
+
}
|
|
668
|
+
saveDocumentChunks(documentId, chunks) {
|
|
669
|
+
if (this.useSQLite) {
|
|
670
|
+
const db = getDatabase();
|
|
671
|
+
const now = Date.now();
|
|
672
|
+
const transaction = db.transaction((documentId, chunks) => {
|
|
673
|
+
// Delete existing chunks
|
|
674
|
+
const deleteStmt = db.prepare('DELETE FROM document_chunks WHERE document_id = ?');
|
|
675
|
+
deleteStmt.run(documentId);
|
|
676
|
+
// Insert new chunks
|
|
677
|
+
if (chunks.length > 0) {
|
|
678
|
+
const insertStmt = db.prepare(`
|
|
679
|
+
INSERT INTO document_chunks
|
|
680
|
+
(id, document_id, chunk_index, content, start_char, end_char, embedding, created_at)
|
|
681
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
682
|
+
`);
|
|
683
|
+
chunks.forEach(chunk => {
|
|
684
|
+
// Encrypt chunk data
|
|
685
|
+
const encryptedContent = encryptionService.encrypt(chunk.content);
|
|
686
|
+
const encryptedEmbedding = chunk.embedding
|
|
687
|
+
? encryptionService.encrypt(JSON.stringify(chunk.embedding))
|
|
688
|
+
: null;
|
|
689
|
+
insertStmt.run(chunk.id, documentId, chunk.chunkIndex, encryptedContent, chunk.startChar || null, chunk.endChar || null, encryptedEmbedding, now);
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
transaction(documentId, chunks);
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
// Fallback to JSON
|
|
697
|
+
try {
|
|
698
|
+
let chunksData = {};
|
|
699
|
+
if (fs.existsSync(this.documentChunksFile)) {
|
|
700
|
+
const data = fs.readFileSync(this.documentChunksFile, 'utf8');
|
|
701
|
+
chunksData = JSON.parse(data);
|
|
702
|
+
}
|
|
703
|
+
chunksData[documentId] = chunks;
|
|
704
|
+
fs.writeFileSync(this.documentChunksFile, JSON.stringify(chunksData, null, 2));
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
console.error('Failed to save document chunks to JSON:', error);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
deleteDocumentChunks(documentId) {
|
|
712
|
+
if (this.useSQLite) {
|
|
713
|
+
const db = getDatabase();
|
|
714
|
+
const stmt = db.prepare('DELETE FROM document_chunks WHERE document_id = ?');
|
|
715
|
+
const result = stmt.run(documentId);
|
|
716
|
+
return result.changes > 0;
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
// Fallback to JSON
|
|
720
|
+
try {
|
|
721
|
+
if (fs.existsSync(this.documentChunksFile)) {
|
|
722
|
+
const data = fs.readFileSync(this.documentChunksFile, 'utf8');
|
|
723
|
+
const chunksData = JSON.parse(data);
|
|
724
|
+
if (chunksData[documentId]) {
|
|
725
|
+
delete chunksData[documentId];
|
|
726
|
+
fs.writeFileSync(this.documentChunksFile, JSON.stringify(chunksData, null, 2));
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
console.error('Failed to delete document chunks from JSON:', error);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
// Export singleton instance
|
|
739
|
+
const storageService = new StorageService();
|
|
740
|
+
export default storageService;
|
|
741
|
+
//# sourceMappingURL=storage.js.map
|