tt-help-cli-ycl 1.4.1 → 1.4.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tt-help-cli-ycl",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/tag.js CHANGED
@@ -654,7 +654,7 @@ export async function handleScoreAll(parsed) {
654
654
  );
655
655
  const memHeapRecycleMb = Math.max(
656
656
  128,
657
- parseInt(process.env.TAG_SCOREALL_RECYCLE_HEAP_MB || "320", 10) || 320,
657
+ parseInt(process.env.TAG_SCOREALL_RECYCLE_HEAP_MB || "512", 10) || 512,
658
658
  );
659
659
  const recycleCooldownMs = Math.max(
660
660
  0,
@@ -687,9 +687,9 @@ export async function handleScoreAll(parsed) {
687
687
  }
688
688
  }
689
689
 
690
- async function recycleCdpSession(reason) {
690
+ async function recycleCdpSession(reason, { switchPort = true } = {}) {
691
691
  const oldPort = cdpPort;
692
- if (switchPortOnRecycle) {
692
+ if (switchPort && switchPortOnRecycle) {
693
693
  cdpPort = pickNextPort(cdpPort);
694
694
  cdpOpts.port = cdpPort;
695
695
  clientMeta.port = cdpPort;
@@ -722,6 +722,7 @@ export async function handleScoreAll(parsed) {
722
722
  }
723
723
  await recycleCdpSession(
724
724
  `mem rss=${rssMB.toFixed(0)}MB heap=${heapMB.toFixed(0)}MB (threshold rss=${memRssRecycleMb} heap=${memHeapRecycleMb})`,
725
+ { switchPort: false },
725
726
  );
726
727
  return;
727
728
  }
@@ -730,7 +731,7 @@ export async function handleScoreAll(parsed) {
730
731
  totalScored > 0 &&
731
732
  totalScored % periodicRecycleEvery === 0
732
733
  ) {
733
- await recycleCdpSession(`periodic every ${totalScored} tasks`);
734
+ await recycleCdpSession(`periodic every ${totalScored} tasks`, { switchPort: false });
734
735
  }
735
736
  }
736
737
 
@@ -267,7 +267,7 @@ const HELP_TEXT = [
267
267
  " 环境变量:",
268
268
  " TAG_SCOREALL_PORT_POOL_SIZE=N 端口池大小(默认 10)",
269
269
  " TAG_SCOREALL_RECYCLE_RSS_MB=N 内存回收阈值 RSS MB(默认 900)",
270
- " TAG_SCOREALL_RECYCLE_HEAP_MB=N 内存回收阈值 Heap MB(默认 320)",
270
+ " TAG_SCOREALL_RECYCLE_HEAP_MB=N 内存回收阈值 Heap MB(默认 512)",
271
271
  " 示例: tt-help tag score-all",
272
272
  " tt-help tag score-all --countries ES -s http://127.0.0.1:3001",
273
273
  " tt-help tag score-all --port 9222 -s http://127.0.0.1:3001 --discover",
@@ -66,16 +66,26 @@ async function discoverTagsForCountryCli(
66
66
 
67
67
  // 从服务端获取历史 tag
68
68
  const history = await fetchTagHistory(serverUrl, country);
69
-
70
- // 使用统一的 prompt 组装
71
- const prompt = buildDiscoverPrompt(country, count, history, userPrompt);
69
+ const { productive, dead, allExisting: existingSet } = history;
70
+ const existingArr = Array.isArray(existingSet) ? existingSet : [];
71
+
72
+ // 使用统一的 prompt 组装(不传 allExisting,由应用层去重)
73
+ const prompt = buildDiscoverPrompt(
74
+ country,
75
+ count,
76
+ { productive, dead },
77
+ userPrompt,
78
+ );
72
79
 
73
80
  process.stderr.write(
74
- ` [LLM] 正在生成 ${count} 个标签 (国家: ${country}, 语言: ${getLang(country)})...\n`,
81
+ ` [LLM] 正在生成标签 (国家: ${country}, 语言: ${getLang(country)}, 目标: ${count} 个)...\n`,
75
82
  );
76
83
  const content = await callLLM(prompt);
77
84
  const tags = parseTagsFromResponse(content);
78
- const unique = [...new Set(tags)].slice(0, count);
85
+ // 两阶段筛选:先内部去重,再过滤已存在,最后取 count 个
86
+ const unique = [...new Set(tags)]
87
+ .filter((t) => !existingArr.includes(t))
88
+ .slice(0, count);
79
89
 
80
90
  process.stderr.write(
81
91
  ` [LLM] 生成 ${unique.length} 个标签: ${unique.join(", ")}\n`,
@@ -22,11 +22,19 @@ export function insertTag(tag, countries, source = "llm") {
22
22
  return { inserted: false, error: "invalid tag" };
23
23
  }
24
24
  try {
25
+ // ON CONFLICT: 合并 countries 数组,避免跨国家共享 tag 时信息丢失
25
26
  const result = db
26
27
  .prepare(
27
- "INSERT OR IGNORE INTO tags (tag, countries, source) VALUES (?, ?, ?)",
28
+ `INSERT INTO tags (tag, countries, source) VALUES (@tag, @countries, @source)
29
+ ON CONFLICT(tag) DO UPDATE SET countries = (
30
+ SELECT json_group_array(DISTINCT value) FROM (
31
+ SELECT value FROM json_each(countries)
32
+ UNION
33
+ SELECT value FROM json_each(@countries)
34
+ )
35
+ )`,
28
36
  )
29
- .run(normalized, JSON.stringify(countries), source);
37
+ .run({ tag: normalized, countries: JSON.stringify(countries), source });
30
38
  return { inserted: result.changes > 0, tag: normalized };
31
39
  } catch (e) {
32
40
  return { inserted: false, error: e.message };
@@ -45,7 +45,7 @@ export async function callLLM(prompt) {
45
45
  model: LLM_MODEL,
46
46
  messages: [{ role: "user", content: prompt }],
47
47
  max_tokens: 1024,
48
- temperature: 0.3,
48
+ temperature: 0.7,
49
49
  }),
50
50
  });
51
51
 
@@ -182,13 +182,6 @@ export function buildDiscoverPrompt(
182
182
  ? `\nWhy previous tags failed: ${errorPatterns.join("; ")}. Avoid tags likely to have same issues.`
183
183
  : "";
184
184
 
185
- // 已存在的所有 tag(防止重复生成)
186
- const allExisting = history.allExisting || [];
187
- const existingHint =
188
- allExisting.length > 0
189
- ? `\nTags already in database (DO NOT generate these again): ${allExisting.slice(0, 50).join(", ")}.`
190
- : "";
191
-
192
185
  const userHint = userPrompt
193
186
  ? `\nAdditional focus: ${userPrompt}. Generate tags specifically for this niche.`
194
187
  : "";
@@ -244,7 +237,7 @@ Think about the MOST COMMON hashtags that real sellers in ${country} use on TikT
244
237
  - Common seller identities (shop, boutique, small business, creator...)
245
238
  - Well-known community tags (support local, marketplace, second round...)
246
239
 
247
- ${qualityWarning}${productiveHint}${deadHint}${errorHint}${existingHint}${userHint}${strategyReview}
240
+ ${qualityWarning}${productiveHint}${deadHint}${errorHint}${userHint}${strategyReview}
248
241
 
249
242
  ## Quality check before responding
250
243
 
@@ -255,15 +248,16 @@ For each tag you consider, ask yourself: "Have I actually seen this hashtag used
255
248
  Return ONLY a JSON object with two fields:
256
249
  {
257
250
  "strategy": "Your analysis (2-3 sentences). Acknowledge what worked/failed before and explain why your chosen tags are likely real and commonly used.",
258
- "tags": ["tag1", "tag2", "tag3", "tag4"]
251
+ "tags": ["tag1", "tag2", "tag3", "tag4", "..."]
259
252
  }
260
253
 
261
254
  Rules:
255
+ - Brainstorm broadly — generate 12-15 distinct, diverse tag ideas covering different categories (selling verbs, product types, community tags, marketplace terms, seasonal trends)
256
+ - We will filter duplicates and existing tags on our end, so explore freely
262
257
  - QUALITY over creativity — 3 real, commonly used tags beat 4 invented ones
263
258
  - NEVER invent compound words or translate concepts into hashtags
264
259
  - Prefer simpler, more common tags over hyper-specific ones
265
- - Look for tags with broad appeal (many potential posters)
266
- - Do NOT generate tags that already exist`;
260
+ - Look for tags with broad appeal (many potential posters)`;
267
261
  }
268
262
 
269
263
  // ====== discover: 单国家标签发现 ======
@@ -281,10 +275,10 @@ export async function discoverTagsForCountry(
281
275
  // 读取历史打分记录
282
276
  const productive = store.getTagsByCountry(country, 50);
283
277
  const dead = store.getDeadTags(country);
284
- // 获取该国所有已存在的 tag 名(防止重复生成)
278
+ // 获取该国所有已存在的 tag 名(应用层去重用)
285
279
  const allTags = store.getTagsByCountry(country, 0);
286
- const allExisting = allTags.map((t) => t.tag);
287
- const history = { productive, dead, allExisting };
280
+ const allExisting = new Set(allTags.map((t) => t.tag));
281
+ const history = { productive, dead };
288
282
 
289
283
  // 读取历史策略效果(自学习)
290
284
  const { getDiscoverRound, getDiscoverEffectiveness } =
@@ -305,9 +299,11 @@ export async function discoverTagsForCountry(
305
299
  );
306
300
  const content = await callLLM(prompt);
307
301
 
308
- // 解析响应:提取 strategy 和 tags
302
+ // 解析响应:提取 strategy 和 tags,筛选掉数据库中已存在的
309
303
  const { strategy, tags } = parseDiscoverResponse(content);
310
- const unique = [...new Set(tags)].slice(0, count);
304
+ const unique = [...new Set(tags)]
305
+ .filter((t) => !allExisting.has(t))
306
+ .slice(0, count);
311
307
 
312
308
  console.error(
313
309
  `[tag-service] LLM strategy: ${strategy?.substring(0, 150) || "(none)"}`,