@huyooo/ai-chat-storage 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-BRWTH4LQ.js +642 -0
- package/dist/chunk-GCKDCC3O.js +678 -0
- package/dist/index.d.ts +1378 -0
- package/dist/index.js +66 -0
- package/dist/postgres-PBPM2KDK.js +6 -0
- package/dist/sqlite-AJRNISP2.js +6 -0
- package/package.json +36 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
// src/adapters/sqlite/index.ts
|
|
2
|
+
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
4
|
+
|
|
5
|
+
// src/schema.ts
|
|
6
|
+
import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
|
|
7
|
+
var sessions = sqliteTable("sessions", {
|
|
8
|
+
id: text("id").primaryKey(),
|
|
9
|
+
appId: text("app_id"),
|
|
10
|
+
userId: text("user_id"),
|
|
11
|
+
title: text("title").notNull(),
|
|
12
|
+
model: text("model").notNull(),
|
|
13
|
+
mode: text("mode").notNull(),
|
|
14
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
15
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
16
|
+
}, (table) => [
|
|
17
|
+
index("idx_sessions_tenant").on(table.appId, table.userId),
|
|
18
|
+
index("idx_sessions_updated").on(table.updatedAt)
|
|
19
|
+
]);
|
|
20
|
+
var messages = sqliteTable("messages", {
|
|
21
|
+
id: text("id").primaryKey(),
|
|
22
|
+
sessionId: text("session_id").notNull(),
|
|
23
|
+
appId: text("app_id"),
|
|
24
|
+
userId: text("user_id"),
|
|
25
|
+
role: text("role").notNull(),
|
|
26
|
+
content: text("content").notNull(),
|
|
27
|
+
thinking: text("thinking"),
|
|
28
|
+
toolCalls: text("tool_calls"),
|
|
29
|
+
searchResults: text("search_results"),
|
|
30
|
+
operationIds: text("operation_ids"),
|
|
31
|
+
timestamp: integer("timestamp", { mode: "timestamp" }).notNull()
|
|
32
|
+
}, (table) => [
|
|
33
|
+
index("idx_messages_session").on(table.sessionId),
|
|
34
|
+
index("idx_messages_tenant").on(table.appId, table.userId)
|
|
35
|
+
]);
|
|
36
|
+
var operations = sqliteTable("operations", {
|
|
37
|
+
id: text("id").primaryKey(),
|
|
38
|
+
sessionId: text("session_id").notNull(),
|
|
39
|
+
messageId: text("message_id"),
|
|
40
|
+
appId: text("app_id"),
|
|
41
|
+
userId: text("user_id"),
|
|
42
|
+
command: text("command").notNull(),
|
|
43
|
+
operationType: text("operation_type").notNull(),
|
|
44
|
+
affectedFiles: text("affected_files").notNull(),
|
|
45
|
+
backupPath: text("backup_path"),
|
|
46
|
+
status: text("status").notNull().default("pending"),
|
|
47
|
+
errorMessage: text("error_message"),
|
|
48
|
+
timestamp: integer("timestamp", { mode: "timestamp" }).notNull()
|
|
49
|
+
}, (table) => [
|
|
50
|
+
index("idx_operations_session").on(table.sessionId),
|
|
51
|
+
index("idx_operations_message").on(table.messageId),
|
|
52
|
+
index("idx_operations_tenant").on(table.appId, table.userId)
|
|
53
|
+
]);
|
|
54
|
+
var backups = sqliteTable("backups", {
|
|
55
|
+
id: text("id").primaryKey(),
|
|
56
|
+
operationId: text("operation_id").notNull(),
|
|
57
|
+
originalPath: text("original_path").notNull(),
|
|
58
|
+
backupPath: text("backup_path").notNull(),
|
|
59
|
+
fileSize: integer("file_size").notNull(),
|
|
60
|
+
fileHash: text("file_hash").notNull(),
|
|
61
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
62
|
+
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull()
|
|
63
|
+
}, (table) => [
|
|
64
|
+
index("idx_backups_operation").on(table.operationId),
|
|
65
|
+
index("idx_backups_expires").on(table.expiresAt)
|
|
66
|
+
]);
|
|
67
|
+
var trash = sqliteTable("trash", {
|
|
68
|
+
id: text("id").primaryKey(),
|
|
69
|
+
sessionId: text("session_id").notNull(),
|
|
70
|
+
appId: text("app_id"),
|
|
71
|
+
userId: text("user_id"),
|
|
72
|
+
originalPath: text("original_path").notNull(),
|
|
73
|
+
trashPath: text("trash_path").notNull(),
|
|
74
|
+
deletedAt: integer("deleted_at", { mode: "timestamp" }).notNull(),
|
|
75
|
+
autoDeleteAt: integer("auto_delete_at", { mode: "timestamp" }).notNull()
|
|
76
|
+
}, (table) => [
|
|
77
|
+
index("idx_trash_tenant").on(table.appId, table.userId),
|
|
78
|
+
index("idx_trash_auto_delete").on(table.autoDeleteAt)
|
|
79
|
+
]);
|
|
80
|
+
var embeddings = sqliteTable("embeddings", {
|
|
81
|
+
id: text("id").primaryKey(),
|
|
82
|
+
sessionId: text("session_id").notNull(),
|
|
83
|
+
messageId: text("message_id"),
|
|
84
|
+
appId: text("app_id"),
|
|
85
|
+
userId: text("user_id"),
|
|
86
|
+
content: text("content").notNull(),
|
|
87
|
+
contentType: text("content_type").notNull(),
|
|
88
|
+
embedding: text("embedding"),
|
|
89
|
+
// JSON 字符串存储
|
|
90
|
+
metadata: text("metadata"),
|
|
91
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
92
|
+
}, (table) => [
|
|
93
|
+
index("idx_embeddings_session").on(table.sessionId),
|
|
94
|
+
index("idx_embeddings_tenant").on(table.appId, table.userId)
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
// src/adapters/sqlite/base.ts
|
|
98
|
+
import { eq, and } from "drizzle-orm";
|
|
99
|
+
function initSchema(sqlite) {
|
|
100
|
+
sqlite.exec(`
|
|
101
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
102
|
+
id TEXT PRIMARY KEY,
|
|
103
|
+
app_id TEXT,
|
|
104
|
+
user_id TEXT,
|
|
105
|
+
title TEXT NOT NULL,
|
|
106
|
+
model TEXT NOT NULL,
|
|
107
|
+
mode TEXT NOT NULL,
|
|
108
|
+
created_at INTEGER NOT NULL,
|
|
109
|
+
updated_at INTEGER NOT NULL
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
113
|
+
id TEXT PRIMARY KEY,
|
|
114
|
+
session_id TEXT NOT NULL,
|
|
115
|
+
app_id TEXT,
|
|
116
|
+
user_id TEXT,
|
|
117
|
+
role TEXT NOT NULL,
|
|
118
|
+
content TEXT NOT NULL,
|
|
119
|
+
thinking TEXT,
|
|
120
|
+
tool_calls TEXT,
|
|
121
|
+
search_results TEXT,
|
|
122
|
+
operation_ids TEXT,
|
|
123
|
+
timestamp INTEGER NOT NULL
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
CREATE TABLE IF NOT EXISTS operations (
|
|
127
|
+
id TEXT PRIMARY KEY,
|
|
128
|
+
session_id TEXT NOT NULL,
|
|
129
|
+
message_id TEXT,
|
|
130
|
+
app_id TEXT,
|
|
131
|
+
user_id TEXT,
|
|
132
|
+
command TEXT NOT NULL,
|
|
133
|
+
operation_type TEXT NOT NULL,
|
|
134
|
+
affected_files TEXT NOT NULL,
|
|
135
|
+
backup_path TEXT,
|
|
136
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
137
|
+
error_message TEXT,
|
|
138
|
+
timestamp INTEGER NOT NULL
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
CREATE TABLE IF NOT EXISTS backups (
|
|
142
|
+
id TEXT PRIMARY KEY,
|
|
143
|
+
operation_id TEXT NOT NULL,
|
|
144
|
+
original_path TEXT NOT NULL,
|
|
145
|
+
backup_path TEXT NOT NULL,
|
|
146
|
+
file_size INTEGER NOT NULL,
|
|
147
|
+
file_hash TEXT NOT NULL,
|
|
148
|
+
created_at INTEGER NOT NULL,
|
|
149
|
+
expires_at INTEGER NOT NULL
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
CREATE TABLE IF NOT EXISTS trash (
|
|
153
|
+
id TEXT PRIMARY KEY,
|
|
154
|
+
session_id TEXT NOT NULL,
|
|
155
|
+
app_id TEXT,
|
|
156
|
+
user_id TEXT,
|
|
157
|
+
original_path TEXT NOT NULL,
|
|
158
|
+
trash_path TEXT NOT NULL,
|
|
159
|
+
deleted_at INTEGER NOT NULL,
|
|
160
|
+
auto_delete_at INTEGER NOT NULL
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
CREATE TABLE IF NOT EXISTS embeddings (
|
|
164
|
+
id TEXT PRIMARY KEY,
|
|
165
|
+
session_id TEXT NOT NULL,
|
|
166
|
+
message_id TEXT,
|
|
167
|
+
app_id TEXT,
|
|
168
|
+
user_id TEXT,
|
|
169
|
+
content TEXT NOT NULL,
|
|
170
|
+
content_type TEXT NOT NULL,
|
|
171
|
+
embedding TEXT,
|
|
172
|
+
metadata TEXT,
|
|
173
|
+
created_at INTEGER NOT NULL
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
-- \u7D22\u5F15
|
|
177
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_tenant ON sessions(app_id, user_id);
|
|
178
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_updated ON sessions(updated_at);
|
|
179
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
|
|
180
|
+
CREATE INDEX IF NOT EXISTS idx_messages_tenant ON messages(app_id, user_id);
|
|
181
|
+
CREATE INDEX IF NOT EXISTS idx_operations_session ON operations(session_id);
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_operations_message ON operations(message_id);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_backups_operation ON backups(operation_id);
|
|
184
|
+
CREATE INDEX IF NOT EXISTS idx_backups_expires ON backups(expires_at);
|
|
185
|
+
CREATE INDEX IF NOT EXISTS idx_trash_tenant ON trash(app_id, user_id);
|
|
186
|
+
CREATE INDEX IF NOT EXISTS idx_trash_auto_delete ON trash(auto_delete_at);
|
|
187
|
+
CREATE INDEX IF NOT EXISTS idx_embeddings_session ON embeddings(session_id);
|
|
188
|
+
`);
|
|
189
|
+
}
|
|
190
|
+
function buildSessionsTenantCondition(ctx) {
|
|
191
|
+
const conditions = [];
|
|
192
|
+
if (ctx.appId) conditions.push(eq(sessions.appId, ctx.appId));
|
|
193
|
+
if (ctx.userId) conditions.push(eq(sessions.userId, ctx.userId));
|
|
194
|
+
return conditions.length > 0 ? and(...conditions) : void 0;
|
|
195
|
+
}
|
|
196
|
+
function buildMessagesTenantCondition(ctx) {
|
|
197
|
+
const conditions = [];
|
|
198
|
+
if (ctx.appId) conditions.push(eq(messages.appId, ctx.appId));
|
|
199
|
+
if (ctx.userId) conditions.push(eq(messages.userId, ctx.userId));
|
|
200
|
+
return conditions.length > 0 ? and(...conditions) : void 0;
|
|
201
|
+
}
|
|
202
|
+
function buildOperationsTenantCondition(ctx) {
|
|
203
|
+
const conditions = [];
|
|
204
|
+
if (ctx.appId) conditions.push(eq(operations.appId, ctx.appId));
|
|
205
|
+
if (ctx.userId) conditions.push(eq(operations.userId, ctx.userId));
|
|
206
|
+
return conditions.length > 0 ? and(...conditions) : void 0;
|
|
207
|
+
}
|
|
208
|
+
function buildTrashTenantCondition(ctx) {
|
|
209
|
+
const conditions = [];
|
|
210
|
+
if (ctx.appId) conditions.push(eq(trash.appId, ctx.appId));
|
|
211
|
+
if (ctx.userId) conditions.push(eq(trash.userId, ctx.userId));
|
|
212
|
+
return conditions.length > 0 ? and(...conditions) : void 0;
|
|
213
|
+
}
|
|
214
|
+
function cosineSimilarity(a, b) {
|
|
215
|
+
if (a.length !== b.length) return 0;
|
|
216
|
+
let dotProduct = 0;
|
|
217
|
+
let normA = 0;
|
|
218
|
+
let normB = 0;
|
|
219
|
+
for (let i = 0; i < a.length; i++) {
|
|
220
|
+
dotProduct += a[i] * b[i];
|
|
221
|
+
normA += a[i] * a[i];
|
|
222
|
+
normB += b[i] * b[i];
|
|
223
|
+
}
|
|
224
|
+
if (normA === 0 || normB === 0) return 0;
|
|
225
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/adapters/sqlite/sessions.ts
|
|
229
|
+
import { eq as eq2, and as and2, desc } from "drizzle-orm";
|
|
230
|
+
function toSessionRecord(row) {
|
|
231
|
+
return {
|
|
232
|
+
id: row.id,
|
|
233
|
+
appId: row.appId,
|
|
234
|
+
userId: row.userId,
|
|
235
|
+
title: row.title,
|
|
236
|
+
model: row.model,
|
|
237
|
+
mode: row.mode,
|
|
238
|
+
createdAt: row.createdAt,
|
|
239
|
+
updatedAt: row.updatedAt
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function getSessions(db, ctx) {
|
|
243
|
+
const tenantCondition = buildSessionsTenantCondition(ctx);
|
|
244
|
+
const rows = db.select().from(sessions).where(tenantCondition).orderBy(desc(sessions.updatedAt)).all();
|
|
245
|
+
return rows.map(toSessionRecord);
|
|
246
|
+
}
|
|
247
|
+
function getSession(db, id, ctx) {
|
|
248
|
+
const tenantCondition = buildSessionsTenantCondition(ctx);
|
|
249
|
+
const condition = tenantCondition ? and2(eq2(sessions.id, id), tenantCondition) : eq2(sessions.id, id);
|
|
250
|
+
const row = db.select().from(sessions).where(condition).get();
|
|
251
|
+
return row ? toSessionRecord(row) : null;
|
|
252
|
+
}
|
|
253
|
+
function createSession(db, input, ctx) {
|
|
254
|
+
const now = /* @__PURE__ */ new Date();
|
|
255
|
+
const record = {
|
|
256
|
+
id: input.id,
|
|
257
|
+
appId: ctx.appId || null,
|
|
258
|
+
userId: ctx.userId || null,
|
|
259
|
+
title: input.title,
|
|
260
|
+
model: input.model,
|
|
261
|
+
mode: input.mode,
|
|
262
|
+
createdAt: now,
|
|
263
|
+
updatedAt: now
|
|
264
|
+
};
|
|
265
|
+
db.insert(sessions).values({
|
|
266
|
+
id: record.id,
|
|
267
|
+
appId: record.appId,
|
|
268
|
+
userId: record.userId,
|
|
269
|
+
title: record.title,
|
|
270
|
+
model: record.model,
|
|
271
|
+
mode: record.mode,
|
|
272
|
+
createdAt: record.createdAt,
|
|
273
|
+
updatedAt: record.updatedAt
|
|
274
|
+
}).run();
|
|
275
|
+
return record;
|
|
276
|
+
}
|
|
277
|
+
function updateSession(db, id, data, ctx) {
|
|
278
|
+
const tenantCondition = buildSessionsTenantCondition(ctx);
|
|
279
|
+
const condition = tenantCondition ? and2(eq2(sessions.id, id), tenantCondition) : eq2(sessions.id, id);
|
|
280
|
+
const updateData = { updatedAt: /* @__PURE__ */ new Date() };
|
|
281
|
+
if (data.title !== void 0) updateData.title = data.title;
|
|
282
|
+
if (data.model !== void 0) updateData.model = data.model;
|
|
283
|
+
if (data.mode !== void 0) updateData.mode = data.mode;
|
|
284
|
+
db.update(sessions).set(updateData).where(condition).run();
|
|
285
|
+
}
|
|
286
|
+
function deleteSession(db, id, ctx) {
|
|
287
|
+
const tenantCondition = buildSessionsTenantCondition(ctx);
|
|
288
|
+
const condition = tenantCondition ? and2(eq2(sessions.id, id), tenantCondition) : eq2(sessions.id, id);
|
|
289
|
+
db.delete(sessions).where(condition).run();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/adapters/sqlite/messages.ts
|
|
293
|
+
import { eq as eq3, and as and3, gt } from "drizzle-orm";
|
|
294
|
+
function toMessageRecord(row) {
|
|
295
|
+
return {
|
|
296
|
+
id: row.id,
|
|
297
|
+
sessionId: row.sessionId,
|
|
298
|
+
appId: row.appId,
|
|
299
|
+
userId: row.userId,
|
|
300
|
+
role: row.role,
|
|
301
|
+
content: row.content,
|
|
302
|
+
thinking: row.thinking,
|
|
303
|
+
toolCalls: row.toolCalls,
|
|
304
|
+
searchResults: row.searchResults,
|
|
305
|
+
operationIds: row.operationIds,
|
|
306
|
+
timestamp: row.timestamp
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function getMessages(db, sessionId) {
|
|
310
|
+
const rows = db.select().from(messages).where(eq3(messages.sessionId, sessionId)).orderBy(messages.timestamp).all();
|
|
311
|
+
return rows.map(toMessageRecord);
|
|
312
|
+
}
|
|
313
|
+
function getMessage(db, id, ctx) {
|
|
314
|
+
const tenantCondition = buildMessagesTenantCondition(ctx);
|
|
315
|
+
const condition = tenantCondition ? and3(eq3(messages.id, id), tenantCondition) : eq3(messages.id, id);
|
|
316
|
+
const row = db.select().from(messages).where(condition).get();
|
|
317
|
+
return row ? toMessageRecord(row) : null;
|
|
318
|
+
}
|
|
319
|
+
function saveMessage(db, input, ctx) {
|
|
320
|
+
const now = /* @__PURE__ */ new Date();
|
|
321
|
+
const record = {
|
|
322
|
+
id: input.id,
|
|
323
|
+
sessionId: input.sessionId,
|
|
324
|
+
appId: ctx.appId || null,
|
|
325
|
+
userId: ctx.userId || null,
|
|
326
|
+
role: input.role,
|
|
327
|
+
content: input.content,
|
|
328
|
+
thinking: input.thinking,
|
|
329
|
+
toolCalls: input.toolCalls,
|
|
330
|
+
searchResults: input.searchResults,
|
|
331
|
+
operationIds: input.operationIds,
|
|
332
|
+
timestamp: now
|
|
333
|
+
};
|
|
334
|
+
db.insert(messages).values({
|
|
335
|
+
id: record.id,
|
|
336
|
+
sessionId: record.sessionId,
|
|
337
|
+
appId: record.appId,
|
|
338
|
+
userId: record.userId,
|
|
339
|
+
role: record.role,
|
|
340
|
+
content: record.content,
|
|
341
|
+
thinking: record.thinking,
|
|
342
|
+
toolCalls: record.toolCalls,
|
|
343
|
+
searchResults: record.searchResults,
|
|
344
|
+
operationIds: record.operationIds,
|
|
345
|
+
timestamp: record.timestamp
|
|
346
|
+
}).run();
|
|
347
|
+
db.update(sessions).set({ updatedAt: now }).where(eq3(sessions.id, input.sessionId)).run();
|
|
348
|
+
return record;
|
|
349
|
+
}
|
|
350
|
+
function deleteMessagesAfter(db, sessionId, timestamp) {
|
|
351
|
+
db.delete(messages).where(and3(
|
|
352
|
+
eq3(messages.sessionId, sessionId),
|
|
353
|
+
gt(messages.timestamp, timestamp)
|
|
354
|
+
)).run();
|
|
355
|
+
}
|
|
356
|
+
function deleteSessionMessages(db, sessionId) {
|
|
357
|
+
db.delete(messages).where(eq3(messages.sessionId, sessionId)).run();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/adapters/sqlite/operations.ts
|
|
361
|
+
import { eq as eq4, and as and4 } from "drizzle-orm";
|
|
362
|
+
function toOperationRecord(row) {
|
|
363
|
+
return {
|
|
364
|
+
id: row.id,
|
|
365
|
+
sessionId: row.sessionId,
|
|
366
|
+
messageId: row.messageId,
|
|
367
|
+
appId: row.appId,
|
|
368
|
+
userId: row.userId,
|
|
369
|
+
command: row.command,
|
|
370
|
+
operationType: row.operationType,
|
|
371
|
+
affectedFiles: row.affectedFiles,
|
|
372
|
+
backupPath: row.backupPath,
|
|
373
|
+
status: row.status,
|
|
374
|
+
errorMessage: row.errorMessage,
|
|
375
|
+
timestamp: row.timestamp
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function getOperations(db, sessionId) {
|
|
379
|
+
const rows = db.select().from(operations).where(eq4(operations.sessionId, sessionId)).orderBy(operations.timestamp).all();
|
|
380
|
+
return rows.map(toOperationRecord);
|
|
381
|
+
}
|
|
382
|
+
function getOperationsByMessage(db, messageId, ctx) {
|
|
383
|
+
const tenantCondition = buildOperationsTenantCondition(ctx);
|
|
384
|
+
const condition = tenantCondition ? and4(eq4(operations.messageId, messageId), tenantCondition) : eq4(operations.messageId, messageId);
|
|
385
|
+
const rows = db.select().from(operations).where(condition).all();
|
|
386
|
+
return rows.map(toOperationRecord);
|
|
387
|
+
}
|
|
388
|
+
function saveOperation(db, input, ctx) {
|
|
389
|
+
const now = /* @__PURE__ */ new Date();
|
|
390
|
+
const record = {
|
|
391
|
+
id: input.id,
|
|
392
|
+
sessionId: input.sessionId,
|
|
393
|
+
messageId: input.messageId,
|
|
394
|
+
appId: ctx.appId || null,
|
|
395
|
+
userId: ctx.userId || null,
|
|
396
|
+
command: input.command,
|
|
397
|
+
operationType: input.operationType,
|
|
398
|
+
affectedFiles: input.affectedFiles,
|
|
399
|
+
backupPath: input.backupPath,
|
|
400
|
+
status: input.status || "pending",
|
|
401
|
+
errorMessage: null,
|
|
402
|
+
timestamp: now
|
|
403
|
+
};
|
|
404
|
+
db.insert(operations).values({
|
|
405
|
+
id: record.id,
|
|
406
|
+
sessionId: record.sessionId,
|
|
407
|
+
messageId: record.messageId,
|
|
408
|
+
appId: record.appId,
|
|
409
|
+
userId: record.userId,
|
|
410
|
+
command: record.command,
|
|
411
|
+
operationType: record.operationType,
|
|
412
|
+
affectedFiles: record.affectedFiles,
|
|
413
|
+
backupPath: record.backupPath,
|
|
414
|
+
status: record.status,
|
|
415
|
+
errorMessage: record.errorMessage,
|
|
416
|
+
timestamp: record.timestamp
|
|
417
|
+
}).run();
|
|
418
|
+
return record;
|
|
419
|
+
}
|
|
420
|
+
function updateOperationStatus(db, id, status, errorMessage) {
|
|
421
|
+
const updateData = { status };
|
|
422
|
+
if (errorMessage !== void 0) {
|
|
423
|
+
updateData.errorMessage = errorMessage;
|
|
424
|
+
}
|
|
425
|
+
db.update(operations).set(updateData).where(eq4(operations.id, id)).run();
|
|
426
|
+
}
|
|
427
|
+
function deleteSessionOperations(db, sessionId) {
|
|
428
|
+
db.delete(operations).where(eq4(operations.sessionId, sessionId)).run();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/adapters/sqlite/backups.ts
|
|
432
|
+
import { eq as eq5, lt } from "drizzle-orm";
|
|
433
|
+
function toBackupRecord(row) {
|
|
434
|
+
return {
|
|
435
|
+
id: row.id,
|
|
436
|
+
operationId: row.operationId,
|
|
437
|
+
originalPath: row.originalPath,
|
|
438
|
+
backupPath: row.backupPath,
|
|
439
|
+
fileSize: row.fileSize,
|
|
440
|
+
fileHash: row.fileHash,
|
|
441
|
+
createdAt: row.createdAt,
|
|
442
|
+
expiresAt: row.expiresAt
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function getBackups(db, operationId) {
|
|
446
|
+
const rows = db.select().from(backups).where(eq5(backups.operationId, operationId)).all();
|
|
447
|
+
return rows.map(toBackupRecord);
|
|
448
|
+
}
|
|
449
|
+
function saveBackup(db, input) {
|
|
450
|
+
const now = /* @__PURE__ */ new Date();
|
|
451
|
+
const record = {
|
|
452
|
+
...input,
|
|
453
|
+
createdAt: now
|
|
454
|
+
};
|
|
455
|
+
db.insert(backups).values({
|
|
456
|
+
id: record.id,
|
|
457
|
+
operationId: record.operationId,
|
|
458
|
+
originalPath: record.originalPath,
|
|
459
|
+
backupPath: record.backupPath,
|
|
460
|
+
fileSize: record.fileSize,
|
|
461
|
+
fileHash: record.fileHash,
|
|
462
|
+
createdAt: record.createdAt,
|
|
463
|
+
expiresAt: record.expiresAt
|
|
464
|
+
}).run();
|
|
465
|
+
return record;
|
|
466
|
+
}
|
|
467
|
+
function deleteExpiredBackups(db) {
|
|
468
|
+
const now = /* @__PURE__ */ new Date();
|
|
469
|
+
const result = db.delete(backups).where(lt(backups.expiresAt, now)).run();
|
|
470
|
+
return result.changes;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/adapters/sqlite/trash.ts
|
|
474
|
+
import { eq as eq6, and as and5, desc as desc2, lt as lt2 } from "drizzle-orm";
|
|
475
|
+
function toTrashRecord(row) {
|
|
476
|
+
return {
|
|
477
|
+
id: row.id,
|
|
478
|
+
sessionId: row.sessionId,
|
|
479
|
+
appId: row.appId,
|
|
480
|
+
userId: row.userId,
|
|
481
|
+
originalPath: row.originalPath,
|
|
482
|
+
trashPath: row.trashPath,
|
|
483
|
+
deletedAt: row.deletedAt,
|
|
484
|
+
autoDeleteAt: row.autoDeleteAt
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function getTrashItems(db, ctx) {
|
|
488
|
+
const tenantCondition = buildTrashTenantCondition(ctx);
|
|
489
|
+
const rows = db.select().from(trash).where(tenantCondition).orderBy(desc2(trash.deletedAt)).all();
|
|
490
|
+
return rows.map(toTrashRecord);
|
|
491
|
+
}
|
|
492
|
+
function moveToTrash(db, input, ctx, config) {
|
|
493
|
+
const now = /* @__PURE__ */ new Date();
|
|
494
|
+
const retentionDays = config.trashRetentionDays || 30;
|
|
495
|
+
const autoDeleteAt = new Date(now.getTime() + retentionDays * 24 * 60 * 60 * 1e3);
|
|
496
|
+
const record = {
|
|
497
|
+
...input,
|
|
498
|
+
appId: ctx.appId || null,
|
|
499
|
+
userId: ctx.userId || null,
|
|
500
|
+
deletedAt: now,
|
|
501
|
+
autoDeleteAt
|
|
502
|
+
};
|
|
503
|
+
db.insert(trash).values({
|
|
504
|
+
id: record.id,
|
|
505
|
+
sessionId: record.sessionId,
|
|
506
|
+
appId: record.appId,
|
|
507
|
+
userId: record.userId,
|
|
508
|
+
originalPath: record.originalPath,
|
|
509
|
+
trashPath: record.trashPath,
|
|
510
|
+
deletedAt: record.deletedAt,
|
|
511
|
+
autoDeleteAt: record.autoDeleteAt
|
|
512
|
+
}).run();
|
|
513
|
+
return record;
|
|
514
|
+
}
|
|
515
|
+
function restoreFromTrash(db, id, ctx) {
|
|
516
|
+
const tenantCondition = buildTrashTenantCondition(ctx);
|
|
517
|
+
const condition = tenantCondition ? and5(eq6(trash.id, id), tenantCondition) : eq6(trash.id, id);
|
|
518
|
+
const row = db.select().from(trash).where(condition).get();
|
|
519
|
+
if (!row) {
|
|
520
|
+
throw new Error("\u56DE\u6536\u7AD9\u8BB0\u5F55\u4E0D\u5B58\u5728");
|
|
521
|
+
}
|
|
522
|
+
db.delete(trash).where(eq6(trash.id, id)).run();
|
|
523
|
+
return toTrashRecord(row);
|
|
524
|
+
}
|
|
525
|
+
function emptyExpiredTrash(db) {
|
|
526
|
+
const now = /* @__PURE__ */ new Date();
|
|
527
|
+
const result = db.delete(trash).where(lt2(trash.autoDeleteAt, now)).run();
|
|
528
|
+
return result.changes;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/adapters/sqlite/embeddings.ts
|
|
532
|
+
import { eq as eq7, and as and6 } from "drizzle-orm";
|
|
533
|
+
function saveEmbedding(db, id, content, embedding, metadata, ctx) {
|
|
534
|
+
const now = /* @__PURE__ */ new Date();
|
|
535
|
+
db.insert(embeddings).values({
|
|
536
|
+
id,
|
|
537
|
+
sessionId: metadata.sessionId,
|
|
538
|
+
messageId: metadata.messageId || null,
|
|
539
|
+
appId: ctx.appId || null,
|
|
540
|
+
userId: ctx.userId || null,
|
|
541
|
+
content,
|
|
542
|
+
contentType: metadata.contentType,
|
|
543
|
+
embedding: JSON.stringify(embedding),
|
|
544
|
+
metadata: JSON.stringify(metadata),
|
|
545
|
+
createdAt: now
|
|
546
|
+
}).run();
|
|
547
|
+
}
|
|
548
|
+
function searchSimilar(db, queryEmbedding, options, ctx) {
|
|
549
|
+
const { limit = 10, threshold = 0.7, sessionId } = options;
|
|
550
|
+
const conditions = [];
|
|
551
|
+
if (ctx.appId) conditions.push(eq7(embeddings.appId, ctx.appId));
|
|
552
|
+
if (ctx.userId) conditions.push(eq7(embeddings.userId, ctx.userId));
|
|
553
|
+
if (sessionId) conditions.push(eq7(embeddings.sessionId, sessionId));
|
|
554
|
+
const rows = db.select().from(embeddings).where(conditions.length > 0 ? and6(...conditions) : void 0).all();
|
|
555
|
+
const results = rows.map((row) => {
|
|
556
|
+
const storedEmbedding = row.embedding ? JSON.parse(row.embedding) : [];
|
|
557
|
+
const similarity = cosineSimilarity(queryEmbedding, storedEmbedding);
|
|
558
|
+
return {
|
|
559
|
+
id: row.id,
|
|
560
|
+
content: row.content,
|
|
561
|
+
contentType: row.contentType,
|
|
562
|
+
similarity,
|
|
563
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
564
|
+
};
|
|
565
|
+
}).filter((r) => r.similarity >= threshold).sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
566
|
+
return results;
|
|
567
|
+
}
|
|
568
|
+
function deleteSessionEmbeddings(db, sessionId) {
|
|
569
|
+
db.delete(embeddings).where(eq7(embeddings.sessionId, sessionId)).run();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/adapters/sqlite/index.ts
|
|
573
|
+
var SqliteAdapter = class {
|
|
574
|
+
sqlite;
|
|
575
|
+
db;
|
|
576
|
+
config;
|
|
577
|
+
constructor(dbPath, config = { type: "sqlite" }) {
|
|
578
|
+
this.sqlite = new Database(dbPath);
|
|
579
|
+
this.db = drizzle(this.sqlite);
|
|
580
|
+
this.config = config;
|
|
581
|
+
initSchema(this.sqlite);
|
|
582
|
+
}
|
|
583
|
+
// ============ 会话 ============
|
|
584
|
+
async getSessions(ctx) {
|
|
585
|
+
return getSessions(this.db, ctx);
|
|
586
|
+
}
|
|
587
|
+
async getSession(id, ctx) {
|
|
588
|
+
return getSession(this.db, id, ctx);
|
|
589
|
+
}
|
|
590
|
+
async createSession(input, ctx) {
|
|
591
|
+
return createSession(this.db, input, ctx);
|
|
592
|
+
}
|
|
593
|
+
async updateSession(id, data, ctx) {
|
|
594
|
+
updateSession(this.db, id, data, ctx);
|
|
595
|
+
}
|
|
596
|
+
async deleteSession(id, ctx) {
|
|
597
|
+
deleteSessionMessages(this.db, id);
|
|
598
|
+
deleteSessionOperations(this.db, id);
|
|
599
|
+
deleteSessionEmbeddings(this.db, id);
|
|
600
|
+
deleteSession(this.db, id, ctx);
|
|
601
|
+
}
|
|
602
|
+
// ============ 消息 ============
|
|
603
|
+
async getMessages(sessionId, ctx) {
|
|
604
|
+
const session = await this.getSession(sessionId, ctx);
|
|
605
|
+
if (!session) return [];
|
|
606
|
+
return getMessages(this.db, sessionId);
|
|
607
|
+
}
|
|
608
|
+
async getMessage(id, ctx) {
|
|
609
|
+
return getMessage(this.db, id, ctx);
|
|
610
|
+
}
|
|
611
|
+
async saveMessage(input, ctx) {
|
|
612
|
+
return saveMessage(this.db, input, ctx);
|
|
613
|
+
}
|
|
614
|
+
async deleteMessagesAfter(sessionId, timestamp, ctx) {
|
|
615
|
+
const session = await this.getSession(sessionId, ctx);
|
|
616
|
+
if (!session) return;
|
|
617
|
+
deleteMessagesAfter(this.db, sessionId, timestamp);
|
|
618
|
+
}
|
|
619
|
+
// ============ 操作日志 ============
|
|
620
|
+
async getOperations(sessionId, ctx) {
|
|
621
|
+
const session = await this.getSession(sessionId, ctx);
|
|
622
|
+
if (!session) return [];
|
|
623
|
+
return getOperations(this.db, sessionId);
|
|
624
|
+
}
|
|
625
|
+
async getOperationsByMessage(messageId, ctx) {
|
|
626
|
+
return getOperationsByMessage(this.db, messageId, ctx);
|
|
627
|
+
}
|
|
628
|
+
async saveOperation(input, ctx) {
|
|
629
|
+
return saveOperation(this.db, input, ctx);
|
|
630
|
+
}
|
|
631
|
+
async updateOperationStatus(id, status, errorMessage) {
|
|
632
|
+
updateOperationStatus(this.db, id, status, errorMessage);
|
|
633
|
+
}
|
|
634
|
+
// ============ 备份 ============
|
|
635
|
+
async getBackups(operationId) {
|
|
636
|
+
return getBackups(this.db, operationId);
|
|
637
|
+
}
|
|
638
|
+
async saveBackup(input) {
|
|
639
|
+
return saveBackup(this.db, input);
|
|
640
|
+
}
|
|
641
|
+
async deleteExpiredBackups() {
|
|
642
|
+
return deleteExpiredBackups(this.db);
|
|
643
|
+
}
|
|
644
|
+
// ============ 回收站 ============
|
|
645
|
+
async getTrashItems(ctx) {
|
|
646
|
+
return getTrashItems(this.db, ctx);
|
|
647
|
+
}
|
|
648
|
+
async moveToTrash(input, ctx) {
|
|
649
|
+
return moveToTrash(this.db, input, ctx, this.config);
|
|
650
|
+
}
|
|
651
|
+
async restoreFromTrash(id, ctx) {
|
|
652
|
+
return restoreFromTrash(this.db, id, ctx);
|
|
653
|
+
}
|
|
654
|
+
async emptyExpiredTrash() {
|
|
655
|
+
return emptyExpiredTrash(this.db);
|
|
656
|
+
}
|
|
657
|
+
// ============ 向量搜索 ============
|
|
658
|
+
async saveEmbedding(id, content, embedding, metadata, ctx) {
|
|
659
|
+
saveEmbedding(this.db, id, content, embedding, metadata, ctx);
|
|
660
|
+
}
|
|
661
|
+
async searchSimilar(queryEmbedding, options, ctx) {
|
|
662
|
+
return searchSimilar(this.db, queryEmbedding, options, ctx);
|
|
663
|
+
}
|
|
664
|
+
// ============ 生命周期 ============
|
|
665
|
+
async close() {
|
|
666
|
+
this.sqlite.close();
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
export {
|
|
671
|
+
sessions,
|
|
672
|
+
messages,
|
|
673
|
+
operations,
|
|
674
|
+
backups,
|
|
675
|
+
trash,
|
|
676
|
+
embeddings,
|
|
677
|
+
SqliteAdapter
|
|
678
|
+
};
|