@huyooo/ai-chat-storage 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,9 +28,11 @@ async function initSchema(sql) {
28
28
  user_id TEXT,
29
29
  role TEXT NOT NULL,
30
30
  content TEXT NOT NULL,
31
- thinking TEXT,
32
- tool_calls TEXT,
33
- search_results TEXT,
31
+ model TEXT,
32
+ mode TEXT,
33
+ web_search_enabled BOOLEAN,
34
+ thinking_enabled BOOLEAN,
35
+ steps TEXT,
34
36
  operation_ids TEXT,
35
37
  timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
36
38
  )
@@ -75,6 +77,24 @@ async function initSchema(sql) {
75
77
  auto_delete_at TIMESTAMPTZ NOT NULL
76
78
  )
77
79
  `;
80
+ await sql`
81
+ CREATE TABLE IF NOT EXISTS user_settings (
82
+ id TEXT PRIMARY KEY,
83
+ app_id TEXT,
84
+ user_id TEXT,
85
+ key TEXT NOT NULL,
86
+ value TEXT NOT NULL,
87
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
88
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
89
+ UNIQUE(app_id, user_id, key)
90
+ )
91
+ `;
92
+ await sql`
93
+ CREATE INDEX IF NOT EXISTS idx_user_settings_tenant ON user_settings(app_id, user_id);
94
+ `;
95
+ await sql`
96
+ CREATE INDEX IF NOT EXISTS idx_user_settings_key ON user_settings(key);
97
+ `;
78
98
  try {
79
99
  await sql`
80
100
  CREATE TABLE IF NOT EXISTS embeddings (
@@ -146,13 +166,16 @@ function toSessionRecord(row) {
146
166
  title: row.title,
147
167
  model: row.model,
148
168
  mode: row.mode,
169
+ webSearchEnabled: row.web_search_enabled,
170
+ thinkingEnabled: row.thinking_enabled,
171
+ hidden: row.hidden,
149
172
  createdAt: new Date(row.created_at),
150
173
  updatedAt: new Date(row.updated_at)
151
174
  };
152
175
  }
153
176
  async function getSessions(sql, ctx) {
154
177
  const rows = await sql`
155
- SELECT id, app_id, user_id, title, model, mode, created_at, updated_at
178
+ SELECT id, app_id, user_id, title, model, mode, web_search_enabled, thinking_enabled, hidden, created_at, updated_at
156
179
  FROM sessions
157
180
  WHERE (${ctx.appId || null}::TEXT IS NULL OR app_id = ${ctx.appId || null})
158
181
  AND (${ctx.userId || null}::TEXT IS NULL OR user_id = ${ctx.userId || null})
@@ -162,7 +185,7 @@ async function getSessions(sql, ctx) {
162
185
  }
163
186
  async function getSession(sql, id, ctx) {
164
187
  const rows = await sql`
165
- SELECT id, app_id, user_id, title, model, mode, created_at, updated_at
188
+ SELECT id, app_id, user_id, title, model, mode, web_search_enabled, thinking_enabled, hidden, created_at, updated_at
166
189
  FROM sessions
167
190
  WHERE id = ${id}
168
191
  AND (${ctx.appId || null}::TEXT IS NULL OR app_id = ${ctx.appId || null})
@@ -172,9 +195,10 @@ async function getSession(sql, id, ctx) {
172
195
  }
173
196
  async function createSession(sql, input, ctx) {
174
197
  const now = /* @__PURE__ */ new Date();
198
+ const hidden = input.hidden ?? false;
175
199
  await sql`
176
- INSERT INTO sessions (id, app_id, user_id, title, model, mode, created_at, updated_at)
177
- VALUES (${input.id}, ${ctx.appId || null}, ${ctx.userId || null}, ${input.title}, ${input.model}, ${input.mode}, ${now}, ${now})
200
+ INSERT INTO sessions (id, app_id, user_id, title, model, mode, web_search_enabled, thinking_enabled, hidden, created_at, updated_at)
201
+ VALUES (${input.id}, ${ctx.appId || null}, ${ctx.userId || null}, ${input.title}, ${input.model}, ${input.mode}, ${input.webSearchEnabled}, ${input.thinkingEnabled}, ${hidden}, ${now}, ${now})
178
202
  `;
179
203
  return {
180
204
  id: input.id,
@@ -183,6 +207,9 @@ async function createSession(sql, input, ctx) {
183
207
  title: input.title,
184
208
  model: input.model,
185
209
  mode: input.mode,
210
+ webSearchEnabled: input.webSearchEnabled,
211
+ thinkingEnabled: input.thinkingEnabled,
212
+ hidden,
186
213
  createdAt: now,
187
214
  updatedAt: now
188
215
  };
@@ -192,9 +219,12 @@ async function updateSession(sql, id, data, ctx) {
192
219
  await sql`
193
220
  UPDATE sessions
194
221
  SET updated_at = ${now},
195
- title = COALESCE(${data.title || null}, title),
196
- model = COALESCE(${data.model || null}, model),
197
- mode = COALESCE(${data.mode || null}, mode)
222
+ title = COALESCE(${data.title ?? null}, title),
223
+ model = COALESCE(${data.model ?? null}, model),
224
+ mode = COALESCE(${data.mode ?? null}, mode),
225
+ web_search_enabled = COALESCE(${data.webSearchEnabled ?? null}, web_search_enabled),
226
+ thinking_enabled = COALESCE(${data.thinkingEnabled ?? null}, thinking_enabled),
227
+ hidden = COALESCE(${data.hidden ?? null}, hidden)
198
228
  WHERE id = ${id}
199
229
  AND (${ctx.appId || null}::TEXT IS NULL OR app_id = ${ctx.appId || null})
200
230
  AND (${ctx.userId || null}::TEXT IS NULL OR user_id = ${ctx.userId || null})
@@ -218,17 +248,19 @@ function toMessageRecord(row) {
218
248
  userId: row.user_id,
219
249
  role: row.role,
220
250
  content: row.content,
221
- thinking: row.thinking,
222
- toolCalls: row.tool_calls,
223
- searchResults: row.search_results,
251
+ model: row.model,
252
+ mode: row.mode,
253
+ webSearchEnabled: row.web_search_enabled,
254
+ thinkingEnabled: row.thinking_enabled,
255
+ steps: row.steps,
224
256
  operationIds: row.operation_ids,
225
257
  timestamp: new Date(row.timestamp)
226
258
  };
227
259
  }
228
260
  async function getMessages(sql, sessionId) {
229
261
  const rows = await sql`
230
- SELECT id, session_id, app_id, user_id, role, content, thinking,
231
- tool_calls, search_results, operation_ids, timestamp
262
+ SELECT id, session_id, app_id, user_id, role, content, model, mode,
263
+ web_search_enabled, thinking_enabled, steps, operation_ids, timestamp
232
264
  FROM messages
233
265
  WHERE session_id = ${sessionId}
234
266
  ORDER BY timestamp
@@ -237,8 +269,8 @@ async function getMessages(sql, sessionId) {
237
269
  }
238
270
  async function getMessage(sql, id, ctx) {
239
271
  const rows = await sql`
240
- SELECT id, session_id, app_id, user_id, role, content, thinking,
241
- tool_calls, search_results, operation_ids, timestamp
272
+ SELECT id, session_id, app_id, user_id, role, content, model, mode,
273
+ web_search_enabled, thinking_enabled, steps, operation_ids, timestamp
242
274
  FROM messages
243
275
  WHERE id = ${id}
244
276
  AND (${ctx.appId || null}::TEXT IS NULL OR app_id = ${ctx.appId || null})
@@ -249,8 +281,8 @@ async function getMessage(sql, id, ctx) {
249
281
  async function saveMessage(sql, input, ctx) {
250
282
  const now = /* @__PURE__ */ new Date();
251
283
  await sql`
252
- INSERT INTO messages (id, session_id, app_id, user_id, role, content, thinking, tool_calls, search_results, operation_ids, timestamp)
253
- VALUES (${input.id}, ${input.sessionId}, ${ctx.appId || null}, ${ctx.userId || null}, ${input.role}, ${input.content}, ${input.thinking}, ${input.toolCalls}, ${input.searchResults}, ${input.operationIds}, ${now})
284
+ INSERT INTO messages (id, session_id, app_id, user_id, role, content, model, mode, web_search_enabled, thinking_enabled, steps, operation_ids, timestamp)
285
+ VALUES (${input.id}, ${input.sessionId}, ${ctx.appId || null}, ${ctx.userId || null}, ${input.role}, ${input.content}, ${input.model}, ${input.mode}, ${input.webSearchEnabled}, ${input.thinkingEnabled}, ${input.steps}, ${input.operationIds}, ${now})
254
286
  `;
255
287
  await sql`UPDATE sessions SET updated_at = ${now} WHERE id = ${input.sessionId}`;
256
288
  return {
@@ -260,13 +292,50 @@ async function saveMessage(sql, input, ctx) {
260
292
  userId: ctx.userId || null,
261
293
  role: input.role,
262
294
  content: input.content,
263
- thinking: input.thinking,
264
- toolCalls: input.toolCalls,
265
- searchResults: input.searchResults,
295
+ model: input.model,
296
+ mode: input.mode,
297
+ webSearchEnabled: input.webSearchEnabled,
298
+ thinkingEnabled: input.thinkingEnabled,
299
+ steps: input.steps,
266
300
  operationIds: input.operationIds,
267
301
  timestamp: now
268
302
  };
269
303
  }
304
+ async function updateMessage(sql, id, data, ctx) {
305
+ const updates = [];
306
+ if (data.content !== void 0) {
307
+ updates.push("content");
308
+ }
309
+ if (data.steps !== void 0) {
310
+ updates.push("steps");
311
+ }
312
+ if (updates.length === 0) return;
313
+ if (data.content !== void 0 && data.steps !== void 0) {
314
+ await sql`
315
+ UPDATE messages
316
+ SET content = ${data.content}, steps = ${data.steps}
317
+ WHERE id = ${id}
318
+ AND (${ctx.appId || null}::TEXT IS NULL OR app_id = ${ctx.appId || null})
319
+ AND (${ctx.userId || null}::TEXT IS NULL OR user_id = ${ctx.userId || null})
320
+ `;
321
+ } else if (data.content !== void 0) {
322
+ await sql`
323
+ UPDATE messages
324
+ SET content = ${data.content}
325
+ WHERE id = ${id}
326
+ AND (${ctx.appId || null}::TEXT IS NULL OR app_id = ${ctx.appId || null})
327
+ AND (${ctx.userId || null}::TEXT IS NULL OR user_id = ${ctx.userId || null})
328
+ `;
329
+ } else if (data.steps !== void 0) {
330
+ await sql`
331
+ UPDATE messages
332
+ SET steps = ${data.steps}
333
+ WHERE id = ${id}
334
+ AND (${ctx.appId || null}::TEXT IS NULL OR app_id = ${ctx.appId || null})
335
+ AND (${ctx.userId || null}::TEXT IS NULL OR user_id = ${ctx.userId || null})
336
+ `;
337
+ }
338
+ }
270
339
  async function deleteMessagesAfter(sql, sessionId, timestamp) {
271
340
  await sql`
272
341
  DELETE FROM messages
@@ -567,6 +636,10 @@ var PostgresAdapter = class {
567
636
  await this.ensureInitialized();
568
637
  return saveMessage(this.sql, input, ctx);
569
638
  }
639
+ async updateMessage(id, data, ctx) {
640
+ await this.ensureInitialized();
641
+ await updateMessage(this.sql, id, data, ctx);
642
+ }
570
643
  async deleteMessagesAfter(sessionId, timestamp, ctx) {
571
644
  await this.ensureInitialized();
572
645
  const session = await this.getSession(sessionId, ctx);
@@ -631,6 +704,50 @@ var PostgresAdapter = class {
631
704
  await this.ensureInitialized();
632
705
  return searchSimilar(this.sql, queryEmbedding, options, ctx);
633
706
  }
707
+ // ============ 用户设置 ============
708
+ async getUserSetting(key, ctx) {
709
+ await this.ensureInitialized();
710
+ const id = `${ctx.appId || "default"}_${ctx.userId || "default"}_${key}`;
711
+ const result = await this.sql`
712
+ SELECT value FROM user_settings
713
+ WHERE id = ${id}
714
+ LIMIT 1
715
+ `;
716
+ return result.length > 0 ? result[0].value : null;
717
+ }
718
+ async setUserSetting(key, value, ctx) {
719
+ await this.ensureInitialized();
720
+ const id = `${ctx.appId || "default"}_${ctx.userId || "default"}_${key}`;
721
+ await this.sql`
722
+ INSERT INTO user_settings (id, app_id, user_id, key, value, created_at, updated_at)
723
+ VALUES (${id}, ${ctx.appId || null}, ${ctx.userId || null}, ${key}, ${value}, NOW(), NOW())
724
+ ON CONFLICT (id) DO UPDATE SET
725
+ value = ${value},
726
+ updated_at = NOW()
727
+ `;
728
+ }
729
+ async getUserSettings(ctx) {
730
+ await this.ensureInitialized();
731
+ const results = await this.sql`
732
+ SELECT key, value FROM user_settings
733
+ WHERE app_id IS NOT DISTINCT FROM ${ctx.appId || null}
734
+ AND user_id IS NOT DISTINCT FROM ${ctx.userId || null}
735
+ `;
736
+ const settings = {};
737
+ for (const row of results) {
738
+ settings[row.key] = row.value;
739
+ }
740
+ return settings;
741
+ }
742
+ async deleteUserSetting(key, ctx) {
743
+ await this.ensureInitialized();
744
+ await this.sql`
745
+ DELETE FROM user_settings
746
+ WHERE key = ${key}
747
+ AND app_id IS NOT DISTINCT FROM ${ctx.appId || null}
748
+ AND user_id IS NOT DISTINCT FROM ${ctx.userId || null}
749
+ `;
750
+ }
634
751
  // ============ 生命周期 ============
635
752
  async close() {
636
753
  await this.sql.end();
@@ -11,6 +11,10 @@ var sessions = sqliteTable("sessions", {
11
11
  title: text("title").notNull(),
12
12
  model: text("model").notNull(),
13
13
  mode: text("mode").notNull(),
14
+ webSearchEnabled: integer("web_search_enabled", { mode: "boolean" }).notNull().default(true),
15
+ thinkingEnabled: integer("thinking_enabled", { mode: "boolean" }).notNull().default(true),
16
+ /** 是否在 tab 栏隐藏(关闭但不删除) */
17
+ hidden: integer("hidden", { mode: "boolean" }).notNull().default(false),
14
18
  createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
15
19
  updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
16
20
  }, (table) => [
@@ -24,9 +28,16 @@ var messages = sqliteTable("messages", {
24
28
  userId: text("user_id"),
25
29
  role: text("role").notNull(),
26
30
  content: text("content").notNull(),
27
- thinking: text("thinking"),
28
- toolCalls: text("tool_calls"),
29
- searchResults: text("search_results"),
31
+ /** 生成此消息时使用的模型 */
32
+ model: text("model"),
33
+ /** 生成此消息时使用的模式 (ask/agent) */
34
+ mode: text("mode"),
35
+ /** 生成此消息时是否启用 web 搜索 */
36
+ webSearchEnabled: integer("web_search_enabled", { mode: "boolean" }),
37
+ /** 生成此消息时是否启用深度思考 */
38
+ thinkingEnabled: integer("thinking_enabled", { mode: "boolean" }),
39
+ /** 执行步骤列表 JSON */
40
+ steps: text("steps"),
30
41
  operationIds: text("operation_ids"),
31
42
  timestamp: integer("timestamp", { mode: "timestamp" }).notNull()
32
43
  }, (table) => [
@@ -105,6 +116,9 @@ function initSchema(sqlite) {
105
116
  title TEXT NOT NULL,
106
117
  model TEXT NOT NULL,
107
118
  mode TEXT NOT NULL,
119
+ web_search_enabled INTEGER NOT NULL DEFAULT 1,
120
+ thinking_enabled INTEGER NOT NULL DEFAULT 1,
121
+ hidden INTEGER NOT NULL DEFAULT 0,
108
122
  created_at INTEGER NOT NULL,
109
123
  updated_at INTEGER NOT NULL
110
124
  );
@@ -116,9 +130,11 @@ function initSchema(sqlite) {
116
130
  user_id TEXT,
117
131
  role TEXT NOT NULL,
118
132
  content TEXT NOT NULL,
119
- thinking TEXT,
120
- tool_calls TEXT,
121
- search_results TEXT,
133
+ model TEXT,
134
+ mode TEXT,
135
+ web_search_enabled INTEGER,
136
+ thinking_enabled INTEGER,
137
+ steps TEXT,
122
138
  operation_ids TEXT,
123
139
  timestamp INTEGER NOT NULL
124
140
  );
@@ -173,6 +189,17 @@ function initSchema(sqlite) {
173
189
  created_at INTEGER NOT NULL
174
190
  );
175
191
 
192
+ CREATE TABLE IF NOT EXISTS user_settings (
193
+ id TEXT PRIMARY KEY,
194
+ app_id TEXT,
195
+ user_id TEXT,
196
+ key TEXT NOT NULL,
197
+ value TEXT NOT NULL,
198
+ created_at INTEGER NOT NULL,
199
+ updated_at INTEGER NOT NULL,
200
+ UNIQUE(app_id, user_id, key)
201
+ );
202
+
176
203
  -- \u7D22\u5F15
177
204
  CREATE INDEX IF NOT EXISTS idx_sessions_tenant ON sessions(app_id, user_id);
178
205
  CREATE INDEX IF NOT EXISTS idx_sessions_updated ON sessions(updated_at);
@@ -185,7 +212,41 @@ function initSchema(sqlite) {
185
212
  CREATE INDEX IF NOT EXISTS idx_trash_tenant ON trash(app_id, user_id);
186
213
  CREATE INDEX IF NOT EXISTS idx_trash_auto_delete ON trash(auto_delete_at);
187
214
  CREATE INDEX IF NOT EXISTS idx_embeddings_session ON embeddings(session_id);
215
+ CREATE INDEX IF NOT EXISTS idx_user_settings_tenant ON user_settings(app_id, user_id);
216
+ CREATE INDEX IF NOT EXISTS idx_user_settings_key ON user_settings(key);
188
217
  `);
218
+ try {
219
+ sqlite.exec(`ALTER TABLE sessions ADD COLUMN web_search_enabled INTEGER NOT NULL DEFAULT 1`);
220
+ } catch {
221
+ }
222
+ try {
223
+ sqlite.exec(`ALTER TABLE sessions ADD COLUMN thinking_enabled INTEGER NOT NULL DEFAULT 1`);
224
+ } catch {
225
+ }
226
+ try {
227
+ sqlite.exec(`ALTER TABLE messages ADD COLUMN model TEXT`);
228
+ } catch {
229
+ }
230
+ try {
231
+ sqlite.exec(`ALTER TABLE messages ADD COLUMN mode TEXT`);
232
+ } catch {
233
+ }
234
+ try {
235
+ sqlite.exec(`ALTER TABLE messages ADD COLUMN web_search_enabled INTEGER`);
236
+ } catch {
237
+ }
238
+ try {
239
+ sqlite.exec(`ALTER TABLE messages ADD COLUMN thinking_enabled INTEGER`);
240
+ } catch {
241
+ }
242
+ try {
243
+ sqlite.exec(`ALTER TABLE sessions ADD COLUMN hidden INTEGER NOT NULL DEFAULT 0`);
244
+ } catch {
245
+ }
246
+ try {
247
+ sqlite.exec(`ALTER TABLE messages ADD COLUMN steps TEXT`);
248
+ } catch {
249
+ }
189
250
  }
190
251
  function buildSessionsTenantCondition(ctx) {
191
252
  const conditions = [];
@@ -235,6 +296,9 @@ function toSessionRecord(row) {
235
296
  title: row.title,
236
297
  model: row.model,
237
298
  mode: row.mode,
299
+ webSearchEnabled: row.webSearchEnabled,
300
+ thinkingEnabled: row.thinkingEnabled,
301
+ hidden: row.hidden,
238
302
  createdAt: row.createdAt,
239
303
  updatedAt: row.updatedAt
240
304
  };
@@ -259,6 +323,9 @@ function createSession(db, input, ctx) {
259
323
  title: input.title,
260
324
  model: input.model,
261
325
  mode: input.mode,
326
+ webSearchEnabled: input.webSearchEnabled,
327
+ thinkingEnabled: input.thinkingEnabled,
328
+ hidden: input.hidden ?? false,
262
329
  createdAt: now,
263
330
  updatedAt: now
264
331
  };
@@ -269,6 +336,9 @@ function createSession(db, input, ctx) {
269
336
  title: record.title,
270
337
  model: record.model,
271
338
  mode: record.mode,
339
+ webSearchEnabled: record.webSearchEnabled,
340
+ thinkingEnabled: record.thinkingEnabled,
341
+ hidden: record.hidden,
272
342
  createdAt: record.createdAt,
273
343
  updatedAt: record.updatedAt
274
344
  }).run();
@@ -281,6 +351,9 @@ function updateSession(db, id, data, ctx) {
281
351
  if (data.title !== void 0) updateData.title = data.title;
282
352
  if (data.model !== void 0) updateData.model = data.model;
283
353
  if (data.mode !== void 0) updateData.mode = data.mode;
354
+ if (data.webSearchEnabled !== void 0) updateData.webSearchEnabled = data.webSearchEnabled;
355
+ if (data.thinkingEnabled !== void 0) updateData.thinkingEnabled = data.thinkingEnabled;
356
+ if (data.hidden !== void 0) updateData.hidden = data.hidden;
284
357
  db.update(sessions).set(updateData).where(condition).run();
285
358
  }
286
359
  function deleteSession(db, id, ctx) {
@@ -299,9 +372,11 @@ function toMessageRecord(row) {
299
372
  userId: row.userId,
300
373
  role: row.role,
301
374
  content: row.content,
302
- thinking: row.thinking,
303
- toolCalls: row.toolCalls,
304
- searchResults: row.searchResults,
375
+ model: row.model,
376
+ mode: row.mode,
377
+ webSearchEnabled: row.webSearchEnabled,
378
+ thinkingEnabled: row.thinkingEnabled,
379
+ steps: row.steps,
305
380
  operationIds: row.operationIds,
306
381
  timestamp: row.timestamp
307
382
  };
@@ -325,9 +400,11 @@ function saveMessage(db, input, ctx) {
325
400
  userId: ctx.userId || null,
326
401
  role: input.role,
327
402
  content: input.content,
328
- thinking: input.thinking,
329
- toolCalls: input.toolCalls,
330
- searchResults: input.searchResults,
403
+ model: input.model,
404
+ mode: input.mode,
405
+ webSearchEnabled: input.webSearchEnabled,
406
+ thinkingEnabled: input.thinkingEnabled,
407
+ steps: input.steps,
331
408
  operationIds: input.operationIds,
332
409
  timestamp: now
333
410
  };
@@ -338,15 +415,31 @@ function saveMessage(db, input, ctx) {
338
415
  userId: record.userId,
339
416
  role: record.role,
340
417
  content: record.content,
341
- thinking: record.thinking,
342
- toolCalls: record.toolCalls,
343
- searchResults: record.searchResults,
418
+ model: record.model,
419
+ mode: record.mode,
420
+ webSearchEnabled: record.webSearchEnabled,
421
+ thinkingEnabled: record.thinkingEnabled,
422
+ steps: record.steps,
344
423
  operationIds: record.operationIds,
345
424
  timestamp: record.timestamp
346
425
  }).run();
347
426
  db.update(sessions).set({ updatedAt: now }).where(eq3(sessions.id, input.sessionId)).run();
348
427
  return record;
349
428
  }
429
+ function updateMessage(db, id, data, ctx) {
430
+ const tenantCondition = buildMessagesTenantCondition(ctx);
431
+ const condition = tenantCondition ? and3(eq3(messages.id, id), tenantCondition) : eq3(messages.id, id);
432
+ const updateData = {};
433
+ if (data.content !== void 0) {
434
+ updateData.content = data.content;
435
+ }
436
+ if (data.steps !== void 0) {
437
+ updateData.steps = data.steps;
438
+ }
439
+ if (Object.keys(updateData).length > 0) {
440
+ db.update(messages).set(updateData).where(condition).run();
441
+ }
442
+ }
350
443
  function deleteMessagesAfter(db, sessionId, timestamp) {
351
444
  db.delete(messages).where(and3(
352
445
  eq3(messages.sessionId, sessionId),
@@ -569,11 +662,60 @@ function deleteSessionEmbeddings(db, sessionId) {
569
662
  db.delete(embeddings).where(eq7(embeddings.sessionId, sessionId)).run();
570
663
  }
571
664
 
665
+ // src/adapters/sqlite/user-settings.ts
666
+ function getUserSetting(sqlite, key, ctx) {
667
+ const stmt = sqlite.prepare(`
668
+ SELECT value FROM user_settings
669
+ WHERE key = ? AND app_id IS ? AND user_id IS ?
670
+ LIMIT 1
671
+ `);
672
+ const result = stmt.get(key, ctx.appId || null, ctx.userId || null);
673
+ return result?.value || null;
674
+ }
675
+ function setUserSetting(sqlite, key, value, ctx) {
676
+ const now = Date.now();
677
+ const id = `${ctx.appId || "default"}_${ctx.userId || "default"}_${key}`;
678
+ const checkStmt = sqlite.prepare(`
679
+ SELECT created_at FROM user_settings WHERE id = ?
680
+ `);
681
+ const existing = checkStmt.get(id);
682
+ const createdAt = existing?.created_at || now;
683
+ const stmt = sqlite.prepare(`
684
+ INSERT OR REPLACE INTO user_settings
685
+ (id, app_id, user_id, key, value, created_at, updated_at)
686
+ VALUES (?, ?, ?, ?, ?, ?, ?)
687
+ `);
688
+ stmt.run(id, ctx.appId || null, ctx.userId || null, key, value, createdAt, now);
689
+ }
690
+ function getUserSettings(sqlite, ctx) {
691
+ const stmt = sqlite.prepare(`
692
+ SELECT key, value FROM user_settings
693
+ WHERE app_id IS ? AND user_id IS ?
694
+ `);
695
+ const results = stmt.all(ctx.appId || null, ctx.userId || null);
696
+ const settings = {};
697
+ for (const row of results) {
698
+ settings[row.key] = row.value;
699
+ }
700
+ return settings;
701
+ }
702
+ function deleteUserSetting(sqlite, key, ctx) {
703
+ const stmt = sqlite.prepare(`
704
+ DELETE FROM user_settings
705
+ WHERE key = ? AND app_id IS ? AND user_id IS ?
706
+ `);
707
+ stmt.run(key, ctx.appId || null, ctx.userId || null);
708
+ }
709
+
572
710
  // src/adapters/sqlite/index.ts
573
711
  var SqliteAdapter = class {
574
712
  sqlite;
575
713
  db;
576
714
  config;
715
+ // 暴露 sqlite 实例供 user-settings 使用
716
+ get sqliteInstance() {
717
+ return this.sqlite;
718
+ }
577
719
  constructor(dbPath, config = { type: "sqlite" }) {
578
720
  this.sqlite = new Database(dbPath);
579
721
  this.db = drizzle(this.sqlite);
@@ -611,6 +753,9 @@ var SqliteAdapter = class {
611
753
  async saveMessage(input, ctx) {
612
754
  return saveMessage(this.db, input, ctx);
613
755
  }
756
+ async updateMessage(id, data, ctx) {
757
+ updateMessage(this.db, id, data, ctx);
758
+ }
614
759
  async deleteMessagesAfter(sessionId, timestamp, ctx) {
615
760
  const session = await this.getSession(sessionId, ctx);
616
761
  if (!session) return;
@@ -661,6 +806,19 @@ var SqliteAdapter = class {
661
806
  async searchSimilar(queryEmbedding, options, ctx) {
662
807
  return searchSimilar(this.db, queryEmbedding, options, ctx);
663
808
  }
809
+ // ============ 用户设置 ============
810
+ async getUserSetting(key, ctx) {
811
+ return getUserSetting(this.sqlite, key, ctx);
812
+ }
813
+ async setUserSetting(key, value, ctx) {
814
+ return setUserSetting(this.sqlite, key, value, ctx);
815
+ }
816
+ async getUserSettings(ctx) {
817
+ return getUserSettings(this.sqlite, ctx);
818
+ }
819
+ async deleteUserSetting(key, ctx) {
820
+ return deleteUserSetting(this.sqlite, key, ctx);
821
+ }
664
822
  // ============ 生命周期 ============
665
823
  async close() {
666
824
  this.sqlite.close();
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core';
2
+ import Database from 'better-sqlite3';
2
3
 
3
4
  /**
4
5
  * 存储层类型定义
@@ -16,11 +17,15 @@ interface SessionRecord {
16
17
  title: string;
17
18
  model: string;
18
19
  mode: 'agent' | 'plan' | 'ask';
20
+ webSearchEnabled: boolean;
21
+ thinkingEnabled: boolean;
22
+ /** 是否在 tab 栏隐藏(关闭但不删除) */
23
+ hidden: boolean;
19
24
  createdAt: Date;
20
25
  updatedAt: Date;
21
26
  }
22
27
  type CreateSessionInput = Omit<SessionRecord, 'createdAt' | 'updatedAt' | 'appId' | 'userId'>;
23
- type UpdateSessionInput = Partial<Pick<SessionRecord, 'title' | 'model' | 'mode'>>;
28
+ type UpdateSessionInput = Partial<Pick<SessionRecord, 'title' | 'model' | 'mode' | 'webSearchEnabled' | 'thinkingEnabled' | 'hidden'>>;
24
29
  interface MessageRecord {
25
30
  id: string;
26
31
  sessionId: string;
@@ -28,13 +33,22 @@ interface MessageRecord {
28
33
  userId: string | null;
29
34
  role: 'user' | 'assistant';
30
35
  content: string;
31
- thinking: string | null;
32
- toolCalls: string | null;
33
- searchResults: string | null;
36
+ /** 生成此消息时使用的模型 */
37
+ model: string | null;
38
+ /** 生成此消息时使用的模式 (ask/agent) */
39
+ mode: string | null;
40
+ /** 生成此消息时是否启用 web 搜索 */
41
+ webSearchEnabled: boolean | null;
42
+ /** 生成此消息时是否启用深度思考 */
43
+ thinkingEnabled: boolean | null;
44
+ /** 执行步骤列表 JSON */
45
+ steps: string | null;
34
46
  operationIds: string | null;
35
47
  timestamp: Date;
36
48
  }
37
49
  type CreateMessageInput = Omit<MessageRecord, 'timestamp' | 'appId' | 'userId'>;
50
+ /** 更新消息输入(只更新 content 和 steps) */
51
+ type UpdateMessageInput = Partial<Pick<MessageRecord, 'content' | 'steps'>>;
38
52
  type OperationStatus = 'pending' | 'confirmed' | 'executed' | 'reverted' | 'failed';
39
53
  interface OperationRecord {
40
54
  id: string;
@@ -106,6 +120,8 @@ interface StorageAdapter {
106
120
  getMessages(sessionId: string, ctx: StorageContext): Promise<MessageRecord[]>;
107
121
  getMessage(id: string, ctx: StorageContext): Promise<MessageRecord | null>;
108
122
  saveMessage(message: CreateMessageInput, ctx: StorageContext): Promise<MessageRecord>;
123
+ /** 更新消息(用于增量保存助手消息) */
124
+ updateMessage(id: string, data: UpdateMessageInput, ctx: StorageContext): Promise<void>;
109
125
  deleteMessagesAfter(sessionId: string, timestamp: Date, ctx: StorageContext): Promise<void>;
110
126
  getOperations(sessionId: string, ctx: StorageContext): Promise<OperationRecord[]>;
111
127
  getOperationsByMessage(messageId: string, ctx: StorageContext): Promise<OperationRecord[]>;
@@ -118,6 +134,10 @@ interface StorageAdapter {
118
134
  moveToTrash?(item: Omit<TrashRecord, 'deletedAt' | 'autoDeleteAt' | 'appId' | 'userId'>, ctx: StorageContext): Promise<TrashRecord>;
119
135
  restoreFromTrash?(id: string, ctx: StorageContext): Promise<TrashRecord>;
120
136
  emptyExpiredTrash?(): Promise<number>;
137
+ getUserSetting(key: string, ctx: StorageContext): Promise<string | null>;
138
+ setUserSetting(key: string, value: string, ctx: StorageContext): Promise<void>;
139
+ getUserSettings(ctx: StorageContext): Promise<Record<string, string>>;
140
+ deleteUserSetting(key: string, ctx: StorageContext): Promise<void>;
121
141
  saveEmbedding?(id: string, content: string, embedding: number[], metadata: {
122
142
  sessionId: string;
123
143
  messageId?: string;
@@ -257,6 +277,57 @@ declare const sessions: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
257
277
  }, {}, {
258
278
  length: number | undefined;
259
279
  }>;
280
+ webSearchEnabled: drizzle_orm_sqlite_core.SQLiteColumn<{
281
+ name: "web_search_enabled";
282
+ tableName: "sessions";
283
+ dataType: "boolean";
284
+ columnType: "SQLiteBoolean";
285
+ data: boolean;
286
+ driverParam: number;
287
+ notNull: true;
288
+ hasDefault: true;
289
+ isPrimaryKey: false;
290
+ isAutoincrement: false;
291
+ hasRuntimeDefault: false;
292
+ enumValues: undefined;
293
+ baseColumn: never;
294
+ identity: undefined;
295
+ generated: undefined;
296
+ }, {}, {}>;
297
+ thinkingEnabled: drizzle_orm_sqlite_core.SQLiteColumn<{
298
+ name: "thinking_enabled";
299
+ tableName: "sessions";
300
+ dataType: "boolean";
301
+ columnType: "SQLiteBoolean";
302
+ data: boolean;
303
+ driverParam: number;
304
+ notNull: true;
305
+ hasDefault: true;
306
+ isPrimaryKey: false;
307
+ isAutoincrement: false;
308
+ hasRuntimeDefault: false;
309
+ enumValues: undefined;
310
+ baseColumn: never;
311
+ identity: undefined;
312
+ generated: undefined;
313
+ }, {}, {}>;
314
+ hidden: drizzle_orm_sqlite_core.SQLiteColumn<{
315
+ name: "hidden";
316
+ tableName: "sessions";
317
+ dataType: "boolean";
318
+ columnType: "SQLiteBoolean";
319
+ data: boolean;
320
+ driverParam: number;
321
+ notNull: true;
322
+ hasDefault: true;
323
+ isPrimaryKey: false;
324
+ isAutoincrement: false;
325
+ hasRuntimeDefault: false;
326
+ enumValues: undefined;
327
+ baseColumn: never;
328
+ identity: undefined;
329
+ generated: undefined;
330
+ }, {}, {}>;
260
331
  createdAt: drizzle_orm_sqlite_core.SQLiteColumn<{
261
332
  name: "created_at";
262
333
  tableName: "sessions";
@@ -413,8 +484,8 @@ declare const messages: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
413
484
  }, {}, {
414
485
  length: number | undefined;
415
486
  }>;
416
- thinking: drizzle_orm_sqlite_core.SQLiteColumn<{
417
- name: "thinking";
487
+ model: drizzle_orm_sqlite_core.SQLiteColumn<{
488
+ name: "model";
418
489
  tableName: "messages";
419
490
  dataType: "string";
420
491
  columnType: "SQLiteText";
@@ -432,8 +503,8 @@ declare const messages: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
432
503
  }, {}, {
433
504
  length: number | undefined;
434
505
  }>;
435
- toolCalls: drizzle_orm_sqlite_core.SQLiteColumn<{
436
- name: "tool_calls";
506
+ mode: drizzle_orm_sqlite_core.SQLiteColumn<{
507
+ name: "mode";
437
508
  tableName: "messages";
438
509
  dataType: "string";
439
510
  columnType: "SQLiteText";
@@ -451,8 +522,42 @@ declare const messages: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
451
522
  }, {}, {
452
523
  length: number | undefined;
453
524
  }>;
454
- searchResults: drizzle_orm_sqlite_core.SQLiteColumn<{
455
- name: "search_results";
525
+ webSearchEnabled: drizzle_orm_sqlite_core.SQLiteColumn<{
526
+ name: "web_search_enabled";
527
+ tableName: "messages";
528
+ dataType: "boolean";
529
+ columnType: "SQLiteBoolean";
530
+ data: boolean;
531
+ driverParam: number;
532
+ notNull: false;
533
+ hasDefault: false;
534
+ isPrimaryKey: false;
535
+ isAutoincrement: false;
536
+ hasRuntimeDefault: false;
537
+ enumValues: undefined;
538
+ baseColumn: never;
539
+ identity: undefined;
540
+ generated: undefined;
541
+ }, {}, {}>;
542
+ thinkingEnabled: drizzle_orm_sqlite_core.SQLiteColumn<{
543
+ name: "thinking_enabled";
544
+ tableName: "messages";
545
+ dataType: "boolean";
546
+ columnType: "SQLiteBoolean";
547
+ data: boolean;
548
+ driverParam: number;
549
+ notNull: false;
550
+ hasDefault: false;
551
+ isPrimaryKey: false;
552
+ isAutoincrement: false;
553
+ hasRuntimeDefault: false;
554
+ enumValues: undefined;
555
+ baseColumn: never;
556
+ identity: undefined;
557
+ generated: undefined;
558
+ }, {}, {}>;
559
+ steps: drizzle_orm_sqlite_core.SQLiteColumn<{
560
+ name: "steps";
456
561
  tableName: "messages";
457
562
  dataType: "string";
458
563
  columnType: "SQLiteText";
@@ -1259,6 +1364,7 @@ declare class SqliteAdapter implements StorageAdapter {
1259
1364
  private sqlite;
1260
1365
  private db;
1261
1366
  private config;
1367
+ get sqliteInstance(): Database.Database;
1262
1368
  constructor(dbPath: string, config?: StorageConfig);
1263
1369
  getSessions(ctx: StorageContext): Promise<SessionRecord[]>;
1264
1370
  getSession(id: string, ctx: StorageContext): Promise<SessionRecord | null>;
@@ -1268,6 +1374,7 @@ declare class SqliteAdapter implements StorageAdapter {
1268
1374
  getMessages(sessionId: string, ctx: StorageContext): Promise<MessageRecord[]>;
1269
1375
  getMessage(id: string, ctx: StorageContext): Promise<MessageRecord | null>;
1270
1376
  saveMessage(input: CreateMessageInput, ctx: StorageContext): Promise<MessageRecord>;
1377
+ updateMessage(id: string, data: UpdateMessageInput, ctx: StorageContext): Promise<void>;
1271
1378
  deleteMessagesAfter(sessionId: string, timestamp: Date, ctx: StorageContext): Promise<void>;
1272
1379
  getOperations(sessionId: string, ctx: StorageContext): Promise<OperationRecord[]>;
1273
1380
  getOperationsByMessage(messageId: string, ctx: StorageContext): Promise<OperationRecord[]>;
@@ -1286,6 +1393,10 @@ declare class SqliteAdapter implements StorageAdapter {
1286
1393
  contentType: EmbeddingRecord['contentType'];
1287
1394
  }, ctx: StorageContext): Promise<void>;
1288
1395
  searchSimilar(queryEmbedding: number[], options: VectorSearchOptions, ctx: StorageContext): Promise<VectorSearchResult[]>;
1396
+ getUserSetting(key: string, ctx: StorageContext): Promise<string | null>;
1397
+ setUserSetting(key: string, value: string, ctx: StorageContext): Promise<void>;
1398
+ getUserSettings(ctx: StorageContext): Promise<Record<string, string>>;
1399
+ deleteUserSetting(key: string, ctx: StorageContext): Promise<void>;
1289
1400
  close(): Promise<void>;
1290
1401
  }
1291
1402
 
@@ -1309,6 +1420,7 @@ declare class PostgresAdapter implements StorageAdapter {
1309
1420
  getMessages(sessionId: string, ctx: StorageContext): Promise<MessageRecord[]>;
1310
1421
  getMessage(id: string, ctx: StorageContext): Promise<MessageRecord | null>;
1311
1422
  saveMessage(input: CreateMessageInput, ctx: StorageContext): Promise<MessageRecord>;
1423
+ updateMessage(id: string, data: UpdateMessageInput, ctx: StorageContext): Promise<void>;
1312
1424
  deleteMessagesAfter(sessionId: string, timestamp: Date, ctx: StorageContext): Promise<void>;
1313
1425
  getOperations(sessionId: string, ctx: StorageContext): Promise<OperationRecord[]>;
1314
1426
  getOperationsByMessage(messageId: string, ctx: StorageContext): Promise<OperationRecord[]>;
@@ -1327,6 +1439,10 @@ declare class PostgresAdapter implements StorageAdapter {
1327
1439
  contentType: EmbeddingRecord['contentType'];
1328
1440
  }, ctx: StorageContext): Promise<void>;
1329
1441
  searchSimilar(queryEmbedding: number[], options: VectorSearchOptions, ctx: StorageContext): Promise<VectorSearchResult[]>;
1442
+ getUserSetting(key: string, ctx: StorageContext): Promise<string | null>;
1443
+ setUserSetting(key: string, value: string, ctx: StorageContext): Promise<void>;
1444
+ getUserSettings(ctx: StorageContext): Promise<Record<string, string>>;
1445
+ deleteUserSetting(key: string, ctx: StorageContext): Promise<void>;
1330
1446
  close(): Promise<void>;
1331
1447
  }
1332
1448
 
@@ -1375,4 +1491,4 @@ declare function getDefaultBackupDir(): string;
1375
1491
  */
1376
1492
  declare function getDefaultTrashDir(): string;
1377
1493
 
1378
- export { type BackupRecord, type CreateMessageInput, type CreateOperationInput, type CreateSessionInput, type EmbeddingRecord, type MessageRecord, type OperationRecord, type OperationStatus, PostgresAdapter, type SessionRecord, SqliteAdapter, type StorageAdapter, type StorageConfig, type StorageContext, type TrashRecord, type UpdateSessionInput, type VectorSearchOptions, type VectorSearchResult, backups, createStorage, embeddings, getDefaultBackupDir, getDefaultStoragePath, getDefaultTrashDir, messages, operations, sessions, trash };
1494
+ export { type BackupRecord, type CreateMessageInput, type CreateOperationInput, type CreateSessionInput, type EmbeddingRecord, type MessageRecord, type OperationRecord, type OperationStatus, PostgresAdapter, type SessionRecord, SqliteAdapter, type StorageAdapter, type StorageConfig, type StorageContext, type TrashRecord, type UpdateMessageInput, type UpdateSessionInput, type VectorSearchOptions, type VectorSearchResult, backups, createStorage, embeddings, getDefaultBackupDir, getDefaultStoragePath, getDefaultTrashDir, messages, operations, sessions, trash };
package/dist/index.js CHANGED
@@ -6,10 +6,10 @@ import {
6
6
  operations,
7
7
  sessions,
8
8
  trash
9
- } from "./chunk-GCKDCC3O.js";
9
+ } from "./chunk-LZ2LBU6Q.js";
10
10
  import {
11
11
  PostgresAdapter
12
- } from "./chunk-BRWTH4LQ.js";
12
+ } from "./chunk-7AKL7W3Y.js";
13
13
 
14
14
  // src/index.ts
15
15
  import { mkdirSync, existsSync } from "fs";
@@ -24,14 +24,14 @@ async function createStorage(config) {
24
24
  if (!existsSync(dir)) {
25
25
  mkdirSync(dir, { recursive: true });
26
26
  }
27
- const { SqliteAdapter: SqliteAdapter2 } = await import("./sqlite-AJRNISP2.js");
27
+ const { SqliteAdapter: SqliteAdapter2 } = await import("./sqlite-5SSRI3KS.js");
28
28
  return new SqliteAdapter2(config.sqlitePath, config);
29
29
  }
30
30
  case "postgres": {
31
31
  if (!config.postgresUrl) {
32
32
  throw new Error("PostgreSQL \u9700\u8981\u63D0\u4F9B postgresUrl \u914D\u7F6E");
33
33
  }
34
- const { PostgresAdapter: PostgresAdapter2 } = await import("./postgres-PBPM2KDK.js");
34
+ const { PostgresAdapter: PostgresAdapter2 } = await import("./postgres-4AZE26G4.js");
35
35
  return new PostgresAdapter2(config.postgresUrl, config);
36
36
  }
37
37
  default:
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PostgresAdapter
3
- } from "./chunk-BRWTH4LQ.js";
3
+ } from "./chunk-7AKL7W3Y.js";
4
4
  export {
5
5
  PostgresAdapter
6
6
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SqliteAdapter
3
- } from "./chunk-GCKDCC3O.js";
3
+ } from "./chunk-LZ2LBU6Q.js";
4
4
  export {
5
5
  SqliteAdapter
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huyooo/ai-chat-storage",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "AI Chat 统一存储层 - SQLite",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -8,8 +8,8 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "import": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
13
  }
14
14
  },
15
15
  "files": [