import { SemanticSimilarityEngine } from '@/utils/semantic-similarity-engine'; import { MessageTarget, SendMessageType, OFFSCREEN_MESSAGE_TYPES, BACKGROUND_MESSAGE_TYPES, } from '@/common/message-types'; // Global semantic similarity engine instance let similarityEngine: SemanticSimilarityEngine | null = null; interface OffscreenMessage { target: MessageTarget | string; type: SendMessageType | string; } interface SimilarityEngineInitMessage extends OffscreenMessage { type: SendMessageType.SimilarityEngineInit; config: any; } interface SimilarityEngineComputeBatchMessage extends OffscreenMessage { type: SendMessageType.SimilarityEngineComputeBatch; pairs: { text1: string; text2: string }[]; options?: Record; } interface SimilarityEngineGetEmbeddingMessage extends OffscreenMessage { type: 'similarityEngineCompute'; text: string; options?: Record; } interface SimilarityEngineGetEmbeddingsBatchMessage extends OffscreenMessage { type: 'similarityEngineBatchCompute'; texts: string[]; options?: Record; } interface SimilarityEngineStatusMessage extends OffscreenMessage { type: 'similarityEngineStatus'; } type MessageResponse = { result?: string; error?: string; success?: boolean; similarities?: number[]; embedding?: number[]; embeddings?: number[][]; isInitialized?: boolean; currentConfig?: any; }; // Listen for messages from the extension chrome.runtime.onMessage.addListener( ( message: OffscreenMessage, _sender: chrome.runtime.MessageSender, sendResponse: (response: MessageResponse) => void, ) => { if (message.target !== MessageTarget.Offscreen) { return; } try { switch (message.type) { case SendMessageType.SimilarityEngineInit: case OFFSCREEN_MESSAGE_TYPES.SIMILARITY_ENGINE_INIT: { const initMsg = message as SimilarityEngineInitMessage; console.log('Offscreen: Received similarity engine init message:', message.type); handleSimilarityEngineInit(initMsg.config) .then(() => sendResponse({ success: true })) .catch((error) => sendResponse({ success: false, error: error.message })); break; } case SendMessageType.SimilarityEngineComputeBatch: { const computeMsg = message as SimilarityEngineComputeBatchMessage; handleComputeSimilarityBatch(computeMsg.pairs, computeMsg.options) .then((similarities) => sendResponse({ success: true, similarities })) .catch((error) => sendResponse({ success: false, error: error.message })); break; } case OFFSCREEN_MESSAGE_TYPES.SIMILARITY_ENGINE_COMPUTE: { const embeddingMsg = message as SimilarityEngineGetEmbeddingMessage; handleGetEmbedding(embeddingMsg.text, embeddingMsg.options) .then((embedding) => { console.log('Offscreen: Sending embedding response:', { length: embedding.length, type: typeof embedding, constructor: embedding.constructor.name, isFloat32Array: embedding instanceof Float32Array, firstFewValues: Array.from(embedding.slice(0, 5)), }); const embeddingArray = Array.from(embedding); console.log('Offscreen: Converted to array:', { length: embeddingArray.length, type: typeof embeddingArray, isArray: Array.isArray(embeddingArray), firstFewValues: embeddingArray.slice(0, 5), }); sendResponse({ success: true, embedding: embeddingArray }); }) .catch((error) => sendResponse({ success: false, error: error.message })); break; } case OFFSCREEN_MESSAGE_TYPES.SIMILARITY_ENGINE_BATCH_COMPUTE: { const batchMsg = message as SimilarityEngineGetEmbeddingsBatchMessage; handleGetEmbeddingsBatch(batchMsg.texts, batchMsg.options) .then((embeddings) => sendResponse({ success: true, embeddings: embeddings.map((emb) => Array.from(emb)), }), ) .catch((error) => sendResponse({ success: false, error: error.message })); break; } case OFFSCREEN_MESSAGE_TYPES.SIMILARITY_ENGINE_STATUS: { handleGetEngineStatus() .then((status: any) => sendResponse({ success: true, ...status })) .catch((error: any) => sendResponse({ success: false, error: error.message })); break; } default: sendResponse({ error: `Unknown message type: ${message.type}` }); } } catch (error) { if (error instanceof Error) { sendResponse({ error: error.message }); } else { sendResponse({ error: 'Unknown error occurred' }); } } // Return true to indicate we'll respond asynchronously return true; }, ); // Global variable to track current model state let currentModelConfig: any = null; /** * Check if engine reinitialization is needed */ function needsReinitialization(newConfig: any): boolean { if (!similarityEngine || !currentModelConfig) { return true; } // Check if key configuration has changed const keyFields = ['modelPreset', 'modelVersion', 'modelIdentifier', 'dimension']; for (const field of keyFields) { if (newConfig[field] !== currentModelConfig[field]) { console.log( `Offscreen: ${field} changed from ${currentModelConfig[field]} to ${newConfig[field]}`, ); return true; } } return false; } /** * Progress callback function type */ type ProgressCallback = (progress: { status: string; progress: number; message?: string }) => void; /** * Initialize semantic similarity engine */ async function handleSimilarityEngineInit(config: any): Promise { console.log('Offscreen: Initializing semantic similarity engine with config:', config); console.log('Offscreen: Config useLocalFiles:', config.useLocalFiles); console.log('Offscreen: Config modelPreset:', config.modelPreset); console.log('Offscreen: Config modelVersion:', config.modelVersion); console.log('Offscreen: Config modelDimension:', config.modelDimension); console.log('Offscreen: Config modelIdentifier:', config.modelIdentifier); // Check if reinitialization is needed const needsReinit = needsReinitialization(config); console.log('Offscreen: Needs reinitialization:', needsReinit); if (!needsReinit) { console.log('Offscreen: Using existing engine (no changes detected)'); await updateModelStatus('ready', 100); return; } // If engine already exists, clean up old instance first (support model switching) if (similarityEngine) { console.log('Offscreen: Cleaning up existing engine for model switch...'); try { // Properly call dispose method to clean up all resources await similarityEngine.dispose(); console.log('Offscreen: Previous engine disposed successfully'); } catch (error) { console.warn('Offscreen: Failed to dispose previous engine:', error); } similarityEngine = null; currentModelConfig = null; // Clear vector data in IndexedDB to ensure data consistency try { console.log('Offscreen: Clearing IndexedDB vector data for model switch...'); await clearVectorIndexedDB(); console.log('Offscreen: IndexedDB vector data cleared successfully'); } catch (error) { console.warn('Offscreen: Failed to clear IndexedDB vector data:', error); } } try { // Update status to initializing await updateModelStatus('initializing', 10); // Create progress callback function const progressCallback: ProgressCallback = async (progress) => { console.log('Offscreen: Progress update:', progress); await updateModelStatus(progress.status, progress.progress); }; // Create engine instance and pass progress callback similarityEngine = new SemanticSimilarityEngine(config); console.log('Offscreen: Starting engine initialization with progress tracking...'); // Use enhanced initialization method (if progress callback is supported) if (typeof (similarityEngine as any).initializeWithProgress === 'function') { await (similarityEngine as any).initializeWithProgress(progressCallback); } else { // Fallback to standard initialization method console.log('Offscreen: Using standard initialization (no progress callback support)'); await updateModelStatus('downloading', 30); await similarityEngine.initialize(); await updateModelStatus('ready', 100); } // Save current configuration currentModelConfig = { ...config }; console.log('Offscreen: Semantic similarity engine initialized successfully'); } catch (error) { console.error('Offscreen: Failed to initialize semantic similarity engine:', error); // Update status to error const errorMessage = error instanceof Error ? error.message : 'Unknown initialization error'; const errorType = analyzeErrorType(errorMessage); await updateModelStatus('error', 0, errorMessage, errorType); // Clean up failed instance similarityEngine = null; currentModelConfig = null; throw error; } } /** * Clear vector data in IndexedDB */ async function clearVectorIndexedDB(): Promise { try { // Clear vector search related IndexedDB databases const dbNames = ['VectorSearchDB', 'ContentIndexerDB', 'SemanticSimilarityDB']; for (const dbName of dbNames) { try { // Try to delete database const deleteRequest = indexedDB.deleteDatabase(dbName); await new Promise((resolve, _reject) => { deleteRequest.onsuccess = () => { console.log(`Offscreen: Successfully deleted database: ${dbName}`); resolve(); }; deleteRequest.onerror = () => { console.warn(`Offscreen: Failed to delete database: ${dbName}`, deleteRequest.error); resolve(); // 不阻塞其他数据库的清理 }; deleteRequest.onblocked = () => { console.warn(`Offscreen: Database deletion blocked: ${dbName}`); resolve(); // 不阻塞其他数据库的清理 }; }); } catch (error) { console.warn(`Offscreen: Error deleting database ${dbName}:`, error); } } } catch (error) { console.error('Offscreen: Failed to clear vector IndexedDB:', error); throw error; } } // Analyze error type function analyzeErrorType(errorMessage: string): 'network' | 'file' | 'unknown' { const message = errorMessage.toLowerCase(); if ( message.includes('network') || message.includes('fetch') || message.includes('timeout') || message.includes('connection') || message.includes('cors') || message.includes('failed to fetch') ) { return 'network'; } if ( message.includes('corrupt') || message.includes('invalid') || message.includes('format') || message.includes('parse') || message.includes('decode') || message.includes('onnx') ) { return 'file'; } return 'unknown'; } // Helper function to update model status async function updateModelStatus( status: string, progress: number, errorMessage?: string, errorType?: string, ) { try { const modelState = { status, downloadProgress: progress, isDownloading: status === 'downloading' || status === 'initializing', lastUpdated: Date.now(), errorMessage: errorMessage || '', errorType: errorType || '', }; // In offscreen document, update storage through message passing to background script // because offscreen document may not have direct chrome.storage access if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) { await chrome.storage.local.set({ modelState }); } else { // If chrome.storage is not available, pass message to background script console.log('Offscreen: chrome.storage not available, sending message to background'); try { await chrome.runtime.sendMessage({ type: BACKGROUND_MESSAGE_TYPES.UPDATE_MODEL_STATUS, modelState: modelState, }); } catch (messageError) { console.error('Offscreen: Failed to send status update message:', messageError); } } } catch (error) { console.error('Offscreen: Failed to update model status:', error); } } /** * Batch compute semantic similarity */ async function handleComputeSimilarityBatch( pairs: { text1: string; text2: string }[], options: Record = {}, ): Promise { if (!similarityEngine) { throw new Error('Similarity engine not initialized. Please reinitialize the engine.'); } console.log(`Offscreen: Computing similarities for ${pairs.length} pairs`); const similarities = await similarityEngine.computeSimilarityBatch(pairs, options); console.log('Offscreen: Similarity computation completed'); return similarities; } /** * Get embedding vector for single text */ async function handleGetEmbedding( text: string, options: Record = {}, ): Promise { if (!similarityEngine) { throw new Error('Similarity engine not initialized. Please reinitialize the engine.'); } console.log(`Offscreen: Getting embedding for text: "${text.substring(0, 50)}..."`); const embedding = await similarityEngine.getEmbedding(text, options); console.log('Offscreen: Embedding computation completed'); return embedding; } /** * Batch get embedding vectors for texts */ async function handleGetEmbeddingsBatch( texts: string[], options: Record = {}, ): Promise { if (!similarityEngine) { throw new Error('Similarity engine not initialized. Please reinitialize the engine.'); } console.log(`Offscreen: Getting embeddings for ${texts.length} texts`); const embeddings = await similarityEngine.getEmbeddingsBatch(texts, options); console.log('Offscreen: Batch embedding computation completed'); return embeddings; } /** * Get engine status */ async function handleGetEngineStatus(): Promise<{ isInitialized: boolean; currentConfig: any; }> { return { isInitialized: !!similarityEngine, currentConfig: currentModelConfig, }; } console.log('Offscreen: Semantic similarity engine handler loaded');