import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../database/app_database.dart'; import '../llm/prompt_builder.dart'; import 'database_provider.dart'; import 'llm_provider.dart'; // ── Insight type enum ───────────────────────────────────────────────────────── enum InsightType { weekly, monthly, query } // ── Insight notifier — fetches data, builds prompt, calls LLM, caches result ── class InsightNotifier extends Notifier> { @override AsyncValue build() => const AsyncData(null); Future generateInsight({ InsightType type = InsightType.weekly, bool forceRefresh = false, }) async { state = const AsyncLoading(); try { // Ensure model is initialised before generating await ref.read(llmReadyProvider.future); if (!ref.mounted) return; final now = DateTime.now(); final end = DateTime(now.year, now.month, now.day, 23, 59, 59); final start = end.subtract(const Duration(days: 6)); final dao = ref.read(moodDaoProvider); final entries = await dao.getEntriesWithActivitiesInRange(start, end); if (!ref.mounted) return; if (entries.length < 2) { state = const AsyncData(null); return; } final prompt = PromptBuilder.buildWeeklyPrompt(entries); final hash = _hashPrompt(prompt); if (!forceRefresh) { final cached = await ref.read(insightDaoProvider).getCached(hash); if (!ref.mounted) return; if (cached != null) { state = AsyncData(cached.responseText); return; } } final llm = ref.read(llmServiceProvider); final buffer = StringBuffer(); await for (final token in llm.generateStream(prompt)) { if (!ref.mounted) return; buffer.write(token); state = AsyncData(buffer.toString()); } if (!ref.mounted) return; final result = buffer.toString().trim(); await ref.read(insightDaoProvider).upsertInsight( LlmInsightsCompanion.insert( type: type.name, promptHash: hash, responseText: result, rangeStart: Value(start), rangeEnd: Value(end), ), ); if (!ref.mounted) return; state = AsyncData(result); } catch (e, st) { if (!ref.mounted) return; state = AsyncError(e, st); } } void clear() => state = const AsyncData(null); static String _hashPrompt(String prompt) => sha256.convert(utf8.encode(prompt)).toString(); } final insightNotifierProvider = NotifierProvider.autoDispose>( InsightNotifier.new);