Skip to content
This repository has been archived by the owner on Jan 8, 2025. It is now read-only.

Commit

Permalink
WIP, blocked by firebase/flutterfire#13269
Browse files Browse the repository at this point in the history
  • Loading branch information
csells committed Oct 8, 2024
1 parent 5b278ed commit f337c1b
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 35 deletions.
41 changes: 12 additions & 29 deletions lib/data/recipe_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Recipe {
required this.description,
required this.ingredients,
required this.instructions,
required this.embedding,
this.tags = const [],
this.notes = '',
});
Expand All @@ -20,6 +21,7 @@ class Recipe {
description: '',
ingredients: [],
instructions: [],
embedding: [],
tags: [],
notes: '',
);
Expand All @@ -32,6 +34,7 @@ class Recipe {
instructions: List<String>.from(json['instructions']),
tags: json['tags'] == null ? [] : List<String>.from(json['tags']),
notes: json['notes'] ?? '',
embedding: List<double>.from(json['embedding']),
);

final String id;
Expand All @@ -41,6 +44,7 @@ class Recipe {
final List<String> instructions;
final List<String> tags;
final String notes;
final List<double> embedding;

Map<String, dynamic> toJson() => {
'id': id,
Expand All @@ -52,13 +56,18 @@ class Recipe {
'notes': notes,
};

static Future<List<Recipe>> loadFrom(String json) async {
static List<Recipe> loadFrom(String json) {
final jsonList = jsonDecode(json) as List;
return [for (final json in jsonList) Recipe.fromJson(json)];
}

@override
String toString() => '''# $title
static String getEmbeddingString(
String title,
String description,
List<String> ingredients,
List<String> instructions,
) =>
'''# $title
$description
## Ingredients
Expand All @@ -68,29 +77,3 @@ ${ingredients.join('\n')}
${instructions.join('\n')}
''';
}

class RecipeEmbedding {
RecipeEmbedding({
required this.id,
required this.embedding,
});

factory RecipeEmbedding.fromJson(Map<String, dynamic> json) =>
RecipeEmbedding(
id: json['id'],
embedding: List<double>.from(json['embedding']),
);

final String id;
final List<double> embedding;

static Future<List<RecipeEmbedding>> loadFrom(String json) async {
final jsonList = jsonDecode(json) as List;
return [for (final json in jsonList) RecipeEmbedding.fromJson(json)];
}

Map<String, dynamic> toJson() => {
'id': id,
'embedding': embedding,
};
}
35 changes: 31 additions & 4 deletions lib/data/recipe_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import 'dart:async';
import 'dart:convert';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';

import 'recipe_data.dart';

class RecipeRepository {
static const newRecipeID = '__NEW_RECIPE__';
static const _assetFileName = 'assets/recipes_default.json';
static const _defaultRecipesAsset = 'assets/recipes_default.json';

static List<Recipe>? _recipes;
static final items = ValueNotifier<Iterable<Recipe>>([]);
Expand Down Expand Up @@ -60,21 +62,46 @@ class RecipeRepository {

static Future<void> _loadRecipes() async {
final recipesCollection = _recipesCollection;
final provider = VertexProvider(
embeddingModel: FirebaseVertexAI.instance.generativeModel(
model: 'text-embedding-004',
),
);

// Check if the collection exists and has documents
final snapshot = await recipesCollection.limit(1).get();
if (snapshot.docs.isEmpty) {
// If the collection is empty, seed it with default recipes
final contents = await rootBundle.loadString(_assetFileName);
final contents = await rootBundle.loadString(_defaultRecipesAsset);
final jsonList = json.decode(contents) as List;
final defaultRecipes =
jsonList.map((json) => Recipe.fromJson(json)).toList();
final defaultRecipes = <Recipe>[];
for (var json in jsonList) {
final title = json['title'];
final description = json['description'];
final ingredients = List<String>.from(json['ingredients']);
final instructions = List<String>.from(json['instructions']);

final s = Recipe.getEmbeddingString(
title,
description,
ingredients,
instructions,
);

final embedding = await provider.getDocumentEmbedding(s);

json['embedding'] = embedding;
defaultRecipes.add(Recipe.fromJson(json));
}

// Add default recipes to Firestore
for (var recipe in defaultRecipes) {
await recipesCollection.doc(recipe.id).set(recipe.toJson());
}

// dump the recipes to the console
print(jsonEncode(defaultRecipes));

_recipes = defaultRecipes;
} else {
// If the collection exists and has documents, fetch all recipes
Expand Down
17 changes: 15 additions & 2 deletions lib/pages/edit_recipe_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,25 @@ Generate a response in JSON format with the following schema:
),
);

void _onDone() {
Future<void> _onDone() async {
if (!_formKey.currentState!.validate()) return;

final embedding = await _provider.getDocumentEmbedding(
Recipe.getEmbeddingString(
_titleController.text,
_descriptionController.text,
_ingredientsController.text.split('\n'),
_instructionsController.text.split('\n'),
),
);

final recipe = Recipe(
id: _isNewRecipe ? const Uuid().v4() : widget.recipe.id,
title: _titleController.text,
description: _descriptionController.text,
ingredients: _ingredientsController.text.split('\n'),
instructions: _instructionsController.text.split('\n'),
embedding: embedding,
);

if (_isNewRecipe) {
Expand All @@ -167,7 +177,10 @@ Generate a response in JSON format with the following schema:
RecipeRepository.updateRecipe(recipe);
}

if (context.mounted) context.goNamed('home');
if (context.mounted) {
// ignore: use_build_context_synchronously
context.goNamed('home');
}
}

Future<void> _onMagic() async {
Expand Down

0 comments on commit f337c1b

Please sign in to comment.