feat(ai): add AI trace tracking and admin panel
- 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
This commit is contained in:
@@ -3,6 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:recipe_flutter/features/admin/data/admin_repository.dart';
|
||||
import 'package:recipe_flutter/features/admin/domain/admin_ai_categorize_result.dart';
|
||||
import 'package:recipe_flutter/features/admin/domain/admin_ai_trace.dart';
|
||||
import 'package:recipe_flutter/features/admin/domain/admin_ai_trace_detail.dart';
|
||||
import 'package:recipe_flutter/features/admin/domain/admin_category_node.dart';
|
||||
import 'package:recipe_flutter/features/admin/domain/admin_inventory_item.dart';
|
||||
import 'package:recipe_flutter/features/admin/domain/admin_pantry_item.dart';
|
||||
@@ -20,99 +22,187 @@ class TestAdminRepositoryWrapper implements AdminRepository {
|
||||
TestAdminRepositoryWrapper(this._fakeRepo);
|
||||
|
||||
@override
|
||||
Future<List<ReceiptAlias>> listReceiptAliases() => _fakeRepo.listReceiptAliases();
|
||||
Future<List<ReceiptAlias>> listReceiptAliases() =>
|
||||
_fakeRepo.listReceiptAliases();
|
||||
|
||||
@override
|
||||
Future<List<AdminProduct>> listGlobalProducts() => _fakeRepo.listGlobalProducts();
|
||||
Future<List<AdminProduct>> listGlobalProducts() =>
|
||||
_fakeRepo.listGlobalProducts();
|
||||
|
||||
@override
|
||||
Future<void> updateReceiptAlias(int id, {String? receiptName, int? productId, bool? isGlobal}) => _fakeRepo.updateReceiptAlias(id, receiptName: receiptName, productId: productId, isGlobal: isGlobal);
|
||||
Future<void> updateReceiptAlias(int id,
|
||||
{String? receiptName, int? productId, bool? isGlobal}) =>
|
||||
_fakeRepo.updateReceiptAlias(id,
|
||||
receiptName: receiptName, productId: productId, isGlobal: isGlobal);
|
||||
|
||||
// Stub implementations for other required methods
|
||||
@override
|
||||
Future<List<AdminAiCategorizeResult>> aiCategorizeBulk({List<int>? productIds}) => throw UnimplementedError();
|
||||
Future<List<AdminAiCategorizeResult>> aiCategorizeBulk(
|
||||
{List<int>? productIds}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<int> bulkSetCategory(List<int> ids, {required int? categoryId}) => throw UnimplementedError();
|
||||
Future<int> bulkSetCategory(List<int> ids, {required int? categoryId}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<AdminInventoryItem> createAdminInventory({int? userId, required int productId, required double quantity, required String unit, String? location, String? brand, String? receiptName, String? suitableFor, String? comment}) => throw UnimplementedError();
|
||||
Future<AdminInventoryItem> createAdminInventory(
|
||||
{int? userId,
|
||||
required int productId,
|
||||
required double quantity,
|
||||
required String unit,
|
||||
String? location,
|
||||
String? brand,
|
||||
String? receiptName,
|
||||
String? suitableFor,
|
||||
String? comment}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<AdminPantryItem> createAdminPantry({int? userId, required int productId, String? location}) => throw UnimplementedError();
|
||||
Future<AdminPantryItem> createAdminPantry(
|
||||
{int? userId, required int productId, String? location}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<Map<String, dynamic>> createProduct(String name, {int? categoryId}) => throw UnimplementedError();
|
||||
Future<Map<String, dynamic>> createProduct(String name, {int? categoryId}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<UserAdmin> createUser({required String username, required String email, required String password, String role = 'user'}) => throw UnimplementedError();
|
||||
Future<UserAdmin> createUser(
|
||||
{required String username,
|
||||
required String email,
|
||||
required String password,
|
||||
String role = 'user'}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> deleteUser(int userId) => throw UnimplementedError();
|
||||
@override
|
||||
Future<List<AdminInventoryItem>> listAdminInventory({int? userId, String? sort}) => throw UnimplementedError();
|
||||
Future<List<AdminInventoryItem>> listAdminInventory(
|
||||
{int? userId, String? sort}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<List<AdminPantryItem>> listAdminPantry({int? userId}) => throw UnimplementedError();
|
||||
Future<List<AdminPantryItem>> listAdminPantry({int? userId}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<List<AiModelInfo>> listAiModels() => throw UnimplementedError();
|
||||
@override
|
||||
Future<List<AdminCategoryNode>> listCategoryTree() => throw UnimplementedError();
|
||||
Future<AdminAiTraceDetail> getAiTraceById(String traceId) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<List<AdminProduct>> listDeletedProducts() => throw UnimplementedError();
|
||||
Future<AdminAiTraceListResponse> listAiTraces(
|
||||
{required AdminAiTraceSource source,
|
||||
int limit = 25,
|
||||
String? cursor,
|
||||
String? period,
|
||||
bool onlyErrors = false}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<List<PendingProduct>> listPendingProducts() => throw UnimplementedError();
|
||||
Future<List<AdminCategoryNode>> listCategoryTree() =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<List<PendingProduct>> listPrivateProducts() => throw UnimplementedError();
|
||||
Future<List<AdminProduct>> listDeletedProducts() =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<List<PendingProduct>> listPendingProducts() =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<List<PendingProduct>> listPrivateProducts() =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<List<AdminProduct>> listProducts() => throw UnimplementedError();
|
||||
@override
|
||||
Future<List<AdminProduct>> listSelectableProductsForAdmin({bool forceRefresh = false}) => throw UnimplementedError();
|
||||
Future<List<AdminProduct>> listSelectableProductsForAdmin(
|
||||
{bool forceRefresh = false}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<List<UserAdmin>> listUsers() => throw UnimplementedError();
|
||||
@override
|
||||
Future<void> mergeAdminInventory({required int sourceInventoryId, required int targetInventoryId}) => throw UnimplementedError();
|
||||
Future<void> mergeAdminInventory(
|
||||
{required int sourceInventoryId, required int targetInventoryId}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> mergeProducts({required int sourceProductId, required int targetProductId}) => throw UnimplementedError();
|
||||
Future<void> mergeProducts(
|
||||
{required int sourceProductId, required int targetProductId}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> mergeProductsPrivate({required int sourceProductId, required int targetProductId}) => throw UnimplementedError();
|
||||
Future<void> mergeProductsPrivate(
|
||||
{required int sourceProductId, required int targetProductId}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> moveAdminInventoryToPantry(int inventoryId) => throw UnimplementedError();
|
||||
Future<void> moveAdminInventoryToPantry(int inventoryId) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> moveAdminPantryToInventory(int pantryItemId, Map<String, dynamic> body) => throw UnimplementedError();
|
||||
Future<void> moveAdminPantryToInventory(
|
||||
int pantryItemId, Map<String, dynamic> body) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<Map<String, dynamic>> previewAdminInventoryMerge({required int sourceInventoryId, required int targetInventoryId}) => throw UnimplementedError();
|
||||
Future<Map<String, dynamic>> previewAdminInventoryMerge(
|
||||
{required int sourceInventoryId, required int targetInventoryId}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<Map<String, dynamic>> previewMerge({required int sourceProductId, required int targetProductId}) => throw UnimplementedError();
|
||||
Future<Map<String, dynamic>> previewMerge(
|
||||
{required int sourceProductId, required int targetProductId}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<AdminProduct> promotePrivateProduct(int productId) => throw UnimplementedError();
|
||||
Future<AdminProduct> promotePrivateProduct(int productId) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> removeAdminInventory(int inventoryId) => throw UnimplementedError();
|
||||
Future<void> removeAdminInventory(int inventoryId) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> removeAdminPantryItem(int pantryItemId) => throw UnimplementedError();
|
||||
Future<void> removeAdminPantryItem(int pantryItemId) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> removeProduct(int productId) => throw UnimplementedError();
|
||||
@override
|
||||
Future<void> removeReceiptAlias(int id) => throw UnimplementedError();
|
||||
@override
|
||||
Future<Map<String, dynamic>> resetPassword(int userId) => throw UnimplementedError();
|
||||
Future<Map<String, dynamic>> resetPassword(int userId) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> restoreProduct(int productId) => throw UnimplementedError();
|
||||
@override
|
||||
Future<UserAdmin> setPremium(int userId, {required bool isPremium}) => throw UnimplementedError();
|
||||
Future<UserAdmin> setPremium(int userId, {required bool isPremium}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> setProductCategory(int productId, {required int? categoryId}) => throw UnimplementedError();
|
||||
Future<void> setProductCategory(int productId, {required int? categoryId}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> setProductStatus(int productId, String status) => throw UnimplementedError();
|
||||
Future<void> setProductStatus(int productId, String status) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<UserAdmin> setRecipeSharing(int userId, {required bool canShareRecipes}) => throw UnimplementedError();
|
||||
Future<UserAdmin> setRecipeSharing(int userId,
|
||||
{required bool canShareRecipes}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<UserAdmin> setRole(int userId, String newRole) => throw UnimplementedError();
|
||||
Future<UserAdmin> setRole(int userId, String newRole) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<AdminInventoryItem> updateAdminInventory(int inventoryId, {int? productId, double? quantity, String? unit, String? location, String? brand, String? receiptName, String? suitableFor, String? comment}) => throw UnimplementedError();
|
||||
Future<AdminInventoryItem> updateAdminInventory(int inventoryId,
|
||||
{int? productId,
|
||||
double? quantity,
|
||||
String? unit,
|
||||
String? location,
|
||||
String? brand,
|
||||
String? receiptName,
|
||||
String? suitableFor,
|
||||
String? comment}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<AdminPantryItem> updateAdminPantry(int pantryItemId, {int? productId, String? location}) => throw UnimplementedError();
|
||||
Future<AdminPantryItem> updateAdminPantry(int pantryItemId,
|
||||
{int? productId, String? location}) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> updateCanonicalName(int productId, String canonicalName) => throw UnimplementedError();
|
||||
Future<void> updateCanonicalName(int productId, String canonicalName) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> updateCanonicalNamePrivate(int productId, String canonicalName) => throw UnimplementedError();
|
||||
Future<void> updateCanonicalNamePrivate(
|
||||
int productId, String canonicalName) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> updateEmail(int userId, String email) => throw UnimplementedError();
|
||||
Future<void> updateEmail(int userId, String email) =>
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
Future<void> upsertReceiptAlias({required String receiptName, required int productId, bool isGlobal = false}) => throw UnimplementedError();
|
||||
Future<void> upsertReceiptAlias(
|
||||
{required String receiptName,
|
||||
required int productId,
|
||||
bool isGlobal = false}) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
// Simple fake that only implements the methods we need
|
||||
@@ -128,7 +218,8 @@ class FakeAdminRepository {
|
||||
return _products;
|
||||
}
|
||||
|
||||
Future<void> updateReceiptAlias(int id, {String? receiptName, int? productId, bool? isGlobal}) async {
|
||||
Future<void> updateReceiptAlias(int id,
|
||||
{String? receiptName, int? productId, bool? isGlobal}) async {
|
||||
// Find and update alias
|
||||
final index = _aliases.indexWhere((a) => a.id == id);
|
||||
if (index >= 0) {
|
||||
|
||||
Reference in New Issue
Block a user