@kaikybrofc/omnizap-system 2.1.8
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/.env.example +534 -0
- package/LICENSE +21 -0
- package/README.md +431 -0
- package/RELEASE-v2.1.2.md +83 -0
- package/app/config/adminIdentity.js +87 -0
- package/app/config/baileysConfig.js +693 -0
- package/app/config/groupUtils.js +388 -0
- package/app/connection/socketController.js +992 -0
- package/app/controllers/messageController.js +354 -0
- package/app/modules/adminModule/groupCommandHandlers.js +1294 -0
- package/app/modules/adminModule/groupEventHandlers.js +355 -0
- package/app/modules/aiModule/catCommand.js +1006 -0
- package/app/modules/broadcastModule/noticeCommand.js +416 -0
- package/app/modules/gameModule/diceCommand.js +67 -0
- package/app/modules/menuModule/common.js +311 -0
- package/app/modules/menuModule/menus.js +59 -0
- package/app/modules/playModule/playCommand.js +1615 -0
- package/app/modules/quoteModule/quoteCommand.js +851 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +786 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.js +2082 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +760 -0
- package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
- package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +172 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
- package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
- package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1859 -0
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +6738 -0
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
- package/app/modules/statsModule/globalRankingCommand.js +65 -0
- package/app/modules/statsModule/noMessageCommand.js +288 -0
- package/app/modules/statsModule/rankingCommand.js +60 -0
- package/app/modules/statsModule/rankingCommon.js +889 -0
- package/app/modules/stickerModule/addStickerMetadata.js +239 -0
- package/app/modules/stickerModule/convertToWebp.js +390 -0
- package/app/modules/stickerModule/stickerCommand.js +454 -0
- package/app/modules/stickerModule/stickerConvertCommand.js +156 -0
- package/app/modules/stickerModule/stickerTextCommand.js +657 -0
- package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
- package/app/modules/stickerPackModule/autoPackCollectorService.js +284 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +466 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +88 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +571 -0
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +449 -0
- package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +180 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +4078 -0
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +598 -0
- package/app/modules/stickerPackModule/stickerClassificationService.js +588 -0
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +102 -0
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +7506 -0
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1095 -0
- package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +108 -0
- package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +110 -0
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +440 -0
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +337 -0
- package/app/modules/stickerPackModule/stickerPackMessageService.js +296 -0
- package/app/modules/stickerPackModule/stickerPackRepository.js +442 -0
- package/app/modules/stickerPackModule/stickerPackService.js +788 -0
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +51 -0
- package/app/modules/stickerPackModule/stickerPackUtils.js +97 -0
- package/app/modules/stickerPackModule/stickerStorageService.js +507 -0
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +233 -0
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +205 -0
- package/app/modules/systemMetricsModule/pingCommand.js +421 -0
- package/app/modules/tiktokModule/tiktokCommand.js +798 -0
- package/app/modules/userModule/userCommand.js +1217 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +177 -0
- package/app/observability/metrics.js +734 -0
- package/app/services/captchaService.js +492 -0
- package/app/services/dbWriteQueue.js +572 -0
- package/app/services/groupMetadataService.js +279 -0
- package/app/services/lidMapService.js +663 -0
- package/app/services/messagePersistenceService.js +56 -0
- package/app/services/newsBroadcastService.js +351 -0
- package/app/services/pokeApiService.js +398 -0
- package/app/services/queueUtils.js +57 -0
- package/app/services/socketState.js +7 -0
- package/app/store/aiPromptStore.js +38 -0
- package/app/store/groupConfigStore.js +58 -0
- package/app/store/premiumUserStore.js +36 -0
- package/app/utils/antiLink/antiLinkModule.js +804 -0
- package/app/utils/http/getImageBufferModule.js +18 -0
- package/app/utils/json/jsonSanitizer.js +113 -0
- package/app/utils/json/jsonSanitizer.test.js +40 -0
- package/app/utils/logger/loggerModule.js +262 -0
- package/app/utils/systemMetrics/systemMetricsModule.js +91 -0
- package/database/index.js +2052 -0
- package/database/init.js +516 -0
- package/database/migrations/20260203_0001_sticker_packs.sql +54 -0
- package/database/migrations/20260210_0003_rpg_pokemon.sql +58 -0
- package/database/migrations/20260210_0004_rpg_shiny_biome.sql +9 -0
- package/database/migrations/20260210_0005_rpg_missions.sql +14 -0
- package/database/migrations/20260210_0006_rpg_world_pokedex_traits.sql +27 -0
- package/database/migrations/20260210_0007_rpg_raid_pvp.sql +56 -0
- package/database/migrations/20260210_0008_rpg_social_system.sql +195 -0
- package/database/migrations/20260211_0009_rpg_social_xp.sql +36 -0
- package/database/migrations/20260222_0010_remove_message_xp.sql +2 -0
- package/database/migrations/20260226_0011_sticker_asset_classification.sql +17 -0
- package/database/migrations/20260226_0012_sticker_pack_engagement.sql +16 -0
- package/database/migrations/20260226_0013_sticker_marketplace_intelligence.sql +19 -0
- package/database/migrations/20260226_0014_sticker_pack_publish_flow.sql +30 -0
- package/database/migrations/20260226_0014_sticker_worker_queues.sql +42 -0
- package/database/migrations/20260226_0015_sticker_auto_pack_curation_integrity.sql +18 -0
- package/database/migrations/20260226_0016_sticker_web_google_auth_persistence.sql +34 -0
- package/database/migrations/20260226_0017_sticker_web_admin_ban.sql +22 -0
- package/database/migrations/20260226_0018_sticker_web_admin_moderator.sql +18 -0
- package/database/migrations/20260227_0019_sticker_classification_v2_signals.sql +12 -0
- package/database/migrations/20260227_0020_semantic_theme_clusters.sql +35 -0
- package/docker-compose.yml +103 -0
- package/ecosystem.prod.config.cjs +35 -0
- package/eslint.config.js +61 -0
- package/index.js +437 -0
- package/ml/clip_classifier/Dockerfile +16 -0
- package/ml/clip_classifier/README.md +120 -0
- package/ml/clip_classifier/adaptive_scoring.py +40 -0
- package/ml/clip_classifier/classifier.py +654 -0
- package/ml/clip_classifier/embedding_store.py +481 -0
- package/ml/clip_classifier/env_loader.py +15 -0
- package/ml/clip_classifier/llm_label_expander.py +144 -0
- package/ml/clip_classifier/main.py +213 -0
- package/ml/clip_classifier/requirements.txt +10 -0
- package/ml/clip_classifier/similarity_engine.py +74 -0
- package/observability/alert-rules.yml +60 -0
- package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
- package/observability/grafana/dashboards/omnizap-overview.json +170 -0
- package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
- package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
- package/observability/loki-config.yml +38 -0
- package/observability/mysql-exporter.cnf +5 -0
- package/observability/mysql-setup.sql +46 -0
- package/observability/prometheus.yml +32 -0
- package/observability/promtail-config.yml +84 -0
- package/package.json +109 -0
- package/public/api-docs/index.html +144 -0
- package/public/css/github-project-panel.css +297 -0
- package/public/css/stickers-admin.css +1272 -0
- package/public/css/styles.css +671 -0
- package/public/index.html +1311 -0
- package/public/js/apps/apiDocsApp.js +310 -0
- package/public/js/apps/createPackApp.js +2069 -0
- package/public/js/apps/homeApp.js +396 -0
- package/public/js/apps/stickersAdminApp.js +1744 -0
- package/public/js/apps/stickersApp.js +4830 -0
- package/public/js/catalog.js +1019 -0
- package/public/js/github-panel/components/CommitList.js +34 -0
- package/public/js/github-panel/components/ErrorState.js +16 -0
- package/public/js/github-panel/components/GithubProjectPanel.js +106 -0
- package/public/js/github-panel/components/ReleaseList.js +38 -0
- package/public/js/github-panel/components/SkeletonPanel.js +22 -0
- package/public/js/github-panel/components/StatCard.js +15 -0
- package/public/js/github-panel/index.js +15 -0
- package/public/js/github-panel/useGithubRepoData.js +154 -0
- package/public/js/github-panel/vendor/react.js +11 -0
- package/public/js/runtime/react-runtime.js +19 -0
- package/public/licenca/index.html +106 -0
- package/public/stickers/admin/index.html +23 -0
- package/public/stickers/create/index.html +47 -0
- package/public/stickers/index.html +48 -0
- package/public/termos-de-uso/index.html +125 -0
- package/scripts/cache-bust.mjs +107 -0
- package/scripts/deploy.sh +458 -0
- package/scripts/github-deploy-notify.mjs +174 -0
- package/scripts/release.sh +129 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
2
|
+
|
|
3
|
+
const parseJson = (value, fallback = null) => {
|
|
4
|
+
if (value === null || value === undefined) return fallback;
|
|
5
|
+
if (typeof value === 'object') return value;
|
|
6
|
+
if (Buffer.isBuffer(value)) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(value.toString('utf8'));
|
|
9
|
+
} catch {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(value);
|
|
17
|
+
} catch {
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return fallback;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const clampNumber = (value, min, max) => Math.max(min, Math.min(max, Number(value)));
|
|
26
|
+
|
|
27
|
+
const deriveEntropyNormalized = (entropyValue, topLabels = []) => {
|
|
28
|
+
const entropy = Number(entropyValue);
|
|
29
|
+
if (!Number.isFinite(entropy) || entropy <= 0) return 0;
|
|
30
|
+
const k = Array.isArray(topLabels) ? topLabels.length : 0;
|
|
31
|
+
if (k > 1) {
|
|
32
|
+
const maxEntropy = Math.log(k);
|
|
33
|
+
if (maxEntropy > 0) return clampNumber(entropy / maxEntropy, 0, 1);
|
|
34
|
+
}
|
|
35
|
+
const legacyThreshold = 2.5;
|
|
36
|
+
return clampNumber(entropy / legacyThreshold, 0, 1);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const normalizeClassificationRow = (row) => {
|
|
40
|
+
if (!row) return null;
|
|
41
|
+
|
|
42
|
+
const topLabels = parseJson(row.top_labels, []);
|
|
43
|
+
const similarImages = parseJson(row.similar_images, []);
|
|
44
|
+
const llmSubtags = parseJson(row.llm_subtags, []);
|
|
45
|
+
const llmStyleTraits = parseJson(row.llm_style_traits, []);
|
|
46
|
+
const llmEmotions = parseJson(row.llm_emotions, []);
|
|
47
|
+
const llmPackSuggestions = parseJson(row.llm_pack_suggestions, []);
|
|
48
|
+
const entropy = row.entropy !== null && row.entropy !== undefined ? Number(row.entropy) : null;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
asset_id: row.asset_id,
|
|
52
|
+
provider: row.provider || 'clip',
|
|
53
|
+
model_name: row.model_name || null,
|
|
54
|
+
classification_version: row.classification_version || 'v1',
|
|
55
|
+
category: row.category || null,
|
|
56
|
+
confidence: row.confidence !== null && row.confidence !== undefined ? Number(row.confidence) : null,
|
|
57
|
+
entropy,
|
|
58
|
+
entropy_normalized: entropy !== null ? Number(deriveEntropyNormalized(entropy, topLabels).toFixed(6)) : null,
|
|
59
|
+
confidence_margin: row.confidence_margin !== null && row.confidence_margin !== undefined ? Number(row.confidence_margin) : null,
|
|
60
|
+
affinity_weight: row.affinity_weight !== null && row.affinity_weight !== undefined ? Number(row.affinity_weight) : null,
|
|
61
|
+
nsfw_score: row.nsfw_score !== null && row.nsfw_score !== undefined ? Number(row.nsfw_score) : null,
|
|
62
|
+
is_nsfw: row.is_nsfw === 1 || row.is_nsfw === true,
|
|
63
|
+
ambiguous: row.ambiguous === 1 || row.ambiguous === true,
|
|
64
|
+
image_hash: row.image_hash || null,
|
|
65
|
+
all_scores: parseJson(row.all_scores, {}),
|
|
66
|
+
top_labels: Array.isArray(topLabels) ? topLabels : [],
|
|
67
|
+
similar_images: Array.isArray(similarImages) ? similarImages : [],
|
|
68
|
+
llm_subtags: Array.isArray(llmSubtags) ? llmSubtags : [],
|
|
69
|
+
llm_style_traits: Array.isArray(llmStyleTraits) ? llmStyleTraits : [],
|
|
70
|
+
llm_emotions: Array.isArray(llmEmotions) ? llmEmotions : [],
|
|
71
|
+
llm_pack_suggestions: Array.isArray(llmPackSuggestions) ? llmPackSuggestions : [],
|
|
72
|
+
semantic_cluster_id:
|
|
73
|
+
row.semantic_cluster_id !== null && row.semantic_cluster_id !== undefined
|
|
74
|
+
? Number(row.semantic_cluster_id)
|
|
75
|
+
: null,
|
|
76
|
+
semantic_cluster_slug: row.semantic_cluster_slug || null,
|
|
77
|
+
classified_at: row.classified_at,
|
|
78
|
+
updated_at: row.updated_at,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export async function findStickerClassificationByAssetId(assetId, connection = null) {
|
|
83
|
+
const rows = await executeQuery(
|
|
84
|
+
`SELECT * FROM ${TABLES.STICKER_ASSET_CLASSIFICATION} WHERE asset_id = ? LIMIT 1`,
|
|
85
|
+
[assetId],
|
|
86
|
+
connection,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return normalizeClassificationRow(rows?.[0] || null);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function listStickerClassificationsByAssetIds(assetIds, connection = null) {
|
|
93
|
+
if (!Array.isArray(assetIds) || !assetIds.length) return [];
|
|
94
|
+
|
|
95
|
+
const uniqueIds = Array.from(new Set(assetIds.filter(Boolean)));
|
|
96
|
+
if (!uniqueIds.length) return [];
|
|
97
|
+
|
|
98
|
+
const placeholders = uniqueIds.map(() => '?').join(', ');
|
|
99
|
+
const rows = await executeQuery(
|
|
100
|
+
`SELECT * FROM ${TABLES.STICKER_ASSET_CLASSIFICATION} WHERE asset_id IN (${placeholders})`,
|
|
101
|
+
uniqueIds,
|
|
102
|
+
connection,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const normalized = rows.map((row) => normalizeClassificationRow(row));
|
|
106
|
+
const byAssetId = new Map(normalized.map((entry) => [entry.asset_id, entry]));
|
|
107
|
+
return uniqueIds.map((assetId) => byAssetId.get(assetId)).filter(Boolean);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function upsertStickerAssetClassification(payload, connection = null) {
|
|
111
|
+
await executeQuery(
|
|
112
|
+
`INSERT INTO ${TABLES.STICKER_ASSET_CLASSIFICATION}
|
|
113
|
+
(asset_id, provider, model_name, classification_version, category, confidence, entropy, confidence_margin, nsfw_score, is_nsfw, all_scores, top_labels, affinity_weight, image_hash, ambiguous, llm_subtags, llm_style_traits, llm_emotions, llm_pack_suggestions, semantic_cluster_id, semantic_cluster_slug, similar_images, classified_at)
|
|
114
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
115
|
+
ON DUPLICATE KEY UPDATE
|
|
116
|
+
provider = VALUES(provider),
|
|
117
|
+
model_name = VALUES(model_name),
|
|
118
|
+
classification_version = VALUES(classification_version),
|
|
119
|
+
category = VALUES(category),
|
|
120
|
+
confidence = VALUES(confidence),
|
|
121
|
+
entropy = VALUES(entropy),
|
|
122
|
+
confidence_margin = VALUES(confidence_margin),
|
|
123
|
+
nsfw_score = VALUES(nsfw_score),
|
|
124
|
+
is_nsfw = VALUES(is_nsfw),
|
|
125
|
+
all_scores = VALUES(all_scores),
|
|
126
|
+
top_labels = VALUES(top_labels),
|
|
127
|
+
affinity_weight = VALUES(affinity_weight),
|
|
128
|
+
image_hash = VALUES(image_hash),
|
|
129
|
+
ambiguous = VALUES(ambiguous),
|
|
130
|
+
llm_subtags = VALUES(llm_subtags),
|
|
131
|
+
llm_style_traits = VALUES(llm_style_traits),
|
|
132
|
+
llm_emotions = VALUES(llm_emotions),
|
|
133
|
+
llm_pack_suggestions = VALUES(llm_pack_suggestions),
|
|
134
|
+
semantic_cluster_id = VALUES(semantic_cluster_id),
|
|
135
|
+
semantic_cluster_slug = VALUES(semantic_cluster_slug),
|
|
136
|
+
similar_images = VALUES(similar_images),
|
|
137
|
+
classified_at = CURRENT_TIMESTAMP,
|
|
138
|
+
updated_at = CURRENT_TIMESTAMP`,
|
|
139
|
+
[
|
|
140
|
+
payload.asset_id,
|
|
141
|
+
payload.provider || 'clip',
|
|
142
|
+
payload.model_name || null,
|
|
143
|
+
payload.classification_version || 'v1',
|
|
144
|
+
payload.category || null,
|
|
145
|
+
payload.confidence ?? null,
|
|
146
|
+
payload.entropy ?? null,
|
|
147
|
+
payload.confidence_margin ?? null,
|
|
148
|
+
payload.nsfw_score ?? null,
|
|
149
|
+
payload.is_nsfw ? 1 : 0,
|
|
150
|
+
payload.all_scores ? JSON.stringify(payload.all_scores) : JSON.stringify({}),
|
|
151
|
+
payload.top_labels ? JSON.stringify(payload.top_labels) : JSON.stringify([]),
|
|
152
|
+
payload.affinity_weight ?? null,
|
|
153
|
+
payload.image_hash || null,
|
|
154
|
+
payload.ambiguous ? 1 : 0,
|
|
155
|
+
payload.llm_subtags ? JSON.stringify(payload.llm_subtags) : JSON.stringify([]),
|
|
156
|
+
payload.llm_style_traits ? JSON.stringify(payload.llm_style_traits) : JSON.stringify([]),
|
|
157
|
+
payload.llm_emotions ? JSON.stringify(payload.llm_emotions) : JSON.stringify([]),
|
|
158
|
+
payload.llm_pack_suggestions ? JSON.stringify(payload.llm_pack_suggestions) : JSON.stringify([]),
|
|
159
|
+
payload.semantic_cluster_id ?? null,
|
|
160
|
+
payload.semantic_cluster_slug || null,
|
|
161
|
+
payload.similar_images ? JSON.stringify(payload.similar_images) : JSON.stringify([]),
|
|
162
|
+
],
|
|
163
|
+
connection,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return findStickerClassificationByAssetId(payload.asset_id, connection);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function updateStickerClassificationSemanticCluster(
|
|
170
|
+
assetId,
|
|
171
|
+
{ semanticClusterId = null, semanticClusterSlug = null } = {},
|
|
172
|
+
connection = null,
|
|
173
|
+
) {
|
|
174
|
+
if (!assetId) return null;
|
|
175
|
+
|
|
176
|
+
await executeQuery(
|
|
177
|
+
`UPDATE ${TABLES.STICKER_ASSET_CLASSIFICATION}
|
|
178
|
+
SET semantic_cluster_id = ?, semantic_cluster_slug = ?, updated_at = CURRENT_TIMESTAMP
|
|
179
|
+
WHERE asset_id = ?`,
|
|
180
|
+
[
|
|
181
|
+
semanticClusterId ?? null,
|
|
182
|
+
semanticClusterSlug || null,
|
|
183
|
+
assetId,
|
|
184
|
+
],
|
|
185
|
+
connection,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return findStickerClassificationByAssetId(assetId, connection);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export async function listClipImageEmbeddingsByImageHashes(imageHashes, connection = null) {
|
|
192
|
+
const uniqueHashes = Array.from(
|
|
193
|
+
new Set((Array.isArray(imageHashes) ? imageHashes : [])
|
|
194
|
+
.map((value) => String(value || '').trim().toLowerCase())
|
|
195
|
+
.filter((value) => value.length === 64)),
|
|
196
|
+
);
|
|
197
|
+
if (!uniqueHashes.length) return [];
|
|
198
|
+
|
|
199
|
+
const placeholders = uniqueHashes.map(() => '?').join(', ');
|
|
200
|
+
try {
|
|
201
|
+
const rows = await executeQuery(
|
|
202
|
+
`SELECT image_hash, embedding, embedding_dim
|
|
203
|
+
FROM clip_image_embedding_cache
|
|
204
|
+
WHERE image_hash IN (${placeholders})`,
|
|
205
|
+
uniqueHashes,
|
|
206
|
+
connection,
|
|
207
|
+
);
|
|
208
|
+
return Array.isArray(rows) ? rows : [];
|
|
209
|
+
} catch {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function deleteStickerAssetClassificationByAssetId(assetId, connection = null) {
|
|
215
|
+
const result = await executeQuery(
|
|
216
|
+
`DELETE FROM ${TABLES.STICKER_ASSET_CLASSIFICATION}
|
|
217
|
+
WHERE asset_id = ?`,
|
|
218
|
+
[assetId],
|
|
219
|
+
connection,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
return Number(result?.affectedRows || 0);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const clampInt = (value, fallback, min, max) => {
|
|
226
|
+
const numeric = Number(value);
|
|
227
|
+
if (!Number.isFinite(numeric)) return fallback;
|
|
228
|
+
return Math.max(min, Math.min(max, Math.floor(numeric)));
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export async function listAssetsForModelUpgradeReprocess(
|
|
232
|
+
{ currentVersion, limit = 150, offset = 0 } = {},
|
|
233
|
+
connection = null,
|
|
234
|
+
) {
|
|
235
|
+
const normalizedVersion = String(currentVersion || '').trim();
|
|
236
|
+
if (!normalizedVersion) return [];
|
|
237
|
+
|
|
238
|
+
const safeLimit = clampInt(limit, 150, 1, 1000);
|
|
239
|
+
const safeOffset = clampInt(offset, 0, 0, 500000);
|
|
240
|
+
const rows = await executeQuery(
|
|
241
|
+
`SELECT c.asset_id
|
|
242
|
+
FROM ${TABLES.STICKER_ASSET_CLASSIFICATION} c
|
|
243
|
+
LEFT JOIN ${TABLES.STICKER_ASSET_REPROCESS_QUEUE} q
|
|
244
|
+
ON q.asset_id = c.asset_id
|
|
245
|
+
AND q.reason = 'MODEL_UPGRADE'
|
|
246
|
+
AND q.status IN ('pending', 'processing')
|
|
247
|
+
WHERE c.classification_version <> ?
|
|
248
|
+
AND q.id IS NULL
|
|
249
|
+
ORDER BY c.updated_at ASC
|
|
250
|
+
LIMIT ${safeLimit} OFFSET ${safeOffset}`,
|
|
251
|
+
[normalizedVersion],
|
|
252
|
+
connection,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
return rows.map((row) => row.asset_id).filter(Boolean);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function listAssetsForLowConfidenceReprocess(
|
|
259
|
+
{ confidenceThreshold = 0.65, staleHours = 48, limit = 150, offset = 0 } = {},
|
|
260
|
+
connection = null,
|
|
261
|
+
) {
|
|
262
|
+
const threshold = Number(confidenceThreshold);
|
|
263
|
+
if (!Number.isFinite(threshold)) return [];
|
|
264
|
+
|
|
265
|
+
const safeStaleHours = clampInt(staleHours, 48, 1, 24 * 365);
|
|
266
|
+
const safeLimit = clampInt(limit, 150, 1, 1000);
|
|
267
|
+
const safeOffset = clampInt(offset, 0, 0, 500000);
|
|
268
|
+
|
|
269
|
+
const rows = await executeQuery(
|
|
270
|
+
`SELECT c.asset_id
|
|
271
|
+
FROM ${TABLES.STICKER_ASSET_CLASSIFICATION} c
|
|
272
|
+
LEFT JOIN ${TABLES.STICKER_ASSET_REPROCESS_QUEUE} q
|
|
273
|
+
ON q.asset_id = c.asset_id
|
|
274
|
+
AND q.reason = 'LOW_CONFIDENCE'
|
|
275
|
+
AND q.status IN ('pending', 'processing')
|
|
276
|
+
WHERE c.confidence IS NOT NULL
|
|
277
|
+
AND c.confidence < ?
|
|
278
|
+
AND c.updated_at <= (UTC_TIMESTAMP() - INTERVAL ${safeStaleHours} HOUR)
|
|
279
|
+
AND q.id IS NULL
|
|
280
|
+
ORDER BY c.confidence ASC, c.updated_at ASC
|
|
281
|
+
LIMIT ${safeLimit} OFFSET ${safeOffset}`,
|
|
282
|
+
[threshold],
|
|
283
|
+
connection,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
return rows.map((row) => row.asset_id).filter(Boolean);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function listAssetsForPrioritySignalBackfillReprocess(
|
|
290
|
+
{ limit = 200, offset = 0 } = {},
|
|
291
|
+
connection = null,
|
|
292
|
+
) {
|
|
293
|
+
const safeLimit = clampInt(limit, 200, 1, 2000);
|
|
294
|
+
const safeOffset = clampInt(offset, 0, 0, 500000);
|
|
295
|
+
const rows = await executeQuery(
|
|
296
|
+
`SELECT
|
|
297
|
+
c.asset_id,
|
|
298
|
+
MAX(
|
|
299
|
+
(CASE WHEN c.category IS NULL OR TRIM(c.category) = '' THEN 1 ELSE 0 END)
|
|
300
|
+
+ (CASE WHEN c.confidence IS NULL THEN 1 ELSE 0 END)
|
|
301
|
+
+ (CASE WHEN c.entropy IS NULL THEN 1 ELSE 0 END)
|
|
302
|
+
+ (CASE WHEN c.confidence_margin IS NULL THEN 1 ELSE 0 END)
|
|
303
|
+
+ (CASE WHEN c.affinity_weight IS NULL THEN 1 ELSE 0 END)
|
|
304
|
+
+ (CASE WHEN c.image_hash IS NULL OR TRIM(c.image_hash) = '' THEN 1 ELSE 0 END)
|
|
305
|
+
+ (CASE WHEN COALESCE(JSON_LENGTH(c.all_scores), 0) = 0 THEN 1 ELSE 0 END)
|
|
306
|
+
+ (CASE WHEN COALESCE(JSON_LENGTH(c.top_labels), 0) = 0 THEN 1 ELSE 0 END)
|
|
307
|
+
+ (CASE WHEN COALESCE(JSON_LENGTH(c.llm_subtags), 0) = 0 THEN 1 ELSE 0 END)
|
|
308
|
+
+ (CASE WHEN COALESCE(JSON_LENGTH(c.llm_style_traits), 0) = 0 THEN 1 ELSE 0 END)
|
|
309
|
+
+ (CASE WHEN COALESCE(JSON_LENGTH(c.llm_emotions), 0) = 0 THEN 1 ELSE 0 END)
|
|
310
|
+
+ (CASE WHEN COALESCE(JSON_LENGTH(c.llm_pack_suggestions), 0) = 0 THEN 1 ELSE 0 END)
|
|
311
|
+
+ (CASE WHEN c.semantic_cluster_id IS NULL THEN 1 ELSE 0 END)
|
|
312
|
+
+ (CASE WHEN c.semantic_cluster_slug IS NULL OR TRIM(c.semantic_cluster_slug) = '' THEN 1 ELSE 0 END)
|
|
313
|
+
) AS missing_signal_score
|
|
314
|
+
FROM ${TABLES.STICKER_ASSET_CLASSIFICATION} c
|
|
315
|
+
LEFT JOIN ${TABLES.STICKER_PACK_ITEM} i ON i.sticker_id = c.asset_id
|
|
316
|
+
LEFT JOIN ${TABLES.STICKER_PACK} p ON p.id = i.pack_id AND p.deleted_at IS NULL
|
|
317
|
+
LEFT JOIN ${TABLES.STICKER_PACK_ENGAGEMENT} e ON e.pack_id = p.id
|
|
318
|
+
LEFT JOIN ${TABLES.STICKER_ASSET_REPROCESS_QUEUE} q
|
|
319
|
+
ON q.asset_id = c.asset_id
|
|
320
|
+
AND q.reason = 'MODEL_UPGRADE'
|
|
321
|
+
AND q.status IN ('pending', 'processing')
|
|
322
|
+
WHERE q.id IS NULL
|
|
323
|
+
GROUP BY c.asset_id
|
|
324
|
+
HAVING missing_signal_score > 0
|
|
325
|
+
ORDER BY
|
|
326
|
+
missing_signal_score DESC,
|
|
327
|
+
MAX(CASE WHEN p.pack_status = 'ready' THEN 1 ELSE 0 END) DESC,
|
|
328
|
+
MAX(CASE WHEN p.visibility = 'public' THEN 1 ELSE 0 END) DESC,
|
|
329
|
+
MAX(COALESCE(e.like_count, 0) + COALESCE(e.open_count, 0) * 0.02) DESC,
|
|
330
|
+
COUNT(i.pack_id) DESC,
|
|
331
|
+
MAX(c.updated_at) ASC
|
|
332
|
+
LIMIT ${safeLimit} OFFSET ${safeOffset}`,
|
|
333
|
+
[],
|
|
334
|
+
connection,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
return rows.map((row) => row.asset_id).filter(Boolean);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export async function listStickerClassificationsForDeterministicReprocess(
|
|
341
|
+
{
|
|
342
|
+
limit = 250,
|
|
343
|
+
cursorAssetId = '',
|
|
344
|
+
entropyThreshold = 0.8,
|
|
345
|
+
affinityThreshold = 0.3,
|
|
346
|
+
} = {},
|
|
347
|
+
connection = null,
|
|
348
|
+
) {
|
|
349
|
+
const safeLimit = clampInt(limit, 250, 1, 2000);
|
|
350
|
+
const normalizedCursor = String(cursorAssetId || '').trim();
|
|
351
|
+
const normalizedEntropyThreshold = Number.isFinite(Number(entropyThreshold))
|
|
352
|
+
? Number(entropyThreshold)
|
|
353
|
+
: 0.8;
|
|
354
|
+
const normalizedAffinityThreshold = Number.isFinite(Number(affinityThreshold))
|
|
355
|
+
? Number(affinityThreshold)
|
|
356
|
+
: 0.3;
|
|
357
|
+
|
|
358
|
+
const params = [normalizedEntropyThreshold, normalizedAffinityThreshold];
|
|
359
|
+
const cursorClause = normalizedCursor ? 'AND c.asset_id > ?' : '';
|
|
360
|
+
if (normalizedCursor) {
|
|
361
|
+
params.push(normalizedCursor);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const rows = await executeQuery(
|
|
365
|
+
`SELECT
|
|
366
|
+
c.asset_id,
|
|
367
|
+
c.top_labels,
|
|
368
|
+
c.llm_subtags,
|
|
369
|
+
c.llm_style_traits,
|
|
370
|
+
c.llm_emotions,
|
|
371
|
+
c.llm_pack_suggestions,
|
|
372
|
+
c.affinity_weight,
|
|
373
|
+
c.entropy,
|
|
374
|
+
c.ambiguous
|
|
375
|
+
FROM ${TABLES.STICKER_ASSET_CLASSIFICATION} c
|
|
376
|
+
WHERE (
|
|
377
|
+
c.ambiguous = 1
|
|
378
|
+
OR COALESCE(c.entropy, 0) > ?
|
|
379
|
+
OR COALESCE(c.affinity_weight, 0) < ?
|
|
380
|
+
)
|
|
381
|
+
${cursorClause}
|
|
382
|
+
ORDER BY c.asset_id ASC
|
|
383
|
+
LIMIT ${safeLimit}`,
|
|
384
|
+
params,
|
|
385
|
+
connection,
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
return rows.map((row) => normalizeClassificationRow(row));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export async function updateStickerClassificationDeterministicSignals(
|
|
392
|
+
assetId,
|
|
393
|
+
{ llmSubtags = [], affinityWeight = null, ambiguous = 0 } = {},
|
|
394
|
+
connection = null,
|
|
395
|
+
) {
|
|
396
|
+
if (!assetId) return null;
|
|
397
|
+
const normalizedAffinityWeight = Number.isFinite(Number(affinityWeight))
|
|
398
|
+
? Math.max(0, Math.min(1, Number(affinityWeight)))
|
|
399
|
+
: null;
|
|
400
|
+
const normalizedAmbiguous = ambiguous === 1 || ambiguous === true ? 1 : 0;
|
|
401
|
+
const normalizedSubtags = Array.isArray(llmSubtags)
|
|
402
|
+
? llmSubtags.map((value) => String(value || '').trim()).filter(Boolean)
|
|
403
|
+
: [];
|
|
404
|
+
|
|
405
|
+
await executeQuery(
|
|
406
|
+
`UPDATE ${TABLES.STICKER_ASSET_CLASSIFICATION}
|
|
407
|
+
SET llm_subtags = ?, affinity_weight = ?, ambiguous = ?, updated_at = CURRENT_TIMESTAMP
|
|
408
|
+
WHERE asset_id = ?`,
|
|
409
|
+
[
|
|
410
|
+
JSON.stringify(normalizedSubtags),
|
|
411
|
+
normalizedAffinityWeight,
|
|
412
|
+
normalizedAmbiguous,
|
|
413
|
+
assetId,
|
|
414
|
+
],
|
|
415
|
+
connection,
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
return findStickerClassificationByAssetId(assetId, connection);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export async function listClassificationCategoryDistribution({ days = 7 } = {}, connection = null) {
|
|
422
|
+
const safeDays = clampInt(days, 7, 1, 365);
|
|
423
|
+
const rows = await executeQuery(
|
|
424
|
+
`SELECT
|
|
425
|
+
LOWER(TRIM(COALESCE(category, 'unknown'))) AS category,
|
|
426
|
+
COUNT(*) AS total
|
|
427
|
+
FROM ${TABLES.STICKER_ASSET_CLASSIFICATION}
|
|
428
|
+
WHERE updated_at >= (UTC_TIMESTAMP() - INTERVAL ${safeDays} DAY)
|
|
429
|
+
GROUP BY LOWER(TRIM(COALESCE(category, 'unknown')))`,
|
|
430
|
+
[],
|
|
431
|
+
connection,
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
const distribution = new Map();
|
|
435
|
+
let total = 0;
|
|
436
|
+
for (const row of rows) {
|
|
437
|
+
const category = String(row.category || 'unknown').trim() || 'unknown';
|
|
438
|
+
const count = Number(row.total || 0);
|
|
439
|
+
if (!count) continue;
|
|
440
|
+
distribution.set(category, count);
|
|
441
|
+
total += count;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
days: safeDays,
|
|
446
|
+
total,
|
|
447
|
+
categories: distribution,
|
|
448
|
+
};
|
|
449
|
+
}
|