inspiration-agent 0.0.1
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 +368 -0
- package/dist/.env.example +53 -0
- package/dist/README.md +368 -0
- package/dist/agent/ai.agent.js +712 -0
- package/dist/channel/feishu-connection.js +238 -0
- package/dist/channel/feishu.service.js +222 -0
- package/dist/cli.js +659 -0
- package/dist/config/system.prompt.txt +312 -0
- package/dist/index.js +176 -0
- package/dist/model/base-llm.service.js +320 -0
- package/dist/model/deepseek.service.js +18 -0
- package/dist/model/kimi.service.js +19 -0
- package/dist/model/minimax.service.js +35 -0
- package/dist/model/zhipu.service.js +26 -0
- package/dist/services/database.service.js +697 -0
- package/dist/services/memory.manager.js +486 -0
- package/dist/services/message.queue.js +157 -0
- package/dist/tools/browser.tools.js +814 -0
- package/dist/tools/command.tools.js +361 -0
- package/dist/tools/file.tools.js +222 -0
- package/dist/tools/memory.tools.js +393 -0
- package/dist/tools/scheduler.tools.js +559 -0
- package/dist/tools/search.tools.js +208 -0
- package/dist/utils/logger.js +52 -0
- package/dist/utils/markdown-renderer.js +207 -0
- package/package.json +51 -0
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
const sqlite3 = require("sqlite3").verbose();
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 数据库服务类
|
|
7
|
+
* 负责所有数据库操作,包括对话历史、智能体配置和长期记忆
|
|
8
|
+
*/
|
|
9
|
+
class DatabaseService {
|
|
10
|
+
constructor() {
|
|
11
|
+
const dbPathFromEnv = process.env.DATABASE_PATH || "./data/ai_agent.db";
|
|
12
|
+
this.dbPath = path.resolve(dbPathFromEnv);
|
|
13
|
+
this.db = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 连接数据库
|
|
18
|
+
* @returns {Promise<void>}
|
|
19
|
+
*/
|
|
20
|
+
async connect() {
|
|
21
|
+
const dbDir = path.dirname(this.dbPath);
|
|
22
|
+
if (!fs.existsSync(dbDir)) {
|
|
23
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
this.db = new sqlite3.Database(this.dbPath, (err) => {
|
|
28
|
+
if (err) {
|
|
29
|
+
reject(err);
|
|
30
|
+
} else {
|
|
31
|
+
resolve();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 初始化所有数据表
|
|
39
|
+
* @returns {Promise<void>}
|
|
40
|
+
*/
|
|
41
|
+
async initTables() {
|
|
42
|
+
const createConversationTableSQL = `
|
|
43
|
+
CREATE TABLE IF NOT EXISTS conversation_history (
|
|
44
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
45
|
+
role TEXT NOT NULL,
|
|
46
|
+
content TEXT NOT NULL,
|
|
47
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
48
|
+
)
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
const createLongTermMemoryTableSQL = `
|
|
52
|
+
CREATE TABLE IF NOT EXISTS long_term_memory (
|
|
53
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
54
|
+
type TEXT NOT NULL,
|
|
55
|
+
content TEXT NOT NULL,
|
|
56
|
+
keywords TEXT,
|
|
57
|
+
importance INTEGER DEFAULT 0,
|
|
58
|
+
access_count INTEGER DEFAULT 0,
|
|
59
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
60
|
+
last_accessed DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
61
|
+
)
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
const createMemoryTypeIndexSQL = `
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_memory_type ON long_term_memory(type)
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
const createMemoryImportanceIndexSQL = `
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_memory_importance ON long_term_memory(importance DESC)
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
const createScheduledTasksTableSQL = `
|
|
73
|
+
CREATE TABLE IF NOT EXISTS scheduled_tasks (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
75
|
+
name TEXT NOT NULL,
|
|
76
|
+
description TEXT,
|
|
77
|
+
cron_expression TEXT NOT NULL,
|
|
78
|
+
prompt TEXT NOT NULL,
|
|
79
|
+
enabled INTEGER DEFAULT 1,
|
|
80
|
+
last_run_at DATETIME,
|
|
81
|
+
next_run_at DATETIME,
|
|
82
|
+
run_count INTEGER DEFAULT 0,
|
|
83
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
84
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
85
|
+
)
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const createScheduledTasksIndexSQL = `
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_enabled ON scheduled_tasks(enabled)
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
const createTokenUsageTableSQL = `
|
|
93
|
+
CREATE TABLE IF NOT EXISTS token_usage (
|
|
94
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
95
|
+
date TEXT NOT NULL UNIQUE,
|
|
96
|
+
input_tokens INTEGER DEFAULT 0,
|
|
97
|
+
output_tokens INTEGER DEFAULT 0,
|
|
98
|
+
total_tokens INTEGER DEFAULT 0,
|
|
99
|
+
request_count INTEGER DEFAULT 0,
|
|
100
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
101
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
102
|
+
)
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
const createTokenUsageDateIndexSQL = `
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_token_usage_date ON token_usage(date)
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
this.db.serialize(() => {
|
|
111
|
+
this.db.run(createConversationTableSQL, (err) => {
|
|
112
|
+
if (err) {
|
|
113
|
+
reject(err);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
this.db.run(createLongTermMemoryTableSQL, (err) => {
|
|
118
|
+
if (err) {
|
|
119
|
+
reject(err);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.db.run(createScheduledTasksTableSQL, (err) => {
|
|
124
|
+
if (err) {
|
|
125
|
+
reject(err);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this.db.run(createTokenUsageTableSQL, (err) => {
|
|
130
|
+
if (err) {
|
|
131
|
+
reject(err);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
this.db.run(createMemoryTypeIndexSQL, (err) => {
|
|
136
|
+
if (err) {
|
|
137
|
+
reject(err);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
this.db.run(createMemoryImportanceIndexSQL, (err) => {
|
|
142
|
+
if (err) {
|
|
143
|
+
reject(err);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
this.db.run(createScheduledTasksIndexSQL, (err) => {
|
|
148
|
+
if (err) {
|
|
149
|
+
reject(err);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
this.db.run(createTokenUsageDateIndexSQL, (err) => {
|
|
154
|
+
if (err) {
|
|
155
|
+
reject(err);
|
|
156
|
+
} else {
|
|
157
|
+
resolve();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 保存对话消息
|
|
166
|
+
* @param {string} role - 消息角色(user/assistant)
|
|
167
|
+
* @param {string} content - 消息内容
|
|
168
|
+
* @returns {Promise<{id: number}>} 插入的记录 ID
|
|
169
|
+
*/
|
|
170
|
+
async saveMessage(role, content) {
|
|
171
|
+
const sql = `INSERT INTO conversation_history (role, content) VALUES (?, ?)`;
|
|
172
|
+
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
this.db.run(sql, [role, content], function (err) {
|
|
175
|
+
if (err) {
|
|
176
|
+
reject(err);
|
|
177
|
+
} else {
|
|
178
|
+
resolve({ id: this.lastID });
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 获取所有对话消息
|
|
186
|
+
* @returns {Promise<Array>} 消息数组
|
|
187
|
+
*/
|
|
188
|
+
async getAllMessages() {
|
|
189
|
+
const sql = `SELECT role, content, created_at FROM conversation_history ORDER BY id ASC`;
|
|
190
|
+
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
this.db.all(sql, [], (err, rows) => {
|
|
193
|
+
if (err) {
|
|
194
|
+
reject(err);
|
|
195
|
+
} else {
|
|
196
|
+
resolve(rows);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 清空对话历史
|
|
204
|
+
* @returns {Promise<void>}
|
|
205
|
+
*/
|
|
206
|
+
async clearHistory() {
|
|
207
|
+
const sql = `DELETE FROM conversation_history`;
|
|
208
|
+
|
|
209
|
+
return new Promise((resolve, reject) => {
|
|
210
|
+
this.db.run(sql, (err) => {
|
|
211
|
+
if (err) {
|
|
212
|
+
reject(err);
|
|
213
|
+
} else {
|
|
214
|
+
resolve();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 保存长期记忆
|
|
222
|
+
* @param {Object} memoryData - 记忆数据
|
|
223
|
+
* @param {string} memoryData.type - 记忆类型
|
|
224
|
+
* @param {string} memoryData.content - 记忆内容
|
|
225
|
+
* @param {Array<string>} memoryData.keywords - 关键词数组
|
|
226
|
+
* @param {number} memoryData.importance - 重要性分数(默认 0)
|
|
227
|
+
* @returns {Promise<{id: number}>} 插入的记录 ID
|
|
228
|
+
*/
|
|
229
|
+
async saveLongTermMemory(memoryData) {
|
|
230
|
+
const { type, content, keywords, importance = 0 } = memoryData;
|
|
231
|
+
const sql = `
|
|
232
|
+
INSERT INTO long_term_memory (type, content, keywords, importance)
|
|
233
|
+
VALUES (?, ?, ?, ?)
|
|
234
|
+
`;
|
|
235
|
+
|
|
236
|
+
return new Promise((resolve, reject) => {
|
|
237
|
+
this.db.run(sql, [type, content, JSON.stringify(keywords || []), importance], function (err) {
|
|
238
|
+
if (err) {
|
|
239
|
+
reject(err);
|
|
240
|
+
} else {
|
|
241
|
+
resolve({ id: this.lastID });
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 获取长期记忆
|
|
249
|
+
* @param {string|null} type - 记忆类型过滤,null 表示不过滤
|
|
250
|
+
* @param {number} limit - 返回结果数量限制,默认 20
|
|
251
|
+
* @returns {Promise<Array>} 记忆数组
|
|
252
|
+
*/
|
|
253
|
+
async getLongTermMemories(type = null, limit = 20) {
|
|
254
|
+
let sql = `SELECT * FROM long_term_memory`;
|
|
255
|
+
const params = [];
|
|
256
|
+
|
|
257
|
+
if (type) {
|
|
258
|
+
sql += ` WHERE type = ?`;
|
|
259
|
+
params.push(type);
|
|
260
|
+
} else {
|
|
261
|
+
sql += ` WHERE type != 'conversation'`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
sql += ` ORDER BY importance DESC, last_accessed DESC LIMIT ?`;
|
|
265
|
+
params.push(limit);
|
|
266
|
+
|
|
267
|
+
return new Promise((resolve, reject) => {
|
|
268
|
+
this.db.all(sql, params, (err, rows) => {
|
|
269
|
+
if (err) {
|
|
270
|
+
reject(err);
|
|
271
|
+
} else {
|
|
272
|
+
const memories = rows.map((row) => ({
|
|
273
|
+
id: row.id,
|
|
274
|
+
type: row.type,
|
|
275
|
+
content: row.content,
|
|
276
|
+
keywords: JSON.parse(row.keywords || "[]"),
|
|
277
|
+
importance: row.importance,
|
|
278
|
+
access_count: row.access_count,
|
|
279
|
+
created_at: row.created_at,
|
|
280
|
+
last_accessed: row.last_accessed,
|
|
281
|
+
}));
|
|
282
|
+
resolve(memories);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 搜索长期记忆
|
|
290
|
+
* @param {string} query - 搜索关键词
|
|
291
|
+
* @param {number} limit - 返回结果数量限制,默认 10
|
|
292
|
+
* @returns {Promise<Array>} 匹配的记忆数组
|
|
293
|
+
*/
|
|
294
|
+
async searchLongTermMemories(query, limit = 10) {
|
|
295
|
+
const sql = `
|
|
296
|
+
SELECT * FROM long_term_memory
|
|
297
|
+
WHERE (content LIKE ? OR keywords LIKE ?) AND type != 'conversation'
|
|
298
|
+
ORDER BY importance DESC, last_accessed DESC
|
|
299
|
+
LIMIT ?
|
|
300
|
+
`;
|
|
301
|
+
|
|
302
|
+
const searchPattern = `%${query}%`;
|
|
303
|
+
|
|
304
|
+
return new Promise((resolve, reject) => {
|
|
305
|
+
this.db.all(sql, [searchPattern, searchPattern, limit], (err, rows) => {
|
|
306
|
+
if (err) {
|
|
307
|
+
reject(err);
|
|
308
|
+
} else {
|
|
309
|
+
const memories = rows.map((row) => ({
|
|
310
|
+
id: row.id,
|
|
311
|
+
type: row.type,
|
|
312
|
+
content: row.content,
|
|
313
|
+
keywords: JSON.parse(row.keywords || "[]"),
|
|
314
|
+
importance: row.importance,
|
|
315
|
+
access_count: row.access_count,
|
|
316
|
+
created_at: row.created_at,
|
|
317
|
+
last_accessed: row.last_accessed,
|
|
318
|
+
}));
|
|
319
|
+
resolve(memories);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 更新记忆访问记录
|
|
327
|
+
* @param {number} id - 记忆 ID
|
|
328
|
+
* @returns {Promise<void>}
|
|
329
|
+
*/
|
|
330
|
+
async updateMemoryAccess(id) {
|
|
331
|
+
const sql = `
|
|
332
|
+
UPDATE long_term_memory
|
|
333
|
+
SET access_count = access_count + 1,
|
|
334
|
+
last_accessed = CURRENT_TIMESTAMP
|
|
335
|
+
WHERE id = ?
|
|
336
|
+
`;
|
|
337
|
+
|
|
338
|
+
return new Promise((resolve, reject) => {
|
|
339
|
+
this.db.run(sql, [id], (err) => {
|
|
340
|
+
if (err) {
|
|
341
|
+
reject(err);
|
|
342
|
+
} else {
|
|
343
|
+
resolve();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 删除长期记忆
|
|
351
|
+
* @param {number} id - 记忆 ID
|
|
352
|
+
* @returns {Promise<{deleted: boolean}>} 是否删除成功
|
|
353
|
+
*/
|
|
354
|
+
async deleteLongTermMemory(id) {
|
|
355
|
+
const sql = `DELETE FROM long_term_memory WHERE id = ?`;
|
|
356
|
+
|
|
357
|
+
return new Promise((resolve, reject) => {
|
|
358
|
+
this.db.run(sql, [id], function (err) {
|
|
359
|
+
if (err) {
|
|
360
|
+
reject(err);
|
|
361
|
+
} else {
|
|
362
|
+
resolve({ deleted: this.changes > 0 });
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async saveScheduledTask(taskData) {
|
|
369
|
+
const { name, description, cronExpression, prompt, enabled = true, nextRunAt } = taskData;
|
|
370
|
+
const sql = `
|
|
371
|
+
INSERT INTO scheduled_tasks (name, description, cron_expression, prompt, enabled, next_run_at)
|
|
372
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
373
|
+
`;
|
|
374
|
+
|
|
375
|
+
return new Promise((resolve, reject) => {
|
|
376
|
+
this.db.run(
|
|
377
|
+
sql,
|
|
378
|
+
[name, description || "", cronExpression, prompt, enabled ? 1 : 0, nextRunAt || null],
|
|
379
|
+
function (err) {
|
|
380
|
+
if (err) {
|
|
381
|
+
reject(err);
|
|
382
|
+
} else {
|
|
383
|
+
resolve({ id: this.lastID });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async getAllScheduledTasks() {
|
|
391
|
+
const sql = `SELECT * FROM scheduled_tasks ORDER BY created_at ASC`;
|
|
392
|
+
|
|
393
|
+
return new Promise((resolve, reject) => {
|
|
394
|
+
this.db.all(sql, [], (err, rows) => {
|
|
395
|
+
if (err) {
|
|
396
|
+
reject(err);
|
|
397
|
+
} else {
|
|
398
|
+
const tasks = rows.map((row) => ({
|
|
399
|
+
id: row.id,
|
|
400
|
+
name: row.name,
|
|
401
|
+
description: row.description,
|
|
402
|
+
cronExpression: row.cron_expression,
|
|
403
|
+
prompt: row.prompt,
|
|
404
|
+
enabled: row.enabled === 1,
|
|
405
|
+
lastRunAt: row.last_run_at,
|
|
406
|
+
nextRunAt: row.next_run_at,
|
|
407
|
+
runCount: row.run_count,
|
|
408
|
+
createdAt: row.created_at,
|
|
409
|
+
updatedAt: row.updated_at,
|
|
410
|
+
}));
|
|
411
|
+
resolve(tasks);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async getScheduledTaskById(id) {
|
|
418
|
+
const sql = `SELECT * FROM scheduled_tasks WHERE id = ?`;
|
|
419
|
+
|
|
420
|
+
return new Promise((resolve, reject) => {
|
|
421
|
+
this.db.get(sql, [id], (err, row) => {
|
|
422
|
+
if (err) {
|
|
423
|
+
reject(err);
|
|
424
|
+
} else if (row) {
|
|
425
|
+
const task = {
|
|
426
|
+
id: row.id,
|
|
427
|
+
name: row.name,
|
|
428
|
+
description: row.description,
|
|
429
|
+
cronExpression: row.cron_expression,
|
|
430
|
+
prompt: row.prompt,
|
|
431
|
+
enabled: row.enabled === 1,
|
|
432
|
+
lastRunAt: row.last_run_at,
|
|
433
|
+
nextRunAt: row.next_run_at,
|
|
434
|
+
runCount: row.run_count,
|
|
435
|
+
createdAt: row.created_at,
|
|
436
|
+
updatedAt: row.updated_at,
|
|
437
|
+
};
|
|
438
|
+
resolve(task);
|
|
439
|
+
} else {
|
|
440
|
+
resolve(null);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async updateScheduledTask(id, taskData) {
|
|
447
|
+
const { name, description, cronExpression, prompt, enabled, nextRunAt } = taskData;
|
|
448
|
+
const updates = [];
|
|
449
|
+
const values = [];
|
|
450
|
+
|
|
451
|
+
if (name !== undefined) {
|
|
452
|
+
updates.push("name = ?");
|
|
453
|
+
values.push(name);
|
|
454
|
+
}
|
|
455
|
+
if (description !== undefined) {
|
|
456
|
+
updates.push("description = ?");
|
|
457
|
+
values.push(description);
|
|
458
|
+
}
|
|
459
|
+
if (cronExpression !== undefined) {
|
|
460
|
+
updates.push("cron_expression = ?");
|
|
461
|
+
values.push(cronExpression);
|
|
462
|
+
}
|
|
463
|
+
if (prompt !== undefined) {
|
|
464
|
+
updates.push("prompt = ?");
|
|
465
|
+
values.push(prompt);
|
|
466
|
+
}
|
|
467
|
+
if (enabled !== undefined) {
|
|
468
|
+
updates.push("enabled = ?");
|
|
469
|
+
values.push(enabled ? 1 : 0);
|
|
470
|
+
}
|
|
471
|
+
if (nextRunAt !== undefined) {
|
|
472
|
+
updates.push("next_run_at = ?");
|
|
473
|
+
values.push(nextRunAt);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (updates.length === 0) {
|
|
477
|
+
return { updated: false };
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
updates.push("updated_at = CURRENT_TIMESTAMP");
|
|
481
|
+
values.push(id);
|
|
482
|
+
|
|
483
|
+
const sql = `UPDATE scheduled_tasks SET ${updates.join(", ")} WHERE id = ?`;
|
|
484
|
+
|
|
485
|
+
return new Promise((resolve, reject) => {
|
|
486
|
+
this.db.run(sql, values, function (err) {
|
|
487
|
+
if (err) {
|
|
488
|
+
reject(err);
|
|
489
|
+
} else {
|
|
490
|
+
resolve({ updated: this.changes > 0, id: id });
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async updateTaskRunInfo(id, lastRunAt, nextRunAt) {
|
|
497
|
+
const sql = `
|
|
498
|
+
UPDATE scheduled_tasks
|
|
499
|
+
SET last_run_at = ?,
|
|
500
|
+
next_run_at = ?,
|
|
501
|
+
run_count = run_count + 1,
|
|
502
|
+
updated_at = CURRENT_TIMESTAMP
|
|
503
|
+
WHERE id = ?
|
|
504
|
+
`;
|
|
505
|
+
|
|
506
|
+
return new Promise((resolve, reject) => {
|
|
507
|
+
this.db.run(sql, [lastRunAt, nextRunAt, id], function (err) {
|
|
508
|
+
if (err) {
|
|
509
|
+
reject(err);
|
|
510
|
+
} else {
|
|
511
|
+
resolve({ updated: this.changes > 0 });
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async deleteScheduledTask(id) {
|
|
518
|
+
const sql = `DELETE FROM scheduled_tasks WHERE id = ?`;
|
|
519
|
+
|
|
520
|
+
return new Promise((resolve, reject) => {
|
|
521
|
+
this.db.run(sql, [id], function (err) {
|
|
522
|
+
if (err) {
|
|
523
|
+
reject(err);
|
|
524
|
+
} else {
|
|
525
|
+
resolve({ deleted: this.changes > 0 });
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async getEnabledScheduledTasks() {
|
|
532
|
+
const sql = `SELECT * FROM scheduled_tasks WHERE enabled = 1 ORDER BY created_at ASC`;
|
|
533
|
+
|
|
534
|
+
return new Promise((resolve, reject) => {
|
|
535
|
+
this.db.all(sql, [], (err, rows) => {
|
|
536
|
+
if (err) {
|
|
537
|
+
reject(err);
|
|
538
|
+
} else {
|
|
539
|
+
const tasks = rows.map((row) => ({
|
|
540
|
+
id: row.id,
|
|
541
|
+
name: row.name,
|
|
542
|
+
description: row.description,
|
|
543
|
+
cronExpression: row.cron_expression,
|
|
544
|
+
prompt: row.prompt,
|
|
545
|
+
enabled: row.enabled === 1,
|
|
546
|
+
lastRunAt: row.last_run_at,
|
|
547
|
+
nextRunAt: row.next_run_at,
|
|
548
|
+
runCount: row.run_count,
|
|
549
|
+
createdAt: row.created_at,
|
|
550
|
+
updatedAt: row.updated_at,
|
|
551
|
+
}));
|
|
552
|
+
resolve(tasks);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
_getLocalDateString() {
|
|
559
|
+
const now = new Date();
|
|
560
|
+
const year = now.getFullYear();
|
|
561
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
562
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
563
|
+
return `${year}-${month}-${day}`;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async recordTokenUsage(inputTokens, outputTokens) {
|
|
567
|
+
const today = this._getLocalDateString();
|
|
568
|
+
const sql = `
|
|
569
|
+
INSERT INTO token_usage (date, input_tokens, output_tokens, total_tokens, request_count)
|
|
570
|
+
VALUES (?, ?, ?, ?, 1)
|
|
571
|
+
ON CONFLICT(date) DO UPDATE SET
|
|
572
|
+
input_tokens = input_tokens + ?,
|
|
573
|
+
output_tokens = output_tokens + ?,
|
|
574
|
+
total_tokens = total_tokens + ?,
|
|
575
|
+
request_count = request_count + 1,
|
|
576
|
+
updated_at = CURRENT_TIMESTAMP
|
|
577
|
+
`;
|
|
578
|
+
|
|
579
|
+
const totalTokens = inputTokens + outputTokens;
|
|
580
|
+
|
|
581
|
+
return new Promise((resolve, reject) => {
|
|
582
|
+
this.db.run(
|
|
583
|
+
sql,
|
|
584
|
+
[today, inputTokens, outputTokens, totalTokens, inputTokens, outputTokens, totalTokens],
|
|
585
|
+
function (err) {
|
|
586
|
+
if (err) {
|
|
587
|
+
reject(err);
|
|
588
|
+
} else {
|
|
589
|
+
resolve({ success: true });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async getTodayTokenUsage() {
|
|
597
|
+
const today = this._getLocalDateString();
|
|
598
|
+
const sql = `SELECT * FROM token_usage WHERE date = ?`;
|
|
599
|
+
|
|
600
|
+
return new Promise((resolve, reject) => {
|
|
601
|
+
this.db.get(sql, [today], (err, row) => {
|
|
602
|
+
if (err) {
|
|
603
|
+
reject(err);
|
|
604
|
+
} else if (row) {
|
|
605
|
+
resolve({
|
|
606
|
+
date: row.date,
|
|
607
|
+
inputTokens: row.input_tokens,
|
|
608
|
+
outputTokens: row.output_tokens,
|
|
609
|
+
totalTokens: row.total_tokens,
|
|
610
|
+
requestCount: row.request_count,
|
|
611
|
+
});
|
|
612
|
+
} else {
|
|
613
|
+
resolve({
|
|
614
|
+
date: today,
|
|
615
|
+
inputTokens: 0,
|
|
616
|
+
outputTokens: 0,
|
|
617
|
+
totalTokens: 0,
|
|
618
|
+
requestCount: 0,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async getTotalTokenUsage() {
|
|
626
|
+
const sql = `
|
|
627
|
+
SELECT
|
|
628
|
+
SUM(input_tokens) as total_input_tokens,
|
|
629
|
+
SUM(output_tokens) as total_output_tokens,
|
|
630
|
+
SUM(total_tokens) as total_tokens,
|
|
631
|
+
SUM(request_count) as total_request_count
|
|
632
|
+
FROM token_usage
|
|
633
|
+
`;
|
|
634
|
+
|
|
635
|
+
return new Promise((resolve, reject) => {
|
|
636
|
+
this.db.get(sql, [], (err, row) => {
|
|
637
|
+
if (err) {
|
|
638
|
+
reject(err);
|
|
639
|
+
} else {
|
|
640
|
+
resolve({
|
|
641
|
+
totalInputTokens: row.total_input_tokens || 0,
|
|
642
|
+
totalOutputTokens: row.total_output_tokens || 0,
|
|
643
|
+
totalTokens: row.total_tokens || 0,
|
|
644
|
+
totalRequestCount: row.total_request_count || 0,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
async getTokenUsageByDateRange(startDate, endDate) {
|
|
652
|
+
const sql = `
|
|
653
|
+
SELECT * FROM token_usage
|
|
654
|
+
WHERE date >= ? AND date <= ?
|
|
655
|
+
ORDER BY date ASC
|
|
656
|
+
`;
|
|
657
|
+
|
|
658
|
+
return new Promise((resolve, reject) => {
|
|
659
|
+
this.db.all(sql, [startDate, endDate], (err, rows) => {
|
|
660
|
+
if (err) {
|
|
661
|
+
reject(err);
|
|
662
|
+
} else {
|
|
663
|
+
const usage = rows.map((row) => ({
|
|
664
|
+
date: row.date,
|
|
665
|
+
inputTokens: row.input_tokens,
|
|
666
|
+
outputTokens: row.output_tokens,
|
|
667
|
+
totalTokens: row.total_tokens,
|
|
668
|
+
requestCount: row.request_count,
|
|
669
|
+
}));
|
|
670
|
+
resolve(usage);
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* 关闭数据库连接
|
|
678
|
+
* @returns {Promise<void>}
|
|
679
|
+
*/
|
|
680
|
+
async close() {
|
|
681
|
+
return new Promise((resolve, reject) => {
|
|
682
|
+
if (this.db) {
|
|
683
|
+
this.db.close((err) => {
|
|
684
|
+
if (err) {
|
|
685
|
+
reject(err);
|
|
686
|
+
} else {
|
|
687
|
+
resolve();
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
} else {
|
|
691
|
+
resolve();
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
module.exports = DatabaseService;
|