Files
Nils-Johan Gynther ca1eed5061
Test Suite / backend-pr-quick (push) Has been skipped
Test Suite / quick-import-pr-quick (push) Has been skipped
Test Suite / backend-full (push) Successful in 2m31s
Test Suite / flutter-quality (push) Failing after 1m12s
feat(ai): enhance AI trace warnings with product context
- Added `productName` field to `AdminAiWarning` to include product context in warnings
- Updated `collectWarnings` to extract and include `rawName` as `productName` in AI trace warnings
- Added `signals` field to `FlyerParseItem` type for detailed product signals
- Enhanced Flutter admin panel to display product names in AI trace warnings
- Added new `AdminAiTraceResponse` DTO for AI trace data structure
2026-05-24 20:55:14 +02:00

139 lines
4.4 KiB
Dart

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;
final String? productName;
const AdminAiWarning({
required this.code,
required this.kind,
required this.title,
required this.message,
required this.severity,
required this.location,
required this.itemIndex,
required this.productName,
});
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(),
productName: json['productName']?.toString(),
);
}
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,
productName: null,
);
}
}
class AdminAiTraceDetail {
final String id;
final AdminAiTraceSource source;
final AdminAiTraceStatus status;
final DateTime createdAt;
final int userId;
final String userLabel;
final int? sessionId;
final String? fileName;
final String? model;
final int? durationMs;
final int? retryCount;
final int? chunkCount;
final List<AdminAiWarning> warnings;
final List<String> legacyWarnings;
final String? error;
final String? prompt;
final String? rawOutput;
final Map<String, dynamic>? normalizedOutput;
final Map<String, dynamic> summary;
const AdminAiTraceDetail({
required this.id,
required this.source,
required this.status,
required this.createdAt,
required this.userId,
required this.userLabel,
required this.sessionId,
required this.fileName,
required this.model,
required this.durationMs,
required this.retryCount,
required this.chunkCount,
required this.warnings,
required this.legacyWarnings,
required this.error,
required this.prompt,
required this.rawOutput,
required this.normalizedOutput,
required this.summary,
});
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;
final summaryMap = json['summary'] is Map
? Map<String, dynamic>.from(json['summary'] as Map)
: const <String, dynamic>{};
return AdminAiTraceDetail(
id: (json['id'] ?? '').toString(),
source: AdminAiTraceSourceX.fromApi(json['source']?.toString()),
status: AdminAiTraceStatusX.fromApi(json['status']?.toString()),
createdAt: DateTime.tryParse((json['createdAt'] ?? '').toString()) ??
DateTime.fromMillisecondsSinceEpoch(0),
userId: (json['userId'] as num?)?.toInt() ?? 0,
userLabel: (json['userLabel'] ?? '').toString(),
sessionId: (json['sessionId'] as num?)?.toInt(),
fileName: json['fileName']?.toString(),
model: json['model']?.toString(),
durationMs: (json['durationMs'] as num?)?.toInt(),
retryCount: (json['retryCount'] as num?)?.toInt(),
chunkCount: (json['chunkCount'] as num?)?.toInt(),
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(),
normalizedOutput: normalizedOutputMap,
summary: summaryMap,
);
}
}