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/tag-service.js
CHANGED
|
@@ -150,6 +150,7 @@ export function buildDiscoverPrompt(
|
|
|
150
150
|
hardRules = [],
|
|
151
151
|
existingCount = 0,
|
|
152
152
|
forceNicheSwitch = null,
|
|
153
|
+
lastRoundFeedback = null,
|
|
153
154
|
) {
|
|
154
155
|
const lang = getLang(country);
|
|
155
156
|
const langNames = {
|
|
@@ -167,8 +168,285 @@ export function buildDiscoverPrompt(
|
|
|
167
168
|
};
|
|
168
169
|
const langName = langNames[lang] || lang;
|
|
169
170
|
|
|
170
|
-
//
|
|
171
|
-
|
|
171
|
+
// ====== 上轮反馈:告知 LLM 上一轮生成了什么、哪些被去重 ======
|
|
172
|
+
let lastRoundRejectionHint = "";
|
|
173
|
+
if (lastRoundFeedback) {
|
|
174
|
+
if (lastRoundFeedback.allRejected) {
|
|
175
|
+
lastRoundRejectionHint = `\n=== ⛔ CRITICAL: Last round ALL tags were DUPLICATES ===\nYou generated: ${lastRoundFeedback.raw.join(", ")}\nALL of these already exist in our database — NONE were added.\nYou MUST generate COMPLETELY DIFFERENT tags this round. Do NOT repeat any of the above.\nTry a totally different product category, use-case, or selling context.`;
|
|
176
|
+
} else if (lastRoundFeedback.rejected.length > 0) {
|
|
177
|
+
lastRoundRejectionHint = `\n=== ⚠️ Last round feedback ===\nYou generated: ${lastRoundFeedback.raw.join(", ")}\nRejected (already exist): ${lastRoundFeedback.rejected.join(", ")}\nAdded: ${lastRoundFeedback.survived.join(", ") || "(none)"}\nAvoid the rejected tags above — they already exist in our database.`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 正样本:该国高分 tag。当 forceNicheSwitch 激活时,仅展示目标 niche 的 productive tag
|
|
182
|
+
let productive = history.productive || [];
|
|
183
|
+
if (forceNicheSwitch && productive.length > 0) {
|
|
184
|
+
const targetNiche = forceNicheSwitch.to;
|
|
185
|
+
const NICHE_KEYWORDS = {
|
|
186
|
+
fashion: [
|
|
187
|
+
"clothes",
|
|
188
|
+
"kleidung",
|
|
189
|
+
"kleding",
|
|
190
|
+
"vestiti",
|
|
191
|
+
"ropa",
|
|
192
|
+
"outfit",
|
|
193
|
+
"mode",
|
|
194
|
+
"shoes",
|
|
195
|
+
"sneakers",
|
|
196
|
+
"zapatos",
|
|
197
|
+
"chaussures",
|
|
198
|
+
"vintage",
|
|
199
|
+
"secondhand",
|
|
200
|
+
"thrift",
|
|
201
|
+
"moda",
|
|
202
|
+
"scarpe",
|
|
203
|
+
"schuhe",
|
|
204
|
+
"schoenen",
|
|
205
|
+
"cipő",
|
|
206
|
+
"boty",
|
|
207
|
+
"tenisky",
|
|
208
|
+
"sapatos",
|
|
209
|
+
"calzado",
|
|
210
|
+
],
|
|
211
|
+
food: [
|
|
212
|
+
"food",
|
|
213
|
+
"bakery",
|
|
214
|
+
"brot",
|
|
215
|
+
"brood",
|
|
216
|
+
"pane",
|
|
217
|
+
"comida",
|
|
218
|
+
"restaurant",
|
|
219
|
+
"cafe",
|
|
220
|
+
"kitchen",
|
|
221
|
+
"konyha",
|
|
222
|
+
"patisserie",
|
|
223
|
+
"boulangerie",
|
|
224
|
+
"wypieki",
|
|
225
|
+
"etterm",
|
|
226
|
+
"essen",
|
|
227
|
+
"eten",
|
|
228
|
+
"jedzenie",
|
|
229
|
+
"étel",
|
|
230
|
+
"cibo",
|
|
231
|
+
"chleb",
|
|
232
|
+
"bread",
|
|
233
|
+
"pekar",
|
|
234
|
+
"bageri",
|
|
235
|
+
"forno",
|
|
236
|
+
"padaria",
|
|
237
|
+
],
|
|
238
|
+
handmade_crafts: [
|
|
239
|
+
"handmade",
|
|
240
|
+
"handwerk",
|
|
241
|
+
"handgemaakt",
|
|
242
|
+
"artigianato",
|
|
243
|
+
"hechoamano",
|
|
244
|
+
"kézműves",
|
|
245
|
+
"kreativ",
|
|
246
|
+
"craft",
|
|
247
|
+
"diy",
|
|
248
|
+
"knutselen",
|
|
249
|
+
"ceramic",
|
|
250
|
+
"keramiek",
|
|
251
|
+
"kerzen",
|
|
252
|
+
"gyertya",
|
|
253
|
+
"resin",
|
|
254
|
+
"macrame",
|
|
255
|
+
"pottery",
|
|
256
|
+
"weaving",
|
|
257
|
+
"fattoamano",
|
|
258
|
+
"handgemacht",
|
|
259
|
+
"rękodzieło",
|
|
260
|
+
"ručně",
|
|
261
|
+
],
|
|
262
|
+
home_living: [
|
|
263
|
+
"home",
|
|
264
|
+
"wohnung",
|
|
265
|
+
"huis",
|
|
266
|
+
"casa",
|
|
267
|
+
"lakberendezes",
|
|
268
|
+
"interior",
|
|
269
|
+
"deco",
|
|
270
|
+
"decoration",
|
|
271
|
+
"einrichten",
|
|
272
|
+
"furniture",
|
|
273
|
+
"möbel",
|
|
274
|
+
"meubilair",
|
|
275
|
+
"epipla",
|
|
276
|
+
"butor",
|
|
277
|
+
"nabytek",
|
|
278
|
+
"tapic",
|
|
279
|
+
"reinigung",
|
|
280
|
+
"cleaning",
|
|
281
|
+
"muebles",
|
|
282
|
+
"mobili",
|
|
283
|
+
"meble",
|
|
284
|
+
"bútor",
|
|
285
|
+
"dom",
|
|
286
|
+
"otthon",
|
|
287
|
+
"thuis",
|
|
288
|
+
"zuhause",
|
|
289
|
+
],
|
|
290
|
+
electronics_tech: [
|
|
291
|
+
"tech",
|
|
292
|
+
"elektronik",
|
|
293
|
+
"electronica",
|
|
294
|
+
"gadget",
|
|
295
|
+
"phone",
|
|
296
|
+
"smartphone",
|
|
297
|
+
"laptop",
|
|
298
|
+
"computer",
|
|
299
|
+
"audio",
|
|
300
|
+
"headphones",
|
|
301
|
+
"charger",
|
|
302
|
+
"usb",
|
|
303
|
+
"playstation",
|
|
304
|
+
"gaming",
|
|
305
|
+
"telefon",
|
|
306
|
+
"handy",
|
|
307
|
+
"tablet",
|
|
308
|
+
"pc",
|
|
309
|
+
"console",
|
|
310
|
+
"smartwatch",
|
|
311
|
+
"cuffie",
|
|
312
|
+
"auricolari",
|
|
313
|
+
"caricatore",
|
|
314
|
+
],
|
|
315
|
+
pets_animals: [
|
|
316
|
+
"pet",
|
|
317
|
+
"hund",
|
|
318
|
+
"dog",
|
|
319
|
+
"cat",
|
|
320
|
+
"kat",
|
|
321
|
+
"kuty",
|
|
322
|
+
"konia",
|
|
323
|
+
"pets",
|
|
324
|
+
"tier",
|
|
325
|
+
"dieren",
|
|
326
|
+
"animale",
|
|
327
|
+
"animal",
|
|
328
|
+
"mascota",
|
|
329
|
+
"animais",
|
|
330
|
+
"zwierzę",
|
|
331
|
+
"állat",
|
|
332
|
+
"cane",
|
|
333
|
+
"gatto",
|
|
334
|
+
"perro",
|
|
335
|
+
"chien",
|
|
336
|
+
"chat",
|
|
337
|
+
],
|
|
338
|
+
beauty_health: [
|
|
339
|
+
"beauty",
|
|
340
|
+
"kosmetik",
|
|
341
|
+
"cosmetica",
|
|
342
|
+
"beaute",
|
|
343
|
+
"belleza",
|
|
344
|
+
"szepseg",
|
|
345
|
+
"hair",
|
|
346
|
+
"haj",
|
|
347
|
+
"skincare",
|
|
348
|
+
"makeup",
|
|
349
|
+
"parfum",
|
|
350
|
+
"perfume",
|
|
351
|
+
"wellness",
|
|
352
|
+
"gesundheit",
|
|
353
|
+
"salud",
|
|
354
|
+
"sante",
|
|
355
|
+
"szépség",
|
|
356
|
+
"krása",
|
|
357
|
+
],
|
|
358
|
+
books_collectibles: [
|
|
359
|
+
"book",
|
|
360
|
+
"buch",
|
|
361
|
+
"boek",
|
|
362
|
+
"libro",
|
|
363
|
+
"knihy",
|
|
364
|
+
"konyv",
|
|
365
|
+
"collectible",
|
|
366
|
+
"sammlung",
|
|
367
|
+
"verzameling",
|
|
368
|
+
"collezionismo",
|
|
369
|
+
"coleccion",
|
|
370
|
+
"antique",
|
|
371
|
+
"antiquariat",
|
|
372
|
+
"vintage",
|
|
373
|
+
"vinyl",
|
|
374
|
+
"schallplatten",
|
|
375
|
+
"pokemon",
|
|
376
|
+
"kaarten",
|
|
377
|
+
"moedas",
|
|
378
|
+
"mince",
|
|
379
|
+
"livre",
|
|
380
|
+
"książka",
|
|
381
|
+
"carte",
|
|
382
|
+
],
|
|
383
|
+
services_professional: [
|
|
384
|
+
"service",
|
|
385
|
+
"dienst",
|
|
386
|
+
"servizio",
|
|
387
|
+
"servicio",
|
|
388
|
+
"szolgaltatas",
|
|
389
|
+
"handwerker",
|
|
390
|
+
"craftsman",
|
|
391
|
+
"repair",
|
|
392
|
+
"reparatur",
|
|
393
|
+
"reparatie",
|
|
394
|
+
"riparazione",
|
|
395
|
+
"reparacion",
|
|
396
|
+
"cleaning",
|
|
397
|
+
"reinigung",
|
|
398
|
+
"moving",
|
|
399
|
+
"umzug",
|
|
400
|
+
"verhuur",
|
|
401
|
+
"mudanca",
|
|
402
|
+
"florist",
|
|
403
|
+
"photography",
|
|
404
|
+
"fotograf",
|
|
405
|
+
"usługa",
|
|
406
|
+
"szolgáltatás",
|
|
407
|
+
"serviço",
|
|
408
|
+
],
|
|
409
|
+
marketplace_local: [
|
|
410
|
+
"market",
|
|
411
|
+
"markt",
|
|
412
|
+
"mercato",
|
|
413
|
+
"mercado",
|
|
414
|
+
"piac",
|
|
415
|
+
"trh",
|
|
416
|
+
"flohmarkt",
|
|
417
|
+
"rommelmarkt",
|
|
418
|
+
"brocante",
|
|
419
|
+
"bazar",
|
|
420
|
+
"mercadillo",
|
|
421
|
+
"feira",
|
|
422
|
+
"local",
|
|
423
|
+
"lokal",
|
|
424
|
+
"locale",
|
|
425
|
+
"winkel",
|
|
426
|
+
"boutique",
|
|
427
|
+
"shop",
|
|
428
|
+
"tienda",
|
|
429
|
+
"negozio",
|
|
430
|
+
"bol",
|
|
431
|
+
"webshop",
|
|
432
|
+
"eshop",
|
|
433
|
+
"sklep",
|
|
434
|
+
"obchod",
|
|
435
|
+
"bolt",
|
|
436
|
+
"butik",
|
|
437
|
+
],
|
|
438
|
+
};
|
|
439
|
+
const keywords = NICHE_KEYWORDS[targetNiche] || [];
|
|
440
|
+
const nicheFiltered = productive.filter((t) =>
|
|
441
|
+
keywords.some((kw) => t.tag.toLowerCase().includes(kw.toLowerCase())),
|
|
442
|
+
);
|
|
443
|
+
if (nicheFiltered.length > 0) {
|
|
444
|
+
console.error(
|
|
445
|
+
`[tag-service] 🎯 niche-filtered productive: ${productive.length} → ${nicheFiltered.length} (target: ${targetNiche})`,
|
|
446
|
+
);
|
|
447
|
+
productive = nicheFiltered;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
172
450
|
const productiveHint =
|
|
173
451
|
productive.length > 0
|
|
174
452
|
? `\nTags that already worked well for ${country} (with scores): ${productive
|
|
@@ -335,7 +613,7 @@ ${hardRules.map((r) => ` ${r.ruleType === "hard" ? "⛔" : "💡"} ${r.ruleText
|
|
|
335
613
|
: ""
|
|
336
614
|
}
|
|
337
615
|
|
|
338
|
-
${productiveHint}${deadHint}${repeatedDeadHint}${langPatternHint}${errorHint}${userHint}${strategyReview}${healthHint}${nicheHint}${forceNicheSwitchHint}
|
|
616
|
+
${lastRoundRejectionHint}${productiveHint}${deadHint}${repeatedDeadHint}${langPatternHint}${errorHint}${userHint}${strategyReview}${healthHint}${nicheHint}${forceNicheSwitchHint}
|
|
339
617
|
|
|
340
618
|
## Quality check before responding
|
|
341
619
|
|
|
@@ -517,17 +795,44 @@ export async function discoverTagsForCountry(
|
|
|
517
795
|
// 读取历史打分记录
|
|
518
796
|
const productive = store.getTagsByCountry(country, 50);
|
|
519
797
|
const dead = store.getDeadTags(country);
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const
|
|
798
|
+
|
|
799
|
+
// ====== P0修复: 清理 truly dead + 分类未分类 dead,释放去重空间 ======
|
|
800
|
+
const { classifyDeadTags, cleanupTrulyDeadTags: cleanupTrulyDead } =
|
|
801
|
+
await import("./tag-selflearn.js");
|
|
802
|
+
const classifyResult = classifyDeadTags();
|
|
803
|
+
if (classifyResult.classified > 0) {
|
|
804
|
+
console.error(
|
|
805
|
+
`[tag-service] 🧹 ${country} 分类了 ${classifyResult.classified} 个 dead tag (not_exist=${classifyResult.not_exist}, no_match=${classifyResult.no_match}, wrong_country=${classifyResult.wrong_country})`,
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
const cleanupResult = cleanupTrulyDead(2);
|
|
809
|
+
if (cleanupResult.deleted > 0) {
|
|
810
|
+
console.error(
|
|
811
|
+
`[tag-service] 🗑️ ${country} 清理了 ${cleanupResult.deleted} 个确认不存在的 dead tag,释放去重空间`,
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// 全局去重:仅用 productive + scored 的活跃 tag 名做去重(排除 dead)
|
|
816
|
+
// dead tag 同名重新生成时 insertTag 会将其重置为 new
|
|
817
|
+
const { getActiveTagNames } = await import("./db-tags.js");
|
|
818
|
+
const allExisting = getActiveTagNames();
|
|
523
819
|
console.error(
|
|
524
|
-
`[tag-service] ${country}
|
|
820
|
+
`[tag-service] ${country} 活跃去重集合: ${allExisting.size} 个 (productive+scored, 排除dead)`,
|
|
525
821
|
);
|
|
526
822
|
|
|
527
823
|
// 获取最近生成的标签(用于检测重复生成)
|
|
528
|
-
const { getRecentGeneratedTags } =
|
|
824
|
+
const { getRecentGeneratedTags, getLastRoundAllGeneratedTags } =
|
|
825
|
+
await import("./db-tags.js");
|
|
529
826
|
const recentGenerated = getRecentGeneratedTags(country, 10);
|
|
530
827
|
|
|
828
|
+
// ====== 上轮反馈:告知 LLM 上一轮全部被去重的情况 ======
|
|
829
|
+
const lastRoundFeedback = getLastRoundAllGeneratedTags(country);
|
|
830
|
+
if (lastRoundFeedback?.allRejected) {
|
|
831
|
+
console.error(
|
|
832
|
+
`[tag-service] 🔁 ${country} 上轮全部被去重: ${lastRoundFeedback.raw.join(", ")}`,
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
|
|
531
836
|
const history = { productive, dead, recentGenerated };
|
|
532
837
|
|
|
533
838
|
// 读取历史策略效果(自学习)
|
|
@@ -542,7 +847,7 @@ export async function discoverTagsForCountry(
|
|
|
542
847
|
);
|
|
543
848
|
}
|
|
544
849
|
|
|
545
|
-
// 组装 prompt(加入健康指标、niche
|
|
850
|
+
// 组装 prompt(加入健康指标、niche 推荐、硬规则、上轮反馈)
|
|
546
851
|
const prompt = buildDiscoverPrompt(
|
|
547
852
|
country,
|
|
548
853
|
count,
|
|
@@ -554,6 +859,7 @@ export async function discoverTagsForCountry(
|
|
|
554
859
|
hardRules,
|
|
555
860
|
allExisting.size,
|
|
556
861
|
forceNicheSwitch,
|
|
862
|
+
lastRoundFeedback,
|
|
557
863
|
);
|
|
558
864
|
console.error(
|
|
559
865
|
`[tag-service] LLM discover ${country} round=${round} (lang=${getLang(country)}, count=${count}, temp=${temperature}, health=${health?.healthStatus || "unknown"})`,
|
|
@@ -591,12 +897,13 @@ export async function discoverTagsForCountry(
|
|
|
591
897
|
if (result.inserted) inserted.push(result.tag);
|
|
592
898
|
}
|
|
593
899
|
|
|
594
|
-
// 记录 discover log(策略 + 结果 + 健康状态 + niche)
|
|
900
|
+
// 记录 discover log(策略 + 结果 + 健康状态 + niche + 原始 tag)
|
|
595
901
|
const { insertDiscoverLog } = await import("./db-tags.js");
|
|
596
902
|
insertDiscoverLog({
|
|
597
903
|
country,
|
|
598
904
|
round,
|
|
599
905
|
strategy,
|
|
906
|
+
tagsRaw: deduped,
|
|
600
907
|
tagsGenerated: unique,
|
|
601
908
|
tagsAdded: inserted.length,
|
|
602
909
|
productiveSample: productive.slice(0, 20).map((t) => t.tag),
|