67a7590525
- Add AiTrace model to Prisma schema with relations to User - Implement AiTraceService with CRUD operations for AI traces - Add new admin panel for AI traces with filtering and detail views - Integrate trace persistence in receipt import flow - Add API endpoints for listing and retrieving AI traces - Update Flutter admin UI with new AI tab and navigation - Add new domain models for AI traces and details - Add migration for AiTrace table creation BREAKING CHANGE: None
108 lines
3.2 KiB
Dart
108 lines
3.2 KiB
Dart
enum AdminAiTraceSource { receipt, flyer }
|
|
|
|
enum AdminAiTraceStatus { success, warning, error }
|
|
|
|
extension AdminAiTraceSourceX on AdminAiTraceSource {
|
|
String get apiValue =>
|
|
this == AdminAiTraceSource.receipt ? 'receipt' : 'flyer';
|
|
|
|
String get label => this == AdminAiTraceSource.receipt ? 'Kvitto' : 'Flyer';
|
|
|
|
static AdminAiTraceSource fromApi(String? value) {
|
|
if (value == 'receipt') return AdminAiTraceSource.receipt;
|
|
return AdminAiTraceSource.flyer;
|
|
}
|
|
}
|
|
|
|
extension AdminAiTraceStatusX on AdminAiTraceStatus {
|
|
String get label => switch (this) {
|
|
AdminAiTraceStatus.success => 'OK',
|
|
AdminAiTraceStatus.warning => 'Varning',
|
|
AdminAiTraceStatus.error => 'Fel',
|
|
};
|
|
|
|
static AdminAiTraceStatus fromApi(String? value) {
|
|
return switch (value) {
|
|
'error' => AdminAiTraceStatus.error,
|
|
'warning' => AdminAiTraceStatus.warning,
|
|
_ => AdminAiTraceStatus.success,
|
|
};
|
|
}
|
|
}
|
|
|
|
class AdminAiTraceListItem {
|
|
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 warningsCount;
|
|
final bool hasPrompt;
|
|
final bool hasOutput;
|
|
final String? error;
|
|
|
|
const AdminAiTraceListItem({
|
|
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.warningsCount,
|
|
required this.hasPrompt,
|
|
required this.hasOutput,
|
|
required this.error,
|
|
});
|
|
|
|
factory AdminAiTraceListItem.fromJson(Map<String, dynamic> json) {
|
|
return AdminAiTraceListItem(
|
|
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(),
|
|
warningsCount: (json['warningsCount'] as num?)?.toInt() ?? 0,
|
|
hasPrompt: json['hasPrompt'] == true,
|
|
hasOutput: json['hasOutput'] == true,
|
|
error: json['error']?.toString(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class AdminAiTraceListResponse {
|
|
final List<AdminAiTraceListItem> items;
|
|
final String? nextCursor;
|
|
|
|
const AdminAiTraceListResponse({
|
|
required this.items,
|
|
required this.nextCursor,
|
|
});
|
|
|
|
factory AdminAiTraceListResponse.fromJson(Map<String, dynamic> json) {
|
|
final rawItems = (json['items'] as List<dynamic>?) ?? const [];
|
|
return AdminAiTraceListResponse(
|
|
items: rawItems
|
|
.whereType<Map>()
|
|
.map((entry) =>
|
|
AdminAiTraceListItem.fromJson(Map<String, dynamic>.from(entry)))
|
|
.toList(),
|
|
nextCursor: json['nextCursor']?.toString(),
|
|
);
|
|
}
|
|
}
|