const PhoneRuntime = { _loadedModels: new Map(), _runtimeType: 'mock', async initializeRuntime() { this._detectRuntime(); log('Runtime: ' + this._runtimeType); }, _detectRuntime() { if (typeof navigator !== 'undefined' && navigator.gpu) { this._runtimeType = 'webgpu'; } else if (typeof WebAssembly !== 'undefined') { this._runtimeType = 'wasm'; } else { this._runtimeType = 'mock'; } // Native CoreML/MLX only available in native iOS app, not Safari }, selectBestRuntime() { return this._runtimeType; }, async loadTextEmbeddingModel(modelId) { // v1: mock load; v2 would use transformers.js or ONNX Runtime Web this._loadedModels.set(modelId || 'text-embedding', { type: 'embedding', loaded: true }); log('Loaded embedding model (mock)'); return true; }, async loadImageModel(modelId) { this._loadedModels.set(modelId || 'image-classifier', { type: 'image', loaded: true }); log('Loaded image model (mock)'); return true; }, async loadRedactionModel(modelId) { this._loadedModels.set(modelId || 'privacy-redact', { type: 'redaction', loaded: true }); log('Loaded redaction model (mock)'); return true; }, isModelLoaded(modelId) { return this._loadedModels.has(modelId); }, async runTextEmbedding(text) { if (!this._loadedModels.has('text-embedding')) { await this.loadTextEmbeddingModel(); } // v1: return deterministic mock embedding vector const vec = new Array(384).fill(0); for (let i = 0; i < text.length && i < 384; i++) { vec[i] = (text.charCodeAt(i) % 100) / 100; } return { embedding: vec, model: 'mock-embedding', runtime: this._runtimeType }; }, async runImageClassification(imageDataUrl) { if (!this._loadedModels.has('image-classifier')) { await this.loadImageModel(); } // v1: mock classification based on image size const mockLabels = ['cat', 'dog', 'bird', 'car', 'tree']; const idx = (imageDataUrl.length % mockLabels.length); return { labels: [ { label: mockLabels[idx], score: 0.92 }, { label: mockLabels[(idx + 1) % mockLabels.length], score: 0.05 }, ], model: 'mock-image', runtime: this._runtimeType, }; }, async runPrivacyRedaction(text) { if (!this._loadedModels.has('privacy-redact')) { await this.loadRedactionModel(); } // Simple regex-based redaction for demo const redacted = text .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[EMAIL]') .replace(/\b\d{3}-\d{2}-\d{4}\b/g, '[SSN]') .replace(/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, '[CARD]'); return { redacted, entities_removed: text !== redacted, model: 'mock-redact', runtime: this._runtimeType }; }, async hashModelWeights() { return 'mock-model-hash-' + this._runtimeType; }, getRuntimeStatus() { return { type: this._runtimeType, models_loaded: Array.from(this._loadedModels.keys()), webgpu: !!navigator.gpu, wasm: typeof WebAssembly !== 'undefined', }; } };