pixivflow 2.8.0 → 2.10.0

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.
Files changed (87) hide show
  1. package/README.md +6 -0
  2. package/README_EN.md +4 -0
  3. package/dist/commands/TagsCommand.d.ts +25 -0
  4. package/dist/commands/TagsCommand.d.ts.map +1 -0
  5. package/dist/commands/TagsCommand.js +176 -0
  6. package/dist/commands/TagsCommand.js.map +1 -0
  7. package/dist/commands/TopicCommand.d.ts +19 -0
  8. package/dist/commands/TopicCommand.d.ts.map +1 -0
  9. package/dist/commands/TopicCommand.js +113 -0
  10. package/dist/commands/TopicCommand.js.map +1 -0
  11. package/dist/commands/index.d.ts +3 -1
  12. package/dist/commands/index.d.ts.map +1 -1
  13. package/dist/commands/index.js +7 -1
  14. package/dist/commands/index.js.map +1 -1
  15. package/dist/config/types.d.ts +45 -2
  16. package/dist/config/types.d.ts.map +1 -1
  17. package/dist/config/validation.d.ts.map +1 -1
  18. package/dist/config/validation.js +40 -3
  19. package/dist/config/validation.js.map +1 -1
  20. package/dist/download/DownloadManager.d.ts.map +1 -1
  21. package/dist/download/DownloadManager.js +5 -2
  22. package/dist/download/DownloadManager.js.map +1 -1
  23. package/dist/download/handlers/IllustrationTargetHandler.d.ts +5 -1
  24. package/dist/download/handlers/IllustrationTargetHandler.d.ts.map +1 -1
  25. package/dist/download/handlers/IllustrationTargetHandler.js +26 -1
  26. package/dist/download/handlers/IllustrationTargetHandler.js.map +1 -1
  27. package/dist/download/handlers/NovelTargetHandler.d.ts +4 -1
  28. package/dist/download/handlers/NovelTargetHandler.d.ts.map +1 -1
  29. package/dist/download/handlers/NovelTargetHandler.js +23 -1
  30. package/dist/download/handlers/NovelTargetHandler.js.map +1 -1
  31. package/dist/package.json +1 -1
  32. package/dist/pixiv/PixivClient.d.ts +10 -1
  33. package/dist/pixiv/PixivClient.d.ts.map +1 -1
  34. package/dist/pixiv/PixivClient.js +25 -0
  35. package/dist/pixiv/PixivClient.js.map +1 -1
  36. package/dist/pixiv/client/SearchService.d.ts +2 -1
  37. package/dist/pixiv/client/SearchService.d.ts.map +1 -1
  38. package/dist/pixiv/client/SearchService.js +10 -0
  39. package/dist/pixiv/client/SearchService.js.map +1 -1
  40. package/dist/pixiv/types.d.ts +15 -0
  41. package/dist/pixiv/types.d.ts.map +1 -1
  42. package/dist/storage/Database.d.ts +2 -0
  43. package/dist/storage/Database.d.ts.map +1 -1
  44. package/dist/storage/Database.js +4 -0
  45. package/dist/storage/Database.js.map +1 -1
  46. package/dist/tags/TagDiscoveryService.d.ts +22 -0
  47. package/dist/tags/TagDiscoveryService.d.ts.map +1 -0
  48. package/dist/tags/TagDiscoveryService.js +137 -0
  49. package/dist/tags/TagDiscoveryService.js.map +1 -0
  50. package/dist/tags/TagDiscoveryStore.d.ts +21 -0
  51. package/dist/tags/TagDiscoveryStore.d.ts.map +1 -0
  52. package/dist/tags/TagDiscoveryStore.js +67 -0
  53. package/dist/tags/TagDiscoveryStore.js.map +1 -0
  54. package/dist/tags/TagPlanApplier.d.ts +22 -0
  55. package/dist/tags/TagPlanApplier.d.ts.map +1 -0
  56. package/dist/tags/TagPlanApplier.js +86 -0
  57. package/dist/tags/TagPlanApplier.js.map +1 -0
  58. package/dist/tags/types.d.ts +36 -0
  59. package/dist/tags/types.d.ts.map +1 -0
  60. package/dist/tags/types.js +3 -0
  61. package/dist/tags/types.js.map +1 -0
  62. package/dist/topic/TopicCache.d.ts +23 -0
  63. package/dist/topic/TopicCache.d.ts.map +1 -0
  64. package/dist/topic/TopicCache.js +78 -0
  65. package/dist/topic/TopicCache.js.map +1 -0
  66. package/dist/topic/TopicPipeline.d.ts +49 -0
  67. package/dist/topic/TopicPipeline.d.ts.map +1 -0
  68. package/dist/topic/TopicPipeline.js +224 -0
  69. package/dist/topic/TopicPipeline.js.map +1 -0
  70. package/dist/topic/TopicResolver.d.ts +31 -0
  71. package/dist/topic/TopicResolver.d.ts.map +1 -0
  72. package/dist/topic/TopicResolver.js +162 -0
  73. package/dist/topic/TopicResolver.js.map +1 -0
  74. package/dist/topic/TopicTagScorer.d.ts +13 -0
  75. package/dist/topic/TopicTagScorer.d.ts.map +1 -0
  76. package/dist/topic/TopicTagScorer.js +109 -0
  77. package/dist/topic/TopicTagScorer.js.map +1 -0
  78. package/dist/topic/createTopicPipeline.d.ts +12 -0
  79. package/dist/topic/createTopicPipeline.d.ts.map +1 -0
  80. package/dist/topic/createTopicPipeline.js +31 -0
  81. package/dist/topic/createTopicPipeline.js.map +1 -0
  82. package/dist/topic/types.d.ts +93 -0
  83. package/dist/topic/types.d.ts.map +1 -0
  84. package/dist/topic/types.js +8 -0
  85. package/dist/topic/types.js.map +1 -0
  86. package/dist/webui/package.json +1 -1
  87. package/package.json +1 -1
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tags/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import type { TopicContentType, TopicSpace } from './types';
2
+ /**
3
+ * Durable Topic cache stored beside the SQLite database (which lives on the
4
+ * persistent data volume in Docker). One small JSON file per
5
+ * `<topic, contentType>`, written atomically. No extra database or service.
6
+ *
7
+ * Daily scheduler runs normally hit the fresh cache; only a missing/expired
8
+ * entry triggers discovery. A stale entry is kept and reused if refresh fails.
9
+ */
10
+ export declare class TopicCache {
11
+ private readonly directory;
12
+ private readonly now;
13
+ constructor(directory: string, now?: () => number);
14
+ static forDatabase(databasePath: string): TopicCache;
15
+ private pathFor;
16
+ private read;
17
+ /** Returns a cached space only if it has not expired. */
18
+ loadFresh(topic: string, contentType: TopicContentType): Promise<TopicSpace | undefined>;
19
+ /** Returns a cached space even if expired (used for graceful fallback). */
20
+ loadAny(topic: string, contentType: TopicContentType): Promise<TopicSpace | undefined>;
21
+ save(space: TopicSpace): Promise<string>;
22
+ }
23
+ //# sourceMappingURL=TopicCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TopicCache.d.ts","sourceRoot":"","sources":["../../src/topic/TopicCache.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE5D;;;;;;;GAOG;AACH,qBAAa,UAAU;IAEnB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;gBADH,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,MAAM,MAAiB;IAG/C,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU;IAMpD,OAAO,CAAC,OAAO;YAQD,IAAI;IAYlB,yDAAyD;IACnD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAQ9F,2EAA2E;IACrE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAItF,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;CAa/C"}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TopicCache = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const node_crypto_1 = require("node:crypto");
7
+ /**
8
+ * Durable Topic cache stored beside the SQLite database (which lives on the
9
+ * persistent data volume in Docker). One small JSON file per
10
+ * `<topic, contentType>`, written atomically. No extra database or service.
11
+ *
12
+ * Daily scheduler runs normally hit the fresh cache; only a missing/expired
13
+ * entry triggers discovery. A stale entry is kept and reused if refresh fails.
14
+ */
15
+ class TopicCache {
16
+ directory;
17
+ now;
18
+ constructor(directory, now = Date.now) {
19
+ this.directory = directory;
20
+ this.now = now;
21
+ }
22
+ static forDatabase(databasePath) {
23
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
24
+ const { dirname } = require('node:path');
25
+ return new TopicCache((0, node_path_1.join)(dirname(databasePath), 'topic-cache'));
26
+ }
27
+ pathFor(topic, contentType) {
28
+ const digest = (0, node_crypto_1.createHash)('sha256')
29
+ .update(`${contentType}:${topic.trim().normalize('NFKC').toLocaleLowerCase()}`)
30
+ .digest('hex')
31
+ .slice(0, 16);
32
+ return (0, node_path_1.join)(this.directory, `topic-${digest}.json`);
33
+ }
34
+ async read(path) {
35
+ try {
36
+ const space = JSON.parse(await node_fs_1.promises.readFile(path, 'utf8'));
37
+ if (space.version !== 1 || !space.topic || !Array.isArray(space.tags))
38
+ return undefined;
39
+ return space;
40
+ }
41
+ catch (error) {
42
+ const code = error.code;
43
+ if (code === 'ENOENT' || error instanceof SyntaxError)
44
+ return undefined;
45
+ throw error;
46
+ }
47
+ }
48
+ /** Returns a cached space only if it has not expired. */
49
+ async loadFresh(topic, contentType) {
50
+ const space = await this.read(this.pathFor(topic, contentType));
51
+ if (!space)
52
+ return undefined;
53
+ const expires = Date.parse(space.expiresAt);
54
+ if (!Number.isFinite(expires) || expires <= this.now())
55
+ return undefined;
56
+ return space;
57
+ }
58
+ /** Returns a cached space even if expired (used for graceful fallback). */
59
+ async loadAny(topic, contentType) {
60
+ return this.read(this.pathFor(topic, contentType));
61
+ }
62
+ async save(space) {
63
+ await node_fs_1.promises.mkdir(this.directory, { recursive: true });
64
+ const path = this.pathFor(space.topic, space.contentType);
65
+ const tmp = `${path}.${process.pid}.${(0, node_crypto_1.createHash)('sha256').update(path + space.createdAt).digest('hex').slice(0, 8)}.tmp`;
66
+ try {
67
+ await node_fs_1.promises.writeFile(tmp, `${JSON.stringify(space, null, 2)}\n`, 'utf8');
68
+ await node_fs_1.promises.rename(tmp, path);
69
+ }
70
+ catch (error) {
71
+ await node_fs_1.promises.unlink(tmp).catch(() => undefined);
72
+ throw error;
73
+ }
74
+ return path;
75
+ }
76
+ }
77
+ exports.TopicCache = TopicCache;
78
+ //# sourceMappingURL=TopicCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TopicCache.js","sourceRoot":"","sources":["../../src/topic/TopicCache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { TargetConfig } from '../config';
2
+ import type { TopicResolver } from './TopicResolver';
3
+ import type { TopicCandidate, TopicClient, TopicCollectOptions, TopicContentType, TopicDiscoveryOptions, WorkLike } from './types';
4
+ export interface TopicSelection {
5
+ candidates: TopicCandidate[];
6
+ selected: TopicCandidate[];
7
+ resolvedTagCount: number;
8
+ rawCount: number;
9
+ dedupedCount: number;
10
+ acceptedCount: number;
11
+ }
12
+ /**
13
+ * Resolves a topic to a tag space, collects that day's works across the tags,
14
+ * filters by lightweight metadata relevance and ranks by local popularity, then
15
+ * returns only the chosen full work objects for the existing download pipeline.
16
+ * Serial, bounded, low-memory: the retained set is `limit` items; the pool is
17
+ * capped at maxCandidates and released after selection.
18
+ */
19
+ export declare class TopicPipeline {
20
+ private readonly client;
21
+ private readonly resolver;
22
+ private readonly requestDelayMs;
23
+ constructor(client: TopicClient, resolver: TopicResolver, requestDelayMs?: number);
24
+ selectWorks<T extends WorkLike>(target: TargetConfig, contentType: TopicContentType, day: string, limit: number, discovery: TopicDiscoveryOptions, collect: TopicCollectOptions): Promise<{
25
+ works: T[];
26
+ selection: TopicSelection;
27
+ }>;
28
+ private searchDay;
29
+ private toCandidate;
30
+ /**
31
+ * Lightweight metadata relevance. Tags dominate (Pixiv's own taxonomy);
32
+ * title/caption add smaller boosts. The seed tag is strong evidence.
33
+ * No text model — case/symbol-insensitive substring matching only.
34
+ */
35
+ private metadataScore;
36
+ /**
37
+ * Rank primarily by topic relevance tier, then by local popularity within a
38
+ * tier. Relevance wins because the goal is "belongs to the topic", with
39
+ * popularity deciding which on-topic work to download.
40
+ */
41
+ private relevanceTier;
42
+ private rankCompare;
43
+ private topByPopularity;
44
+ private onDay;
45
+ private normalize;
46
+ private key;
47
+ private bound;
48
+ }
49
+ //# sourceMappingURL=TopicPipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TopicPipeline.d.ts","sourceRoot":"","sources":["../../src/topic/TopicPipeline.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,QAAQ,EACT,MAAM,SAAS,CAAC;AAYjB,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,qBAAa,aAAa;IAEtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,cAAc;gBAFd,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,aAAa,EACvB,cAAc,SAAM;IAGjC,WAAW,CAAC,CAAC,SAAS,QAAQ,EAClC,MAAM,EAAE,YAAY,EACpB,WAAW,EAAE,gBAAgB,EAC7B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,qBAAqB,EAChC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC;QAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAAC,SAAS,EAAE,cAAc,CAAA;KAAE,CAAC;YAgEvC,SAAS;IAmBvB,OAAO,CAAC,WAAW;IAenB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAmCrB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,KAAK;CAKd"}
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TopicPipeline = void 0;
4
+ const promises_1 = require("node:timers/promises");
5
+ const logger_1 = require("../logger");
6
+ const pixiv_utils_1 = require("../utils/pixiv-utils");
7
+ const COLLECT_DEFAULTS = { maxPerTag: 40, maxCandidates: 250, minMetadataScore: 0.35 };
8
+ /** Small, platform-level tags that are never evidence of topic membership. */
9
+ const STOP_TAGS = new Set([
10
+ 'r-18', 'r-18g', 'r-15', 'original', 'オリジナル', '創作', '女の子', '男の子',
11
+ 'イラスト', '漫画', 'manga', 'illustration', 'artwork', '落書き', 'らくがき',
12
+ '1000users入り', '5000users入り', '10000users入り', '500users入り', '100users入り',
13
+ 'pixiv', 'commission', 'skeb', '依頼絵', '仕事絵',
14
+ ]);
15
+ /**
16
+ * Resolves a topic to a tag space, collects that day's works across the tags,
17
+ * filters by lightweight metadata relevance and ranks by local popularity, then
18
+ * returns only the chosen full work objects for the existing download pipeline.
19
+ * Serial, bounded, low-memory: the retained set is `limit` items; the pool is
20
+ * capped at maxCandidates and released after selection.
21
+ */
22
+ class TopicPipeline {
23
+ client;
24
+ resolver;
25
+ requestDelayMs;
26
+ constructor(client, resolver, requestDelayMs = 500) {
27
+ this.client = client;
28
+ this.resolver = resolver;
29
+ this.requestDelayMs = requestDelayMs;
30
+ }
31
+ async selectWorks(target, contentType, day, limit, discovery, collect) {
32
+ const topic = (target.topic ?? '').trim();
33
+ const { space } = await this.resolver.resolve(topic, contentType, discovery);
34
+ const tagScores = new Map(space.tags.map((t) => [this.key(t.name), t.score]));
35
+ const maxPerTag = this.bound(collect.maxPerTag, COLLECT_DEFAULTS.maxPerTag, 5, 100);
36
+ const maxCandidates = this.bound(collect.maxCandidates, COLLECT_DEFAULTS.maxCandidates, 20, 500);
37
+ const minMetadataScore = collect.minMetadataScore ?? COLLECT_DEFAULTS.minMetadataScore;
38
+ const byId = new Map();
39
+ let rawCount = 0;
40
+ const tagNames = space.tags.map((t) => t.name);
41
+ for (let i = 0; i < tagNames.length; i++) {
42
+ if (byId.size >= maxCandidates)
43
+ break;
44
+ const tag = tagNames[i];
45
+ const works = await this.searchDay(contentType, tag, day, maxPerTag);
46
+ rawCount += works.length;
47
+ for (const work of works) {
48
+ if (byId.has(work.id))
49
+ continue;
50
+ byId.set(work.id, { work, candidate: this.toCandidate(work, contentType) });
51
+ if (byId.size >= maxCandidates)
52
+ break;
53
+ }
54
+ logger_1.logger.debug('[TopicCollector] type=' + contentType + ' tag=' + tag + ' day=' + day + ' fetched=' + works.length + ' pool=' + byId.size);
55
+ if (i < tagNames.length - 1 && this.requestDelayMs > 0)
56
+ await (0, promises_1.setTimeout)(this.requestDelayMs);
57
+ }
58
+ const dedupedCount = byId.size;
59
+ logger_1.logger.info('[TopicCollector] type=' + contentType + ' raw=' + rawCount + ' deduplicated=' + dedupedCount);
60
+ const seedKey = this.key(topic);
61
+ const accepted = [];
62
+ for (const entry of byId.values()) {
63
+ entry.candidate.metadataScore = this.metadataScore(entry.candidate, seedKey, tagScores);
64
+ if (entry.candidate.metadataScore >= minMetadataScore)
65
+ accepted.push(entry);
66
+ }
67
+ if (accepted.length === 0 && byId.size > 0) {
68
+ const fallback = [...byId.values()]
69
+ .filter((e) => e.candidate.tags.some((t) => this.key(t) === seedKey))
70
+ .sort((a, b) => b.candidate.popularity - a.candidate.popularity);
71
+ accepted.push(...fallback.slice(0, Math.max(limit, 1)));
72
+ logger_1.logger.warn('[MetadataTopicFilter] type=' + contentType + ' none above threshold ' + minMetadataScore + '; kept ' + accepted.length + ' seed-tag fallback');
73
+ }
74
+ const chosen = this.topByPopularity(accepted, limit);
75
+ const selected = chosen.map((e) => e.candidate);
76
+ logger_1.logger.info('[MetadataTopicFilter] accepted=' + accepted.length);
77
+ if (selected[0]) {
78
+ logger_1.logger.info('[PopularityRanker] selected=' + selected[0].id + ' popularity=' + selected[0].popularity.toFixed(1) + ' meta=' + selected[0].metadataScore.toFixed(2) + ' title=' + selected[0].title);
79
+ }
80
+ return {
81
+ works: chosen.map((e) => e.work),
82
+ selection: {
83
+ candidates: [...byId.values()].map((e) => e.candidate),
84
+ selected,
85
+ resolvedTagCount: space.tags.length,
86
+ rawCount,
87
+ dedupedCount,
88
+ acceptedCount: accepted.length,
89
+ },
90
+ };
91
+ }
92
+ async searchDay(contentType, tag, day, limit) {
93
+ try {
94
+ const opts = { startDate: day, endDate: day };
95
+ const works = contentType === 'illustration'
96
+ ? await this.client.searchIllustrationsForTags(tag, limit, opts)
97
+ : await this.client.searchNovelsForTags(tag, limit, opts);
98
+ // Date is already enforced server-side + pager stop; keep a cheap guard.
99
+ return works.filter((w) => this.onDay(w.create_date, day));
100
+ }
101
+ catch (error) {
102
+ logger_1.logger.warn('[TopicCollector] search failed tag=' + tag + ' type=' + contentType + ': ' + (error instanceof Error ? error.message : String(error)));
103
+ return [];
104
+ }
105
+ }
106
+ toCandidate(work, type) {
107
+ const popularity = (0, pixiv_utils_1.calculatePopularityScore)(work);
108
+ return {
109
+ id: work.id,
110
+ type,
111
+ title: work.title ?? '',
112
+ caption: work.caption ?? '',
113
+ tags: (work.tags ?? []).map((t) => t.name).filter(Boolean),
114
+ bookmarks: Number(work.total_bookmarks ?? work.bookmark_count ?? 0) || 0,
115
+ views: Number(work.total_view ?? work.view_count ?? 0) || 0,
116
+ popularity,
117
+ metadataScore: 0,
118
+ };
119
+ }
120
+ /**
121
+ * Lightweight metadata relevance. Tags dominate (Pixiv's own taxonomy);
122
+ * title/caption add smaller boosts. The seed tag is strong evidence.
123
+ * No text model — case/symbol-insensitive substring matching only.
124
+ */
125
+ metadataScore(candidate, seedKey, tagScores) {
126
+ let seedHit = false;
127
+ let relatedSum = 0;
128
+ let relatedHits = 0;
129
+ for (const tag of candidate.tags) {
130
+ const k = this.key(tag);
131
+ if (STOP_TAGS.has(k))
132
+ continue;
133
+ if (k === seedKey) {
134
+ seedHit = true;
135
+ continue;
136
+ }
137
+ const related = tagScores.get(k);
138
+ if (related !== undefined) {
139
+ relatedSum += Math.min(related, 0.6);
140
+ relatedHits += 1;
141
+ }
142
+ }
143
+ const hayTitle = this.normalize(candidate.title);
144
+ const hayCaption = this.normalize(candidate.caption);
145
+ const titleSeed = !!seedKey && hayTitle.includes(seedKey);
146
+ const captionSeed = !!seedKey && hayCaption.includes(seedKey);
147
+ if (seedHit) {
148
+ // Core topic work: the seed tag itself is the strongest evidence.
149
+ let score = 1.0 + Math.min(relatedSum, 0.5);
150
+ if (titleSeed)
151
+ score += 0.4;
152
+ return score;
153
+ }
154
+ // No seed tag: the work must show several independent related tags (or a
155
+ // high-weight one plus text) to count as on-topic. A single marginal tag
156
+ // deliberately does NOT clear the bar, so a hugely popular tangential work
157
+ // cannot crowd out core topic works.
158
+ let score = 0;
159
+ const strongRelated = [...tagScores.entries()].some(([k, w]) => w >= 0.6 && candidate.tags.some((t) => this.key(t) === k));
160
+ if (titleSeed)
161
+ score += 0.8;
162
+ if (captionSeed)
163
+ score += 0.4;
164
+ if (strongRelated)
165
+ score += 0.5;
166
+ score += Math.min(relatedSum, 0.6) * (relatedHits >= 2 ? 1 : 0.5);
167
+ return score;
168
+ }
169
+ /**
170
+ * Rank primarily by topic relevance tier, then by local popularity within a
171
+ * tier. Relevance wins because the goal is "belongs to the topic", with
172
+ * popularity deciding which on-topic work to download.
173
+ */
174
+ relevanceTier(c) {
175
+ if (c.metadataScore >= 1.0)
176
+ return 3; // carries the seed tag / strong evidence
177
+ if (c.metadataScore >= 0.7)
178
+ return 2;
179
+ return 1;
180
+ }
181
+ rankCompare(a, b) {
182
+ const tier = this.relevanceTier(b) - this.relevanceTier(a);
183
+ if (tier !== 0)
184
+ return tier;
185
+ return b.popularity - a.popularity;
186
+ }
187
+ topByPopularity(items, limit) {
188
+ if (items.length <= limit)
189
+ return items.sort((a, b) => this.rankCompare(a.candidate, b.candidate));
190
+ // O(n) top-`limit` selection (limit is tiny, e.g. 1); avoids a full sort.
191
+ const top = [];
192
+ for (const item of items) {
193
+ if (top.length < limit) {
194
+ top.push(item);
195
+ top.sort((a, b) => this.rankCompare(a.candidate, b.candidate));
196
+ }
197
+ else if (this.rankCompare(item.candidate, top[top.length - 1].candidate) < 0) {
198
+ top[top.length - 1] = item;
199
+ top.sort((a, b) => this.rankCompare(a.candidate, b.candidate));
200
+ }
201
+ }
202
+ return top;
203
+ }
204
+ onDay(createDate, day) {
205
+ if (!createDate)
206
+ return true;
207
+ return createDate.slice(0, 10) === day;
208
+ }
209
+ normalize(value) {
210
+ return value.toLowerCase().normalize('NFKC');
211
+ }
212
+ key(value) {
213
+ return value.trim().normalize('NFKC').toLocaleLowerCase();
214
+ }
215
+ bound(value, fallback, min, max) {
216
+ const resolved = value ?? fallback;
217
+ if (!Number.isInteger(resolved) || resolved < min || resolved > max)
218
+ return fallback;
219
+ return resolved;
220
+ }
221
+ }
222
+ exports.TopicPipeline = TopicPipeline;
223
+ function seedText(key) { return key; }
224
+ //# sourceMappingURL=TopicPipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TopicPipeline.js","sourceRoot":"","sources":["../../src/topic/TopicPipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { TopicCache } from './TopicCache';
2
+ import type { TopicClient, TopicContentType, TopicDiscoveryOptions, TopicSpace } from './types';
3
+ /**
4
+ * Resolves a user topic into a bounded, cached set of related Pixiv tags.
5
+ *
6
+ * Discovery is intentionally one-hop around the ORIGINAL seed only (depth = 1):
7
+ * related tags never become new seeds, so the topic cannot drift outward. All
8
+ * network use is bounded and serial. Refresh failures degrade gracefully to a
9
+ * stale cache, then to the seed tag alone.
10
+ */
11
+ export declare class TopicResolver {
12
+ private readonly client;
13
+ private readonly cache;
14
+ private readonly requestDelayMs;
15
+ private readonly scorer;
16
+ constructor(client: TopicClient, cache: TopicCache, requestDelayMs?: number);
17
+ /** Resolve tags for a topic+type, using cache when fresh. Never throws for
18
+ * discovery errors: it always returns at least the seed tag. */
19
+ resolve(topic: string, contentType: TopicContentType, options?: TopicDiscoveryOptions): Promise<{
20
+ space: TopicSpace;
21
+ fromCache: boolean;
22
+ degraded: boolean;
23
+ }>;
24
+ private discover;
25
+ /** Serial, bounded fetches: autocomplete → topic sample → small background. */
26
+ private gatherSamples;
27
+ private seedOnly;
28
+ private ageDays;
29
+ private bound;
30
+ }
31
+ //# sourceMappingURL=TopicResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TopicResolver.d.ts","sourceRoot":"","sources":["../../src/topic/TopicResolver.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAEV,WAAW,EACX,gBAAgB,EAChB,qBAAqB,EACrB,UAAU,EAEX,MAAM,SAAS,CAAC;AAiBjB;;;;;;;GAOG;AACH,qBAAa,aAAa;IAItB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,cAAc;IALjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAG5B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,UAAU,EACjB,cAAc,SAAM;IAGvC;qEACiE;IAC3D,OAAO,CACX,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,gBAAgB,EAC7B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC;QAAE,KAAK,EAAE,UAAU,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;YAyC1D,QAAQ;IA+CtB,+EAA+E;YACjE,aAAa;IAsB3B,OAAO,CAAC,QAAQ;IAchB,OAAO,CAAC,OAAO;IAMf,OAAO,CAAC,KAAK;CAOd"}
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TopicResolver = void 0;
4
+ const promises_1 = require("node:timers/promises");
5
+ const logger_1 = require("../logger");
6
+ const TopicTagScorer_1 = require("./TopicTagScorer");
7
+ /** Platform-wide tags never worth searching as topic keys or scoring metadata. */
8
+ const STOP_TAGS = new Set([
9
+ 'r-18', 'r-18g', 'r-15', 'original', 'オリジナル', '創作', '女の子', '男の子',
10
+ 'イラスト', '漫画', 'manga', 'illustration', 'artwork', '落書き', 'らくがき',
11
+ '1000users入り', '5000users入り', '10000users入り', '500users入り', '100users入り',
12
+ 'pixiv', 'commission', 'skeb', '依頼絵', '仕事絵', 'aiイラスト',
13
+ ]);
14
+ const DEFAULTS = {
15
+ maxTags: 12,
16
+ sampleWorks: 100,
17
+ cacheDays: 7,
18
+ minScore: 0.22,
19
+ };
20
+ /**
21
+ * Resolves a user topic into a bounded, cached set of related Pixiv tags.
22
+ *
23
+ * Discovery is intentionally one-hop around the ORIGINAL seed only (depth = 1):
24
+ * related tags never become new seeds, so the topic cannot drift outward. All
25
+ * network use is bounded and serial. Refresh failures degrade gracefully to a
26
+ * stale cache, then to the seed tag alone.
27
+ */
28
+ class TopicResolver {
29
+ client;
30
+ cache;
31
+ requestDelayMs;
32
+ scorer = new TopicTagScorer_1.TopicTagScorer();
33
+ constructor(client, cache, requestDelayMs = 500) {
34
+ this.client = client;
35
+ this.cache = cache;
36
+ this.requestDelayMs = requestDelayMs;
37
+ }
38
+ /** Resolve tags for a topic+type, using cache when fresh. Never throws for
39
+ * discovery errors: it always returns at least the seed tag. */
40
+ async resolve(topic, contentType, options = {}) {
41
+ const seed = topic.trim();
42
+ if (!seed)
43
+ throw new Error('Topic must not be empty');
44
+ const maxTags = this.bound(options.maxTags, DEFAULTS.maxTags, 1, 40, 'maxTags');
45
+ const sampleWorks = this.bound(options.sampleWorks, DEFAULTS.sampleWorks, 10, 200, 'sampleWorks');
46
+ const cacheDays = this.bound(options.cacheDays, DEFAULTS.cacheDays, 1, 90, 'cacheDays');
47
+ const minScore = options.minScore ?? DEFAULTS.minScore;
48
+ if (!options.refresh) {
49
+ const fresh = await this.cache.loadFresh(seed, contentType);
50
+ if (fresh) {
51
+ logger_1.logger.info(`[TopicResolver] topic=${seed} type=${contentType} cache hit age=${this.ageDays(fresh)}d tags=${fresh.tags.length}`);
52
+ return { space: fresh, fromCache: true, degraded: false };
53
+ }
54
+ }
55
+ logger_1.logger.info(`[TopicResolver] refreshing topic=${seed} type=${contentType}`);
56
+ try {
57
+ const space = await this.discover(seed, contentType, { maxTags, sampleWorks, cacheDays, minScore });
58
+ await this.cache.save(space);
59
+ logger_1.logger.info(`[TopicResolver] topic=${seed} type=${contentType} resolvedTags=${space.tags.length} sampled=${space.sampledWorks}`);
60
+ for (const tag of space.tags.slice(0, maxTags)) {
61
+ logger_1.logger.debug(`[TopicResolver] tag=${tag.name} score=${tag.score} occ=${tag.occurrences} spec=${tag.specificity}`);
62
+ }
63
+ return { space, fromCache: false, degraded: false };
64
+ }
65
+ catch (error) {
66
+ const stale = await this.cache.loadAny(seed, contentType);
67
+ if (stale) {
68
+ logger_1.logger.warn(`[TopicResolver] refresh failed, using stale cache topic=${seed}`, {
69
+ error: error instanceof Error ? error.message : String(error),
70
+ });
71
+ return { space: stale, fromCache: true, degraded: true };
72
+ }
73
+ logger_1.logger.warn(`[TopicResolver] refresh failed with no cache, falling back to seed topic=${seed}`, {
74
+ error: error instanceof Error ? error.message : String(error),
75
+ });
76
+ return { space: this.seedOnly(seed, contentType, cacheDays), fromCache: false, degraded: true };
77
+ }
78
+ }
79
+ async discover(seed, contentType, cfg) {
80
+ const [suggested, topicWorks, backgroundWorks] = await this.gatherSamples(seed, contentType, cfg.sampleWorks);
81
+ const suggestedNames = new Set(suggested.map((t) => this.scorer.key(t.name)).filter(Boolean));
82
+ const related = this.scorer.score({
83
+ seed,
84
+ topicWorks,
85
+ backgroundWorks,
86
+ suggestedNames,
87
+ });
88
+ // Seed tag always leads the space with score 1.0; keep related tags above
89
+ // the relevance floor and cap the total.
90
+ const seedTag = {
91
+ name: seed,
92
+ translatedName: suggested.find((t) => this.scorer.key(t.name) === this.scorer.key(seed))?.translated_name,
93
+ score: 1,
94
+ occurrences: topicWorks.length,
95
+ coverage: 1,
96
+ specificity: 1,
97
+ suggested: suggestedNames.has(this.scorer.key(seed)),
98
+ seed: true,
99
+ };
100
+ const accepted = related
101
+ .filter((tag) => tag.score >= cfg.minScore)
102
+ .filter((tag) => !STOP_TAGS.has(this.scorer.key(tag.name)))
103
+ .slice(0, cfg.maxTags - 1);
104
+ const tags = [seedTag, ...accepted];
105
+ const now = Date.now();
106
+ return {
107
+ version: 1,
108
+ topic: seed,
109
+ contentType,
110
+ createdAt: new Date(now).toISOString(),
111
+ expiresAt: new Date(now + cfg.cacheDays * 24 * 60 * 60_000).toISOString(),
112
+ sampleSize: cfg.sampleWorks,
113
+ sampledWorks: topicWorks.length,
114
+ tags,
115
+ };
116
+ }
117
+ /** Serial, bounded fetches: autocomplete → topic sample → small background. */
118
+ async gatherSamples(seed, contentType, sampleWorks) {
119
+ const suggested = await this.client.getTagAutocomplete(seed).catch(() => []);
120
+ if (this.requestDelayMs > 0)
121
+ await (0, promises_1.setTimeout)(this.requestDelayMs);
122
+ const topicWorks = contentType === 'illustration'
123
+ ? await this.client.searchIllustrationsForTags(seed, sampleWorks)
124
+ : await this.client.searchNovelsForTags(seed, sampleWorks);
125
+ // A small, cheap background sample (a common platform tag) estimates how
126
+ // generic a co-occurring tag is. Bounded to keep requests/memory low.
127
+ if (this.requestDelayMs > 0)
128
+ await (0, promises_1.setTimeout)(this.requestDelayMs);
129
+ const backgroundWorks = contentType === 'illustration'
130
+ ? await this.client.searchIllustrationsForTags('イラスト', 40).catch(() => [])
131
+ : await this.client.searchNovelsForTags('小説', 40).catch(() => []);
132
+ return [suggested, topicWorks, backgroundWorks];
133
+ }
134
+ seedOnly(seed, contentType, cacheDays) {
135
+ const now = Date.now();
136
+ return {
137
+ version: 1,
138
+ topic: seed,
139
+ contentType,
140
+ createdAt: new Date(now).toISOString(),
141
+ expiresAt: new Date(now + cacheDays * 24 * 60 * 60_000).toISOString(),
142
+ sampleSize: 0,
143
+ sampledWorks: 0,
144
+ tags: [{ name: seed, score: 1, occurrences: 0, coverage: 1, specificity: 1, suggested: false, seed: true }],
145
+ };
146
+ }
147
+ ageDays(space) {
148
+ const created = Date.parse(space.createdAt);
149
+ if (!Number.isFinite(created))
150
+ return 0;
151
+ return Math.max(0, Math.round((Date.now() - created) / 86_400_000));
152
+ }
153
+ bound(value, fallback, min, max, name) {
154
+ const resolved = value ?? fallback;
155
+ if (!Number.isInteger(resolved) || resolved < min || resolved > max) {
156
+ throw new Error(`topicDiscovery.${name} must be an integer between ${min} and ${max}`);
157
+ }
158
+ return resolved;
159
+ }
160
+ }
161
+ exports.TopicResolver = TopicResolver;
162
+ //# sourceMappingURL=TopicResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TopicResolver.js","sourceRoot":"","sources":["../../src/topic/TopicResolver.ts"],"names":[],"mappings":";;;AAAA,mDAA2D;AAE3D,sCAAmC;AAEnC,qDAAkD;AAUlD,kFAAkF;AAClF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACxB,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK;IAChE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM;IAC/D,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY;IACxE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ;CACtD,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAG;IACf,OAAO,EAAE,EAAE;IACX,WAAW,EAAE,GAAG;IAChB,SAAS,EAAE,CAAC;IACZ,QAAQ,EAAE,IAAI;CACf,CAAC;AAEF;;;;;;;GAOG;AACH,MAAa,aAAa;IAIL;IACA;IACA;IALF,MAAM,GAAG,IAAI,+BAAc,EAAE,CAAC;IAE/C,YACmB,MAAmB,EACnB,KAAiB,EACjB,iBAAiB,GAAG;QAFpB,WAAM,GAAN,MAAM,CAAa;QACnB,UAAK,GAAL,KAAK,CAAY;QACjB,mBAAc,GAAd,cAAc,CAAM;IACpC,CAAC;IAEJ;qEACiE;IACjE,KAAK,CAAC,OAAO,CACX,KAAa,EACb,WAA6B,EAC7B,UAAiC,EAAE;QAEnC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAEtD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;QAChF,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,EAAE,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;QAClG,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC;QACxF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;QAEvD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACrB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAC5D,IAAI,KAAK,EAAE,CAAC;gBACV,eAAM,CAAC,IAAI,CAAC,yBAAyB,IAAI,SAAS,WAAW,kBAAkB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;gBACjI,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YAC5D,CAAC;QACH,CAAC;QAED,eAAM,CAAC,IAAI,CAAC,oCAAoC,IAAI,SAAS,WAAW,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;YACpG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC7B,eAAM,CAAC,IAAI,CAAC,yBAAyB,IAAI,SAAS,WAAW,iBAAiB,KAAK,CAAC,IAAI,CAAC,MAAM,YAAY,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;YACjI,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC;gBAC/C,eAAM,CAAC,KAAK,CAAC,uBAAuB,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,KAAK,QAAQ,GAAG,CAAC,WAAW,SAAS,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;YACpH,CAAC;YACD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAC1D,IAAI,KAAK,EAAE,CAAC;gBACV,eAAM,CAAC,IAAI,CAAC,2DAA2D,IAAI,EAAE,EAAE;oBAC7E,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;iBAC9D,CAAC,CAAC;gBACH,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YAC3D,CAAC;YACD,eAAM,CAAC,IAAI,CAAC,4EAA4E,IAAI,EAAE,EAAE;gBAC9F,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC,CAAC;YACH,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAClG,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,QAAQ,CACpB,IAAY,EACZ,WAA6B,EAC7B,GAAkF;QAElF,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,eAAe,CAAC,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;QAE9G,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9F,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAChC,IAAI;YACJ,UAAU;YACV,eAAe;YACf,cAAc;SACf,CAAC,CAAC;QAEH,0EAA0E;QAC1E,yCAAyC;QACzC,MAAM,OAAO,GAAgB;YAC3B,IAAI,EAAE,IAAI;YACV,cAAc,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,eAAe;YACzG,KAAK,EAAE,CAAC;YACR,WAAW,EAAE,UAAU,CAAC,MAAM;YAC9B,QAAQ,EAAE,CAAC;YACX,WAAW,EAAE,CAAC;YACd,SAAS,EAAE,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACpD,IAAI,EAAE,IAAI;SACX,CAAC;QAEF,MAAM,QAAQ,GAAG,OAAO;aACrB,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,QAAQ,CAAC;aAC1C,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;aAC1D,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC;QAEpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,OAAO;YACL,OAAO,EAAE,CAAC;YACV,KAAK,EAAE,IAAI;YACX,WAAW;YACX,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE;YACtC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,SAAS,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE;YACzE,UAAU,EAAE,GAAG,CAAC,WAAW;YAC3B,YAAY,EAAE,UAAU,CAAC,MAAM;YAC/B,IAAI;SACL,CAAC;IACJ,CAAC;IAED,+EAA+E;IACvE,KAAK,CAAC,aAAa,CACzB,IAAY,EACZ,WAA6B,EAC7B,WAAmB;QAEnB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAuD,CAAC,CAAC;QAClI,IAAI,IAAI,CAAC,cAAc,GAAG,CAAC;YAAE,MAAM,IAAA,qBAAK,EAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAE9D,MAAM,UAAU,GAAG,WAAW,KAAK,cAAc;YAC/C,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,0BAA0B,CAAC,IAAI,EAAE,WAAW,CAAC;YACjE,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAE7D,yEAAyE;QACzE,sEAAsE;QACtE,IAAI,IAAI,CAAC,cAAc,GAAG,CAAC;YAAE,MAAM,IAAA,qBAAK,EAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC9D,MAAM,eAAe,GAAG,WAAW,KAAK,cAAc;YACpD,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,0BAA0B,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAgB,CAAC;YACxF,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAgB,CAAC,CAAC;QAElF,OAAO,CAAC,SAAS,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IAClD,CAAC;IAEO,QAAQ,CAAC,IAAY,EAAE,WAA6B,EAAE,SAAiB;QAC7E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,OAAO;YACL,OAAO,EAAE,CAAC;YACV,KAAK,EAAE,IAAI;YACX,WAAW;YACX,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE;YACtC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,SAAS,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE;YACrE,UAAU,EAAE,CAAC;YACb,YAAY,EAAE,CAAC;YACf,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;SAC5G,CAAC;IACJ,CAAC;IAEO,OAAO,CAAC,KAAiB;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IACtE,CAAC;IAEO,KAAK,CAAC,KAAyB,EAAE,QAAgB,EAAE,GAAW,EAAE,GAAW,EAAE,IAAY;QAC/F,MAAM,QAAQ,GAAG,KAAK,IAAI,QAAQ,CAAC;QACnC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,GAAG,IAAI,QAAQ,GAAG,GAAG,EAAE,CAAC;YACpE,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,+BAA+B,GAAG,QAAQ,GAAG,EAAE,CAAC,CAAC;QACzF,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAzJD,sCAyJC"}
@@ -0,0 +1,13 @@
1
+ import type { ResolvedTag, WorkLike } from './types';
2
+ export interface ScorerInput {
3
+ seed: string;
4
+ topicWorks: WorkLike[];
5
+ backgroundWorks: WorkLike[];
6
+ suggestedNames: Set<string>;
7
+ }
8
+ export declare class TopicTagScorer {
9
+ /** Normalize for matching/keys: trim, NFKC, lower-case. */
10
+ key(value: string): string;
11
+ score(input: ScorerInput): ResolvedTag[];
12
+ }
13
+ //# sourceMappingURL=TopicTagScorer.d.ts.map