feat(ai): enhance AI trace warnings and reason codes system
- Added structured warning system with `AdminAiWarning` type in backend and Flutter - Implemented detailed reason descriptors with `FlyerReasonDescriptor` for parse and match operations - Added `legacyWarnings` field to maintain backward compatibility - Enhanced AI trace service to collect and format warnings with item-level context - Updated flyer import services to include detailed reason descriptions in responses - Added Swedish diacritic preservation for cheese variants (Prästost, Herrgårdsost, Grevéost) - Implemented UTF-8 content validation for AI responses - Added new reason code definitions in `reason-codes.ts` - Updated Flutter UI to display structured warnings with severity indicators - Added error report generation and copy functionality in admin panel - Added comprehensive test coverage for new warning system and cheese normalization BREAKING CHANGE: AI trace warnings are now structured objects instead of simple strings
This commit is contained in:
@@ -1,5 +1,53 @@
|
||||
import 'admin_ai_trace.dart';
|
||||
|
||||
class AdminAiWarning {
|
||||
final String code;
|
||||
final String kind;
|
||||
final String title;
|
||||
final String message;
|
||||
final String severity;
|
||||
final String? location;
|
||||
final int? itemIndex;
|
||||
|
||||
const AdminAiWarning({
|
||||
required this.code,
|
||||
required this.kind,
|
||||
required this.title,
|
||||
required this.message,
|
||||
required this.severity,
|
||||
required this.location,
|
||||
required this.itemIndex,
|
||||
});
|
||||
|
||||
factory AdminAiWarning.fromJson(Map<String, dynamic> json) {
|
||||
return AdminAiWarning(
|
||||
code: (json['code'] ?? '').toString(),
|
||||
kind: (json['kind'] ?? '').toString(),
|
||||
title: (json['title'] ?? '').toString(),
|
||||
message: (json['message'] ?? '').toString(),
|
||||
severity: (json['severity'] ?? '').toString(),
|
||||
location: json['location']?.toString(),
|
||||
itemIndex: (json['itemIndex'] as num?)?.toInt(),
|
||||
);
|
||||
}
|
||||
|
||||
factory AdminAiWarning.fromLegacy(String value) {
|
||||
final trimmed = value.trim();
|
||||
final parts = trimmed.split(':');
|
||||
final kind = parts.isNotEmpty ? parts.first : 'parse';
|
||||
final code = parts.length > 1 ? parts.sublist(1).join(':') : trimmed;
|
||||
return AdminAiWarning(
|
||||
code: code,
|
||||
kind: kind,
|
||||
title: trimmed,
|
||||
message: trimmed,
|
||||
severity: 'warning',
|
||||
location: null,
|
||||
itemIndex: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AdminAiTraceDetail {
|
||||
final String id;
|
||||
final AdminAiTraceSource source;
|
||||
@@ -13,7 +61,8 @@ class AdminAiTraceDetail {
|
||||
final int? durationMs;
|
||||
final int? retryCount;
|
||||
final int? chunkCount;
|
||||
final List<String> warnings;
|
||||
final List<AdminAiWarning> warnings;
|
||||
final List<String> legacyWarnings;
|
||||
final String? error;
|
||||
final String? prompt;
|
||||
final String? rawOutput;
|
||||
@@ -34,6 +83,7 @@ class AdminAiTraceDetail {
|
||||
required this.retryCount,
|
||||
required this.chunkCount,
|
||||
required this.warnings,
|
||||
required this.legacyWarnings,
|
||||
required this.error,
|
||||
required this.prompt,
|
||||
required this.rawOutput,
|
||||
@@ -43,6 +93,7 @@ class AdminAiTraceDetail {
|
||||
|
||||
factory AdminAiTraceDetail.fromJson(Map<String, dynamic> json) {
|
||||
final warningsRaw = (json['warnings'] as List<dynamic>?) ?? const [];
|
||||
final legacyWarningsRaw = (json['legacyWarnings'] as List<dynamic>?) ?? const [];
|
||||
final normalizedOutputMap = json['normalizedOutput'] is Map
|
||||
? Map<String, dynamic>.from(json['normalizedOutput'] as Map)
|
||||
: null;
|
||||
@@ -64,7 +115,15 @@ class AdminAiTraceDetail {
|
||||
durationMs: (json['durationMs'] as num?)?.toInt(),
|
||||
retryCount: (json['retryCount'] as num?)?.toInt(),
|
||||
chunkCount: (json['chunkCount'] as num?)?.toInt(),
|
||||
warnings: warningsRaw.map((entry) => entry.toString()).toList(),
|
||||
warnings: warningsRaw
|
||||
.map((entry) {
|
||||
if (entry is Map) {
|
||||
return AdminAiWarning.fromJson(Map<String, dynamic>.from(entry));
|
||||
}
|
||||
return AdminAiWarning.fromLegacy(entry.toString());
|
||||
})
|
||||
.toList(),
|
||||
legacyWarnings: legacyWarningsRaw.map((entry) => entry.toString()).toList(),
|
||||
error: json['error']?.toString(),
|
||||
prompt: json['prompt']?.toString(),
|
||||
rawOutput: json['rawOutput']?.toString(),
|
||||
|
||||
Reference in New Issue
Block a user