tt-help-cli-ycl 1.4.0 → 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 +1 -1
- package/src/cli/tag.js +11 -11
- package/src/lib/constants.js +9 -0
- package/src/lib/tag-discover.js +15 -5
- package/src/watch/db-tags.js +10 -2
- package/src/watch/tag-service.js +13 -17
package/package.json
CHANGED
package/src/cli/tag.js
CHANGED
|
@@ -629,10 +629,8 @@ export async function handleScoreAll(parsed) {
|
|
|
629
629
|
|
|
630
630
|
await setupPageRequestBlocking(page);
|
|
631
631
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
parseInt(process.env.TAG_SCOREALL_PORT_POOL_START || "7222", 10) || 7222,
|
|
635
|
-
);
|
|
632
|
+
// 端口池起始以 CLI --port / 环境变量 / 默认 7222 为准
|
|
633
|
+
const portPoolStart = cdpPort;
|
|
636
634
|
const portPoolSize = Math.max(
|
|
637
635
|
2,
|
|
638
636
|
parseInt(process.env.TAG_SCOREALL_PORT_POOL_SIZE || "10", 10) || 10,
|
|
@@ -656,7 +654,7 @@ export async function handleScoreAll(parsed) {
|
|
|
656
654
|
);
|
|
657
655
|
const memHeapRecycleMb = Math.max(
|
|
658
656
|
128,
|
|
659
|
-
parseInt(process.env.TAG_SCOREALL_RECYCLE_HEAP_MB || "
|
|
657
|
+
parseInt(process.env.TAG_SCOREALL_RECYCLE_HEAP_MB || "512", 10) || 512,
|
|
660
658
|
);
|
|
661
659
|
const recycleCooldownMs = Math.max(
|
|
662
660
|
0,
|
|
@@ -689,9 +687,9 @@ export async function handleScoreAll(parsed) {
|
|
|
689
687
|
}
|
|
690
688
|
}
|
|
691
689
|
|
|
692
|
-
async function recycleCdpSession(reason) {
|
|
690
|
+
async function recycleCdpSession(reason, { switchPort = true } = {}) {
|
|
693
691
|
const oldPort = cdpPort;
|
|
694
|
-
if (switchPortOnRecycle) {
|
|
692
|
+
if (switchPort && switchPortOnRecycle) {
|
|
695
693
|
cdpPort = pickNextPort(cdpPort);
|
|
696
694
|
cdpOpts.port = cdpPort;
|
|
697
695
|
clientMeta.port = cdpPort;
|
|
@@ -724,6 +722,7 @@ export async function handleScoreAll(parsed) {
|
|
|
724
722
|
}
|
|
725
723
|
await recycleCdpSession(
|
|
726
724
|
`mem rss=${rssMB.toFixed(0)}MB heap=${heapMB.toFixed(0)}MB (threshold rss=${memRssRecycleMb} heap=${memHeapRecycleMb})`,
|
|
725
|
+
{ switchPort: false },
|
|
727
726
|
);
|
|
728
727
|
return;
|
|
729
728
|
}
|
|
@@ -732,7 +731,7 @@ export async function handleScoreAll(parsed) {
|
|
|
732
731
|
totalScored > 0 &&
|
|
733
732
|
totalScored % periodicRecycleEvery === 0
|
|
734
733
|
) {
|
|
735
|
-
await recycleCdpSession(`periodic every ${totalScored} tasks
|
|
734
|
+
await recycleCdpSession(`periodic every ${totalScored} tasks`, { switchPort: false });
|
|
736
735
|
}
|
|
737
736
|
}
|
|
738
737
|
|
|
@@ -987,7 +986,9 @@ export async function handleScoreAll(parsed) {
|
|
|
987
986
|
|
|
988
987
|
if (is403) {
|
|
989
988
|
consecutive403++;
|
|
990
|
-
log(
|
|
989
|
+
log(
|
|
990
|
+
` 🚫 HTTP 403 — 连续 ${consecutive403}/${CONSECUTIVE_403_BLOCKED} 次`,
|
|
991
|
+
);
|
|
991
992
|
} else {
|
|
992
993
|
consecutive403 = 0;
|
|
993
994
|
}
|
|
@@ -995,8 +996,7 @@ export async function handleScoreAll(parsed) {
|
|
|
995
996
|
log(` ❌ 失败: ${e.message}`);
|
|
996
997
|
try {
|
|
997
998
|
// 使用当前正在处理的 tag(tag 变量在 try 块开头已赋值,catch 中仍可访问)
|
|
998
|
-
const failedTag =
|
|
999
|
-
typeof tag === "string" && tag ? tag : "unknown";
|
|
999
|
+
const failedTag = typeof tag === "string" && tag ? tag : "unknown";
|
|
1000
1000
|
await reportToServer(
|
|
1001
1001
|
baseUrl,
|
|
1002
1002
|
{
|
package/src/lib/constants.js
CHANGED
|
@@ -260,8 +260,17 @@ const HELP_TEXT = [
|
|
|
260
260
|
" 选项:",
|
|
261
261
|
" --countries <CSV> 目标国家,逗号分隔(默认 13 个欧洲国家)",
|
|
262
262
|
" -s, --server <URL> 服务端地址(默认 http://127.0.0.1:3000)",
|
|
263
|
+
" --port <N> 起始 CDP 端口,端口池默认为 N~N+9(默认 7222)",
|
|
264
|
+
" --discover 开启自动发现:连续 3 轮无新标签时自动生成",
|
|
265
|
+
" 行为:",
|
|
266
|
+
" 连续 3 次 HTTP 403 → 判断账号被封 → 等 2 分钟后自动切换到池中另一端口",
|
|
267
|
+
" 环境变量:",
|
|
268
|
+
" TAG_SCOREALL_PORT_POOL_SIZE=N 端口池大小(默认 10)",
|
|
269
|
+
" TAG_SCOREALL_RECYCLE_RSS_MB=N 内存回收阈值 RSS MB(默认 900)",
|
|
270
|
+
" TAG_SCOREALL_RECYCLE_HEAP_MB=N 内存回收阈值 Heap MB(默认 512)",
|
|
263
271
|
" 示例: tt-help tag score-all",
|
|
264
272
|
" tt-help tag score-all --countries ES -s http://127.0.0.1:3001",
|
|
273
|
+
" tt-help tag score-all --port 9222 -s http://127.0.0.1:3001 --discover",
|
|
265
274
|
"",
|
|
266
275
|
" config [show|set|unset|reset]",
|
|
267
276
|
" config 查看当前配置",
|
package/src/lib/tag-discover.js
CHANGED
|
@@ -66,16 +66,26 @@ async function discoverTagsForCountryCli(
|
|
|
66
66
|
|
|
67
67
|
// 从服务端获取历史 tag
|
|
68
68
|
const history = await fetchTagHistory(serverUrl, country);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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]
|
|
81
|
+
` [LLM] 正在生成标签 (国家: ${country}, 语言: ${getLang(country)}, 目标: ${count} 个)...\n`,
|
|
75
82
|
);
|
|
76
83
|
const content = await callLLM(prompt);
|
|
77
84
|
const tags = parseTagsFromResponse(content);
|
|
78
|
-
|
|
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`,
|
package/src/watch/db-tags.js
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/src/watch/tag-service.js
CHANGED
|
@@ -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.
|
|
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}${
|
|
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
|
|
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)]
|
|
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)"}`,
|