@kaikybrofc/omnizap-system 2.2.9 → 2.3.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.
- package/README.md +20 -18
- package/app/config/adminIdentity.js +1 -3
- package/app/connection/socketController.js +10 -20
- package/app/controllers/messageController.js +7 -28
- package/app/modules/aiModule/catCommand.js +29 -192
- package/app/modules/broadcastModule/noticeCommand.js +28 -97
- package/app/modules/gameModule/diceCommand.js +6 -32
- package/app/modules/playModule/playCommand.js +57 -258
- package/app/modules/quoteModule/quoteCommand.js +2 -4
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1 -13
- package/app/modules/statsModule/noMessageCommand.js +16 -84
- package/app/modules/statsModule/rankingCommand.js +5 -25
- package/app/modules/statsModule/rankingCommon.js +1 -9
- package/app/modules/stickerModule/convertToWebp.js +4 -27
- package/app/modules/stickerModule/stickerCommand.js +13 -24
- package/app/modules/stickerModule/stickerTextCommand.js +13 -25
- package/app/modules/stickerPackModule/autoPackCollectorService.js +16 -7
- package/app/modules/stickerPackModule/domainEventOutboxRepository.js +20 -36
- package/app/modules/stickerPackModule/domainEvents.js +2 -11
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +13 -50
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +2 -15
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +14 -41
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +25 -95
- package/app/modules/stickerPackModule/stickerAssetRepository.js +12 -31
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +13 -18
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +284 -709
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +27 -106
- package/app/modules/stickerPackModule/stickerClassificationService.js +46 -77
- package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +13 -53
- package/app/modules/stickerPackModule/stickerDomainEventBus.js +10 -16
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +13 -34
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +1 -4
- package/app/modules/stickerPackModule/stickerObjectStorageService.js +26 -26
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +32 -187
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +6 -15
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +6 -32
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +12 -36
- package/app/modules/stickerPackModule/stickerPackMessageService.js +12 -40
- package/app/modules/stickerPackModule/stickerPackRepository.js +23 -66
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +9 -21
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +10 -40
- package/app/modules/stickerPackModule/stickerPackService.js +50 -115
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +2 -21
- package/app/modules/stickerPackModule/stickerPackUtils.js +13 -3
- package/app/modules/stickerPackModule/stickerStorageService.js +16 -65
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +4 -22
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +14 -29
- package/app/modules/systemMetricsModule/pingCommand.js +9 -39
- package/app/modules/tiktokModule/tiktokCommand.js +17 -109
- package/app/modules/userModule/userCommand.js +2 -88
- package/app/observability/metrics.js +5 -16
- package/app/services/captchaService.js +1 -6
- package/app/services/dbWriteQueue.js +3 -18
- package/app/services/featureFlagService.js +2 -8
- package/app/services/newsBroadcastService.js +0 -1
- package/app/services/queueUtils.js +2 -4
- package/app/services/whatsappLoginLinkService.js +7 -9
- package/app/store/premiumUserStore.js +1 -2
- package/app/utils/antiLink/antiLinkModule.js +3 -233
- package/app/utils/logger/loggerModule.js +9 -34
- package/app/utils/systemMetrics/systemMetricsModule.js +1 -4
- package/database/init.js +1 -8
- package/docker-compose.yml +27 -27
- package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +220 -0
- package/docs/seo/satellite-page-template.md +91 -0
- package/docs/seo/satellite-pages-phase1.json +349 -0
- package/eslint.config.js +2 -15
- package/index.js +8 -36
- package/ml/clip_classifier/README.md +4 -6
- package/observability/alert-rules.yml +12 -12
- package/observability/grafana/provisioning/dashboards/dashboards.yml +1 -1
- package/package.json +8 -3
- package/public/api-docs/index.html +224 -141
- package/public/bot-whatsapp-para-grupo/index.html +306 -0
- package/public/bot-whatsapp-sem-programar/index.html +306 -0
- package/public/comandos/index.html +428 -0
- package/public/como-automatizar-avisos-no-whatsapp/index.html +306 -0
- package/public/como-criar-comandos-whatsapp/index.html +306 -0
- package/public/como-evitar-spam-no-whatsapp/index.html +306 -0
- package/public/como-moderar-grupo-whatsapp/index.html +306 -0
- package/public/como-organizar-comunidade-whatsapp/index.html +306 -0
- package/public/css/github-project-panel.css +20 -15
- package/public/css/stickers-admin.css +55 -39
- package/public/css/styles.css +37 -29
- package/public/index.html +1060 -1417
- package/public/js/apps/apiDocsApp.js +36 -153
- package/public/js/apps/createPackApp.js +69 -332
- package/public/js/apps/homeApp.js +201 -434
- package/public/js/apps/loginApp.js +3 -12
- package/public/js/apps/stickersAdminApp.js +190 -181
- package/public/js/apps/stickersApp.js +507 -1366
- package/public/js/catalog.js +11 -74
- package/public/js/github-panel/components/ErrorState.js +1 -8
- package/public/js/github-panel/components/GithubProjectPanel.js +2 -9
- package/public/js/github-panel/components/SkeletonPanel.js +1 -11
- package/public/js/github-panel/components/StatCard.js +1 -7
- package/public/js/github-panel/vendor/react.js +1 -9
- package/public/js/runtime/react-runtime.js +1 -9
- package/public/licenca/index.html +104 -86
- package/public/login/index.html +315 -321
- package/public/melhor-bot-whatsapp-para-grupos/index.html +306 -0
- package/public/sitemap.xml +45 -0
- package/public/stickers/admin/index.html +14 -19
- package/public/stickers/create/index.html +39 -43
- package/public/stickers/index.html +97 -41
- package/public/termos-de-uso/index.html +142 -115
- package/public/user/index.html +347 -346
- package/scripts/cache-bust.mjs +5 -24
- package/scripts/generate-seo-satellite-pages.mjs +431 -0
- package/scripts/run-prettier-all.mjs +25 -0
- package/scripts/sticker-catalog-loadtest.mjs +13 -11
- package/scripts/sticker-worker-task.mjs +1 -4
- package/scripts/sync-readme-snapshot.mjs +3 -2
- package/server/controllers/stickerCatalogController.js +407 -704
- package/server/http/httpServer.js +2 -10
- package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
- package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
- package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
- package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
- package/server/routes/stickerCatalog/catalogRouter.js +11 -13
- package/kaikybrofc-omnizap-system-2.2.9.tgz +0 -0
|
@@ -11,40 +11,15 @@ const parseEnvBool = (value, fallback) => {
|
|
|
11
11
|
const clampNumber = (value, min, max) => Math.max(min, Math.min(max, Number(value)));
|
|
12
12
|
|
|
13
13
|
const RECLASSIFICATION_ENABLED = parseEnvBool(process.env.STICKER_SEMANTIC_RECLASSIFICATION_ENABLED, true);
|
|
14
|
-
const RECLASSIFICATION_BATCH_SIZE = Math.max(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
22
|
-
const RECLASSIFICATION_ENTROPY_THRESHOLD = Number.isFinite(Number(process.env.STICKER_SEMANTIC_RECLASSIFICATION_ENTROPY_THRESHOLD))
|
|
23
|
-
? Number(process.env.STICKER_SEMANTIC_RECLASSIFICATION_ENTROPY_THRESHOLD)
|
|
24
|
-
: 0.8;
|
|
25
|
-
const RECLASSIFICATION_AFFINITY_THRESHOLD = Number.isFinite(Number(process.env.STICKER_SEMANTIC_RECLASSIFICATION_AFFINITY_THRESHOLD))
|
|
26
|
-
? Number(process.env.STICKER_SEMANTIC_RECLASSIFICATION_AFFINITY_THRESHOLD)
|
|
27
|
-
: 0.3;
|
|
28
|
-
|
|
29
|
-
const STOPWORDS = [
|
|
30
|
-
'image',
|
|
31
|
-
'sticker',
|
|
32
|
-
'wallpaper',
|
|
33
|
-
'social_media',
|
|
34
|
-
'internet',
|
|
35
|
-
'picture',
|
|
36
|
-
];
|
|
37
|
-
const GENERIC_TERMS = [
|
|
38
|
-
'cool',
|
|
39
|
-
'nice',
|
|
40
|
-
'funny',
|
|
41
|
-
'random',
|
|
42
|
-
'art',
|
|
43
|
-
];
|
|
14
|
+
const RECLASSIFICATION_BATCH_SIZE = Math.max(50, Math.min(2000, Number(process.env.STICKER_SEMANTIC_RECLASSIFICATION_BATCH_SIZE) || 400));
|
|
15
|
+
const RECLASSIFICATION_MAX_PER_CYCLE = Math.max(100, Math.min(20_000, Number(process.env.STICKER_SEMANTIC_RECLASSIFICATION_MAX_PER_CYCLE) || 2000));
|
|
16
|
+
const RECLASSIFICATION_ENTROPY_THRESHOLD = Number.isFinite(Number(process.env.STICKER_SEMANTIC_RECLASSIFICATION_ENTROPY_THRESHOLD)) ? Number(process.env.STICKER_SEMANTIC_RECLASSIFICATION_ENTROPY_THRESHOLD) : 0.8;
|
|
17
|
+
const RECLASSIFICATION_AFFINITY_THRESHOLD = Number.isFinite(Number(process.env.STICKER_SEMANTIC_RECLASSIFICATION_AFFINITY_THRESHOLD)) ? Number(process.env.STICKER_SEMANTIC_RECLASSIFICATION_AFFINITY_THRESHOLD) : 0.3;
|
|
18
|
+
|
|
19
|
+
const STOPWORDS = ['image', 'sticker', 'wallpaper', 'social_media', 'internet', 'picture'];
|
|
20
|
+
const GENERIC_TERMS = ['cool', 'nice', 'funny', 'random', 'art'];
|
|
44
21
|
const SEMANTIC_GROUPS = ['anime', 'meme', 'kawaii', 'horror', 'reaction'];
|
|
45
|
-
const OPPOSITE_THEME_PAIRS = [
|
|
46
|
-
['kawaii', 'horror'],
|
|
47
|
-
];
|
|
22
|
+
const OPPOSITE_THEME_PAIRS = [['kawaii', 'horror']];
|
|
48
23
|
|
|
49
24
|
const DICTIONARY_MAP = {
|
|
50
25
|
'cute anime girl': 'kawaii_anime_girl',
|
|
@@ -66,15 +41,13 @@ const toSnakeCase = (value) =>
|
|
|
66
41
|
|
|
67
42
|
const STOPWORD_PHRASES = new Set(STOPWORDS.map((value) => toSnakeCase(value)).filter(Boolean));
|
|
68
43
|
const STOPWORD_WORDS = new Set(
|
|
69
|
-
STOPWORDS
|
|
70
|
-
.map((value) => toSnakeCase(value))
|
|
44
|
+
STOPWORDS.map((value) => toSnakeCase(value))
|
|
71
45
|
.flatMap((value) => value.split('_'))
|
|
72
46
|
.filter((value) => value.length >= 3),
|
|
73
47
|
);
|
|
74
48
|
const GENERIC_TERM_SET = new Set(GENERIC_TERMS.map((value) => toSnakeCase(value)).filter(Boolean));
|
|
75
49
|
const GENERIC_TERM_WORDS = new Set(
|
|
76
|
-
GENERIC_TERMS
|
|
77
|
-
.map((value) => toSnakeCase(value))
|
|
50
|
+
GENERIC_TERMS.map((value) => toSnakeCase(value))
|
|
78
51
|
.flatMap((value) => value.split('_'))
|
|
79
52
|
.filter((value) => value.length >= 3),
|
|
80
53
|
);
|
|
@@ -342,9 +315,7 @@ export const reclassify = (classification = {}) => {
|
|
|
342
315
|
dominantTokens = mappedTokens.slice();
|
|
343
316
|
}
|
|
344
317
|
|
|
345
|
-
const normalizedSubtags = dominantTokens
|
|
346
|
-
.map((entry) => normalizeTokenValue(entry.token))
|
|
347
|
-
.filter(Boolean);
|
|
318
|
+
const normalizedSubtags = dominantTokens.map((entry) => normalizeTokenValue(entry.token)).filter(Boolean);
|
|
348
319
|
|
|
349
320
|
const outputSubtags = Array.from(new Set(normalizedSubtags));
|
|
350
321
|
const updatedAffinityWeight = Number(clampNumber(cohesionScore / 100, 0, 1).toFixed(6));
|
|
@@ -358,12 +329,7 @@ export const reclassify = (classification = {}) => {
|
|
|
358
329
|
};
|
|
359
330
|
};
|
|
360
331
|
|
|
361
|
-
export const batchReprocess = async ({
|
|
362
|
-
maxItems = RECLASSIFICATION_MAX_PER_CYCLE,
|
|
363
|
-
batchSize = RECLASSIFICATION_BATCH_SIZE,
|
|
364
|
-
entropyThreshold = RECLASSIFICATION_ENTROPY_THRESHOLD,
|
|
365
|
-
affinityThreshold = RECLASSIFICATION_AFFINITY_THRESHOLD,
|
|
366
|
-
} = {}) => {
|
|
332
|
+
export const batchReprocess = async ({ maxItems = RECLASSIFICATION_MAX_PER_CYCLE, batchSize = RECLASSIFICATION_BATCH_SIZE, entropyThreshold = RECLASSIFICATION_ENTROPY_THRESHOLD, affinityThreshold = RECLASSIFICATION_AFFINITY_THRESHOLD } = {}) => {
|
|
367
333
|
const safeMaxItems = Math.max(0, Math.min(50_000, Number(maxItems) || RECLASSIFICATION_MAX_PER_CYCLE));
|
|
368
334
|
const safeBatchSize = Math.max(1, Math.min(2000, Number(batchSize) || RECLASSIFICATION_BATCH_SIZE));
|
|
369
335
|
|
|
@@ -417,10 +383,7 @@ export const batchReprocess = async ({
|
|
|
417
383
|
const currentAmbiguous = row?.ambiguous ? 1 : 0;
|
|
418
384
|
const nextAmbiguous = output.ambiguous ? 1 : 0;
|
|
419
385
|
|
|
420
|
-
const shouldUpdate =
|
|
421
|
-
currentAmbiguous !== nextAmbiguous
|
|
422
|
-
|| hasNumericDifference(currentAffinity, nextAffinity)
|
|
423
|
-
|| !areListsEqual(currentSubtags, nextSubtags);
|
|
386
|
+
const shouldUpdate = currentAmbiguous !== nextAmbiguous || hasNumericDifference(currentAffinity, nextAffinity) || !areListsEqual(currentSubtags, nextSubtags);
|
|
424
387
|
|
|
425
388
|
if (!shouldUpdate) {
|
|
426
389
|
stats.skipped += 1;
|
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
applyDictionaryMapping,
|
|
6
|
-
calculateCohesion,
|
|
7
|
-
detectConflict,
|
|
8
|
-
detectDominantTheme,
|
|
9
|
-
normalizeTokens,
|
|
10
|
-
reclassify,
|
|
11
|
-
} from './semanticReclassificationEngine.js';
|
|
4
|
+
import { applyDictionaryMapping, calculateCohesion, detectConflict, detectDominantTheme, normalizeTokens, reclassify } from './semanticReclassificationEngine.js';
|
|
12
5
|
|
|
13
6
|
test('normalizeTokens deve limpar termos genéricos, stopwords e duplicatas', () => {
|
|
14
7
|
const tokens = normalizeTokens({
|
|
15
|
-
llm_subtags: [
|
|
16
|
-
'Cute Anime Girl Sticker',
|
|
17
|
-
'cute anime girl',
|
|
18
|
-
'image',
|
|
19
|
-
'co',
|
|
20
|
-
'random art',
|
|
21
|
-
],
|
|
8
|
+
llm_subtags: ['Cute Anime Girl Sticker', 'cute anime girl', 'image', 'co', 'random art'],
|
|
22
9
|
llm_style_traits: ['Kawaii style'],
|
|
23
10
|
llm_emotions: ['happy face'],
|
|
24
11
|
llm_pack_suggestions: ['social media picture'],
|
|
@@ -4,10 +4,7 @@ import OpenAI from 'openai';
|
|
|
4
4
|
|
|
5
5
|
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
6
6
|
import logger from '../../utils/logger/loggerModule.js';
|
|
7
|
-
import {
|
|
8
|
-
findStickerClassificationByAssetId,
|
|
9
|
-
updateStickerClassificationSemanticCluster,
|
|
10
|
-
} from './stickerAssetClassificationRepository.js';
|
|
7
|
+
import { findStickerClassificationByAssetId, updateStickerClassificationSemanticCluster } from './stickerAssetClassificationRepository.js';
|
|
11
8
|
|
|
12
9
|
const parseEnvBool = (value, fallback) => {
|
|
13
10
|
if (value === undefined || value === null || value === '') return fallback;
|
|
@@ -19,12 +16,9 @@ const parseEnvBool = (value, fallback) => {
|
|
|
19
16
|
|
|
20
17
|
const ENABLE_SEMANTIC_CLUSTERING = parseEnvBool(process.env.ENABLE_SEMANTIC_CLUSTERING, false);
|
|
21
18
|
const OPENAI_TIMEOUT_MS = Math.max(1_000, Number(process.env.SEMANTIC_CLUSTER_OPENAI_TIMEOUT_MS) || 10_000);
|
|
22
|
-
const EMBEDDING_MODEL = String(process.env.SEMANTIC_CLUSTER_EMBEDDING_MODEL || 'text-embedding-3-small').trim()
|
|
23
|
-
|| 'text-embedding-3-small';
|
|
19
|
+
const EMBEDDING_MODEL = String(process.env.SEMANTIC_CLUSTER_EMBEDDING_MODEL || 'text-embedding-3-small').trim() || 'text-embedding-3-small';
|
|
24
20
|
const SLUG_MODEL = String(process.env.SEMANTIC_CLUSTER_SLUG_MODEL || 'gpt-4o-mini').trim() || 'gpt-4o-mini';
|
|
25
|
-
const SIMILARITY_THRESHOLD = Number.isFinite(Number(process.env.SEMANTIC_CLUSTER_SIMILARITY_THRESHOLD))
|
|
26
|
-
? Math.max(0.5, Math.min(0.99, Number(process.env.SEMANTIC_CLUSTER_SIMILARITY_THRESHOLD)))
|
|
27
|
-
: 0.87;
|
|
21
|
+
const SIMILARITY_THRESHOLD = Number.isFinite(Number(process.env.SEMANTIC_CLUSTER_SIMILARITY_THRESHOLD)) ? Math.max(0.5, Math.min(0.99, Number(process.env.SEMANTIC_CLUSTER_SIMILARITY_THRESHOLD))) : 0.87;
|
|
28
22
|
const MAX_CLUSTER_SCAN = Math.max(100, Math.min(20_000, Number(process.env.SEMANTIC_CLUSTER_MAX_SCAN) || 5_000));
|
|
29
23
|
const MAX_SUGGESTIONS_PER_ASSET = Math.max(1, Math.min(20, Number(process.env.SEMANTIC_CLUSTER_MAX_SUGGESTIONS_PER_ASSET) || 8));
|
|
30
24
|
const CLUSTERING_CONCURRENCY = Math.max(1, Math.min(8, Number(process.env.SEMANTIC_CLUSTER_CONCURRENCY) || 2));
|
|
@@ -72,13 +66,13 @@ const fallbackSlugFromSuggestion = (suggestionText) => {
|
|
|
72
66
|
};
|
|
73
67
|
|
|
74
68
|
const hashSuggestion = (normalizedSuggestion) =>
|
|
75
|
-
createHash('sha256')
|
|
69
|
+
createHash('sha256')
|
|
70
|
+
.update(String(normalizedSuggestion || ''), 'utf8')
|
|
71
|
+
.digest('hex');
|
|
76
72
|
|
|
77
73
|
const serializeEmbedding = (embedding = []) => {
|
|
78
74
|
const vector = Array.isArray(embedding) ? embedding : [];
|
|
79
|
-
const clean = vector
|
|
80
|
-
.map((value) => Number(value))
|
|
81
|
-
.filter((value) => Number.isFinite(value));
|
|
75
|
+
const clean = vector.map((value) => Number(value)).filter((value) => Number.isFinite(value));
|
|
82
76
|
if (!clean.length) return { dim: 0, buffer: Buffer.alloc(0) };
|
|
83
77
|
const buffer = Buffer.allocUnsafe(clean.length * 4);
|
|
84
78
|
for (let index = 0; index < clean.length; index += 1) {
|
|
@@ -157,14 +151,7 @@ const getSuggestionCacheRow = async (normalizedSuggestion) => {
|
|
|
157
151
|
};
|
|
158
152
|
};
|
|
159
153
|
|
|
160
|
-
const upsertSuggestionCacheRow = async ({
|
|
161
|
-
suggestionText,
|
|
162
|
-
normalizedText,
|
|
163
|
-
semanticClusterId,
|
|
164
|
-
canonicalSlug,
|
|
165
|
-
embedding = [],
|
|
166
|
-
similarity = null,
|
|
167
|
-
}) => {
|
|
154
|
+
const upsertSuggestionCacheRow = async ({ suggestionText, normalizedText, semanticClusterId, canonicalSlug, embedding = [], similarity = null }) => {
|
|
168
155
|
const normalized = normalizeSuggestion(normalizedText || suggestionText);
|
|
169
156
|
if (!normalized || !semanticClusterId) return false;
|
|
170
157
|
const suggestionHash = hashSuggestion(normalized);
|
|
@@ -184,16 +171,7 @@ const upsertSuggestionCacheRow = async ({
|
|
|
184
171
|
embedding = VALUES(embedding),
|
|
185
172
|
last_similarity = VALUES(last_similarity),
|
|
186
173
|
updated_at = CURRENT_TIMESTAMP`,
|
|
187
|
-
[
|
|
188
|
-
suggestionHash,
|
|
189
|
-
String(suggestionText || normalized).slice(0, 512),
|
|
190
|
-
normalized,
|
|
191
|
-
semanticClusterId,
|
|
192
|
-
canonicalSlug || null,
|
|
193
|
-
dim,
|
|
194
|
-
buffer,
|
|
195
|
-
similarity !== null && Number.isFinite(Number(similarity)) ? Number(Number(similarity).toFixed(6)) : null,
|
|
196
|
-
],
|
|
174
|
+
[suggestionHash, String(suggestionText || normalized).slice(0, 512), normalized, semanticClusterId, canonicalSlug || null, dim, buffer, similarity !== null && Number.isFinite(Number(similarity)) ? Number(Number(similarity).toFixed(6)) : null],
|
|
197
175
|
);
|
|
198
176
|
return true;
|
|
199
177
|
};
|
|
@@ -277,9 +255,7 @@ const generateEmbedding = async (text) => {
|
|
|
277
255
|
});
|
|
278
256
|
const vector = response?.data?.[0]?.embedding;
|
|
279
257
|
if (!Array.isArray(vector) || !vector.length) return null;
|
|
280
|
-
const clean = vector
|
|
281
|
-
.map((value) => Number(value))
|
|
282
|
-
.filter((value) => Number.isFinite(value));
|
|
258
|
+
const clean = vector.map((value) => Number(value)).filter((value) => Number.isFinite(value));
|
|
283
259
|
return clean.length ? clean : null;
|
|
284
260
|
};
|
|
285
261
|
|
|
@@ -540,19 +516,16 @@ const drainSemanticClusterQueue = async () => {
|
|
|
540
516
|
}
|
|
541
517
|
};
|
|
542
518
|
|
|
543
|
-
export const enqueueSemanticClusterResolution = ({
|
|
544
|
-
assetId,
|
|
545
|
-
suggestions = [],
|
|
546
|
-
fallbackText = '',
|
|
547
|
-
force = false,
|
|
548
|
-
} = {}) => {
|
|
519
|
+
export const enqueueSemanticClusterResolution = ({ assetId, suggestions = [], fallbackText = '', force = false } = {}) => {
|
|
549
520
|
const normalizedAssetId = String(assetId || '').trim();
|
|
550
521
|
if (!normalizedAssetId || !ENABLE_SEMANTIC_CLUSTERING) return false;
|
|
551
522
|
|
|
552
523
|
pendingTasksByAssetId.set(normalizedAssetId, {
|
|
553
524
|
assetId: normalizedAssetId,
|
|
554
525
|
suggestions: sanitizeSuggestions(suggestions),
|
|
555
|
-
fallbackText: String(fallbackText || '')
|
|
526
|
+
fallbackText: String(fallbackText || '')
|
|
527
|
+
.trim()
|
|
528
|
+
.slice(0, 255),
|
|
556
529
|
force: Boolean(force),
|
|
557
530
|
});
|
|
558
531
|
scheduleQueueDrain();
|
|
@@ -71,10 +71,7 @@ const normalizeClassificationRow = (row) => {
|
|
|
71
71
|
llm_style_traits: Array.isArray(llmStyleTraits) ? llmStyleTraits : [],
|
|
72
72
|
llm_emotions: Array.isArray(llmEmotions) ? llmEmotions : [],
|
|
73
73
|
llm_pack_suggestions: Array.isArray(llmPackSuggestions) ? llmPackSuggestions : [],
|
|
74
|
-
semantic_cluster_id:
|
|
75
|
-
row.semantic_cluster_id !== null && row.semantic_cluster_id !== undefined
|
|
76
|
-
? Number(row.semantic_cluster_id)
|
|
77
|
-
: null,
|
|
74
|
+
semantic_cluster_id: row.semantic_cluster_id !== null && row.semantic_cluster_id !== undefined ? Number(row.semantic_cluster_id) : null,
|
|
78
75
|
semantic_cluster_slug: row.semantic_cluster_slug || null,
|
|
79
76
|
classified_at: row.classified_at,
|
|
80
77
|
updated_at: row.updated_at,
|
|
@@ -82,11 +79,7 @@ const normalizeClassificationRow = (row) => {
|
|
|
82
79
|
};
|
|
83
80
|
|
|
84
81
|
export async function findStickerClassificationByAssetId(assetId, connection = null) {
|
|
85
|
-
const rows = await executeQuery(
|
|
86
|
-
`SELECT * FROM ${TABLES.STICKER_ASSET_CLASSIFICATION} WHERE asset_id = ? LIMIT 1`,
|
|
87
|
-
[assetId],
|
|
88
|
-
connection,
|
|
89
|
-
);
|
|
82
|
+
const rows = await executeQuery(`SELECT * FROM ${TABLES.STICKER_ASSET_CLASSIFICATION} WHERE asset_id = ? LIMIT 1`, [assetId], connection);
|
|
90
83
|
|
|
91
84
|
return normalizeClassificationRow(rows?.[0] || null);
|
|
92
85
|
}
|
|
@@ -98,11 +91,7 @@ export async function listStickerClassificationsByAssetIds(assetIds, connection
|
|
|
98
91
|
if (!uniqueIds.length) return [];
|
|
99
92
|
|
|
100
93
|
const placeholders = uniqueIds.map(() => '?').join(', ');
|
|
101
|
-
const rows = await executeQuery(
|
|
102
|
-
`SELECT * FROM ${TABLES.STICKER_ASSET_CLASSIFICATION} WHERE asset_id IN (${placeholders})`,
|
|
103
|
-
uniqueIds,
|
|
104
|
-
connection,
|
|
105
|
-
);
|
|
94
|
+
const rows = await executeQuery(`SELECT * FROM ${TABLES.STICKER_ASSET_CLASSIFICATION} WHERE asset_id IN (${placeholders})`, uniqueIds, connection);
|
|
106
95
|
|
|
107
96
|
const normalized = rows.map((row) => normalizeClassificationRow(row));
|
|
108
97
|
const byAssetId = new Map(normalized.map((entry) => [entry.asset_id, entry]));
|
|
@@ -138,30 +127,7 @@ export async function upsertStickerAssetClassification(payload, connection = nul
|
|
|
138
127
|
similar_images = VALUES(similar_images),
|
|
139
128
|
classified_at = CURRENT_TIMESTAMP,
|
|
140
129
|
updated_at = CURRENT_TIMESTAMP`,
|
|
141
|
-
[
|
|
142
|
-
payload.asset_id,
|
|
143
|
-
payload.provider || 'clip',
|
|
144
|
-
payload.model_name || null,
|
|
145
|
-
payload.classification_version || 'v1',
|
|
146
|
-
payload.category || null,
|
|
147
|
-
payload.confidence ?? null,
|
|
148
|
-
payload.entropy ?? null,
|
|
149
|
-
payload.confidence_margin ?? null,
|
|
150
|
-
payload.nsfw_score ?? null,
|
|
151
|
-
payload.is_nsfw ? 1 : 0,
|
|
152
|
-
payload.all_scores ? JSON.stringify(payload.all_scores) : JSON.stringify({}),
|
|
153
|
-
payload.top_labels ? JSON.stringify(payload.top_labels) : JSON.stringify([]),
|
|
154
|
-
payload.affinity_weight ?? null,
|
|
155
|
-
payload.image_hash || null,
|
|
156
|
-
payload.ambiguous ? 1 : 0,
|
|
157
|
-
payload.llm_subtags ? JSON.stringify(payload.llm_subtags) : JSON.stringify([]),
|
|
158
|
-
payload.llm_style_traits ? JSON.stringify(payload.llm_style_traits) : JSON.stringify([]),
|
|
159
|
-
payload.llm_emotions ? JSON.stringify(payload.llm_emotions) : JSON.stringify([]),
|
|
160
|
-
payload.llm_pack_suggestions ? JSON.stringify(payload.llm_pack_suggestions) : JSON.stringify([]),
|
|
161
|
-
payload.semantic_cluster_id ?? null,
|
|
162
|
-
payload.semantic_cluster_slug || null,
|
|
163
|
-
payload.similar_images ? JSON.stringify(payload.similar_images) : JSON.stringify([]),
|
|
164
|
-
],
|
|
130
|
+
[payload.asset_id, payload.provider || 'clip', payload.model_name || null, payload.classification_version || 'v1', payload.category || null, payload.confidence ?? null, payload.entropy ?? null, payload.confidence_margin ?? null, payload.nsfw_score ?? null, payload.is_nsfw ? 1 : 0, payload.all_scores ? JSON.stringify(payload.all_scores) : JSON.stringify({}), payload.top_labels ? JSON.stringify(payload.top_labels) : JSON.stringify([]), payload.affinity_weight ?? null, payload.image_hash || null, payload.ambiguous ? 1 : 0, payload.llm_subtags ? JSON.stringify(payload.llm_subtags) : JSON.stringify([]), payload.llm_style_traits ? JSON.stringify(payload.llm_style_traits) : JSON.stringify([]), payload.llm_emotions ? JSON.stringify(payload.llm_emotions) : JSON.stringify([]), payload.llm_pack_suggestions ? JSON.stringify(payload.llm_pack_suggestions) : JSON.stringify([]), payload.semantic_cluster_id ?? null, payload.semantic_cluster_slug || null, payload.similar_images ? JSON.stringify(payload.similar_images) : JSON.stringify([])],
|
|
165
131
|
connection,
|
|
166
132
|
);
|
|
167
133
|
|
|
@@ -187,22 +153,14 @@ export async function upsertStickerAssetClassification(payload, connection = nul
|
|
|
187
153
|
return findStickerClassificationByAssetId(payload.asset_id, connection);
|
|
188
154
|
}
|
|
189
155
|
|
|
190
|
-
export async function updateStickerClassificationSemanticCluster(
|
|
191
|
-
assetId,
|
|
192
|
-
{ semanticClusterId = null, semanticClusterSlug = null } = {},
|
|
193
|
-
connection = null,
|
|
194
|
-
) {
|
|
156
|
+
export async function updateStickerClassificationSemanticCluster(assetId, { semanticClusterId = null, semanticClusterSlug = null } = {}, connection = null) {
|
|
195
157
|
if (!assetId) return null;
|
|
196
158
|
|
|
197
159
|
await executeQuery(
|
|
198
160
|
`UPDATE ${TABLES.STICKER_ASSET_CLASSIFICATION}
|
|
199
161
|
SET semantic_cluster_id = ?, semantic_cluster_slug = ?, updated_at = CURRENT_TIMESTAMP
|
|
200
162
|
WHERE asset_id = ?`,
|
|
201
|
-
[
|
|
202
|
-
semanticClusterId ?? null,
|
|
203
|
-
semanticClusterSlug || null,
|
|
204
|
-
assetId,
|
|
205
|
-
],
|
|
163
|
+
[semanticClusterId ?? null, semanticClusterSlug || null, assetId],
|
|
206
164
|
connection,
|
|
207
165
|
);
|
|
208
166
|
|
|
@@ -211,9 +169,15 @@ export async function updateStickerClassificationSemanticCluster(
|
|
|
211
169
|
|
|
212
170
|
export async function listClipImageEmbeddingsByImageHashes(imageHashes, connection = null) {
|
|
213
171
|
const uniqueHashes = Array.from(
|
|
214
|
-
new Set(
|
|
215
|
-
.
|
|
216
|
-
|
|
172
|
+
new Set(
|
|
173
|
+
(Array.isArray(imageHashes) ? imageHashes : [])
|
|
174
|
+
.map((value) =>
|
|
175
|
+
String(value || '')
|
|
176
|
+
.trim()
|
|
177
|
+
.toLowerCase(),
|
|
178
|
+
)
|
|
179
|
+
.filter((value) => value.length === 64),
|
|
180
|
+
),
|
|
217
181
|
);
|
|
218
182
|
if (!uniqueHashes.length) return [];
|
|
219
183
|
|
|
@@ -249,10 +213,7 @@ const clampInt = (value, fallback, min, max) => {
|
|
|
249
213
|
return Math.max(min, Math.min(max, Math.floor(numeric)));
|
|
250
214
|
};
|
|
251
215
|
|
|
252
|
-
export async function listAssetsForModelUpgradeReprocess(
|
|
253
|
-
{ currentVersion, limit = 150, offset = 0 } = {},
|
|
254
|
-
connection = null,
|
|
255
|
-
) {
|
|
216
|
+
export async function listAssetsForModelUpgradeReprocess({ currentVersion, limit = 150, offset = 0 } = {}, connection = null) {
|
|
256
217
|
const normalizedVersion = String(currentVersion || '').trim();
|
|
257
218
|
if (!normalizedVersion) return [];
|
|
258
219
|
|
|
@@ -276,10 +237,7 @@ export async function listAssetsForModelUpgradeReprocess(
|
|
|
276
237
|
return rows.map((row) => row.asset_id).filter(Boolean);
|
|
277
238
|
}
|
|
278
239
|
|
|
279
|
-
export async function listAssetsForLowConfidenceReprocess(
|
|
280
|
-
{ confidenceThreshold = 0.65, staleHours = 48, limit = 150, offset = 0 } = {},
|
|
281
|
-
connection = null,
|
|
282
|
-
) {
|
|
240
|
+
export async function listAssetsForLowConfidenceReprocess({ confidenceThreshold = 0.65, staleHours = 48, limit = 150, offset = 0 } = {}, connection = null) {
|
|
283
241
|
const threshold = Number(confidenceThreshold);
|
|
284
242
|
if (!Number.isFinite(threshold)) return [];
|
|
285
243
|
|
|
@@ -307,10 +265,7 @@ export async function listAssetsForLowConfidenceReprocess(
|
|
|
307
265
|
return rows.map((row) => row.asset_id).filter(Boolean);
|
|
308
266
|
}
|
|
309
267
|
|
|
310
|
-
export async function listAssetsForPrioritySignalBackfillReprocess(
|
|
311
|
-
{ limit = 200, offset = 0 } = {},
|
|
312
|
-
connection = null,
|
|
313
|
-
) {
|
|
268
|
+
export async function listAssetsForPrioritySignalBackfillReprocess({ limit = 200, offset = 0 } = {}, connection = null) {
|
|
314
269
|
const safeLimit = clampInt(limit, 200, 1, 2000);
|
|
315
270
|
const safeOffset = clampInt(offset, 0, 0, 500000);
|
|
316
271
|
const rows = await executeQuery(
|
|
@@ -358,23 +313,11 @@ export async function listAssetsForPrioritySignalBackfillReprocess(
|
|
|
358
313
|
return rows.map((row) => row.asset_id).filter(Boolean);
|
|
359
314
|
}
|
|
360
315
|
|
|
361
|
-
export async function listStickerClassificationsForDeterministicReprocess(
|
|
362
|
-
{
|
|
363
|
-
limit = 250,
|
|
364
|
-
cursorAssetId = '',
|
|
365
|
-
entropyThreshold = 0.8,
|
|
366
|
-
affinityThreshold = 0.3,
|
|
367
|
-
} = {},
|
|
368
|
-
connection = null,
|
|
369
|
-
) {
|
|
316
|
+
export async function listStickerClassificationsForDeterministicReprocess({ limit = 250, cursorAssetId = '', entropyThreshold = 0.8, affinityThreshold = 0.3 } = {}, connection = null) {
|
|
370
317
|
const safeLimit = clampInt(limit, 250, 1, 2000);
|
|
371
318
|
const normalizedCursor = String(cursorAssetId || '').trim();
|
|
372
|
-
const normalizedEntropyThreshold = Number.isFinite(Number(entropyThreshold))
|
|
373
|
-
|
|
374
|
-
: 0.8;
|
|
375
|
-
const normalizedAffinityThreshold = Number.isFinite(Number(affinityThreshold))
|
|
376
|
-
? Number(affinityThreshold)
|
|
377
|
-
: 0.3;
|
|
319
|
+
const normalizedEntropyThreshold = Number.isFinite(Number(entropyThreshold)) ? Number(entropyThreshold) : 0.8;
|
|
320
|
+
const normalizedAffinityThreshold = Number.isFinite(Number(affinityThreshold)) ? Number(affinityThreshold) : 0.3;
|
|
378
321
|
|
|
379
322
|
const params = [normalizedEntropyThreshold, normalizedAffinityThreshold];
|
|
380
323
|
const cursorClause = normalizedCursor ? 'AND c.asset_id > ?' : '';
|
|
@@ -409,30 +352,17 @@ export async function listStickerClassificationsForDeterministicReprocess(
|
|
|
409
352
|
return rows.map((row) => normalizeClassificationRow(row));
|
|
410
353
|
}
|
|
411
354
|
|
|
412
|
-
export async function updateStickerClassificationDeterministicSignals(
|
|
413
|
-
assetId,
|
|
414
|
-
{ llmSubtags = [], affinityWeight = null, ambiguous = 0 } = {},
|
|
415
|
-
connection = null,
|
|
416
|
-
) {
|
|
355
|
+
export async function updateStickerClassificationDeterministicSignals(assetId, { llmSubtags = [], affinityWeight = null, ambiguous = 0 } = {}, connection = null) {
|
|
417
356
|
if (!assetId) return null;
|
|
418
|
-
const normalizedAffinityWeight = Number.isFinite(Number(affinityWeight))
|
|
419
|
-
? Math.max(0, Math.min(1, Number(affinityWeight)))
|
|
420
|
-
: null;
|
|
357
|
+
const normalizedAffinityWeight = Number.isFinite(Number(affinityWeight)) ? Math.max(0, Math.min(1, Number(affinityWeight))) : null;
|
|
421
358
|
const normalizedAmbiguous = ambiguous === 1 || ambiguous === true ? 1 : 0;
|
|
422
|
-
const normalizedSubtags = Array.isArray(llmSubtags)
|
|
423
|
-
? llmSubtags.map((value) => String(value || '').trim()).filter(Boolean)
|
|
424
|
-
: [];
|
|
359
|
+
const normalizedSubtags = Array.isArray(llmSubtags) ? llmSubtags.map((value) => String(value || '').trim()).filter(Boolean) : [];
|
|
425
360
|
|
|
426
361
|
await executeQuery(
|
|
427
362
|
`UPDATE ${TABLES.STICKER_ASSET_CLASSIFICATION}
|
|
428
363
|
SET llm_subtags = ?, affinity_weight = ?, ambiguous = ?, updated_at = CURRENT_TIMESTAMP
|
|
429
364
|
WHERE asset_id = ?`,
|
|
430
|
-
[
|
|
431
|
-
JSON.stringify(normalizedSubtags),
|
|
432
|
-
normalizedAffinityWeight,
|
|
433
|
-
normalizedAmbiguous,
|
|
434
|
-
assetId,
|
|
435
|
-
],
|
|
365
|
+
[JSON.stringify(normalizedSubtags), normalizedAffinityWeight, normalizedAmbiguous, assetId],
|
|
436
366
|
connection,
|
|
437
367
|
);
|
|
438
368
|
|
|
@@ -41,11 +41,7 @@ const normalizeStickerAssetRow = (row) => {
|
|
|
41
41
|
* @returns {Promise<object|null>} Asset encontrado.
|
|
42
42
|
*/
|
|
43
43
|
export async function findStickerAssetBySha256(sha256, connection = null) {
|
|
44
|
-
const rows = await executeQuery(
|
|
45
|
-
`SELECT * FROM ${TABLES.STICKER_ASSET} WHERE sha256 = ? LIMIT 1`,
|
|
46
|
-
[sha256],
|
|
47
|
-
connection,
|
|
48
|
-
);
|
|
44
|
+
const rows = await executeQuery(`SELECT * FROM ${TABLES.STICKER_ASSET} WHERE sha256 = ? LIMIT 1`, [sha256], connection);
|
|
49
45
|
return normalizeStickerAssetRow(rows?.[0] || null);
|
|
50
46
|
}
|
|
51
47
|
|
|
@@ -74,11 +70,7 @@ export async function findStickerAssetsByIds(ids, connection = null) {
|
|
|
74
70
|
if (!uniqueIds.length) return [];
|
|
75
71
|
|
|
76
72
|
const placeholders = uniqueIds.map(() => '?').join(', ');
|
|
77
|
-
const rows = await executeQuery(
|
|
78
|
-
`SELECT * FROM ${TABLES.STICKER_ASSET} WHERE id IN (${placeholders})`,
|
|
79
|
-
uniqueIds,
|
|
80
|
-
connection,
|
|
81
|
-
);
|
|
73
|
+
const rows = await executeQuery(`SELECT * FROM ${TABLES.STICKER_ASSET} WHERE id IN (${placeholders})`, uniqueIds, connection);
|
|
82
74
|
|
|
83
75
|
const normalized = rows.map((row) => normalizeStickerAssetRow(row));
|
|
84
76
|
const byId = new Map(normalized.map((row) => [row.id, row]));
|
|
@@ -143,7 +135,10 @@ export async function listStickerAssetsWithoutPack({ search = '', limit = 120, o
|
|
|
143
135
|
const safeLimit = Math.max(1, Math.min(500, Number(limit) || 120));
|
|
144
136
|
const safeOffset = Math.max(0, Number(offset) || 0);
|
|
145
137
|
const safeLimitWithSentinel = safeLimit + 1;
|
|
146
|
-
const normalizedSearch = String(search || '')
|
|
138
|
+
const normalizedSearch = String(search || '')
|
|
139
|
+
.trim()
|
|
140
|
+
.toLowerCase()
|
|
141
|
+
.slice(0, 140);
|
|
147
142
|
|
|
148
143
|
const whereClauses = ['i.sticker_id IS NULL'];
|
|
149
144
|
const params = [];
|
|
@@ -198,7 +193,10 @@ export async function listClassifiedStickerAssetsWithoutPack({ search = '', limi
|
|
|
198
193
|
const safeLimit = Math.max(1, Math.min(500, Number(limit) || 120));
|
|
199
194
|
const safeOffset = Math.max(0, Number(offset) || 0);
|
|
200
195
|
const safeLimitWithSentinel = safeLimit + 1;
|
|
201
|
-
const normalizedSearch = String(search || '')
|
|
196
|
+
const normalizedSearch = String(search || '')
|
|
197
|
+
.trim()
|
|
198
|
+
.toLowerCase()
|
|
199
|
+
.slice(0, 140);
|
|
202
200
|
|
|
203
201
|
const whereClauses = ['i.sticker_id IS NULL'];
|
|
204
202
|
const params = [];
|
|
@@ -274,14 +272,7 @@ export async function countClassifiedStickerAssetsWithoutPack(connection = null)
|
|
|
274
272
|
* }} [options]
|
|
275
273
|
* @returns {Promise<{ assets: object[], hasMore: boolean, total: number }>}
|
|
276
274
|
*/
|
|
277
|
-
export async function listClassifiedStickerAssetsForCuration({
|
|
278
|
-
limit = 200,
|
|
279
|
-
offset = 0,
|
|
280
|
-
includePacked = true,
|
|
281
|
-
includeUnpacked = true,
|
|
282
|
-
onlyVersionMismatch = null,
|
|
283
|
-
connection = null,
|
|
284
|
-
} = {}) {
|
|
275
|
+
export async function listClassifiedStickerAssetsForCuration({ limit = 200, offset = 0, includePacked = true, includeUnpacked = true, onlyVersionMismatch = null, connection = null } = {}) {
|
|
285
276
|
const safeLimit = Math.max(1, Math.min(1000, Number(limit) || 200));
|
|
286
277
|
const safeOffset = Math.max(0, Number(offset) || 0);
|
|
287
278
|
const safeLimitWithSentinel = safeLimit + 1;
|
|
@@ -346,17 +337,7 @@ export async function createStickerAsset(asset, connection = null) {
|
|
|
346
337
|
`INSERT INTO ${TABLES.STICKER_ASSET}
|
|
347
338
|
(id, owner_jid, sha256, mimetype, is_animated, width, height, size_bytes, storage_path)
|
|
348
339
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
349
|
-
[
|
|
350
|
-
asset.id,
|
|
351
|
-
asset.owner_jid,
|
|
352
|
-
asset.sha256,
|
|
353
|
-
asset.mimetype,
|
|
354
|
-
asset.is_animated ? 1 : 0,
|
|
355
|
-
asset.width ?? null,
|
|
356
|
-
asset.height ?? null,
|
|
357
|
-
asset.size_bytes,
|
|
358
|
-
asset.storage_path,
|
|
359
|
-
],
|
|
340
|
+
[asset.id, asset.owner_jid, asset.sha256, asset.mimetype, asset.is_animated ? 1 : 0, asset.width ?? null, asset.height ?? null, asset.size_bytes, asset.storage_path],
|
|
360
341
|
connection,
|
|
361
342
|
);
|
|
362
343
|
|
|
@@ -3,13 +3,17 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
4
4
|
|
|
5
5
|
const normalizeReason = (value) => {
|
|
6
|
-
const normalized = String(value || '')
|
|
6
|
+
const normalized = String(value || '')
|
|
7
|
+
.trim()
|
|
8
|
+
.toUpperCase();
|
|
7
9
|
if (['MODEL_UPGRADE', 'LOW_CONFIDENCE', 'TREND_SHIFT', 'NSFW_REVIEW'].includes(normalized)) return normalized;
|
|
8
10
|
return null;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
const normalizeStatus = (value) => {
|
|
12
|
-
const normalized = String(value || '')
|
|
14
|
+
const normalized = String(value || '')
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase();
|
|
13
17
|
if (['pending', 'processing', 'completed', 'failed'].includes(normalized)) return normalized;
|
|
14
18
|
return null;
|
|
15
19
|
};
|
|
@@ -40,10 +44,7 @@ const normalizeRow = (row) => {
|
|
|
40
44
|
};
|
|
41
45
|
};
|
|
42
46
|
|
|
43
|
-
export async function enqueueStickerAssetReprocess(
|
|
44
|
-
{ assetId, reason, priority = 50, scheduledAt = null, maxAttempts = 5 },
|
|
45
|
-
connection = null,
|
|
46
|
-
) {
|
|
47
|
+
export async function enqueueStickerAssetReprocess({ assetId, reason, priority = 50, scheduledAt = null, maxAttempts = 5 }, connection = null) {
|
|
47
48
|
const normalizedReason = normalizeReason(reason);
|
|
48
49
|
if (!assetId || !normalizedReason) return false;
|
|
49
50
|
|
|
@@ -80,9 +81,7 @@ export async function claimStickerAssetReprocessTask({ reasons = [], allowRetryF
|
|
|
80
81
|
const normalizedReasons = Array.from(new Set((Array.isArray(reasons) ? reasons : []).map(normalizeReason).filter(Boolean)));
|
|
81
82
|
const reasonFilter = normalizedReasons.length ? `AND reason IN (${normalizedReasons.map(() => '?').join(', ')})` : '';
|
|
82
83
|
|
|
83
|
-
const statusClause = allowRetryFailed
|
|
84
|
-
? "(status = 'pending' OR (status = 'failed' AND attempts < max_attempts))"
|
|
85
|
-
: "status = 'pending'";
|
|
84
|
+
const statusClause = allowRetryFailed ? "(status = 'pending' OR (status = 'failed' AND attempts < max_attempts))" : "status = 'pending'";
|
|
86
85
|
|
|
87
86
|
await executeQuery(
|
|
88
87
|
`UPDATE ${TABLES.STICKER_ASSET_REPROCESS_QUEUE}
|
|
@@ -137,17 +136,13 @@ export async function completeStickerAssetReprocessTask(taskId, connection = nul
|
|
|
137
136
|
return true;
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
export async function failStickerAssetReprocessTask(
|
|
141
|
-
taskId,
|
|
142
|
-
{
|
|
143
|
-
error = null,
|
|
144
|
-
retryDelaySeconds = 0,
|
|
145
|
-
} = {},
|
|
146
|
-
connection = null,
|
|
147
|
-
) {
|
|
139
|
+
export async function failStickerAssetReprocessTask(taskId, { error = null, retryDelaySeconds = 0 } = {}, connection = null) {
|
|
148
140
|
if (!taskId) return false;
|
|
149
141
|
const safeDelay = clampInt(retryDelaySeconds, 0, 0, 86400 * 7);
|
|
150
|
-
const message =
|
|
142
|
+
const message =
|
|
143
|
+
String(error || '')
|
|
144
|
+
.trim()
|
|
145
|
+
.slice(0, 255) || null;
|
|
151
146
|
|
|
152
147
|
await executeQuery(
|
|
153
148
|
`UPDATE ${TABLES.STICKER_ASSET_REPROCESS_QUEUE}
|