tt-help-cli-ycl 1.4.13 → 1.4.15

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.
@@ -93,6 +93,7 @@ function inferClientType(routePath) {
93
93
  if (routePath.startsWith("/api/redo-job")) return "refresh";
94
94
  if (routePath.startsWith("/api/user-update-tasks")) return "attach";
95
95
  if (routePath.startsWith("/api/comment-task")) return "comments";
96
+ if (routePath.startsWith("/api/tags/refresh")) return "tag-refresh";
96
97
  if (routePath.startsWith("/api/tags")) return "scoring";
97
98
  if (
98
99
  routePath.startsWith("/api/job") ||
@@ -142,9 +143,12 @@ export function startWatchServer(
142
143
  let discoverRunning = false;
143
144
  let lastDiscoverTime = 0;
144
145
 
145
- // 标签池充足阈值:new 标签总数超过此值时,定时触发直接跳过(不浪费 LLM token)
146
- const DISCOVER_SUFFICIENT_TOTAL =
147
- parseInt(process.env.TAG_DISCOVER_SUFFICIENT || "30", 10) || 30;
146
+ // 每国标签不足阈值:某国 new 标签低于此值时触发 discover
147
+ const DISCOVER_PER_COUNTRY_THRESHOLD = Math.max(
148
+ 3,
149
+ parseInt(process.env.TAG_DISCOVER_PER_COUNTRY_THRESHOLD || "10", 10) ||
150
+ 10,
151
+ );
148
152
 
149
153
  async function runAutoDiscover(trigger = "timer") {
150
154
  if (discoverRunning) return;
@@ -156,23 +160,14 @@ export function startWatchServer(
156
160
  );
157
161
  return;
158
162
  }
159
- // 先看全局总量:标签池充足时直接跳过,不浪费 LLM token
160
- const globalStats = store.getTagStats();
161
- const totalNew = globalStats?.new || 0;
162
- if (totalNew >= DISCOVER_SUFFICIENT_TOTAL) {
163
- console.error(
164
- `[discover] 标签池充足 (${totalNew} 个 new ≥ ${DISCOVER_SUFFICIENT_TOTAL}),跳过 [触发: ${trigger}]`,
165
- );
166
- return;
167
- }
168
163
  discoverRunning = true;
169
164
  try {
170
- // 只为 new 标签不足的国家发现
165
+ // 按国家维度判断:只为 new 标签不足的国家发现
171
166
  const countriesToDiscover = [];
172
167
  const { getTagsByStatus } = await import("./db-tags.js");
173
168
  for (const country of DEFAULT_TARGET_LOCATIONS) {
174
169
  const newTags = getTagsByStatus("new", 100, 0, country);
175
- if (newTags.length < 10) {
170
+ if (newTags.length < DISCOVER_PER_COUNTRY_THRESHOLD) {
176
171
  countriesToDiscover.push(country);
177
172
  }
178
173
  }
@@ -860,12 +855,13 @@ export function startWatchServer(
860
855
  offset: parseInt(params.offset || "0", 10),
861
856
  },
862
857
  );
863
- // 确保每个用户对象包含 topVideoPlayCount 和 topVideoHref
858
+ // 确保每个用户对象包含 topVideoPlayCount、topVideoHref 和 topVideoIsAd
864
859
  if (result.users && Array.isArray(result.users)) {
865
860
  result.users = result.users.map((u) => ({
866
861
  ...u,
867
862
  topVideoPlayCount: u.topVideoPlayCount || null,
868
863
  topVideoHref: u.topVideoHref || null,
864
+ topVideoIsAd: u.topVideoIsAd ?? null,
869
865
  }));
870
866
  }
871
867
  sendJSON(res, 200, result);
@@ -874,7 +870,7 @@ export function startWatchServer(
874
870
 
875
871
  // 默认:全量(兼容旧调用)
876
872
  const result = store.getTargetUsersByCountry(DEFAULT_TARGET_LOCATIONS);
877
- // 确保每个用户对象包含 topVideoPlayCount 和 topVideoHref
873
+ // 确保每个用户对象包含 topVideoPlayCount、topVideoHref 和 topVideoIsAd
878
874
  if (result.countries && Array.isArray(result.countries)) {
879
875
  for (const country of result.countries) {
880
876
  if (country.users && Array.isArray(country.users)) {
@@ -882,6 +878,7 @@ export function startWatchServer(
882
878
  ...u,
883
879
  topVideoPlayCount: u.topVideoPlayCount || null,
884
880
  topVideoHref: u.topVideoHref || null,
881
+ topVideoIsAd: u.topVideoIsAd ?? null,
885
882
  }));
886
883
  }
887
884
  }
@@ -890,6 +887,17 @@ export function startWatchServer(
890
887
  return;
891
888
  }
892
889
 
890
+ if (
891
+ req.method === "GET" &&
892
+ routePath === "/api/target-users-other-countries"
893
+ ) {
894
+ const result = store.getModifiedTargetUsersOutsideLocations(
895
+ DEFAULT_TARGET_LOCATIONS,
896
+ );
897
+ sendJSON(res, 200, result);
898
+ return;
899
+ }
900
+
893
901
  if (req.method === "GET" && routePath === "/api/client-errors") {
894
902
  sendJSON(res, 200, { clients: store.getClientErrors() });
895
903
  return;
@@ -1327,6 +1335,71 @@ export function startWatchServer(
1327
1335
  return;
1328
1336
  }
1329
1337
 
1338
+ // POST /api/bulk-author-locations — 批量查询作者国家(用于 tag 客户端 enrich 前跳过已知数据)
1339
+ if (req.method === "POST" && routePath === "/api/bulk-author-locations") {
1340
+ try {
1341
+ const body = await readBody(req);
1342
+ const uniqueIds = Array.isArray(body.uniqueIds) ? body.uniqueIds : [];
1343
+ const locations = store.getBulkAuthorLocations(uniqueIds);
1344
+ sendJSON(res, 200, { locations });
1345
+ } catch (e) {
1346
+ sendJSON(res, 500, { error: e.message });
1347
+ }
1348
+ return;
1349
+ }
1350
+
1351
+ // GET /api/tags/refresh-task — 领取一个 productive 标签用于刷新(score>70,未被锁定)
1352
+ if (req.method === "GET" && routePath === "/api/tags/refresh-task") {
1353
+ const task = store.getProductiveRefreshTask();
1354
+ if (!task) {
1355
+ sendJSON(res, 200, {
1356
+ tag: null,
1357
+ message: "no productive tags available for refresh",
1358
+ });
1359
+ return;
1360
+ }
1361
+ sendJSON(res, 200, {
1362
+ tag: task.tag,
1363
+ score: task.score,
1364
+ refreshedAt: task.refreshed_at,
1365
+ });
1366
+ return;
1367
+ }
1368
+
1369
+ // POST /api/tags/refresh-claim { tag } — 锁定 productive 标签用于刷新(防并发冲突)
1370
+ if (req.method === "POST" && routePath === "/api/tags/refresh-claim") {
1371
+ try {
1372
+ const body = await readBody(req);
1373
+ const tag = (body.tag || "").trim().toLowerCase();
1374
+ if (!tag) {
1375
+ sendJSON(res, 400, { error: "tag 不能为空" });
1376
+ return;
1377
+ }
1378
+ const result = store.claimRefreshTag(tag);
1379
+ sendJSON(res, result.ok ? 200 : 409, result);
1380
+ } catch (e) {
1381
+ sendJSON(res, 500, { error: e.message });
1382
+ }
1383
+ return;
1384
+ }
1385
+
1386
+ // POST /api/tags/refresh-result { tag, pushedUsers, error } — 报告刷新结果并解锁
1387
+ if (req.method === "POST" && routePath === "/api/tags/refresh-result") {
1388
+ try {
1389
+ const body = await readBody(req);
1390
+ const tag = (body.tag || "").trim().toLowerCase();
1391
+ if (!tag) {
1392
+ sendJSON(res, 400, { error: "tag 不能为空" });
1393
+ return;
1394
+ }
1395
+ const result = store.reportRefreshResult(tag, body);
1396
+ sendJSON(res, result.ok ? 200 : 400, result);
1397
+ } catch (e) {
1398
+ sendJSON(res, 500, { error: e.message });
1399
+ }
1400
+ return;
1401
+ }
1402
+
1330
1403
  // GET /api/tags/recent-generated?country=ES&lookback=10 — 最近N轮生成的标签(去重)
1331
1404
  if (req.method === "GET" && routePath === "/api/tags/recent-generated") {
1332
1405
  try {
@@ -1915,7 +1988,7 @@ export function startWatchServer(
1915
1988
  );
1916
1989
  setTimeout(() => runAutoDiscover("startup"), 30000);
1917
1990
  console.error(
1918
- `[discover] 定时+按需发现已启动: 定时间隔 ${DISCOVER_INTERVAL_MS / 60000} 分钟, 每国 ${DISCOVER_COUNT_PER_COUNTRY} 个标签, 按需阈值 ${DISCOVER_LOW_THRESHOLD}, 冷却期 ${DISCOVER_COOLDOWN_MS / 1000}s`,
1991
+ `[discover] 定时+按需发现已启动: 定时间隔 ${DISCOVER_INTERVAL_MS / 60000} 分钟, 每国 ${DISCOVER_COUNT_PER_COUNTRY} 个标签, 每国不足阈值 ${DISCOVER_PER_COUNTRY_THRESHOLD}, 按需触发阈值 ${DISCOVER_LOW_THRESHOLD}, 冷却期 ${DISCOVER_COOLDOWN_MS / 1000}s`,
1919
1992
  );
1920
1993
 
1921
1994
  _resolve({ server, port });