{"version":3,"sources":["../src/data/sync/model-sync.ts","../src/data/sync/factory.ts"],"names":[],"mappings":"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","file":"chunk-QCASQ7RG.mjs","sourcesContent":["/**\n * Model Sync Service\n *\n * Downloads model weights progressively from a sync server,\n * with resume support and verification.\n */\n\nimport type { BaseStorage } from '../storage/base-storage';\nimport type {\n  DownloadProgress,\n  ModelMeta,\n  TensorChunk,\n  TokenizerData,\n  SyncProgress,\n} from '../../types';\n\nexport interface ModelManifest {\n  model: ModelMeta;\n  tokenizer: {\n    vocabUrl: string;\n    mergesUrl?: string;\n    specialTokens: Record<string, number>;\n    chatTemplate?: string;\n  };\n  layers: Array<{\n    layerIndex: number;\n    tensors: Array<{\n      name: string;\n      dtype: string;\n      shape: number[];\n      chunks: Array<{\n        index: number;\n        url: string;\n        size: number;\n        hash: string;\n      }>;\n    }>;\n  }>;\n  totalSize: number;\n}\n\nexport class ModelSync {\n  private storage: BaseStorage;\n  private syncUrl: string;\n  private onProgress?: (progress: DownloadProgress) => void;\n  private abortController: AbortController | null = null;\n  private currentProgress: DownloadProgress | null = null;\n  private isPaused = false;\n\n  constructor(\n    storage: BaseStorage,\n    syncUrl: string,\n    onProgress?: (progress: DownloadProgress) => void\n  ) {\n    this.storage = storage;\n    this.syncUrl = syncUrl.replace(/\\/$/, '');\n    this.onProgress = onProgress;\n  }\n\n  /**\n   * Start or resume syncing a model\n   */\n  async startSync(modelId: string): Promise<void> {\n    this.abortController = new AbortController();\n    this.isPaused = false;\n\n    try {\n      // Fetch model manifest\n      const manifest = await this.fetchManifest(modelId);\n\n      // Save model metadata\n      await this.storage.setModelMeta(manifest.model);\n\n      // Download tokenizer\n      await this.downloadTokenizer(modelId, manifest.tokenizer);\n\n      // Initialize progress tracking\n      const existingProgress = await this.storage.getSyncProgress(modelId);\n      const progressMap = new Map<string, SyncProgress>();\n      for (const p of existingProgress) {\n        progressMap.set(p.tensorName, p);\n      }\n\n      // Calculate total chunks\n      let totalChunks = 0;\n      let downloadedChunks = 0;\n      for (const layer of manifest.layers) {\n        for (const tensor of layer.tensors) {\n          totalChunks += tensor.chunks.length;\n          const existing = progressMap.get(tensor.name);\n          if (existing) {\n            downloadedChunks += existing.chunksDownloaded;\n          }\n        }\n      }\n\n      // Update progress\n      this.currentProgress = {\n        modelId,\n        percent: (downloadedChunks / totalChunks) * 100,\n        bytesDownloaded: 0,\n        totalBytes: manifest.totalSize,\n        layersComplete: 0,\n        totalLayers: manifest.layers.length,\n        status: 'downloading',\n      };\n      this.emitProgress();\n\n      // Download layers progressively\n      for (let i = 0; i < manifest.layers.length; i++) {\n        const layer = manifest.layers[i];\n\n        // Check for abort/pause\n        if (this.abortController.signal.aborted) break;\n        while (this.isPaused) {\n          await new Promise((r) => setTimeout(r, 100));\n          if (this.abortController.signal.aborted) break;\n        }\n\n        this.currentProgress.currentLayer = `Layer ${layer.layerIndex}`;\n        this.emitProgress();\n\n        // Download each tensor in the layer\n        for (const tensor of layer.tensors) {\n          await this.downloadTensor(\n            modelId,\n            layer.layerIndex,\n            tensor,\n            progressMap\n          );\n        }\n\n        this.currentProgress.layersComplete = i + 1;\n        this.emitProgress();\n      }\n\n      // Verify and mark complete\n      this.currentProgress.status = 'verifying';\n      this.emitProgress();\n\n      const isComplete = await this.storage.isModelComplete(modelId);\n      if (isComplete) {\n        this.currentProgress.status = 'ready';\n        this.currentProgress.percent = 100;\n      } else {\n        this.currentProgress.status = 'error';\n        this.currentProgress.error = 'Download incomplete';\n      }\n      this.emitProgress();\n    } catch (error) {\n      if (this.currentProgress) {\n        this.currentProgress.status = 'error';\n        this.currentProgress.error =\n          error instanceof Error ? error.message : 'Unknown error';\n        this.emitProgress();\n      }\n      throw error;\n    }\n  }\n\n  /**\n   * Pause the current sync\n   */\n  async pauseSync(): Promise<void> {\n    this.isPaused = true;\n  }\n\n  /**\n   * Resume the current sync\n   */\n  async resumeSync(): Promise<void> {\n    this.isPaused = false;\n  }\n\n  /**\n   * Cancel the current sync\n   */\n  async cancelSync(): Promise<void> {\n    this.abortController?.abort();\n    this.isPaused = false;\n    this.currentProgress = null;\n  }\n\n  /**\n   * Get current progress\n   */\n  getProgress(): DownloadProgress | null {\n    return this.currentProgress;\n  }\n\n  /**\n   * Check if syncing\n   */\n  isSyncing(): boolean {\n    return (\n      this.currentProgress !== null &&\n      this.currentProgress.status === 'downloading'\n    );\n  }\n\n  /**\n   * Fetch model manifest from sync server\n   */\n  private async fetchManifest(modelId: string): Promise<ModelManifest> {\n    const response = await fetch(\n      `${this.syncUrl}/models/${modelId}/manifest.json`,\n      { signal: this.abortController?.signal }\n    );\n\n    if (!response.ok) {\n      throw new Error(`Failed to fetch manifest: ${response.statusText}`);\n    }\n\n    return response.json();\n  }\n\n  /**\n   * Download tokenizer files\n   */\n  private async downloadTokenizer(\n    modelId: string,\n    tokenizerInfo: ModelManifest['tokenizer']\n  ): Promise<void> {\n    // Check if already downloaded\n    const existing = await this.storage.getTokenizer(modelId);\n    if (existing) return;\n\n    // Download vocab\n    const vocabResponse = await fetch(tokenizerInfo.vocabUrl, {\n      signal: this.abortController?.signal,\n    });\n    if (!vocabResponse.ok) {\n      throw new Error(`Failed to download vocab: ${vocabResponse.statusText}`);\n    }\n    const vocab = await vocabResponse.arrayBuffer();\n\n    // Download merges if present\n    let merges: ArrayBuffer | undefined;\n    if (tokenizerInfo.mergesUrl) {\n      const mergesResponse = await fetch(tokenizerInfo.mergesUrl, {\n        signal: this.abortController?.signal,\n      });\n      if (mergesResponse.ok) {\n        merges = await mergesResponse.arrayBuffer();\n      }\n    }\n\n    // Save tokenizer\n    const tokenizerData: TokenizerData = {\n      modelId,\n      vocab,\n      merges,\n      specialTokens: tokenizerInfo.specialTokens,\n      chatTemplate: tokenizerInfo.chatTemplate,\n    };\n    await this.storage.setTokenizer(tokenizerData);\n  }\n\n  /**\n   * Download a tensor's chunks\n   */\n  private async downloadTensor(\n    modelId: string,\n    layerIndex: number,\n    tensor: ModelManifest['layers'][0]['tensors'][0],\n    progressMap: Map<string, SyncProgress>\n  ): Promise<void> {\n    const existing = progressMap.get(tensor.name);\n    const startChunk = existing?.chunksDownloaded ?? 0;\n\n    for (let i = startChunk; i < tensor.chunks.length; i++) {\n      // Check for abort\n      if (this.abortController?.signal.aborted) return;\n      while (this.isPaused) {\n        await new Promise((r) => setTimeout(r, 100));\n        if (this.abortController?.signal.aborted) return;\n      }\n\n      const chunk = tensor.chunks[i];\n\n      // Download chunk\n      const response = await fetch(chunk.url, {\n        signal: this.abortController?.signal,\n      });\n      if (!response.ok) {\n        throw new Error(`Failed to download chunk: ${response.statusText}`);\n      }\n\n      const data = await response.arrayBuffer();\n\n      // Verify hash\n      const hash = await this.computeHash(data);\n      if (hash !== chunk.hash) {\n        throw new Error(`Hash mismatch for ${tensor.name} chunk ${i}`);\n      }\n\n      // Store chunk\n      const tensorChunk: TensorChunk = {\n        id: `${modelId}:${tensor.name}:${i}`,\n        modelId,\n        tensorName: tensor.name,\n        layerIndex,\n        chunkIndex: i,\n        totalChunks: tensor.chunks.length,\n        dtype: tensor.dtype as any,\n        shape: tensor.shape,\n        offset: i * data.byteLength,\n        data,\n        hash,\n      };\n      await this.storage.storeTensorChunk(tensorChunk);\n\n      // Update progress\n      const progress: SyncProgress = {\n        modelId,\n        tensorName: tensor.name,\n        chunksDownloaded: i + 1,\n        totalChunks: tensor.chunks.length,\n        lastSync: new Date().toISOString(),\n      };\n      await this.storage.updateSyncProgress(progress);\n      progressMap.set(tensor.name, progress);\n\n      // Update download progress\n      if (this.currentProgress) {\n        this.currentProgress.bytesDownloaded += data.byteLength;\n        this.currentProgress.percent =\n          (this.currentProgress.bytesDownloaded /\n            this.currentProgress.totalBytes) *\n          100;\n        this.emitProgress();\n      }\n    }\n  }\n\n  /**\n   * Compute SHA-256 hash of data\n   */\n  private async computeHash(data: ArrayBuffer): Promise<string> {\n    const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n    const hashArray = Array.from(new Uint8Array(hashBuffer));\n    return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n  }\n\n  /**\n   * Emit progress update\n   */\n  private emitProgress(): void {\n    if (this.onProgress && this.currentProgress) {\n      this.onProgress({ ...this.currentProgress });\n    }\n  }\n}\n","/**\n * Sync Factory\n *\n * Creates ModelSync instances.\n */\n\nimport { ModelSync } from './model-sync';\nimport type { BaseStorage } from '../storage/base-storage';\nimport type { DownloadProgress } from '../../types';\n\nexport interface SyncOptions {\n  /** Sync server URL */\n  syncUrl: string;\n  /** Progress callback */\n  onProgress?: (progress: DownloadProgress) => void;\n}\n\n/**\n * Create a model sync service\n */\nexport function createModelSync(\n  storage: BaseStorage,\n  options: SyncOptions\n): ModelSync {\n  return new ModelSync(storage, options.syncUrl, options.onProgress);\n}\n\n/**\n * Default sync server URLs\n */\nexport const DEFAULT_SYNC_URLS = {\n  production: 'https://models.affectively.ai',\n  development: 'http://localhost:8787',\n} as const;\n"]}