mioku-plugin-chat 2.0.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/README.md +24 -0
- package/configs/base.ts +12 -0
- package/configs/personalization.ts +84 -0
- package/configs/settings.ts +54 -0
- package/context.ts +98 -0
- package/core/base.ts +936 -0
- package/core/chat-engine.ts +779 -0
- package/core/external-skills.ts +94 -0
- package/core/feature-prompts.ts +47 -0
- package/core/media/audio.ts +73 -0
- package/core/media/gif-extractor.ts +118 -0
- package/core/media/history-media.ts +751 -0
- package/core/media/image-analyzer.ts +447 -0
- package/core/media/markdown-message.ts +190 -0
- package/core/multimodal.ts +180 -0
- package/core/prompt.ts +827 -0
- package/core/tools.ts +862 -0
- package/core/web/searxng.ts +184 -0
- package/core/web/web-reader.ts +792 -0
- package/db.ts +849 -0
- package/humanize/emoji-agent.ts +397 -0
- package/humanize/expression.ts +152 -0
- package/humanize/index.ts +40 -0
- package/humanize/memory.ts +131 -0
- package/humanize/planner.ts +249 -0
- package/humanize/topic.ts +205 -0
- package/humanize/utils.ts +28 -0
- package/index.ts +1830 -0
- package/manage/cooldown.ts +445 -0
- package/manage/group-structured-history.ts +195 -0
- package/manage/idle-check.ts +186 -0
- package/manage/queue-processor.ts +228 -0
- package/manage/rate-limiter.ts +312 -0
- package/manage/session.ts +96 -0
- package/manage/skill-session.ts +173 -0
- package/manage/types.ts +138 -0
- package/package.json +51 -0
- package/tsconfig.json +7 -0
- package/types.ts +381 -0
- package/utils/index.ts +9 -0
- package/utils/message.ts +496 -0
- package/utils/queue.ts +186 -0
package/db.ts
ADDED
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import * as fs from "fs/promises";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import type {
|
|
6
|
+
SessionMeta,
|
|
7
|
+
ChatMessage,
|
|
8
|
+
TopicRecord,
|
|
9
|
+
ExpressionRecord,
|
|
10
|
+
ImageRecord,
|
|
11
|
+
MediaSummaryRecord,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 聊天数据库接口
|
|
16
|
+
*/
|
|
17
|
+
export interface ChatDatabase {
|
|
18
|
+
saveSession(meta: SessionMeta): void;
|
|
19
|
+
getSession(id: string): SessionMeta | null;
|
|
20
|
+
saveMessage(msg: ChatMessage): void;
|
|
21
|
+
getMessages(
|
|
22
|
+
sessionId: string,
|
|
23
|
+
limit?: number,
|
|
24
|
+
before?: number,
|
|
25
|
+
): ChatMessage[];
|
|
26
|
+
// 获取 bot 发送的消息
|
|
27
|
+
getBotMessages(groupId: number, limit?: number): ChatMessage[];
|
|
28
|
+
getStoredGroupNoticeMessages(groupId: number, limit?: number): ChatMessage[];
|
|
29
|
+
getMessagesByUser(
|
|
30
|
+
userId: number,
|
|
31
|
+
sessionId?: string,
|
|
32
|
+
limit?: number,
|
|
33
|
+
): ChatMessage[];
|
|
34
|
+
getAllMessagesByUser(userId: number, sessionId?: string): ChatMessage[];
|
|
35
|
+
getMessagesByTimeRange(
|
|
36
|
+
sessionId: string,
|
|
37
|
+
startTimestamp: number,
|
|
38
|
+
endTimestamp: number,
|
|
39
|
+
): ChatMessage[];
|
|
40
|
+
searchMessages(
|
|
41
|
+
sessionId: string,
|
|
42
|
+
keyword: string,
|
|
43
|
+
limit?: number,
|
|
44
|
+
): ChatMessage[];
|
|
45
|
+
updateCompressedContext(sessionId: string, context: string): void;
|
|
46
|
+
deleteSessionMessages(sessionId: string): void;
|
|
47
|
+
// 删除 bot 发送的消息
|
|
48
|
+
deleteBotMessages(sessionId: string): void;
|
|
49
|
+
// 话题
|
|
50
|
+
saveTopic(topic: TopicRecord): number;
|
|
51
|
+
getTopics(sessionId: string, limit?: number): TopicRecord[];
|
|
52
|
+
getTopicByWindow(
|
|
53
|
+
sessionId: string,
|
|
54
|
+
windowStartAt: number,
|
|
55
|
+
windowEndAt: number,
|
|
56
|
+
): TopicRecord | null;
|
|
57
|
+
updateTopic(
|
|
58
|
+
id: number,
|
|
59
|
+
updates: Partial<
|
|
60
|
+
Pick<TopicRecord, "summary" | "keywords" | "messageCount" | "updatedAt">
|
|
61
|
+
>,
|
|
62
|
+
): void;
|
|
63
|
+
// 表达学习
|
|
64
|
+
saveExpression(expr: ExpressionRecord): void;
|
|
65
|
+
getExpressions(sessionId: string, limit?: number): ExpressionRecord[];
|
|
66
|
+
getExpressionsByUser(userId: number, limit?: number): ExpressionRecord[];
|
|
67
|
+
replaceExpressionsByUser(
|
|
68
|
+
userId: number,
|
|
69
|
+
userName: string,
|
|
70
|
+
expressions: Array<
|
|
71
|
+
Pick<ExpressionRecord, "situation" | "style" | "example">
|
|
72
|
+
>,
|
|
73
|
+
): void;
|
|
74
|
+
getExpressionCount(sessionId: string): number;
|
|
75
|
+
deleteOldestExpressions(sessionId: string, keepCount: number): void;
|
|
76
|
+
// 图片记录
|
|
77
|
+
saveImage(image: ImageRecord): void;
|
|
78
|
+
getImageByHash(hash: string): ImageRecord | null;
|
|
79
|
+
getImageByUrl(url: string): ImageRecord | null;
|
|
80
|
+
getAllImages(): ImageRecord[];
|
|
81
|
+
// 媒体/卡片摘要缓存
|
|
82
|
+
saveMediaSummary(summary: MediaSummaryRecord): void;
|
|
83
|
+
getMediaSummary(key: string): MediaSummaryRecord | null;
|
|
84
|
+
saveMediaSummarySource(sourceKey: string, summaryKey: string): void;
|
|
85
|
+
getMediaSummaryBySource(sourceKey: string): MediaSummaryRecord | null;
|
|
86
|
+
close(): void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* SQLite 数据库实现
|
|
91
|
+
*/
|
|
92
|
+
export async function initDatabase(): Promise<ChatDatabase> {
|
|
93
|
+
const dbDir = path.join(process.cwd(), "data", "chat");
|
|
94
|
+
if (!existsSync(dbDir)) {
|
|
95
|
+
await fs.mkdir(dbDir, { recursive: true });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const dbPath = path.join(dbDir, "chat.db");
|
|
99
|
+
const db = new Database(dbPath);
|
|
100
|
+
|
|
101
|
+
// 开启 WAL 模式提升并发性能
|
|
102
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
103
|
+
|
|
104
|
+
// 创建表
|
|
105
|
+
db.exec(`
|
|
106
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
107
|
+
id TEXT PRIMARY KEY,
|
|
108
|
+
type TEXT NOT NULL,
|
|
109
|
+
target_id INTEGER NOT NULL,
|
|
110
|
+
created_at INTEGER NOT NULL,
|
|
111
|
+
updated_at INTEGER NOT NULL,
|
|
112
|
+
compressed_context TEXT
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
116
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
117
|
+
session_id TEXT NOT NULL,
|
|
118
|
+
role TEXT NOT NULL,
|
|
119
|
+
content TEXT NOT NULL,
|
|
120
|
+
user_id INTEGER,
|
|
121
|
+
user_name TEXT,
|
|
122
|
+
user_role TEXT,
|
|
123
|
+
user_title TEXT,
|
|
124
|
+
group_id INTEGER,
|
|
125
|
+
group_name TEXT,
|
|
126
|
+
timestamp INTEGER NOT NULL,
|
|
127
|
+
message_id INTEGER
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, timestamp);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_messages_user ON messages(user_id, timestamp);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_messages_content ON messages(session_id, content);
|
|
133
|
+
|
|
134
|
+
CREATE TABLE IF NOT EXISTS topics (
|
|
135
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
136
|
+
session_id TEXT NOT NULL,
|
|
137
|
+
title TEXT NOT NULL,
|
|
138
|
+
keywords TEXT NOT NULL DEFAULT '[]',
|
|
139
|
+
summary TEXT NOT NULL,
|
|
140
|
+
message_count INTEGER NOT NULL DEFAULT 0,
|
|
141
|
+
window_start_at INTEGER,
|
|
142
|
+
window_end_at INTEGER,
|
|
143
|
+
created_at INTEGER NOT NULL,
|
|
144
|
+
updated_at INTEGER NOT NULL
|
|
145
|
+
);
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_topics_session ON topics(session_id, updated_at);
|
|
147
|
+
|
|
148
|
+
CREATE TABLE IF NOT EXISTS expressions (
|
|
149
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
150
|
+
session_id TEXT NOT NULL,
|
|
151
|
+
user_id INTEGER NOT NULL,
|
|
152
|
+
user_name TEXT NOT NULL,
|
|
153
|
+
situation TEXT NOT NULL,
|
|
154
|
+
style TEXT NOT NULL,
|
|
155
|
+
example TEXT NOT NULL,
|
|
156
|
+
created_at INTEGER NOT NULL
|
|
157
|
+
);
|
|
158
|
+
CREATE INDEX IF NOT EXISTS idx_expressions_session ON expressions(session_id, created_at);
|
|
159
|
+
CREATE INDEX IF NOT EXISTS idx_expressions_user ON expressions(user_id, created_at);
|
|
160
|
+
|
|
161
|
+
CREATE TABLE IF NOT EXISTS images (
|
|
162
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
163
|
+
hash TEXT NOT NULL UNIQUE,
|
|
164
|
+
url TEXT NOT NULL,
|
|
165
|
+
type TEXT NOT NULL,
|
|
166
|
+
description TEXT NOT NULL,
|
|
167
|
+
emotion TEXT,
|
|
168
|
+
character TEXT,
|
|
169
|
+
file_path TEXT,
|
|
170
|
+
created_at INTEGER NOT NULL
|
|
171
|
+
);
|
|
172
|
+
CREATE INDEX IF NOT EXISTS idx_images_hash ON images(hash);
|
|
173
|
+
CREATE INDEX IF NOT EXISTS idx_images_type ON images(type);
|
|
174
|
+
|
|
175
|
+
CREATE TABLE IF NOT EXISTS media_summaries (
|
|
176
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
177
|
+
key TEXT NOT NULL UNIQUE,
|
|
178
|
+
kind TEXT NOT NULL,
|
|
179
|
+
source TEXT NOT NULL,
|
|
180
|
+
summary TEXT NOT NULL,
|
|
181
|
+
created_at INTEGER NOT NULL
|
|
182
|
+
);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_media_summaries_key ON media_summaries(key);
|
|
184
|
+
CREATE INDEX IF NOT EXISTS idx_media_summaries_kind ON media_summaries(kind);
|
|
185
|
+
|
|
186
|
+
CREATE TABLE IF NOT EXISTS media_summary_sources (
|
|
187
|
+
source_key TEXT PRIMARY KEY,
|
|
188
|
+
summary_key TEXT NOT NULL,
|
|
189
|
+
created_at INTEGER NOT NULL
|
|
190
|
+
);
|
|
191
|
+
CREATE INDEX IF NOT EXISTS idx_media_summary_sources_summary ON media_summary_sources(summary_key);
|
|
192
|
+
`);
|
|
193
|
+
|
|
194
|
+
const topicColumns = db
|
|
195
|
+
.prepare("PRAGMA table_info(topics)")
|
|
196
|
+
.all() as Array<{ name: string }>;
|
|
197
|
+
const hasWindowStartAt = topicColumns.some(
|
|
198
|
+
(column) => column.name === "window_start_at",
|
|
199
|
+
);
|
|
200
|
+
const hasWindowEndAt = topicColumns.some(
|
|
201
|
+
(column) => column.name === "window_end_at",
|
|
202
|
+
);
|
|
203
|
+
if (!hasWindowStartAt) {
|
|
204
|
+
db.exec("ALTER TABLE topics ADD COLUMN window_start_at INTEGER");
|
|
205
|
+
}
|
|
206
|
+
if (!hasWindowEndAt) {
|
|
207
|
+
db.exec("ALTER TABLE topics ADD COLUMN window_end_at INTEGER");
|
|
208
|
+
}
|
|
209
|
+
db.exec(
|
|
210
|
+
"CREATE INDEX IF NOT EXISTS idx_topics_session_window ON topics(session_id, window_end_at)",
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// 预编译语句
|
|
214
|
+
const stmts = {
|
|
215
|
+
upsertSession: db.prepare(`
|
|
216
|
+
INSERT INTO sessions (id, type, target_id, created_at, updated_at, compressed_context)
|
|
217
|
+
VALUES ($id, $type, $targetId, $createdAt, $updatedAt, $compressedContext)
|
|
218
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
219
|
+
updated_at = $updatedAt,
|
|
220
|
+
compressed_context = COALESCE($compressedContext, compressed_context)
|
|
221
|
+
`),
|
|
222
|
+
getSession: db.prepare(`SELECT * FROM sessions WHERE id = $id`),
|
|
223
|
+
insertMessage: db.prepare(`
|
|
224
|
+
INSERT INTO messages (session_id, role, content, user_id, user_name, user_role, user_title, group_id, group_name, timestamp, message_id)
|
|
225
|
+
VALUES ($sessionId, $role, $content, $userId, $userName, $userRole, $userTitle, $groupId, $groupName, $timestamp, $messageId)
|
|
226
|
+
`),
|
|
227
|
+
getMessages: db.prepare(`
|
|
228
|
+
SELECT * FROM messages WHERE session_id = $sessionId ORDER BY timestamp DESC, id DESC LIMIT $limit
|
|
229
|
+
`),
|
|
230
|
+
getMessagesBefore: db.prepare(`
|
|
231
|
+
SELECT * FROM messages WHERE session_id = $sessionId AND timestamp < $timestamp ORDER BY timestamp DESC, id DESC LIMIT $limit
|
|
232
|
+
`),
|
|
233
|
+
getBotMessages: db.prepare(`
|
|
234
|
+
SELECT * FROM messages WHERE group_id = $groupId AND role = 'assistant' ORDER BY timestamp DESC LIMIT $limit
|
|
235
|
+
`),
|
|
236
|
+
getStoredGroupNoticeMessages: db.prepare(`
|
|
237
|
+
SELECT * FROM messages
|
|
238
|
+
WHERE group_id = $groupId AND role = 'user' AND content LIKE '发布了一条群公告:%'
|
|
239
|
+
ORDER BY timestamp DESC LIMIT $limit
|
|
240
|
+
`),
|
|
241
|
+
getMessagesByUser: db.prepare(`
|
|
242
|
+
SELECT * FROM messages WHERE user_id = $userId ORDER BY timestamp DESC LIMIT $limit
|
|
243
|
+
`),
|
|
244
|
+
getAllMessagesByUser: db.prepare(`
|
|
245
|
+
SELECT * FROM messages WHERE user_id = $userId ORDER BY timestamp DESC, id DESC
|
|
246
|
+
`),
|
|
247
|
+
getMessagesByUserInSession: db.prepare(`
|
|
248
|
+
SELECT * FROM messages WHERE user_id = $userId AND session_id = $sessionId ORDER BY timestamp DESC LIMIT $limit
|
|
249
|
+
`),
|
|
250
|
+
getAllMessagesByUserInSession: db.prepare(`
|
|
251
|
+
SELECT * FROM messages WHERE user_id = $userId AND session_id = $sessionId ORDER BY timestamp DESC, id DESC
|
|
252
|
+
`),
|
|
253
|
+
getMessagesByTimeRange: db.prepare(`
|
|
254
|
+
SELECT * FROM messages
|
|
255
|
+
WHERE session_id = $sessionId AND timestamp >= $startTimestamp AND timestamp < $endTimestamp
|
|
256
|
+
ORDER BY timestamp ASC, id ASC
|
|
257
|
+
`),
|
|
258
|
+
updateCompressedContext: db.prepare(`
|
|
259
|
+
UPDATE sessions SET compressed_context = $compressedContext, updated_at = $updatedAt WHERE id = $sessionId
|
|
260
|
+
`),
|
|
261
|
+
deleteSessionMessages: db.prepare(`
|
|
262
|
+
DELETE FROM messages WHERE session_id = $sessionId
|
|
263
|
+
`),
|
|
264
|
+
deleteBotMessages: db.prepare(`
|
|
265
|
+
DELETE FROM messages WHERE session_id = $sessionId AND role = 'assistant'
|
|
266
|
+
`),
|
|
267
|
+
resetSessionContext: db.prepare(`
|
|
268
|
+
UPDATE sessions SET compressed_context = NULL, updated_at = $updatedAt WHERE id = $sessionId
|
|
269
|
+
`),
|
|
270
|
+
// 消息搜索
|
|
271
|
+
searchMessages: db.prepare(`
|
|
272
|
+
SELECT * FROM messages WHERE session_id = $sessionId AND content LIKE $keyword ORDER BY timestamp DESC LIMIT $limit
|
|
273
|
+
`),
|
|
274
|
+
// 话题
|
|
275
|
+
insertTopic: db.prepare(`
|
|
276
|
+
INSERT INTO topics (session_id, title, keywords, summary, message_count, window_start_at, window_end_at, created_at, updated_at)
|
|
277
|
+
VALUES ($sessionId, $title, $keywords, $summary, $messageCount, $windowStartAt, $windowEndAt, $createdAt, $updatedAt)
|
|
278
|
+
`),
|
|
279
|
+
getTopics: db.prepare(`
|
|
280
|
+
SELECT * FROM topics WHERE session_id = $sessionId ORDER BY updated_at DESC LIMIT $limit
|
|
281
|
+
`),
|
|
282
|
+
getTopicByWindow: db.prepare(`
|
|
283
|
+
SELECT * FROM topics
|
|
284
|
+
WHERE session_id = $sessionId AND window_start_at = $windowStartAt AND window_end_at = $windowEndAt
|
|
285
|
+
ORDER BY id DESC
|
|
286
|
+
LIMIT 1
|
|
287
|
+
`),
|
|
288
|
+
updateTopic: db.prepare(`
|
|
289
|
+
UPDATE topics SET summary = $summary, keywords = $keywords, message_count = $messageCount, updated_at = $updatedAt WHERE id = $id
|
|
290
|
+
`),
|
|
291
|
+
// 表达学习
|
|
292
|
+
insertExpression: db.prepare(`
|
|
293
|
+
INSERT INTO expressions (session_id, user_id, user_name, situation, style, example, created_at)
|
|
294
|
+
VALUES ($sessionId, $userId, $userName, $situation, $style, $example, $createdAt)
|
|
295
|
+
`),
|
|
296
|
+
getExpressions: db.prepare(`
|
|
297
|
+
SELECT * FROM expressions WHERE session_id = $sessionId ORDER BY created_at DESC LIMIT $limit
|
|
298
|
+
`),
|
|
299
|
+
getExpressionsByUser: db.prepare(`
|
|
300
|
+
SELECT * FROM expressions WHERE user_id = $userId ORDER BY created_at DESC LIMIT $limit
|
|
301
|
+
`),
|
|
302
|
+
getExpressionCount: db.prepare(`
|
|
303
|
+
SELECT COUNT(*) as count FROM expressions WHERE session_id = $sessionId
|
|
304
|
+
`),
|
|
305
|
+
deleteExpressionsByUser: db.prepare(`
|
|
306
|
+
DELETE FROM expressions WHERE user_id = $userId
|
|
307
|
+
`),
|
|
308
|
+
deleteOldestExpressions: db.prepare(`
|
|
309
|
+
DELETE FROM expressions WHERE session_id = $sessionId AND id NOT IN (
|
|
310
|
+
SELECT id FROM expressions WHERE session_id = $sessionId ORDER BY created_at DESC LIMIT $keepCount
|
|
311
|
+
)
|
|
312
|
+
`),
|
|
313
|
+
// 图片记录
|
|
314
|
+
insertImage: db.prepare(`
|
|
315
|
+
INSERT OR IGNORE INTO images (hash, url, type, description, emotion, character, file_path, created_at)
|
|
316
|
+
VALUES ($hash, $url, $type, $description, $emotion, $character, $filePath, $createdAt)
|
|
317
|
+
`),
|
|
318
|
+
getImageByHash: db.prepare(`SELECT * FROM images WHERE hash = $hash`),
|
|
319
|
+
getImageByUrl: db.prepare(`SELECT * FROM images WHERE url = $url`),
|
|
320
|
+
getAllImages: db.prepare(`SELECT * FROM images ORDER BY created_at DESC`),
|
|
321
|
+
upsertMediaSummary: db.prepare(`
|
|
322
|
+
INSERT INTO media_summaries (key, kind, source, summary, created_at)
|
|
323
|
+
VALUES ($key, $kind, $source, $summary, $createdAt)
|
|
324
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
325
|
+
summary = $summary,
|
|
326
|
+
source = $source,
|
|
327
|
+
created_at = $createdAt
|
|
328
|
+
`),
|
|
329
|
+
getMediaSummary: db.prepare(`SELECT * FROM media_summaries WHERE key = $key`),
|
|
330
|
+
upsertMediaSummarySource: db.prepare(`
|
|
331
|
+
INSERT INTO media_summary_sources (source_key, summary_key, created_at)
|
|
332
|
+
VALUES ($sourceKey, $summaryKey, $createdAt)
|
|
333
|
+
ON CONFLICT(source_key) DO UPDATE SET
|
|
334
|
+
summary_key = $summaryKey,
|
|
335
|
+
created_at = $createdAt
|
|
336
|
+
`),
|
|
337
|
+
getMediaSummaryBySource: db.prepare(`
|
|
338
|
+
SELECT s.*
|
|
339
|
+
FROM media_summary_sources src
|
|
340
|
+
JOIN media_summaries s ON s.key = src.summary_key
|
|
341
|
+
WHERE src.source_key = $sourceKey
|
|
342
|
+
`),
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
saveSession(meta: SessionMeta): void {
|
|
347
|
+
stmts.upsertSession.run({
|
|
348
|
+
$id: meta.id,
|
|
349
|
+
$type: meta.type,
|
|
350
|
+
$targetId: meta.targetId,
|
|
351
|
+
$createdAt: meta.createdAt,
|
|
352
|
+
$updatedAt: meta.updatedAt,
|
|
353
|
+
$compressedContext: meta.compressedContext,
|
|
354
|
+
});
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
getSession(id: string): SessionMeta | null {
|
|
358
|
+
const row = stmts.getSession.get({ $id: id }) as any;
|
|
359
|
+
if (!row) return null;
|
|
360
|
+
return {
|
|
361
|
+
id: row.id,
|
|
362
|
+
type: row.type,
|
|
363
|
+
targetId: row.target_id,
|
|
364
|
+
createdAt: row.created_at,
|
|
365
|
+
updatedAt: row.updated_at,
|
|
366
|
+
compressedContext: row.compressed_context,
|
|
367
|
+
};
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
saveMessage(msg: ChatMessage): void {
|
|
371
|
+
stmts.insertMessage.run({
|
|
372
|
+
$sessionId: msg.sessionId,
|
|
373
|
+
$role: msg.role,
|
|
374
|
+
$content: msg.content,
|
|
375
|
+
$userId: msg.userId ?? null,
|
|
376
|
+
$userName: msg.userName ?? null,
|
|
377
|
+
$userRole: msg.userRole ?? null,
|
|
378
|
+
$userTitle: msg.userTitle ?? null,
|
|
379
|
+
$groupId: msg.groupId ?? null,
|
|
380
|
+
$groupName: msg.groupName ?? null,
|
|
381
|
+
$timestamp: msg.timestamp,
|
|
382
|
+
$messageId: msg.messageId ?? null,
|
|
383
|
+
});
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
getMessages(
|
|
387
|
+
sessionId: string,
|
|
388
|
+
limit: number = 30,
|
|
389
|
+
before?: number,
|
|
390
|
+
): ChatMessage[] {
|
|
391
|
+
const rows = before
|
|
392
|
+
? (stmts.getMessagesBefore.all({ $sessionId: sessionId, $timestamp: before, $limit: limit }) as any[])
|
|
393
|
+
: (stmts.getMessages.all({ $sessionId: sessionId, $limit: limit }) as any[]);
|
|
394
|
+
|
|
395
|
+
return rows
|
|
396
|
+
.map((row) => ({
|
|
397
|
+
id: row.id,
|
|
398
|
+
sessionId: row.session_id,
|
|
399
|
+
role: row.role,
|
|
400
|
+
content: row.content,
|
|
401
|
+
userId: row.user_id,
|
|
402
|
+
userName: row.user_name,
|
|
403
|
+
userRole: row.user_role,
|
|
404
|
+
userTitle: row.user_title,
|
|
405
|
+
groupId: row.group_id,
|
|
406
|
+
groupName: row.group_name,
|
|
407
|
+
timestamp: row.timestamp,
|
|
408
|
+
messageId: row.message_id,
|
|
409
|
+
}))
|
|
410
|
+
.reverse(); // 按时间正序
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
getBotMessages(groupId: number, limit: number = 50): ChatMessage[] {
|
|
414
|
+
const rows = stmts.getBotMessages.all({ $groupId: groupId, $limit: limit }) as any[];
|
|
415
|
+
return rows
|
|
416
|
+
.map((row) => ({
|
|
417
|
+
id: row.id,
|
|
418
|
+
sessionId: row.session_id,
|
|
419
|
+
role: row.role,
|
|
420
|
+
content: row.content,
|
|
421
|
+
userId: row.user_id,
|
|
422
|
+
userName: row.user_name,
|
|
423
|
+
userRole: row.user_role,
|
|
424
|
+
userTitle: row.user_title,
|
|
425
|
+
groupId: row.group_id,
|
|
426
|
+
groupName: row.group_name,
|
|
427
|
+
timestamp: row.timestamp,
|
|
428
|
+
messageId: row.message_id,
|
|
429
|
+
}))
|
|
430
|
+
.reverse(); // 按时间正序
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
getStoredGroupNoticeMessages(
|
|
434
|
+
groupId: number,
|
|
435
|
+
limit: number = 20,
|
|
436
|
+
): ChatMessage[] {
|
|
437
|
+
const rows = stmts.getStoredGroupNoticeMessages.all(
|
|
438
|
+
{ $groupId: groupId, $limit: limit },
|
|
439
|
+
) as any[];
|
|
440
|
+
return rows
|
|
441
|
+
.map((row) => ({
|
|
442
|
+
id: row.id,
|
|
443
|
+
sessionId: row.session_id,
|
|
444
|
+
role: row.role,
|
|
445
|
+
content: row.content,
|
|
446
|
+
userId: row.user_id,
|
|
447
|
+
userName: row.user_name,
|
|
448
|
+
userRole: row.user_role,
|
|
449
|
+
userTitle: row.user_title,
|
|
450
|
+
groupId: row.group_id,
|
|
451
|
+
groupName: row.group_name,
|
|
452
|
+
timestamp: row.timestamp,
|
|
453
|
+
messageId: row.message_id,
|
|
454
|
+
}))
|
|
455
|
+
.reverse();
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
getMessagesByUser(
|
|
459
|
+
userId: number,
|
|
460
|
+
sessionId?: string,
|
|
461
|
+
limit: number = 20,
|
|
462
|
+
): ChatMessage[] {
|
|
463
|
+
const rows = sessionId
|
|
464
|
+
? (stmts.getMessagesByUserInSession.all({
|
|
465
|
+
$userId: userId,
|
|
466
|
+
$sessionId: sessionId,
|
|
467
|
+
$limit: limit,
|
|
468
|
+
}) as any[])
|
|
469
|
+
: (stmts.getMessagesByUser.all({ $userId: userId, $limit: limit }) as any[]);
|
|
470
|
+
|
|
471
|
+
return rows
|
|
472
|
+
.map((row) => ({
|
|
473
|
+
id: row.id,
|
|
474
|
+
sessionId: row.session_id,
|
|
475
|
+
role: row.role,
|
|
476
|
+
content: row.content,
|
|
477
|
+
userId: row.user_id,
|
|
478
|
+
userName: row.user_name,
|
|
479
|
+
userRole: row.user_role,
|
|
480
|
+
userTitle: row.user_title,
|
|
481
|
+
groupId: row.group_id,
|
|
482
|
+
groupName: row.group_name,
|
|
483
|
+
timestamp: row.timestamp,
|
|
484
|
+
messageId: row.message_id,
|
|
485
|
+
}))
|
|
486
|
+
.reverse();
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
getAllMessagesByUser(userId: number, sessionId?: string): ChatMessage[] {
|
|
490
|
+
const rows = sessionId
|
|
491
|
+
? (stmts.getAllMessagesByUserInSession.all({ $userId: userId, $sessionId: sessionId }) as any[])
|
|
492
|
+
: (stmts.getAllMessagesByUser.all({ $userId: userId }) as any[]);
|
|
493
|
+
|
|
494
|
+
return rows
|
|
495
|
+
.map((row) => ({
|
|
496
|
+
id: row.id,
|
|
497
|
+
sessionId: row.session_id,
|
|
498
|
+
role: row.role,
|
|
499
|
+
content: row.content,
|
|
500
|
+
userId: row.user_id,
|
|
501
|
+
userName: row.user_name,
|
|
502
|
+
userRole: row.user_role,
|
|
503
|
+
userTitle: row.user_title,
|
|
504
|
+
groupId: row.group_id,
|
|
505
|
+
groupName: row.group_name,
|
|
506
|
+
timestamp: row.timestamp,
|
|
507
|
+
messageId: row.message_id,
|
|
508
|
+
}))
|
|
509
|
+
.reverse();
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
getMessagesByTimeRange(
|
|
513
|
+
sessionId: string,
|
|
514
|
+
startTimestamp: number,
|
|
515
|
+
endTimestamp: number,
|
|
516
|
+
): ChatMessage[] {
|
|
517
|
+
const rows = stmts.getMessagesByTimeRange.all({
|
|
518
|
+
$sessionId: sessionId,
|
|
519
|
+
$startTimestamp: startTimestamp,
|
|
520
|
+
$endTimestamp: endTimestamp,
|
|
521
|
+
}) as any[];
|
|
522
|
+
return rows.map((row) => ({
|
|
523
|
+
id: row.id,
|
|
524
|
+
sessionId: row.session_id,
|
|
525
|
+
role: row.role,
|
|
526
|
+
content: row.content,
|
|
527
|
+
userId: row.user_id,
|
|
528
|
+
userName: row.user_name,
|
|
529
|
+
userRole: row.user_role,
|
|
530
|
+
userTitle: row.user_title,
|
|
531
|
+
groupId: row.group_id,
|
|
532
|
+
groupName: row.group_name,
|
|
533
|
+
timestamp: row.timestamp,
|
|
534
|
+
messageId: row.message_id,
|
|
535
|
+
}));
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
updateCompressedContext(sessionId: string, context: string): void {
|
|
539
|
+
stmts.updateCompressedContext.run({ $compressedContext: context, $updatedAt: Date.now(), $sessionId: sessionId });
|
|
540
|
+
},
|
|
541
|
+
|
|
542
|
+
deleteSessionMessages(sessionId: string): void {
|
|
543
|
+
stmts.deleteSessionMessages.run({ $sessionId: sessionId });
|
|
544
|
+
stmts.resetSessionContext.run({ $updatedAt: Date.now(), $sessionId: sessionId });
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
deleteBotMessages(sessionId: string): void {
|
|
548
|
+
stmts.deleteBotMessages.run({ $sessionId: sessionId });
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
searchMessages(
|
|
552
|
+
sessionId: string,
|
|
553
|
+
keyword: string,
|
|
554
|
+
limit: number = 20,
|
|
555
|
+
): ChatMessage[] {
|
|
556
|
+
const rows = stmts.searchMessages.all({
|
|
557
|
+
$sessionId: sessionId,
|
|
558
|
+
$keyword: `%${keyword}%`,
|
|
559
|
+
$limit: limit,
|
|
560
|
+
}) as any[];
|
|
561
|
+
return rows
|
|
562
|
+
.map((row) => ({
|
|
563
|
+
id: row.id,
|
|
564
|
+
sessionId: row.session_id,
|
|
565
|
+
role: row.role,
|
|
566
|
+
content: row.content,
|
|
567
|
+
userId: row.user_id,
|
|
568
|
+
userName: row.user_name,
|
|
569
|
+
userRole: row.user_role,
|
|
570
|
+
userTitle: row.user_title,
|
|
571
|
+
groupId: row.group_id,
|
|
572
|
+
groupName: row.group_name,
|
|
573
|
+
timestamp: row.timestamp,
|
|
574
|
+
messageId: row.message_id,
|
|
575
|
+
}))
|
|
576
|
+
.reverse();
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
saveTopic(topic: TopicRecord): number {
|
|
580
|
+
const result = stmts.insertTopic.run({
|
|
581
|
+
$sessionId: topic.sessionId,
|
|
582
|
+
$title: topic.title,
|
|
583
|
+
$keywords: topic.keywords,
|
|
584
|
+
$summary: topic.summary,
|
|
585
|
+
$messageCount: topic.messageCount,
|
|
586
|
+
$windowStartAt: topic.windowStartAt ?? null,
|
|
587
|
+
$windowEndAt: topic.windowEndAt ?? null,
|
|
588
|
+
$createdAt: topic.createdAt,
|
|
589
|
+
$updatedAt: topic.updatedAt,
|
|
590
|
+
});
|
|
591
|
+
return Number(result.lastInsertRowid);
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
getTopics(sessionId: string, limit: number = 10): TopicRecord[] {
|
|
595
|
+
const rows = stmts.getTopics.all({ $sessionId: sessionId, $limit: limit }) as any[];
|
|
596
|
+
return rows.map((row) => ({
|
|
597
|
+
id: row.id,
|
|
598
|
+
sessionId: row.session_id,
|
|
599
|
+
title: row.title,
|
|
600
|
+
keywords: row.keywords,
|
|
601
|
+
summary: row.summary,
|
|
602
|
+
messageCount: row.message_count,
|
|
603
|
+
windowStartAt: row.window_start_at ?? undefined,
|
|
604
|
+
windowEndAt: row.window_end_at ?? undefined,
|
|
605
|
+
createdAt: row.created_at,
|
|
606
|
+
updatedAt: row.updated_at,
|
|
607
|
+
}));
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
getTopicByWindow(
|
|
611
|
+
sessionId: string,
|
|
612
|
+
windowStartAt: number,
|
|
613
|
+
windowEndAt: number,
|
|
614
|
+
): TopicRecord | null {
|
|
615
|
+
const row = stmts.getTopicByWindow.get({
|
|
616
|
+
$sessionId: sessionId,
|
|
617
|
+
$windowStartAt: windowStartAt,
|
|
618
|
+
$windowEndAt: windowEndAt,
|
|
619
|
+
}) as any;
|
|
620
|
+
if (!row) return null;
|
|
621
|
+
return {
|
|
622
|
+
id: row.id,
|
|
623
|
+
sessionId: row.session_id,
|
|
624
|
+
title: row.title,
|
|
625
|
+
keywords: row.keywords,
|
|
626
|
+
summary: row.summary,
|
|
627
|
+
messageCount: row.message_count,
|
|
628
|
+
windowStartAt: row.window_start_at ?? undefined,
|
|
629
|
+
windowEndAt: row.window_end_at ?? undefined,
|
|
630
|
+
createdAt: row.created_at,
|
|
631
|
+
updatedAt: row.updated_at,
|
|
632
|
+
};
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
updateTopic(
|
|
636
|
+
id: number,
|
|
637
|
+
updates: Partial<
|
|
638
|
+
Pick<TopicRecord, "summary" | "keywords" | "messageCount" | "updatedAt">
|
|
639
|
+
>,
|
|
640
|
+
): void {
|
|
641
|
+
// 先获取当前值用于合并
|
|
642
|
+
const current = db
|
|
643
|
+
.prepare("SELECT * FROM topics WHERE id = $id")
|
|
644
|
+
.get({ $id: id }) as any;
|
|
645
|
+
if (!current) return;
|
|
646
|
+
stmts.updateTopic.run({
|
|
647
|
+
$id: id,
|
|
648
|
+
$summary: updates.summary ?? current.summary,
|
|
649
|
+
$keywords: updates.keywords ?? current.keywords,
|
|
650
|
+
$messageCount: updates.messageCount ?? current.message_count,
|
|
651
|
+
$updatedAt: updates.updatedAt ?? Date.now(),
|
|
652
|
+
});
|
|
653
|
+
},
|
|
654
|
+
|
|
655
|
+
saveExpression(expr: ExpressionRecord): void {
|
|
656
|
+
stmts.insertExpression.run({
|
|
657
|
+
$sessionId: expr.sessionId,
|
|
658
|
+
$userId: expr.userId,
|
|
659
|
+
$userName: expr.userName,
|
|
660
|
+
$situation: expr.situation,
|
|
661
|
+
$style: expr.style,
|
|
662
|
+
$example: expr.example,
|
|
663
|
+
$createdAt: expr.createdAt,
|
|
664
|
+
});
|
|
665
|
+
},
|
|
666
|
+
|
|
667
|
+
getExpressions(sessionId: string, limit: number = 50): ExpressionRecord[] {
|
|
668
|
+
const rows = stmts.getExpressions.all({ $sessionId: sessionId, $limit: limit }) as any[];
|
|
669
|
+
return rows.map((row) => ({
|
|
670
|
+
id: row.id,
|
|
671
|
+
sessionId: row.session_id,
|
|
672
|
+
userId: row.user_id,
|
|
673
|
+
userName: row.user_name,
|
|
674
|
+
situation: row.situation,
|
|
675
|
+
style: row.style,
|
|
676
|
+
example: row.example,
|
|
677
|
+
createdAt: row.created_at,
|
|
678
|
+
}));
|
|
679
|
+
},
|
|
680
|
+
|
|
681
|
+
getExpressionsByUser(
|
|
682
|
+
userId: number,
|
|
683
|
+
limit: number = 50,
|
|
684
|
+
): ExpressionRecord[] {
|
|
685
|
+
const rows = stmts.getExpressionsByUser.all({ $userId: userId, $limit: limit }) as any[];
|
|
686
|
+
return rows.map((row) => ({
|
|
687
|
+
id: row.id,
|
|
688
|
+
sessionId: row.session_id,
|
|
689
|
+
userId: row.user_id,
|
|
690
|
+
userName: row.user_name,
|
|
691
|
+
situation: row.situation,
|
|
692
|
+
style: row.style,
|
|
693
|
+
example: row.example,
|
|
694
|
+
createdAt: row.created_at,
|
|
695
|
+
}));
|
|
696
|
+
},
|
|
697
|
+
|
|
698
|
+
replaceExpressionsByUser(
|
|
699
|
+
userId: number,
|
|
700
|
+
userName: string,
|
|
701
|
+
expressions: Array<
|
|
702
|
+
Pick<ExpressionRecord, "situation" | "style" | "example">
|
|
703
|
+
>,
|
|
704
|
+
): void {
|
|
705
|
+
const now = Date.now();
|
|
706
|
+
const tx = db.transaction(
|
|
707
|
+
(
|
|
708
|
+
targetUserId: number,
|
|
709
|
+
targetUserName: string,
|
|
710
|
+
rows: Array<
|
|
711
|
+
Pick<ExpressionRecord, "situation" | "style" | "example">
|
|
712
|
+
>,
|
|
713
|
+
) => {
|
|
714
|
+
stmts.deleteExpressionsByUser.run({ $userId: targetUserId });
|
|
715
|
+
for (const row of rows) {
|
|
716
|
+
stmts.insertExpression.run({
|
|
717
|
+
$sessionId: `user:${targetUserId}`,
|
|
718
|
+
$userId: targetUserId,
|
|
719
|
+
$userName: targetUserName,
|
|
720
|
+
$situation: row.situation,
|
|
721
|
+
$style: row.style,
|
|
722
|
+
$example: row.example,
|
|
723
|
+
$createdAt: now,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
tx(userId, userName, expressions);
|
|
730
|
+
},
|
|
731
|
+
|
|
732
|
+
getExpressionCount(sessionId: string): number {
|
|
733
|
+
const row = stmts.getExpressionCount.get({ $sessionId: sessionId }) as any;
|
|
734
|
+
return row?.count ?? 0;
|
|
735
|
+
},
|
|
736
|
+
|
|
737
|
+
deleteOldestExpressions(sessionId: string, keepCount: number): void {
|
|
738
|
+
stmts.deleteOldestExpressions.run({ $sessionId: sessionId, $keepCount: keepCount });
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
saveImage(image: ImageRecord): void {
|
|
742
|
+
stmts.insertImage.run({
|
|
743
|
+
$hash: image.hash,
|
|
744
|
+
$url: image.url,
|
|
745
|
+
$type: image.type,
|
|
746
|
+
$description: image.description,
|
|
747
|
+
$emotion: image.emotion ?? null,
|
|
748
|
+
$character: image.character ?? null,
|
|
749
|
+
$filePath: image.filePath ?? null,
|
|
750
|
+
$createdAt: image.createdAt,
|
|
751
|
+
});
|
|
752
|
+
},
|
|
753
|
+
|
|
754
|
+
getImageByHash(hash: string): ImageRecord | null {
|
|
755
|
+
const row = stmts.getImageByHash.get({ $hash: hash }) as any;
|
|
756
|
+
if (!row) return null;
|
|
757
|
+
return {
|
|
758
|
+
id: row.id,
|
|
759
|
+
hash: row.hash,
|
|
760
|
+
url: row.url,
|
|
761
|
+
type: row.type,
|
|
762
|
+
description: row.description,
|
|
763
|
+
emotion: row.emotion,
|
|
764
|
+
character: row.character,
|
|
765
|
+
filePath: row.file_path,
|
|
766
|
+
createdAt: row.created_at,
|
|
767
|
+
};
|
|
768
|
+
},
|
|
769
|
+
|
|
770
|
+
getImageByUrl(url: string): ImageRecord | null {
|
|
771
|
+
const row = stmts.getImageByUrl.get({ $url: url }) as any;
|
|
772
|
+
if (!row) return null;
|
|
773
|
+
return {
|
|
774
|
+
id: row.id,
|
|
775
|
+
hash: row.hash,
|
|
776
|
+
url: row.url,
|
|
777
|
+
type: row.type,
|
|
778
|
+
description: row.description,
|
|
779
|
+
emotion: row.emotion,
|
|
780
|
+
character: row.character,
|
|
781
|
+
filePath: row.file_path,
|
|
782
|
+
createdAt: row.created_at,
|
|
783
|
+
};
|
|
784
|
+
},
|
|
785
|
+
|
|
786
|
+
getAllImages(): ImageRecord[] {
|
|
787
|
+
const rows = stmts.getAllImages.all({}) as any[];
|
|
788
|
+
return rows.map((row) => ({
|
|
789
|
+
id: row.id,
|
|
790
|
+
hash: row.hash,
|
|
791
|
+
url: row.url,
|
|
792
|
+
type: row.type,
|
|
793
|
+
description: row.description,
|
|
794
|
+
emotion: row.emotion,
|
|
795
|
+
character: row.character,
|
|
796
|
+
filePath: row.file_path,
|
|
797
|
+
createdAt: row.created_at,
|
|
798
|
+
}));
|
|
799
|
+
},
|
|
800
|
+
|
|
801
|
+
saveMediaSummary(summary: MediaSummaryRecord): void {
|
|
802
|
+
stmts.upsertMediaSummary.run({
|
|
803
|
+
$key: summary.key,
|
|
804
|
+
$kind: summary.kind,
|
|
805
|
+
$source: summary.source,
|
|
806
|
+
$summary: summary.summary,
|
|
807
|
+
$createdAt: summary.createdAt,
|
|
808
|
+
});
|
|
809
|
+
},
|
|
810
|
+
|
|
811
|
+
getMediaSummary(key: string): MediaSummaryRecord | null {
|
|
812
|
+
const row = stmts.getMediaSummary.get({ $key: key }) as any;
|
|
813
|
+
if (!row) return null;
|
|
814
|
+
return {
|
|
815
|
+
id: row.id,
|
|
816
|
+
key: row.key,
|
|
817
|
+
kind: row.kind,
|
|
818
|
+
source: row.source,
|
|
819
|
+
summary: row.summary,
|
|
820
|
+
createdAt: row.created_at,
|
|
821
|
+
};
|
|
822
|
+
},
|
|
823
|
+
|
|
824
|
+
saveMediaSummarySource(sourceKey: string, summaryKey: string): void {
|
|
825
|
+
stmts.upsertMediaSummarySource.run({
|
|
826
|
+
$sourceKey: sourceKey,
|
|
827
|
+
$summaryKey: summaryKey,
|
|
828
|
+
$createdAt: Date.now(),
|
|
829
|
+
});
|
|
830
|
+
},
|
|
831
|
+
|
|
832
|
+
getMediaSummaryBySource(sourceKey: string): MediaSummaryRecord | null {
|
|
833
|
+
const row = stmts.getMediaSummaryBySource.get({ $sourceKey: sourceKey }) as any;
|
|
834
|
+
if (!row) return null;
|
|
835
|
+
return {
|
|
836
|
+
id: row.id,
|
|
837
|
+
key: row.key,
|
|
838
|
+
kind: row.kind,
|
|
839
|
+
source: row.source,
|
|
840
|
+
summary: row.summary,
|
|
841
|
+
createdAt: row.created_at,
|
|
842
|
+
};
|
|
843
|
+
},
|
|
844
|
+
|
|
845
|
+
close(): void {
|
|
846
|
+
db.close();
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
}
|