tt-help-cli-ycl 1.4.17 → 1.4.19
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/package.json +1 -1
- package/src/cli/comments.js +6 -4
- package/src/cli/tag.js +5 -7
- package/src/cli/watch.js +15 -4
- package/src/lib/args.js +22 -7
- package/src/lib/constants.js +12 -2
- package/src/watch/data-store.js +206 -8
- package/src/watch/db-crud.js +16 -1
- package/src/watch/db-schema.js +5 -0
- package/src/watch/db-stats.js +2 -6
- package/src/watch/db-tags.js +106 -6
- package/src/watch/public/app.js +554 -11
- package/src/watch/public/index.html +4 -1
- package/src/watch/public/style.css +41 -0
- package/src/watch/server.js +273 -7
- package/src/watch/tag-service.js +317 -10
package/src/watch/db-tags.js
CHANGED
|
@@ -22,7 +22,13 @@ export function insertTag(tag, countries, source = "llm") {
|
|
|
22
22
|
return { inserted: false, error: "invalid tag" };
|
|
23
23
|
}
|
|
24
24
|
try {
|
|
25
|
-
//
|
|
25
|
+
// 先查是否已存在且为 dead 状态,如果是则重置为 new(给二次机会)
|
|
26
|
+
const existing = db
|
|
27
|
+
.prepare("SELECT status FROM tags WHERE tag = ?")
|
|
28
|
+
.get(normalized);
|
|
29
|
+
const resetDead = existing && existing.status === "dead";
|
|
30
|
+
|
|
31
|
+
// ON CONFLICT: 合并 countries 数组;如果原状态是 dead 则重置为 new
|
|
26
32
|
const result = db
|
|
27
33
|
.prepare(
|
|
28
34
|
`INSERT INTO tags (tag, countries, source) VALUES (@tag, @countries, @source)
|
|
@@ -32,9 +38,12 @@ export function insertTag(tag, countries, source = "llm") {
|
|
|
32
38
|
UNION
|
|
33
39
|
SELECT value FROM json_each(@countries)
|
|
34
40
|
)
|
|
35
|
-
)`,
|
|
41
|
+
)${resetDead ? ", status = 'new', score = NULL, last_error = NULL, dead_reason = NULL, scored_at = NULL, score_count = 0" : ""}`,
|
|
36
42
|
)
|
|
37
43
|
.run({ tag: normalized, countries: JSON.stringify(countries), source });
|
|
44
|
+
if (resetDead) {
|
|
45
|
+
console.error(`[tags] 🔄 dead tag #${normalized} 重新生成,重置为 new`);
|
|
46
|
+
}
|
|
38
47
|
return { inserted: result.changes > 0, tag: normalized };
|
|
39
48
|
} catch (e) {
|
|
40
49
|
return { inserted: false, error: e.message };
|
|
@@ -55,7 +64,8 @@ export function getTagsByStatus(
|
|
|
55
64
|
sql += " AND countries LIKE ?";
|
|
56
65
|
params.push(`%"${country}"%`);
|
|
57
66
|
}
|
|
58
|
-
sql +=
|
|
67
|
+
sql +=
|
|
68
|
+
" ORDER BY refreshed_at DESC NULLS LAST, score ASC, created_at ASC LIMIT ? OFFSET ?";
|
|
59
69
|
params.push(limit, offset);
|
|
60
70
|
const rows = db.prepare(sql).all(...params);
|
|
61
71
|
return rows.map(parseTagRow);
|
|
@@ -96,6 +106,48 @@ export function getTagStats(country = null) {
|
|
|
96
106
|
};
|
|
97
107
|
}
|
|
98
108
|
|
|
109
|
+
export function getRefreshStats(hours = 24) {
|
|
110
|
+
const db = getDb();
|
|
111
|
+
if (!db) return { countries: [], total: 0, refreshed: 0 };
|
|
112
|
+
|
|
113
|
+
const rows = db
|
|
114
|
+
.prepare(
|
|
115
|
+
"SELECT countries FROM tags WHERE status = 'productive' AND score > 70",
|
|
116
|
+
)
|
|
117
|
+
.all();
|
|
118
|
+
const countrySet = new Set();
|
|
119
|
+
for (const r of rows) {
|
|
120
|
+
try {
|
|
121
|
+
for (const c of JSON.parse(r.countries || "[]")) countrySet.add(c);
|
|
122
|
+
} catch {}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const results = [];
|
|
126
|
+
let totalAll = 0;
|
|
127
|
+
let refreshedAll = 0;
|
|
128
|
+
const countryList = [...countrySet].sort();
|
|
129
|
+
|
|
130
|
+
const stmtTotal = db.prepare(
|
|
131
|
+
"SELECT COUNT(*) as cnt FROM tags WHERE status = 'productive' AND score > 70 AND countries LIKE ?",
|
|
132
|
+
);
|
|
133
|
+
const stmtRefresh = db.prepare(
|
|
134
|
+
`SELECT COUNT(*) as cnt FROM tags WHERE status = 'productive' AND score > 70 AND countries LIKE ? AND refreshed_at >= datetime('now', ?)`,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
for (const c of countryList) {
|
|
138
|
+
const like = `%"${c}"%`;
|
|
139
|
+
const total = stmtTotal.get(like).cnt;
|
|
140
|
+
const refreshed = stmtRefresh.get(like, `-${hours} hours`).cnt;
|
|
141
|
+
if (total > 0) {
|
|
142
|
+
totalAll += total;
|
|
143
|
+
refreshedAll += refreshed;
|
|
144
|
+
results.push({ country: c, total, refreshed });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { countries: results, total: totalAll, refreshed: refreshedAll };
|
|
149
|
+
}
|
|
150
|
+
|
|
99
151
|
export function getTagsByCountry(country, minScore = 0) {
|
|
100
152
|
const db = getDb();
|
|
101
153
|
if (!db) return [];
|
|
@@ -233,7 +285,8 @@ export function getAllTags(limit = 200, offset = 0, country = null) {
|
|
|
233
285
|
sql += " WHERE countries LIKE ?";
|
|
234
286
|
params.push(`%"${country}"%`);
|
|
235
287
|
}
|
|
236
|
-
sql +=
|
|
288
|
+
sql +=
|
|
289
|
+
" ORDER BY refreshed_at DESC NULLS LAST, score DESC, created_at DESC LIMIT ? OFFSET ?";
|
|
237
290
|
params.push(limit, offset);
|
|
238
291
|
const rows = db.prepare(sql).all(...params);
|
|
239
292
|
return rows.map(parseTagRow);
|
|
@@ -319,6 +372,7 @@ export function insertDiscoverLog({
|
|
|
319
372
|
round,
|
|
320
373
|
strategy,
|
|
321
374
|
tagsGenerated,
|
|
375
|
+
tagsRaw,
|
|
322
376
|
tagsAdded,
|
|
323
377
|
productiveSample,
|
|
324
378
|
deadSample,
|
|
@@ -332,15 +386,16 @@ export function insertDiscoverLog({
|
|
|
332
386
|
const result = db
|
|
333
387
|
.prepare(
|
|
334
388
|
`INSERT INTO discover_log
|
|
335
|
-
(country, round, strategy, tags_generated, tags_added,
|
|
389
|
+
(country, round, strategy, tags_generated, tags_raw, tags_added,
|
|
336
390
|
productive_sample, dead_sample, avg_productive_score, avg_dead_score)
|
|
337
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
391
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
338
392
|
)
|
|
339
393
|
.run(
|
|
340
394
|
country,
|
|
341
395
|
round,
|
|
342
396
|
strategy || null,
|
|
343
397
|
JSON.stringify(tagsGenerated || []),
|
|
398
|
+
tagsRaw ? JSON.stringify(tagsRaw) : null,
|
|
344
399
|
tagsAdded || 0,
|
|
345
400
|
JSON.stringify(productiveSample || []),
|
|
346
401
|
JSON.stringify(deadSample || []),
|
|
@@ -386,6 +441,51 @@ export function getAllExistingTagNames() {
|
|
|
386
441
|
return new Set(rows.map((r) => r.tag));
|
|
387
442
|
}
|
|
388
443
|
|
|
444
|
+
/**
|
|
445
|
+
* 获取活跃 tag 名集合(仅 productive + scored),用于全局去重
|
|
446
|
+
* 排除 dead tag,因为 insertTag 会在冲突时将 dead 重置为 new。
|
|
447
|
+
*/
|
|
448
|
+
export function getActiveTagNames() {
|
|
449
|
+
const db = getDb();
|
|
450
|
+
if (!db) return new Set();
|
|
451
|
+
const rows = db
|
|
452
|
+
.prepare("SELECT tag FROM tags WHERE status IN ('productive', 'scored')")
|
|
453
|
+
.all();
|
|
454
|
+
return new Set(rows.map((r) => r.tag));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* 获取某国家最近一轮 discover 中 LLM 原始生成的全部 tag(去重前)
|
|
459
|
+
* 用于反馈给 LLM:"你上轮生成了这些,全部已被数据库覆盖,换方向"
|
|
460
|
+
*/
|
|
461
|
+
export function getLastRoundAllGeneratedTags(country) {
|
|
462
|
+
const db = getDb();
|
|
463
|
+
if (!db) return null;
|
|
464
|
+
|
|
465
|
+
const row = db
|
|
466
|
+
.prepare(
|
|
467
|
+
`SELECT tags_raw, tags_generated, tags_added FROM discover_log
|
|
468
|
+
WHERE country = ?
|
|
469
|
+
ORDER BY round DESC
|
|
470
|
+
LIMIT 1`,
|
|
471
|
+
)
|
|
472
|
+
.get(country);
|
|
473
|
+
|
|
474
|
+
if (!row) return null;
|
|
475
|
+
|
|
476
|
+
const raw = JSON.parse(row.tags_raw || "[]");
|
|
477
|
+
const survived = JSON.parse(row.tags_generated || "[]");
|
|
478
|
+
const rejected = raw.filter((t) => !survived.includes(t));
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
raw,
|
|
482
|
+
survived,
|
|
483
|
+
rejected,
|
|
484
|
+
tagsAdded: row.tags_added,
|
|
485
|
+
allRejected: row.tags_added === 0 && raw.length > 0,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
389
489
|
export function getRecentGeneratedTags(country, lookback = 10) {
|
|
390
490
|
const db = getDb();
|
|
391
491
|
if (!db) return [];
|