tt-help-cli-ycl 1.3.95 → 1.3.97
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 +66 -11
- package/src/lib/api-interceptor.js +2 -0
- package/src/lib/tag-fetcher.js +48 -37
- package/src/lib/tiktok-scraper.mjs +16 -0
- package/src/scraper/explore-core.js +6 -3
- package/src/watch/data-store.js +32 -1
- package/src/watch/db-stats.js +3 -0
- package/src/watch/db-tags.js +82 -11
- package/src/watch/public/app.js +493 -10
- package/src/watch/public/index.html +76 -0
- package/src/watch/public/style.css +150 -0
- package/src/watch/server.js +80 -11
- package/src/watch/tag-service.js +43 -22
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
<div class="label">目标商家</div>
|
|
58
58
|
<div class="value target" id="statTarget">0</div>
|
|
59
59
|
</div>
|
|
60
|
+
<div class="stat-card clickable pending-card" id="statTagsCard" onclick="navigateToTags()" style="cursor:pointer">
|
|
61
|
+
<div class="label">关键词</div>
|
|
62
|
+
<div class="value target" id="statTags">0</div>
|
|
63
|
+
</div>
|
|
60
64
|
</div>
|
|
61
65
|
<div id="activeClientsSection" class="active-clients-section" style="display:none">
|
|
62
66
|
<div class="active-clients-bar" id="activeClientsBar"></div>
|
|
@@ -351,6 +355,7 @@
|
|
|
351
355
|
<th class="sortable-target" data-sort="topVideoPlayCount">最大播放量 <span class="sort-icon">↕</span></th>
|
|
352
356
|
<th class="sortable-target" data-sort="latestVideoTime">最近发布 <span class="sort-icon">↕</span></th>
|
|
353
357
|
<th>最近刷新</th>
|
|
358
|
+
<th>操作</th>
|
|
354
359
|
</tr>
|
|
355
360
|
</thead>
|
|
356
361
|
<tbody id="targetTable"></tbody>
|
|
@@ -362,6 +367,77 @@
|
|
|
362
367
|
</div>
|
|
363
368
|
</div>
|
|
364
369
|
</div>
|
|
370
|
+
<div id="tagsPage">
|
|
371
|
+
<div class="stats" style="margin-bottom:16px">
|
|
372
|
+
<div class="stat-card">
|
|
373
|
+
<div class="label">关键词总数</div>
|
|
374
|
+
<div class="value target" id="tagsPageStatTotal">0</div>
|
|
375
|
+
</div>
|
|
376
|
+
<div class="stat-card clickable pending-card" onclick="navigateToMain()" style="background:rgba(167,139,250,0.1)">
|
|
377
|
+
<div class="label">← 返回主页面</div>
|
|
378
|
+
</div>
|
|
379
|
+
<div class="stat-card">
|
|
380
|
+
<div class="label">有效</div>
|
|
381
|
+
<div class="value done" id="tagsPageStatProductive">0</div>
|
|
382
|
+
</div>
|
|
383
|
+
<div class="stat-card">
|
|
384
|
+
<div class="label">无效</div>
|
|
385
|
+
<div class="value error" id="tagsPageStatDead">0</div>
|
|
386
|
+
</div>
|
|
387
|
+
<div class="stat-card">
|
|
388
|
+
<div class="label">待打分</div>
|
|
389
|
+
<div class="value pending" id="tagsPageStatNew">0</div>
|
|
390
|
+
</div>
|
|
391
|
+
<div class="stat-card clickable" id="refreshTagsBtn" style="background:rgba(59,130,246,0.12);cursor:pointer">
|
|
392
|
+
<div class="label">🔄 刷新</div>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
<div class="tags-layout">
|
|
396
|
+
<div class="tags-table-card">
|
|
397
|
+
<h3>关键词列表</h3>
|
|
398
|
+
<div class="controls">
|
|
399
|
+
<button onclick="openAddTagModal()"
|
|
400
|
+
style="padding:6px 12px;border:1px solid #a78bfa;border-radius:6px;background:rgba(167,139,250,0.12);color:#a78bfa;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;transition:all 0.15s">+
|
|
401
|
+
添加关键词</button>
|
|
402
|
+
<input type="text" id="tagsSearchInput" placeholder="搜索关键词...">
|
|
403
|
+
<button data-tags-filter="all" class="active" onclick="setTagsFilter('all')">全部</button>
|
|
404
|
+
<button data-tags-filter="new" onclick="setTagsFilter('new')">待打分</button>
|
|
405
|
+
<button data-tags-filter="scoring" onclick="setTagsFilter('scoring')">打分中</button>
|
|
406
|
+
<button data-tags-filter="productive" onclick="setTagsFilter('productive')"
|
|
407
|
+
style="background:#166534;color:#fff">有效</button>
|
|
408
|
+
<button data-tags-filter="dead" onclick="setTagsFilter('dead')"
|
|
409
|
+
style="background:#7f1d1d;color:#fff">无效</button>
|
|
410
|
+
<select id="tagsCountryFilter"
|
|
411
|
+
style="padding:6px 10px;border:1px solid #333;border-radius:6px;background:#2a2a3a;color:#ccc;font-size:12px;cursor:pointer;outline:none;">
|
|
412
|
+
<option value="">全部国家</option>
|
|
413
|
+
</select>
|
|
414
|
+
</div>
|
|
415
|
+
<div class="table-scroll">
|
|
416
|
+
<table>
|
|
417
|
+
<thead>
|
|
418
|
+
<tr>
|
|
419
|
+
<th style="width:40px">#</th>
|
|
420
|
+
<th style="min-width:120px">关键词</th>
|
|
421
|
+
<th class="sortable-tag" data-sort="score">评分 <span class="sort-icon">↕</span></th>
|
|
422
|
+
<th class="sortable-tag" data-sort="author_count">作者数 <span class="sort-icon">↕</span></th>
|
|
423
|
+
<th class="sortable-tag" data-sort="total_posts">帖子数 <span class="sort-icon">↕</span></th>
|
|
424
|
+
<th class="sortable-tag" data-sort="matched_authors">匹配作者 <span class="sort-icon">↕</span></th>
|
|
425
|
+
<th class="sortable-tag" data-sort="pushed_users">推送用户 <span class="sort-icon">↕</span></th>
|
|
426
|
+
<th>国家</th>
|
|
427
|
+
<th>状态</th>
|
|
428
|
+
<th>来源</th>
|
|
429
|
+
<th>创建时间</th>
|
|
430
|
+
</tr>
|
|
431
|
+
</thead>
|
|
432
|
+
<tbody id="tagsTable"></tbody>
|
|
433
|
+
</table>
|
|
434
|
+
<div id="tagsMoreHint" onclick="loadMoreTags()"
|
|
435
|
+
style="text-align:center;padding:10px 8px;font-size:13px;color:#6b7280;cursor:default;user-select:none;transition:color 0.2s">
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
365
441
|
<script src="app.js"></script>
|
|
366
442
|
</body>
|
|
367
443
|
|
|
@@ -1389,3 +1389,153 @@ td.user-id:hover {
|
|
|
1389
1389
|
height: 140px;
|
|
1390
1390
|
}
|
|
1391
1391
|
}
|
|
1392
|
+
|
|
1393
|
+
/* 自定义国家输入行 */
|
|
1394
|
+
.custom-loc-row {
|
|
1395
|
+
margin-top: 12px;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
.custom-loc-input {
|
|
1399
|
+
width: 100%;
|
|
1400
|
+
padding: 10px 12px;
|
|
1401
|
+
border: 1px solid #333;
|
|
1402
|
+
border-radius: 6px;
|
|
1403
|
+
background: #0f0f13;
|
|
1404
|
+
color: #e0e0e0;
|
|
1405
|
+
font-size: 13px;
|
|
1406
|
+
font-weight: 600;
|
|
1407
|
+
outline: none;
|
|
1408
|
+
text-transform: uppercase;
|
|
1409
|
+
transition: border-color 0.15s;
|
|
1410
|
+
box-sizing: border-box;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
.custom-loc-input:focus {
|
|
1414
|
+
border-color: #a78bfa;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
.custom-loc-input::placeholder {
|
|
1418
|
+
color: #555;
|
|
1419
|
+
font-weight: 400;
|
|
1420
|
+
text-transform: none;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
/* 非商家按钮 */
|
|
1424
|
+
.btn-non-seller {
|
|
1425
|
+
padding: 4px 10px;
|
|
1426
|
+
border: 1px solid #f87171;
|
|
1427
|
+
border-radius: 4px;
|
|
1428
|
+
background: transparent;
|
|
1429
|
+
color: #f87171;
|
|
1430
|
+
font-size: 11px;
|
|
1431
|
+
font-weight: 600;
|
|
1432
|
+
cursor: pointer;
|
|
1433
|
+
transition: all 0.15s;
|
|
1434
|
+
white-space: nowrap;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
.btn-non-seller:hover {
|
|
1438
|
+
background: rgba(248, 113, 113, 0.12);
|
|
1439
|
+
border-color: #ef4444;
|
|
1440
|
+
color: #ef4444;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/* ===== 关键词页面 ===== */
|
|
1444
|
+
#tagsPage {
|
|
1445
|
+
display: none;
|
|
1446
|
+
padding-bottom: 40px;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
#tagsPage.active {
|
|
1450
|
+
display: block;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
.tags-layout {
|
|
1454
|
+
display: flex;
|
|
1455
|
+
flex-direction: column;
|
|
1456
|
+
gap: 16px;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
.tags-table-card {
|
|
1460
|
+
background: #1c1c26;
|
|
1461
|
+
border-radius: 10px;
|
|
1462
|
+
padding: 16px;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
.tags-table-card h3 {
|
|
1466
|
+
font-size: 14px;
|
|
1467
|
+
color: #e0e0e0;
|
|
1468
|
+
margin-bottom: 12px;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
#tagsPage .controls {
|
|
1472
|
+
margin-bottom: 12px;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
#tagsPage .controls button {
|
|
1476
|
+
padding: 5px 10px;
|
|
1477
|
+
border: 1px solid #333;
|
|
1478
|
+
border-radius: 4px;
|
|
1479
|
+
background: #2a2a3a;
|
|
1480
|
+
color: #ccc;
|
|
1481
|
+
font-size: 11px;
|
|
1482
|
+
cursor: pointer;
|
|
1483
|
+
transition: all 0.15s;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
#tagsPage .controls button.active {
|
|
1487
|
+
background: #7c3aed;
|
|
1488
|
+
color: #fff;
|
|
1489
|
+
border-color: #7c3aed;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
#tagsPage .controls button:hover:not(.active) {
|
|
1493
|
+
border-color: #7c3aed;
|
|
1494
|
+
color: #a78bfa;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
#tagsTable td {
|
|
1498
|
+
font-size: 12px;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
#tagsTable td:nth-child(3),
|
|
1502
|
+
#tagsTable td:nth-child(4),
|
|
1503
|
+
#tagsTable td:nth-child(5),
|
|
1504
|
+
#tagsTable td:nth-child(6),
|
|
1505
|
+
#tagsTable td:nth-child(7) {
|
|
1506
|
+
font-family: monospace;
|
|
1507
|
+
text-align: right;
|
|
1508
|
+
color: #9ca3af;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
.form-row {
|
|
1512
|
+
margin-top: 12px;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
.form-row label {
|
|
1516
|
+
display: block;
|
|
1517
|
+
margin-bottom: 4px;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
.checkbox-label {
|
|
1521
|
+
display: inline-flex;
|
|
1522
|
+
align-items: center;
|
|
1523
|
+
gap: 4px;
|
|
1524
|
+
padding: 4px 10px;
|
|
1525
|
+
border: 1px solid #333;
|
|
1526
|
+
border-radius: 4px;
|
|
1527
|
+
background: #0f0f13;
|
|
1528
|
+
color: #ccc;
|
|
1529
|
+
font-size: 12px;
|
|
1530
|
+
cursor: pointer;
|
|
1531
|
+
transition: all 0.15s;
|
|
1532
|
+
user-select: none;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
.checkbox-label:hover {
|
|
1536
|
+
border-color: #a78bfa;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
.checkbox-label input[type="checkbox"] {
|
|
1540
|
+
accent-color: #a78bfa;
|
|
1541
|
+
}
|
package/src/watch/server.js
CHANGED
|
@@ -561,6 +561,26 @@ export function startWatchServer(
|
|
|
561
561
|
return;
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
const nonSellerMatch = routePath.match(
|
|
565
|
+
/^\/api\/user-non-seller\/([^/]+)$/,
|
|
566
|
+
);
|
|
567
|
+
if (req.method === "PUT" && nonSellerMatch) {
|
|
568
|
+
const uniqueId = nonSellerMatch[1];
|
|
569
|
+
try {
|
|
570
|
+
const ret = store.setNonSeller(uniqueId);
|
|
571
|
+
if (ret.error) {
|
|
572
|
+
sendJSON(res, 404, { error: ret.error });
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const ts = new Date().toISOString().slice(11, 19);
|
|
576
|
+
console.error(`[JOB ${ts}] NON-SELLER: ${uniqueId} → ttSeller=false`);
|
|
577
|
+
sendJSON(res, 200, ret);
|
|
578
|
+
} catch (e) {
|
|
579
|
+
sendJSON(res, 400, { error: e.message });
|
|
580
|
+
}
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
564
584
|
if (req.method === "GET" && routePath === "/api/comment-tasks") {
|
|
565
585
|
const limit = parseInt(params.limit) || 1;
|
|
566
586
|
const tasks = store.getPendingCommentTasks(limit);
|
|
@@ -1127,26 +1147,32 @@ export function startWatchServer(
|
|
|
1127
1147
|
return;
|
|
1128
1148
|
}
|
|
1129
1149
|
|
|
1130
|
-
// GET /api/tags
|
|
1150
|
+
// GET /api/tags/stats — 关键词统计(总数/各状态/国家列表)
|
|
1151
|
+
if (req.method === "GET" && routePath === "/api/tags/stats") {
|
|
1152
|
+
const stats = store.getTagStats();
|
|
1153
|
+
sendJSON(res, 200, stats || { total: 0, countries: [] });
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// GET /api/tags?status=&country=&limit=&offset=
|
|
1131
1158
|
if (req.method === "GET" && routePath === "/api/tags") {
|
|
1132
1159
|
const status = params.status || null;
|
|
1133
1160
|
const country = params.country || null;
|
|
1134
|
-
const limit = Math.min(parseInt(params.limit) ||
|
|
1161
|
+
const limit = Math.min(parseInt(params.limit) || 200, 500);
|
|
1162
|
+
const offset = parseInt(params.offset) || 0;
|
|
1163
|
+
const upperCountry = country ? country.toUpperCase() : null;
|
|
1135
1164
|
|
|
1136
1165
|
let tags;
|
|
1137
1166
|
if (status) {
|
|
1138
|
-
tags = store.getTagsByStatus(status, limit);
|
|
1167
|
+
tags = store.getTagsByStatus(status, limit, offset, upperCountry);
|
|
1139
1168
|
} else {
|
|
1140
|
-
tags = store.getAllTags(limit);
|
|
1169
|
+
tags = store.getAllTags(limit, offset, upperCountry);
|
|
1141
1170
|
}
|
|
1142
1171
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
sendJSON(res, 200, { tags, total: tags.length });
|
|
1172
|
+
// 获取总数(带国家过滤)
|
|
1173
|
+
const stats = store.getTagStats(upperCountry);
|
|
1174
|
+
const total = stats ? stats.total : tags.length;
|
|
1175
|
+
sendJSON(res, 200, { tags, total, offset, limit });
|
|
1150
1176
|
return;
|
|
1151
1177
|
}
|
|
1152
1178
|
|
|
@@ -1173,6 +1199,33 @@ export function startWatchServer(
|
|
|
1173
1199
|
return;
|
|
1174
1200
|
}
|
|
1175
1201
|
|
|
1202
|
+
// POST /api/tags/batch-add — 批量添加关键词
|
|
1203
|
+
if (req.method === "POST" && routePath === "/api/tags/batch-add") {
|
|
1204
|
+
try {
|
|
1205
|
+
const body = await readBody(req);
|
|
1206
|
+
const { tags, countries } = body || {};
|
|
1207
|
+
if (!Array.isArray(tags) || tags.length === 0) {
|
|
1208
|
+
sendJSON(res, 400, { error: "tags 不能为空" });
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
if (!Array.isArray(countries) || countries.length === 0) {
|
|
1212
|
+
sendJSON(res, 400, { error: "countries 不能为空" });
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
let added = 0;
|
|
1216
|
+
for (const tag of tags) {
|
|
1217
|
+
for (const c of countries) {
|
|
1218
|
+
const ret = store.insertTag(tag, [c]);
|
|
1219
|
+
if (ret.inserted) added++;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
sendJSON(res, 200, { ok: true, added });
|
|
1223
|
+
} catch (e) {
|
|
1224
|
+
sendJSON(res, 500, { error: e.message });
|
|
1225
|
+
}
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1176
1229
|
// POST /api/tags/productive — CLI 模式上报 productive tag
|
|
1177
1230
|
if (req.method === "POST" && routePath === "/api/tags/productive") {
|
|
1178
1231
|
try {
|
|
@@ -1266,6 +1319,22 @@ export function startWatchServer(
|
|
|
1266
1319
|
console.error(`Watch 监控服务已启动:`);
|
|
1267
1320
|
console.error(` 本地访问: http://127.0.0.1:${port}`);
|
|
1268
1321
|
console.error(` 局域网访问: http://${localIP}:${port}`);
|
|
1322
|
+
|
|
1323
|
+
// 启动时清理超时的 scoring 标签
|
|
1324
|
+
try {
|
|
1325
|
+
const { resetStaleScoringTags } = store;
|
|
1326
|
+
if (resetStaleScoringTags) {
|
|
1327
|
+
const result = resetStaleScoringTags(30);
|
|
1328
|
+
if (result.reset > 0) {
|
|
1329
|
+
console.error(
|
|
1330
|
+
`[启动] 已重置 ${result.reset} 个超时的 scoring 标签`,
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
} catch (e) {
|
|
1335
|
+
console.error(`[启动] 清理 scoring 标签失败: ${e.message}`);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1269
1338
|
_resolve({ server, port });
|
|
1270
1339
|
});
|
|
1271
1340
|
|
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.3,
|
|
49
49
|
}),
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -163,7 +163,7 @@ export function buildDiscoverPrompt(
|
|
|
163
163
|
.map((t) => `${t.tag}(score:${Math.round(t.score)})`)
|
|
164
164
|
.join(
|
|
165
165
|
", ",
|
|
166
|
-
)}.
|
|
166
|
+
)}. Use these as reference for the STYLE and TYPE of tag that works — prefer commonly used words like these.`
|
|
167
167
|
: "";
|
|
168
168
|
|
|
169
169
|
// 负样本:该国 dead tag
|
|
@@ -186,7 +186,7 @@ export function buildDiscoverPrompt(
|
|
|
186
186
|
const allExisting = history.allExisting || [];
|
|
187
187
|
const existingHint =
|
|
188
188
|
allExisting.length > 0
|
|
189
|
-
? `\nTags already in database (DO NOT generate these again): ${allExisting.slice(
|
|
189
|
+
? `\nTags already in database (DO NOT generate these again): ${allExisting.slice(0, 50).join(", ")}.`
|
|
190
190
|
: "";
|
|
191
191
|
|
|
192
192
|
const userHint = userPrompt
|
|
@@ -210,39 +210,60 @@ Based on the above, which strategies produced high-scoring tags? Which failed?
|
|
|
210
210
|
Use this analysis to decide your strategy for this round.`;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
const deadRatio =
|
|
214
|
+
history.dead.length > 0 && history.productive.length > 0
|
|
215
|
+
? (
|
|
216
|
+
(history.dead.length /
|
|
217
|
+
(history.dead.length + history.productive.length)) *
|
|
218
|
+
100
|
|
219
|
+
).toFixed(0)
|
|
220
|
+
: null;
|
|
221
|
+
const qualityWarning =
|
|
222
|
+
deadRatio && Number(deadRatio) > 40
|
|
223
|
+
? `\n⚠️ WARNING: Currently ${deadRatio}% of our generated tags fail (no real TikTok posts). This is critically high. You MUST be more conservative — only suggest hashtags you have HIGH CONFIDENCE actually exist on TikTok.`
|
|
224
|
+
: "";
|
|
225
|
+
|
|
213
226
|
return `You are discovering TikTok hashtags used by people who sell things in ${country}.
|
|
214
227
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
-
|
|
220
|
-
-
|
|
228
|
+
⛔ CRITICAL RULE: ONLY suggest hashtags that you are CONFIDENT actually exist on TikTok with real posts.
|
|
229
|
+
Never invent, construct, or guess compound words. If you aren't sure a hashtag is real, DO NOT suggest it.
|
|
230
|
+
|
|
231
|
+
✅ Good examples of REAL TikTok hashtags:
|
|
232
|
+
- Simple, common words: "verkaufen", "handmade", "secondhand", "bakery", "vintage"
|
|
233
|
+
- Common category tags: "shoplocal", "onlineshopping", "handwerker", "trödel"
|
|
234
|
+
- Established brand/community tags: "smallbusiness", "supportlocal", "vendita"
|
|
235
|
+
❌ BAD examples (INVENTED, will fail):
|
|
236
|
+
- Novel compound words: "sourdoughschaleverkauf", "predavamdruhouseoblečenípraha"
|
|
237
|
+
- Hyper-specific location+product: "dublinvintagelamp", "canalistalisboa"
|
|
238
|
+
- Rare technical terms: "briefmarkensammlungankauf", "aquascapingzubehör"
|
|
239
|
+
- Underscore-connected constructs: "mtg_cardhu", "epoxigyanta_alkotás_eladó"
|
|
240
|
+
|
|
241
|
+
Think about the MOST COMMON hashtags that real sellers in ${country} use on TikTok:
|
|
242
|
+
- Common selling verbs in ${langName} (sell, buy, offer, clearance...)
|
|
243
|
+
- Common product categories in ${langName} (shoes, clothes, furniture, food, pets...)
|
|
244
|
+
- Common seller identities (shop, boutique, small business, creator...)
|
|
245
|
+
- Well-known community tags (support local, marketplace, second round...)
|
|
221
246
|
|
|
222
|
-
|
|
223
|
-
Generate ${count} tags that are ALL DIFFERENT from each other and from any existing tags.${productiveHint}${deadHint}${errorHint}${existingHint}${userHint}${strategyReview}
|
|
247
|
+
${qualityWarning}${productiveHint}${deadHint}${errorHint}${existingHint}${userHint}${strategyReview}
|
|
224
248
|
|
|
225
|
-
##
|
|
249
|
+
## Quality check before responding
|
|
226
250
|
|
|
227
|
-
|
|
228
|
-
1. Which tag directions scored highest? What makes them work?
|
|
229
|
-
2. Which directions completely failed? Why?
|
|
230
|
-
3. What seller niches are NOT yet covered? (e.g., if we have "shop" but no "bakery", "petstore", "bookshop"...)
|
|
231
|
-
4. What specific direction will YOU explore this round? Be concrete.
|
|
251
|
+
For each tag you consider, ask yourself: "Have I actually seen this hashtag used on TikTok, or am I just translating a concept?" If you're translating/constructing — DON'T include it. Only include if you're genuinely confident it's a real, used hashtag.
|
|
232
252
|
|
|
233
253
|
## Output format
|
|
234
254
|
|
|
235
255
|
Return ONLY a JSON object with two fields:
|
|
236
256
|
{
|
|
237
|
-
"strategy": "Your analysis
|
|
257
|
+
"strategy": "Your analysis (2-3 sentences). Acknowledge what worked/failed before and explain why your chosen tags are likely real and commonly used.",
|
|
238
258
|
"tags": ["tag1", "tag2", "tag3", "tag4"]
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
Rules:
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
-
|
|
245
|
-
-
|
|
262
|
+
- QUALITY over creativity — 3 real, commonly used tags beat 4 invented ones
|
|
263
|
+
- NEVER invent compound words or translate concepts into hashtags
|
|
264
|
+
- 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`;
|
|
246
267
|
}
|
|
247
268
|
|
|
248
269
|
// ====== discover: 单国家标签发现 ======
|