Files

92 lines
2.9 KiB
Dart
Raw Permalink Normal View History

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<AsyncValue<String?>> {
@override
AsyncValue<String?> build() => const AsyncData(null);
Future<void> 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, AsyncValue<String?>>(
InsightNotifier.new);