syncTopic method

Future<int> syncTopic(
  1. String topicId
)

Sync a topic's chunks to local storage.

If chunks are already cached locally, this is a no-op. If the local embedding model is ready and chunks lack embeddings, generates embeddings on-device.

Returns the number of chunks synced (0 if already cached).

Implementation

Future<int> syncTopic(String topicId) async {
  if (_syncing.contains(topicId)) return 0;
  if (!_localStore.isReady) return 0;

  // Already cached with embeddings.
  if (_localStore.hasTopicEmbeddings(topicId)) {
    _synced.add(topicId);
    return 0;
  }

  _syncing.add(topicId);
  try {
    // Fetch chunks with embeddings from Supabase.
    final chunksWithEmbeddings = await _supabase.getChunksWithEmbeddings(
      topicId,
    );

    if (chunksWithEmbeddings.isEmpty) {
      _synced.add(topicId);
      return 0;
    }

    final chunks = chunksWithEmbeddings.map((e) => e.$1).toList();
    var embeddings = chunksWithEmbeddings.map((e) => e.$2).toList();

    // If Supabase doesn't have embeddings but local model is ready,
    // generate them on-device.
    final hasAnyEmbedding = embeddings.any((e) => e.isNotEmpty);
    if (!hasAnyEmbedding && _localEmbedding.isReady) {
      dev.log(
        'No Supabase embeddings for topic $topicId, generating on-device...',
        name: 'ChunkSync',
      );
      final generated = <List<double>>[];
      for (final chunk in chunks) {
        final emb = await _localEmbedding.embed(chunk.content);
        generated.add(emb);
      }
      embeddings = generated;
    }

    _localStore.upsertChunks(chunks, embeddings);
    _synced.add(topicId);

    dev.log(
      'Synced ${chunks.length} chunks for topic $topicId '
      '(${embeddings.where((e) => e.isNotEmpty).length} with embeddings)',
      name: 'ChunkSync',
    );
    return chunks.length;
  } catch (e) {
    dev.log('Sync failed for topic $topicId: $e', name: 'ChunkSync');
    return 0;
  } finally {
    _syncing.remove(topicId);
  }
}