llm-content-creator 0.1.0 → 0.1.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/dist/application/workflow/SyncExecutor.d.ts.map +1 -1
- package/dist/application/workflow/SyncExecutor.js +1 -0
- package/dist/application/workflow/SyncExecutor.js.map +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +3 -3
- package/dist/config/index.js.map +1 -1
- package/dist/domain/repositories/TaskRepository.d.ts +1 -0
- package/dist/domain/repositories/TaskRepository.d.ts.map +1 -1
- package/dist/infrastructure/database/MemoryTaskRepository.d.ts +17 -1
- package/dist/infrastructure/database/MemoryTaskRepository.d.ts.map +1 -1
- package/dist/infrastructure/database/MemoryTaskRepository.js +76 -1
- package/dist/infrastructure/database/MemoryTaskRepository.js.map +1 -1
- package/dist/infrastructure/database/SQLiteQualityCheckRepository.d.ts +27 -0
- package/dist/infrastructure/database/SQLiteQualityCheckRepository.d.ts.map +1 -0
- package/dist/infrastructure/database/SQLiteQualityCheckRepository.js +80 -0
- package/dist/infrastructure/database/SQLiteQualityCheckRepository.js.map +1 -0
- package/dist/infrastructure/database/SQLiteResultRepository.d.ts +31 -0
- package/dist/infrastructure/database/SQLiteResultRepository.d.ts.map +1 -0
- package/dist/infrastructure/database/SQLiteResultRepository.js +77 -0
- package/dist/infrastructure/database/SQLiteResultRepository.js.map +1 -0
- package/dist/infrastructure/database/SQLiteTaskRepository.d.ts +12 -0
- package/dist/infrastructure/database/SQLiteTaskRepository.d.ts.map +1 -1
- package/dist/infrastructure/database/SQLiteTaskRepository.js +69 -2
- package/dist/infrastructure/database/SQLiteTaskRepository.js.map +1 -1
- package/dist/infrastructure/database/index.d.ts +23 -2
- package/dist/infrastructure/database/index.d.ts.map +1 -1
- package/dist/infrastructure/database/index.js +33 -2
- package/dist/infrastructure/database/index.js.map +1 -1
- package/dist/infrastructure/redis/connection.d.ts.map +1 -1
- package/dist/infrastructure/redis/connection.js +14 -3
- package/dist/infrastructure/redis/connection.js.map +1 -1
- package/dist/presentation/cli/commands/cancel.d.ts.map +1 -1
- package/dist/presentation/cli/commands/cancel.js +1 -0
- package/dist/presentation/cli/commands/cancel.js.map +1 -1
- package/dist/presentation/cli/commands/create.d.ts.map +1 -1
- package/dist/presentation/cli/commands/create.js +29 -14
- package/dist/presentation/cli/commands/create.js.map +1 -1
- package/dist/presentation/cli/commands/list.d.ts +8 -0
- package/dist/presentation/cli/commands/list.d.ts.map +1 -0
- package/dist/presentation/cli/commands/list.js +154 -0
- package/dist/presentation/cli/commands/list.js.map +1 -0
- package/dist/presentation/cli/commands/result.d.ts.map +1 -1
- package/dist/presentation/cli/commands/result.js +33 -3
- package/dist/presentation/cli/commands/result.js.map +1 -1
- package/dist/presentation/cli/commands/retry.d.ts +8 -0
- package/dist/presentation/cli/commands/retry.d.ts.map +1 -0
- package/dist/presentation/cli/commands/retry.js +203 -0
- package/dist/presentation/cli/commands/retry.js.map +1 -0
- package/dist/presentation/cli/commands/status.d.ts.map +1 -1
- package/dist/presentation/cli/commands/status.js +3 -2
- package/dist/presentation/cli/commands/status.js.map +1 -1
- package/dist/presentation/cli/index.js +4 -0
- package/dist/presentation/cli/index.js.map +1 -1
- package/dist/presentation/cli/utils/cleanup.d.ts +1 -3
- package/dist/presentation/cli/utils/cleanup.d.ts.map +1 -1
- package/dist/presentation/cli/utils/cleanup.js.map +1 -1
- package/dist/presentation/monitor-cli.js +2 -4
- package/dist/presentation/monitor-cli.js.map +1 -1
- package/dist/presentation/worker-cli.js +8 -5
- package/dist/presentation/worker-cli.js.map +1 -1
- package/dist/schedulers/TaskScheduler.d.ts.map +1 -1
- package/dist/schedulers/TaskScheduler.js +13 -5
- package/dist/schedulers/TaskScheduler.js.map +1 -1
- package/dist/workers/TaskWorker.d.ts.map +1 -1
- package/dist/workers/TaskWorker.js +39 -21
- package/dist/workers/TaskWorker.js.map +1 -1
- package/docs/cli-reference.md +623 -0
- package/docs/quick-start.md +47 -0
- package/docs/user-guide.md +191 -24
- package/package.json +3 -1
- package/src/application/workflow/SyncExecutor.ts +1 -0
- package/src/config/index.ts +3 -3
- package/src/domain/repositories/TaskRepository.ts +1 -0
- package/src/infrastructure/database/MemoryTaskRepository.ts +89 -1
- package/src/infrastructure/database/SQLiteQualityCheckRepository.ts +102 -0
- package/src/infrastructure/database/SQLiteResultRepository.ts +95 -0
- package/src/infrastructure/database/SQLiteTaskRepository.ts +80 -2
- package/src/infrastructure/database/index.ts +39 -2
- package/src/infrastructure/redis/connection.ts +16 -3
- package/src/presentation/cli/commands/cancel.ts +1 -0
- package/src/presentation/cli/commands/create.ts +30 -14
- package/src/presentation/cli/commands/list.ts +172 -0
- package/src/presentation/cli/commands/result.ts +33 -3
- package/src/presentation/cli/commands/retry.ts +241 -0
- package/src/presentation/cli/commands/status.ts +3 -2
- package/src/presentation/cli/index.ts +4 -0
- package/src/presentation/cli/utils/cleanup.ts +1 -1
- package/src/presentation/monitor-cli.ts +2 -4
- package/src/presentation/worker-cli.ts +8 -5
- package/src/schedulers/TaskScheduler.ts +13 -5
- package/src/workers/TaskWorker.ts +47 -22
|
@@ -83,11 +83,15 @@ export class SQLiteTaskRepository {
|
|
|
83
83
|
CREATE TABLE IF NOT EXISTS quality_checks (
|
|
84
84
|
id TEXT PRIMARY KEY,
|
|
85
85
|
task_id TEXT NOT NULL,
|
|
86
|
-
|
|
86
|
+
check_type TEXT NOT NULL,
|
|
87
87
|
score REAL,
|
|
88
88
|
passed BOOLEAN NOT NULL DEFAULT 0,
|
|
89
|
+
hard_constraints_passed BOOLEAN NOT NULL DEFAULT 0,
|
|
89
90
|
details TEXT,
|
|
90
91
|
fix_suggestions TEXT,
|
|
92
|
+
rubric_version TEXT,
|
|
93
|
+
model_name TEXT,
|
|
94
|
+
prompt_hash TEXT,
|
|
91
95
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
92
96
|
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
|
93
97
|
);
|
|
@@ -97,6 +101,7 @@ export class SQLiteTaskRepository {
|
|
|
97
101
|
task_id TEXT NOT NULL,
|
|
98
102
|
result_type TEXT NOT NULL,
|
|
99
103
|
content TEXT,
|
|
104
|
+
file_path TEXT,
|
|
100
105
|
metadata TEXT,
|
|
101
106
|
quality_score REAL,
|
|
102
107
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
@@ -415,7 +420,7 @@ export class SQLiteTaskRepository {
|
|
|
415
420
|
WHERE id = ? AND version = ? AND status = ?
|
|
416
421
|
`);
|
|
417
422
|
|
|
418
|
-
const result = stmt.run(TaskStatus.RUNNING, workerId, now, taskId, version, TaskStatus.PENDING);
|
|
423
|
+
const result = stmt.run(TaskStatus.RUNNING, workerId, now, now, taskId, version, TaskStatus.PENDING);
|
|
419
424
|
const claimed = result.changes > 0;
|
|
420
425
|
|
|
421
426
|
if (claimed) {
|
|
@@ -611,6 +616,79 @@ export class SQLiteTaskRepository {
|
|
|
611
616
|
}
|
|
612
617
|
}
|
|
613
618
|
|
|
619
|
+
/**
|
|
620
|
+
* 查询任务列表(支持过滤和分页)
|
|
621
|
+
*/
|
|
622
|
+
async findMany(filter?: any, pagination?: any): Promise<Task[]> {
|
|
623
|
+
const { limit = 50, offset = 0 } = pagination || {};
|
|
624
|
+
|
|
625
|
+
const conditions: string[] = [];
|
|
626
|
+
const values: any[] = [];
|
|
627
|
+
|
|
628
|
+
if (filter?.status) {
|
|
629
|
+
conditions.push('status = ?');
|
|
630
|
+
values.push(filter.status);
|
|
631
|
+
}
|
|
632
|
+
if (filter?.userId) {
|
|
633
|
+
conditions.push('id = ?'); // SQLite 没有 userId 字段,使用 id
|
|
634
|
+
values.push(filter.userId);
|
|
635
|
+
}
|
|
636
|
+
if (filter?.mode) {
|
|
637
|
+
conditions.push('mode = ?');
|
|
638
|
+
values.push(filter.mode);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const whereClause = conditions.length > 0
|
|
642
|
+
? 'WHERE ' + conditions.join(' AND ')
|
|
643
|
+
: '';
|
|
644
|
+
|
|
645
|
+
const stmt = this.db.prepare(`
|
|
646
|
+
SELECT * FROM tasks
|
|
647
|
+
${whereClause}
|
|
648
|
+
ORDER BY created_at DESC
|
|
649
|
+
LIMIT ? OFFSET ?
|
|
650
|
+
`);
|
|
651
|
+
|
|
652
|
+
const rows = stmt.all(...values, limit, offset) as any[];
|
|
653
|
+
return rows.map((row) => this.mapRowToTask(row));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* 统计任务数量
|
|
658
|
+
*/
|
|
659
|
+
async count(filter?: any): Promise<number> {
|
|
660
|
+
const conditions: string[] = [];
|
|
661
|
+
const values: any[] = [];
|
|
662
|
+
|
|
663
|
+
if (filter?.status) {
|
|
664
|
+
conditions.push('status = ?');
|
|
665
|
+
values.push(filter.status);
|
|
666
|
+
}
|
|
667
|
+
if (filter?.userId) {
|
|
668
|
+
conditions.push('id = ?');
|
|
669
|
+
values.push(filter.userId);
|
|
670
|
+
}
|
|
671
|
+
if (filter?.mode) {
|
|
672
|
+
conditions.push('mode = ?');
|
|
673
|
+
values.push(filter.mode);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const whereClause = conditions.length > 0
|
|
677
|
+
? 'WHERE ' + conditions.join(' AND ')
|
|
678
|
+
: '';
|
|
679
|
+
|
|
680
|
+
const stmt = this.db.prepare(`SELECT COUNT(*) as count FROM tasks ${whereClause}`);
|
|
681
|
+
const result = stmt.get(...values) as { count: number };
|
|
682
|
+
return result.count;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* 根据 userId 查询任务列表
|
|
687
|
+
*/
|
|
688
|
+
async findByUserId(userId: string, pagination?: any): Promise<Task[]> {
|
|
689
|
+
return this.findMany({ userId }, pagination);
|
|
690
|
+
}
|
|
691
|
+
|
|
614
692
|
/**
|
|
615
693
|
* 关闭数据库连接
|
|
616
694
|
*/
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
import { config } from '../../config/index.js';
|
|
8
8
|
import { MemoryTaskRepository } from './MemoryTaskRepository.js';
|
|
9
9
|
import { SQLiteTaskRepository } from './SQLiteTaskRepository.js';
|
|
10
|
+
import { SQLiteResultRepository } from './SQLiteResultRepository.js';
|
|
11
|
+
import { SQLiteQualityCheckRepository } from './SQLiteQualityCheckRepository.js';
|
|
12
|
+
import { PostgresTaskRepository } from './PostgresTaskRepository.js';
|
|
10
13
|
import { createLogger } from '../logging/logger.js';
|
|
11
14
|
|
|
12
15
|
const logger = createLogger('Database:Factory');
|
|
@@ -54,9 +57,7 @@ export function createTaskRepository(pool?: any, dbPath?: string) {
|
|
|
54
57
|
|
|
55
58
|
if (dbType === 'postgres') {
|
|
56
59
|
// PostgreSQL 版本(需要数据库连接)
|
|
57
|
-
// 动态导入以避免在不需要时加载 pg
|
|
58
60
|
try {
|
|
59
|
-
const { PostgresTaskRepository } = require('./PostgresTaskRepository.js');
|
|
60
61
|
// 如果提供了 pool,使用提供的;否则由 BaseRepository 自动创建
|
|
61
62
|
logger.info('Using PostgresTaskRepository');
|
|
62
63
|
return new PostgresTaskRepository(pool);
|
|
@@ -74,10 +75,46 @@ export function createTaskRepository(pool?: any, dbPath?: string) {
|
|
|
74
75
|
return new SQLiteTaskRepository(dbPath);
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
/**
|
|
79
|
+
* 创建 Result Repository
|
|
80
|
+
*
|
|
81
|
+
* 根据配置自动选择合适的实现
|
|
82
|
+
*/
|
|
83
|
+
export function createResultRepository(_pool?: any, dbPath?: string) {
|
|
84
|
+
const dbType = config.database.type;
|
|
85
|
+
|
|
86
|
+
logger.info('Creating Result Repository', { databaseType: dbType });
|
|
87
|
+
|
|
88
|
+
// 注意:PostgreSQL 版本需要动态导入,但目前只支持 SQLite
|
|
89
|
+
// 默认使用 SQLiteResultRepository
|
|
90
|
+
logger.info('Using SQLiteResultRepository', { dbPath: dbPath || './data/content-creator.db' });
|
|
91
|
+
return new SQLiteResultRepository(dbPath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 创建 Quality Check Repository
|
|
96
|
+
*
|
|
97
|
+
* 根据配置自动选择合适的实现
|
|
98
|
+
*/
|
|
99
|
+
export function createQualityCheckRepository(_pool?: any, dbPath?: string) {
|
|
100
|
+
const dbType = config.database.type;
|
|
101
|
+
|
|
102
|
+
logger.info('Creating Quality Check Repository', { databaseType: dbType });
|
|
103
|
+
|
|
104
|
+
// 注意:PostgreSQL 版本需要动态导入,但目前只支持 SQLite
|
|
105
|
+
// 默认使用 SQLiteQualityCheckRepository
|
|
106
|
+
logger.info('Using SQLiteQualityCheckRepository', { dbPath: dbPath || './data/content-creator.db' });
|
|
107
|
+
return new SQLiteQualityCheckRepository(dbPath);
|
|
108
|
+
}
|
|
109
|
+
|
|
77
110
|
// 导出具体的 Repository 类(可选使用)
|
|
78
111
|
export { MemoryTaskRepository } from './MemoryTaskRepository.js';
|
|
79
112
|
export { PostgresTaskRepository } from './PostgresTaskRepository.js';
|
|
80
113
|
export { SQLiteTaskRepository } from './SQLiteTaskRepository.js';
|
|
114
|
+
export { PostgresResultRepository } from './ResultRepository.js';
|
|
115
|
+
export { SQLiteResultRepository } from './SQLiteResultRepository.js';
|
|
116
|
+
export { PostgresQualityCheckRepository } from './PostgresQualityCheckRepository.js';
|
|
117
|
+
export { SQLiteQualityCheckRepository } from './SQLiteQualityCheckRepository.js';
|
|
81
118
|
|
|
82
119
|
// 导出单例(全局使用)
|
|
83
120
|
export const taskRepository = createTaskRepository();
|
|
@@ -81,7 +81,7 @@ export class RedisClient {
|
|
|
81
81
|
host: redisUrl.hostname,
|
|
82
82
|
port: parseInt(redisUrl.port) || 6379,
|
|
83
83
|
db: config.redis.db,
|
|
84
|
-
maxRetriesPerRequest:
|
|
84
|
+
maxRetriesPerRequest: null, // BullMQ Worker 要求必须设置为 null
|
|
85
85
|
retryStrategy: (times: number) => {
|
|
86
86
|
const delay = Math.min(times * 50, 2000);
|
|
87
87
|
logger.debug(`Redis retry attempt ${times}, delay: ${delay}ms`);
|
|
@@ -131,9 +131,22 @@ export class RedisClient {
|
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
// 等待连接就绪
|
|
134
|
-
await
|
|
134
|
+
await new Promise((resolve, reject) => {
|
|
135
|
+
this.client.on('ready', () => {
|
|
136
|
+
logger.info('Redis connection established');
|
|
137
|
+
resolve(this.client);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
this.client.on('error', (err: Error) => {
|
|
141
|
+
reject(err);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// 超时处理
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
reject(new Error('Redis connection timeout'));
|
|
147
|
+
}, config.redis.connectTimeout);
|
|
148
|
+
});
|
|
135
149
|
|
|
136
|
-
logger.info('Redis connection established');
|
|
137
150
|
return this.client;
|
|
138
151
|
} catch (error) {
|
|
139
152
|
this.isConnecting = false;
|
|
@@ -11,6 +11,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
11
11
|
import { createSyncExecutor } from '../../../application/workflow/SyncExecutor.js';
|
|
12
12
|
import { MemoryTaskRepository } from '../../../infrastructure/database/MemoryTaskRepository.js';
|
|
13
13
|
import { PostgresTaskRepository } from '../../../infrastructure/database/PostgresTaskRepository.js';
|
|
14
|
+
import { SQLiteTaskRepository } from '../../../infrastructure/database/SQLiteTaskRepository.js';
|
|
14
15
|
import { PostgresResultRepository } from '../../../infrastructure/database/ResultRepository.js';
|
|
15
16
|
import { PostgresQualityCheckRepository } from '../../../infrastructure/database/PostgresQualityCheckRepository.js';
|
|
16
17
|
import { ExecutionMode, TaskPriority } from '../../../domain/entities/Task.js';
|
|
@@ -104,6 +105,10 @@ export const createCommand = new Command('create')
|
|
|
104
105
|
qualityCheckRepo = new PostgresQualityCheckRepository(resources.pool);
|
|
105
106
|
|
|
106
107
|
console.log('✅ 使用 PostgreSQL 持久化存储');
|
|
108
|
+
} else if (config.database.type === 'sqlite') {
|
|
109
|
+
// 使用 SQLite Task Repository,确保任务持久化
|
|
110
|
+
taskRepo = new SQLiteTaskRepository();
|
|
111
|
+
console.log('✅ 使用 SQLite 持久化存储');
|
|
107
112
|
} else {
|
|
108
113
|
// 使用内存数据库(仅用于测试)
|
|
109
114
|
taskRepo = new MemoryTaskRepository();
|
|
@@ -149,11 +154,11 @@ export const createCommand = new Command('create')
|
|
|
149
154
|
|
|
150
155
|
console.log(chalk.yellow.bold('\n💡 后续操作:'));
|
|
151
156
|
console.log(chalk.white('1. 查询任务状态:'));
|
|
152
|
-
console.log(chalk.gray(` pnpm cli result --task-id ${taskId}`));
|
|
157
|
+
console.log(chalk.gray(` pnpm run cli result --task-id ${taskId}`));
|
|
153
158
|
console.log(chalk.white('\n2. 确保 Worker 正在运行:'));
|
|
154
159
|
console.log(chalk.gray(' pnpm run worker'));
|
|
155
|
-
console.log(chalk.white('\n3.
|
|
156
|
-
console.log(chalk.gray(' pnpm
|
|
160
|
+
console.log(chalk.white('\n3. 查看监控面板:'));
|
|
161
|
+
console.log(chalk.gray(' pnpm run monitor'));
|
|
157
162
|
printSeparator();
|
|
158
163
|
|
|
159
164
|
logger.info('Task created via async mode', {
|
|
@@ -164,6 +169,14 @@ export const createCommand = new Command('create')
|
|
|
164
169
|
|
|
165
170
|
} else {
|
|
166
171
|
// ==================== 同步模式:使用 SyncExecutor ====================
|
|
172
|
+
// 为 SQLite 模式创建结果和质检仓储
|
|
173
|
+
if (config.database.type === 'sqlite') {
|
|
174
|
+
const { SQLiteResultRepository } = await import('../../../infrastructure/database/SQLiteResultRepository.js');
|
|
175
|
+
const { SQLiteQualityCheckRepository } = await import('../../../infrastructure/database/SQLiteQualityCheckRepository.js');
|
|
176
|
+
resultRepo = new SQLiteResultRepository();
|
|
177
|
+
qualityCheckRepo = new SQLiteQualityCheckRepository();
|
|
178
|
+
}
|
|
179
|
+
|
|
167
180
|
const executor = createSyncExecutor(taskRepo, {
|
|
168
181
|
databaseType: config.database.type,
|
|
169
182
|
enableLogging: true,
|
|
@@ -311,6 +324,9 @@ export const createCommand = new Command('create')
|
|
|
311
324
|
duration: result.duration
|
|
312
325
|
});
|
|
313
326
|
} // 结束同步模式的 else 块
|
|
327
|
+
|
|
328
|
+
// 任务完成后明确退出
|
|
329
|
+
process.exit(0);
|
|
314
330
|
} catch (error) {
|
|
315
331
|
logger.error('Create command failed', error as Error);
|
|
316
332
|
console.error(chalk.red(`\n❌ 错误: ${error instanceof Error ? error.message : String(error)}`));
|
|
@@ -327,15 +343,7 @@ export const createCommand = new Command('create')
|
|
|
327
343
|
if (resources.servicesInitialized) {
|
|
328
344
|
logger.debug('Starting resource cleanup...');
|
|
329
345
|
|
|
330
|
-
// 1.
|
|
331
|
-
try {
|
|
332
|
-
await closeLogger();
|
|
333
|
-
logger.debug('Logger closed');
|
|
334
|
-
} catch (error) {
|
|
335
|
-
console.log('Error closing logger (ignored):', error);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// 2. 停止 Metrics 服务定时器
|
|
346
|
+
// 1. 停止 Metrics 服务定时器
|
|
339
347
|
try {
|
|
340
348
|
metricsService.stop();
|
|
341
349
|
console.log('Metrics service stopped');
|
|
@@ -343,7 +351,7 @@ export const createCommand = new Command('create')
|
|
|
343
351
|
console.log('Error stopping metrics service (ignored):', error);
|
|
344
352
|
}
|
|
345
353
|
|
|
346
|
-
//
|
|
354
|
+
// 2. 关闭 Redis 客户端连接
|
|
347
355
|
try {
|
|
348
356
|
await redisClient.disconnect();
|
|
349
357
|
console.log('Redis client disconnected');
|
|
@@ -351,7 +359,7 @@ export const createCommand = new Command('create')
|
|
|
351
359
|
console.log('Error disconnecting Redis (ignored):', error);
|
|
352
360
|
}
|
|
353
361
|
|
|
354
|
-
//
|
|
362
|
+
// 3. 关闭 PostgreSQL 连接池
|
|
355
363
|
try {
|
|
356
364
|
if (resources.pool) {
|
|
357
365
|
await resources.pool.end();
|
|
@@ -361,6 +369,14 @@ export const createCommand = new Command('create')
|
|
|
361
369
|
console.log('Error closing PostgreSQL pool (ignored):', error);
|
|
362
370
|
}
|
|
363
371
|
|
|
372
|
+
// 4. 关闭 Logger(必须在最后)
|
|
373
|
+
try {
|
|
374
|
+
await closeLogger();
|
|
375
|
+
logger.debug('Logger closed');
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.log('Error closing logger (ignored):', error);
|
|
378
|
+
}
|
|
379
|
+
|
|
364
380
|
console.log('Resource cleanup completed');
|
|
365
381
|
}
|
|
366
382
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI List Command
|
|
3
|
+
*
|
|
4
|
+
* 列出历史任务
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { createTaskRepository } from '../../../infrastructure/database/index.js';
|
|
10
|
+
import { createLogger } from '../../../infrastructure/logging/logger.js';
|
|
11
|
+
|
|
12
|
+
const logger = createLogger('CLI:List');
|
|
13
|
+
|
|
14
|
+
function printSeparator() {
|
|
15
|
+
console.log(chalk.gray('─'.repeat(100)));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 格式化任务状态显示
|
|
20
|
+
*/
|
|
21
|
+
function formatTaskStatus(status: string): string {
|
|
22
|
+
const statusMap: Record<string, string> = {
|
|
23
|
+
pending: chalk.yellow('等待中'),
|
|
24
|
+
processing: chalk.blue('处理中'),
|
|
25
|
+
running: chalk.blue('运行中'),
|
|
26
|
+
completed: chalk.green('已完成'),
|
|
27
|
+
failed: chalk.red('失败'),
|
|
28
|
+
cancelled: chalk.gray('已取消'),
|
|
29
|
+
};
|
|
30
|
+
return statusMap[status] || status;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 格式化执行模式
|
|
35
|
+
*/
|
|
36
|
+
function formatExecutionMode(mode: string): string {
|
|
37
|
+
const modeMap: Record<string, string> = {
|
|
38
|
+
sync: '同步',
|
|
39
|
+
async: '异步',
|
|
40
|
+
};
|
|
41
|
+
return modeMap[mode] || mode;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 格式化时间显示
|
|
46
|
+
*/
|
|
47
|
+
function formatTime(dateStr: string | undefined): string {
|
|
48
|
+
if (!dateStr) return '-';
|
|
49
|
+
const date = new Date(dateStr);
|
|
50
|
+
const now = new Date();
|
|
51
|
+
const diff = now.getTime() - date.getTime();
|
|
52
|
+
|
|
53
|
+
// 小于1分钟
|
|
54
|
+
if (diff < 60000) {
|
|
55
|
+
return `${Math.floor(diff / 1000)}秒前`;
|
|
56
|
+
}
|
|
57
|
+
// 小于1小时
|
|
58
|
+
if (diff < 3600000) {
|
|
59
|
+
return `${Math.floor(diff / 60000)}分钟前`;
|
|
60
|
+
}
|
|
61
|
+
// 小于24小时
|
|
62
|
+
if (diff < 86400000) {
|
|
63
|
+
return `${Math.floor(diff / 3600000)}小时前`;
|
|
64
|
+
}
|
|
65
|
+
// 大于24小时,显示日期
|
|
66
|
+
return date.toLocaleDateString('zh-CN', {
|
|
67
|
+
month: '2-digit',
|
|
68
|
+
day: '2-digit',
|
|
69
|
+
hour: '2-digit',
|
|
70
|
+
minute: '2-digit',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 格式化持续时间
|
|
76
|
+
*/
|
|
77
|
+
function formatDuration(startedAt: string | undefined, completedAt: string | undefined): string {
|
|
78
|
+
if (!startedAt) return '-';
|
|
79
|
+
if (!completedAt) return '进行中...';
|
|
80
|
+
|
|
81
|
+
const start = new Date(startedAt).getTime();
|
|
82
|
+
const end = new Date(completedAt).getTime();
|
|
83
|
+
const duration = end - start;
|
|
84
|
+
|
|
85
|
+
if (duration < 1000) return `${duration}ms`;
|
|
86
|
+
if (duration < 60000) return `${Math.floor(duration / 1000)}s`;
|
|
87
|
+
return `${Math.floor(duration / 60000)}m ${Math.floor((duration % 60000) / 1000)}s`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const listCommand = new Command('list')
|
|
91
|
+
.description('列出历史任务')
|
|
92
|
+
.option('-s, --status <status>', '筛选状态 (pending, running, completed, failed, cancelled)')
|
|
93
|
+
.option('-m, --mode <mode>', '筛选执行模式 (sync, async)')
|
|
94
|
+
.option('-l, --limit <number>', '显示数量', '20')
|
|
95
|
+
.option('-o, --offset <number>', '偏移量(用于分页)', '0')
|
|
96
|
+
.option('--json', '以 JSON 格式输出')
|
|
97
|
+
.action(async (options) => {
|
|
98
|
+
try {
|
|
99
|
+
const repository = createTaskRepository();
|
|
100
|
+
|
|
101
|
+
// 解析参数
|
|
102
|
+
const limit = parseInt(options.limit) || 20;
|
|
103
|
+
const offset = parseInt(options.offset) || 0;
|
|
104
|
+
|
|
105
|
+
// 构建过滤条件
|
|
106
|
+
const filters: any = {};
|
|
107
|
+
|
|
108
|
+
if (options.status) {
|
|
109
|
+
filters.status = options.status;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (options.mode) {
|
|
113
|
+
filters.mode = options.mode;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 使用 findMany 方法查询任务列表
|
|
117
|
+
const tasks = await repository.findMany(filters, { limit, offset });
|
|
118
|
+
|
|
119
|
+
// JSON 格式输出
|
|
120
|
+
if (options.json) {
|
|
121
|
+
console.log(JSON.stringify(tasks, null, 2));
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 文本格式输出
|
|
126
|
+
printSeparator();
|
|
127
|
+
console.log(chalk.bold.blue('📋 历史任务列表'));
|
|
128
|
+
printSeparator();
|
|
129
|
+
|
|
130
|
+
if (tasks.length === 0) {
|
|
131
|
+
console.log(chalk.yellow('暂无任务记录'));
|
|
132
|
+
console.log();
|
|
133
|
+
console.log(chalk.white('💡 提示:使用以下命令创建新任务'));
|
|
134
|
+
console.log(chalk.gray(' pnpm run cli:create --topic "文章主题" --requirements "创作要求"'));
|
|
135
|
+
console.log();
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 输出任务列表
|
|
140
|
+
tasks.forEach((task: any, index: number) => {
|
|
141
|
+
console.log(chalk.bold.white(`${index + 1}. ${task.topic}`));
|
|
142
|
+
console.log(chalk.gray(` ID: ${task.taskId}`));
|
|
143
|
+
console.log(chalk.gray(` 状态: ${formatTaskStatus(task.status)}`));
|
|
144
|
+
console.log(chalk.gray(` 模式: ${formatExecutionMode(task.mode)}`));
|
|
145
|
+
console.log(chalk.gray(` 创建时间: ${formatTime(task.createdAt?.toISOString())}`));
|
|
146
|
+
console.log(chalk.gray(` 耗时: ${formatDuration(task.startedAt?.toISOString(), task.completedAt?.toISOString())}`));
|
|
147
|
+
|
|
148
|
+
if (task.errorMessage) {
|
|
149
|
+
console.log(chalk.red(` 错误: ${task.errorMessage}`));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// 详细信息提示
|
|
156
|
+
console.log(chalk.white('💡 查看任务详情:'));
|
|
157
|
+
console.log(chalk.gray(' pnpm run cli:status --task-id <任务ID>'));
|
|
158
|
+
console.log(chalk.gray(' pnpm run cli:result --task-id <任务ID>'));
|
|
159
|
+
console.log();
|
|
160
|
+
|
|
161
|
+
logger.info('Listed tasks', {
|
|
162
|
+
count: tasks.length,
|
|
163
|
+
filters,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
process.exit(0);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.error('Failed to list tasks', error as Error);
|
|
169
|
+
console.error(chalk.red('❌ 查询任务列表失败:'), (error as Error).message);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
@@ -44,14 +44,14 @@ export const resultCommand = new Command('result')
|
|
|
44
44
|
if (task.status !== 'completed') {
|
|
45
45
|
console.log(chalk.yellow(`⚠️ 任务尚未完成,当前状态: ${getStatusText(task.status)}`));
|
|
46
46
|
await cleanupResources(taskRepo, resultRepo);
|
|
47
|
-
|
|
47
|
+
process.exit(0);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// JSON格式输出
|
|
51
51
|
if (options.format === 'json') {
|
|
52
52
|
console.log(JSON.stringify(task, null, 2));
|
|
53
53
|
await cleanupResources(taskRepo, resultRepo);
|
|
54
|
-
|
|
54
|
+
process.exit(0);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// 文本格式输出
|
|
@@ -61,7 +61,7 @@ export const resultCommand = new Command('result')
|
|
|
61
61
|
console.log(chalk.white(`状态: ${getStatusText(task.status)}`));
|
|
62
62
|
printSeparator();
|
|
63
63
|
|
|
64
|
-
//
|
|
64
|
+
// 从数据库查询结果(支持 PostgreSQL 和 SQLite)
|
|
65
65
|
if (config.database.type === 'postgres') {
|
|
66
66
|
const { Pool } = await import('pg');
|
|
67
67
|
pool = new Pool({
|
|
@@ -101,6 +101,35 @@ export const resultCommand = new Command('result')
|
|
|
101
101
|
|
|
102
102
|
// 关闭结果查询的连接池
|
|
103
103
|
await pool.end();
|
|
104
|
+
} else if (config.database.type === 'sqlite') {
|
|
105
|
+
// SQLite 模式:使用 SQLiteResultRepository 查询结果
|
|
106
|
+
const { SQLiteResultRepository } = await import('../../../infrastructure/database/SQLiteResultRepository.js');
|
|
107
|
+
resultRepo = new SQLiteResultRepository();
|
|
108
|
+
const results = await resultRepo.findByTaskId(options.taskId);
|
|
109
|
+
|
|
110
|
+
if (results.length === 0) {
|
|
111
|
+
console.log(chalk.yellow('提示: 该任务未生成结果'));
|
|
112
|
+
} else {
|
|
113
|
+
console.log(chalk.blue.bold('\n📋 生成结果'));
|
|
114
|
+
printSeparator();
|
|
115
|
+
|
|
116
|
+
results.forEach((result: any, index: number) => {
|
|
117
|
+
console.log(chalk.white.bold(`${index + 1}. ${result.resultType.toUpperCase()}`));
|
|
118
|
+
printSeparator();
|
|
119
|
+
|
|
120
|
+
if (result.resultType === 'article') {
|
|
121
|
+
console.log(chalk.white('内容:'));
|
|
122
|
+
console.log(chalk.gray(result.content || '(无内容)'));
|
|
123
|
+
if (result.metadata?.wordCount) {
|
|
124
|
+
console.log(chalk.gray(`字数: ${result.metadata.wordCount}`));
|
|
125
|
+
}
|
|
126
|
+
} else if (result.resultType === 'image') {
|
|
127
|
+
console.log(chalk.white('图片 URL:'));
|
|
128
|
+
console.log(chalk.cyan(result.content || '(无 URL)'));
|
|
129
|
+
}
|
|
130
|
+
printSeparator();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
104
133
|
} else {
|
|
105
134
|
// Memory 模式:提示结果仅实时返回
|
|
106
135
|
console.log(chalk.yellow('\n💡 提示: 当前使用 Memory 模式'));
|
|
@@ -111,6 +140,7 @@ export const resultCommand = new Command('result')
|
|
|
111
140
|
|
|
112
141
|
// 清理所有资源
|
|
113
142
|
await cleanupResources(taskRepo, resultRepo);
|
|
143
|
+
process.exit(0);
|
|
114
144
|
|
|
115
145
|
} catch (error) {
|
|
116
146
|
console.error(chalk.red(`❌ 错误: ${error instanceof Error ? error.message : String(error)}`));
|