| |
| import { UMAP } from 'umap-js'; |
| import { loadEmbeddings, mergeFontFamilies, normalizeData, applyPCA } from '../utils/umapCalculator'; |
|
|
| |
| let cachedData = null; |
|
|
| |
| let cachedKNN = { |
| nNeighbors: null, |
| enableFontFusion: null, |
| indices: null, |
| distances: null |
| }; |
|
|
| self.onmessage = async (e) => { |
| const { type, payload } = e.data; |
|
|
| if (type === 'CALCULATE') { |
| await calculateUMAP(payload); |
| } |
| }; |
|
|
| async function calculateUMAP(config) { |
| const { |
| nNeighbors = 15, |
| minDist = 1.0, |
| enableFontFusion = true, |
| randomSeed = 42 |
| } = config; |
|
|
| try { |
| |
| if (!cachedData) { |
| self.postMessage({ type: 'PROGRESS', payload: { stage: 'loading', progress: 0 } }); |
| cachedData = await loadEmbeddings(); |
| } |
|
|
| |
| self.postMessage({ type: 'PROGRESS', payload: { stage: 'preparing', progress: 20 } }); |
|
|
| |
| const fontDataList = cachedData.fonts.map(font => ({ |
| id: font.id, |
| name: font.name, |
| imageName: font.imageName || font.id, |
| family: font.family, |
| google_fonts_url: font.google_fonts_url, |
| weights: font.weights, |
| styles: font.styles, |
| subsets: font.subsets, |
| unicodeRange: font.unicodeRange |
| })); |
|
|
| const embeddingMatrices = cachedData.fonts.map(font => font.embedding); |
|
|
| |
| self.postMessage({ type: 'PROGRESS', payload: { stage: 'merging', progress: 40 } }); |
| const { fontDataList: mergedFonts, embeddingMatrices: mergedEmbeddings } = |
| mergeFontFamilies(fontDataList, embeddingMatrices, enableFontFusion); |
|
|
| |
| self.postMessage({ type: 'PROGRESS', payload: { stage: 'normalizing', progress: 45 } }); |
| const normalizedData = normalizeData(mergedEmbeddings); |
|
|
| self.postMessage({ type: 'PROGRESS', payload: { stage: 'pca', progress: 52 } }); |
| const pcaData = applyPCA(normalizedData, 50); |
|
|
| |
| self.postMessage({ type: 'PROGRESS', payload: { stage: 'umap', progress: 60 } }); |
|
|
| |
| let seed = randomSeed; |
| const randomFn = () => { |
| seed = (seed * 9301 + 49297) % 233280; |
| return seed / 233280; |
| }; |
|
|
| const umapParams = { |
| nComponents: 2, |
| nNeighbors, |
| minDist, |
| metric: 'cosine', |
| random: randomFn |
| }; |
|
|
| const umap = new UMAP(umapParams); |
|
|
| console.log('🚀 Starting UMAP calculation...'); |
| const t1 = performance.now(); |
|
|
| |
| self.postMessage({ type: 'PROGRESS', payload: { stage: 'umap_knn', progress: 65 } }); |
|
|
| |
| |
| |
| const canReuseKNN = cachedKNN.indices && |
| cachedKNN.nNeighbors === nNeighbors && |
| cachedKNN.enableFontFusion === enableFontFusion; |
|
|
| if (canReuseKNN) { |
| console.log('♻️ Reusing cached KNN results'); |
| umap.setPrecomputedKNN(cachedKNN.indices, cachedKNN.distances); |
| umap.initializeFit(pcaData); |
| } else { |
| console.log('🆕 Calculating new KNN'); |
| umap.initializeFit(pcaData); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| if (umap.knnIndices && umap.knnDistances) { |
| cachedKNN = { |
| nNeighbors, |
| enableFontFusion, |
| indices: umap.knnIndices, |
| distances: umap.knnDistances |
| }; |
| } |
| } |
|
|
| const t2 = performance.now(); |
| console.log(`⏱️ KNN & Init took: ${(t2 - t1).toFixed(2)}ms ${canReuseKNN ? '(Cached)' : ''}`); |
|
|
| |
| self.postMessage({ type: 'PROGRESS', payload: { stage: 'umap_optimize', progress: 70 } }); |
| const t3 = performance.now(); |
| umap.optimizeLayout(); |
| const t4 = performance.now(); |
| console.log(`⏱️ Optimization took: ${(t4 - t3).toFixed(2)}ms`); |
| console.log(`⏱️ Total UMAP took: ${(t4 - t1).toFixed(2)}ms`); |
|
|
| const embedding = umap.getEmbedding(); |
|
|
| |
| self.postMessage({ type: 'PROGRESS', payload: { stage: 'finalizing', progress: 90 } }); |
|
|
| const finalData = mergedFonts.map((font, i) => ({ |
| ...font, |
| x: embedding[i][0], |
| y: embedding[i][1] |
| })); |
|
|
| const result = { |
| config: { |
| nNeighbors, |
| minDist, |
| metric: 'cosine', |
| enableFontFusion, |
| testName: `live-n${nNeighbors}-d${minDist}`, |
| randomSeed |
| }, |
| metadata: { |
| generated_at: new Date().toISOString(), |
| total_fonts: finalData.length, |
| method: "umap_from_clip_embeddings_pure_visual_frontend", |
| note: "100% visual embeddings, calculated in browser worker" |
| }, |
| fonts: finalData |
| }; |
|
|
| self.postMessage({ type: 'PROGRESS', payload: { stage: 'complete', progress: 100 } }); |
| self.postMessage({ type: 'RESULT', payload: result }); |
|
|
| } catch (error) { |
| self.postMessage({ |
| type: 'ERROR', |
| payload: error.message || 'Erreur inconnue dans le worker' |
| }); |
| } |
| } |
|
|