Files
rely/lib/core/llm/llm_diagnostics_log.dart
2026-05-19 19:17:35 +02:00

119 lines
3.5 KiB
Dart

import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
const String _llmDiagnosticsLogKey = 'llm_diagnostics_log_v1';
const int _maxLlmDiagnosticsEvents = 40;
const int _maxLlmDiagnosticsDetailChars = 8000;
class LlmDiagnosticsEvent {
const LlmDiagnosticsEvent({
required this.at,
required this.stage,
required this.message,
this.details,
});
factory LlmDiagnosticsEvent.fromJson(Map<String, dynamic> json) {
return LlmDiagnosticsEvent(
at: DateTime.parse(
json['at'] as String? ?? DateTime.now().toUtc().toIso8601String(),
).toLocal(),
stage: json['stage'] as String? ?? 'unknown',
message: json['message'] as String? ?? '',
details: json['details'] as String?,
);
}
final DateTime at;
final String stage;
final String message;
final String? details;
Map<String, dynamic> toJson() {
return <String, dynamic>{
'at': at.toUtc().toIso8601String(),
'stage': stage,
'message': message,
if (details != null) 'details': details,
};
}
}
class LlmDiagnosticsLog {
const LlmDiagnosticsLog();
Future<List<LlmDiagnosticsEvent>> read() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String? raw = prefs.getString(_llmDiagnosticsLogKey);
if (raw == null || raw.isEmpty) {
return const <LlmDiagnosticsEvent>[];
}
try {
final List<dynamic> items = jsonDecode(raw) as List<dynamic>;
return items
.whereType<Map<String, dynamic>>()
.map(LlmDiagnosticsEvent.fromJson)
.toList(growable: false);
} on FormatException {
return const <LlmDiagnosticsEvent>[];
}
}
Future<void> record(String stage, String message, {String? details}) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final List<LlmDiagnosticsEvent> current = await read();
final List<LlmDiagnosticsEvent> next = <LlmDiagnosticsEvent>[
LlmDiagnosticsEvent(
at: DateTime.now(),
stage: stage,
message: message,
details: _compactDetails(details),
),
...current,
].take(_maxLlmDiagnosticsEvents).toList(growable: false);
await prefs.setString(
_llmDiagnosticsLogKey,
jsonEncode(
next
.map((LlmDiagnosticsEvent event) => event.toJson())
.toList(growable: false),
),
);
}
Future<void> clear() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove(_llmDiagnosticsLogKey);
}
String exportText(List<LlmDiagnosticsEvent> events) {
if (events.isEmpty) {
return 'No LLM diagnostics recorded.';
}
return events
.map((LlmDiagnosticsEvent event) {
final String details = event.details == null
? ''
: '\n${event.details}';
return '[${event.at.toIso8601String()}] ${event.stage}: ${event.message}$details';
})
.join('\n\n');
}
}
final Provider<LlmDiagnosticsLog> llmDiagnosticsLogProvider =
Provider<LlmDiagnosticsLog>((Ref ref) => const LlmDiagnosticsLog());
String? _compactDetails(String? value) {
final String? trimmed = value?.trim();
if (trimmed == null || trimmed.isEmpty) {
return null;
}
if (trimmed.length <= _maxLlmDiagnosticsDetailChars) {
return trimmed;
}
return '${trimmed.substring(0, _maxLlmDiagnosticsDetailChars)}\n[truncated]';
}