Skip to content

Commit

Permalink
Feature/audios storage (#131)
Browse files Browse the repository at this point in the history
Allowing users to play the recordings of their memories + upload them to
their own GCP cloud storage bucket (optional)
  • Loading branch information
josancamon19 committed May 8, 2024
2 parents 95a6ce2 + a69ed24 commit 05174fc
Show file tree
Hide file tree
Showing 11 changed files with 636 additions and 320 deletions.
68 changes: 39 additions & 29 deletions apps/AppWithWearable/Tasks.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,67 @@
### Tasks

- [ ] It shouldn't require to reconnect every time that you open the app, it should
- [ ] It shouldn't require to reconnect every time that you open the app, it should
load `ConnectDeviceWidget` and in there listen/reconnect to the device.
- [ ] Device disconnected, display dialog to user asking to reconnect, or take the user back
- [ ] Device disconnected, display dialog to user asking to reconnect, or take the user back
to `find_devices` page.
- [ ] Settings bottom sheet, improve way of handling `***` blurring of api keys, as if you save it
- [x] Settings bottom sheet, improve way of handling `***` blurring of api keys, as if you save it
while is blurred with *, it sets the key to that value, and you have to set them again
- [ ] [iOS] memories and chat page on the bottom do not have the blurred colors pattern, but plain
- [ ] [iOS] memories and chat page on the bottom do not have the blurred colors pattern, but plain
primary color
- [ ] Improve structured memory results performance by sending n previous memories as part of the
structuring but as context, not as part of the structure, so that if there's some reference to a
- [ ] Improve structured memory results performance by sending n previous memories as part of the
structuring but as context, not as part of the structure, so that if there's some reference to a
person, and then you use a pronoun, the LLM understands what you are referring to.
- [ ] Migrate MemoryRecord from SharedPreferences to sqlite
- [ ] Implement [similarity search](https://www.pinecone.io/learn/vector-similarity/) locally
- [ ] Use from the AppStandalone `_ragContext` function as a baseline for creating the query
- [ ] Use from the AppStandalone `_ragContext` function as a baseline for creating the query
embedding.
- [ ] When a memory is created, compute the vector embedding and store it locally.
- [ ] When the user sends a question in the chat, extract from the AppStandalone
the `function_calling` that determines if the message requires context, if that's the case,
retrieve the top 10 most similar vectors ~~ For an initial version we can read all memories
- [ ] When the user sends a question in the chat, extract from the AppStandalone
the `function_calling` that determines if the message requires context, if that's the case,
retrieve the top 10 most similar vectors ~~ For an initial version we can read all memories
from sqlite or SharedPreferences, and compute the formula between the query and each vector.
- [ ] Use that as context, and ask to the LLM. Retrieve the prompt from the AppStandalone.
- [X] -----
- [ ] Another option is to use one of the vector db libraries available for
dart https://github.com/FastCodeAI/DVDB or https://pub.dev/packages/chromadb
- [X] -----
- [ ] Another option is to use one of the vector db libraries available for
dart https://github.com/FastCodeAI/DVDB or https://pub.dev/packages/chromadb
- [ ] Settings Deepgram + openAI key are forced to be set
- [ ] In case an API key fails, either Deepgram WebSocket connection fails, or GPT requests, let the
- [ ] In case an API key fails, either Deepgram WebSocket connection fails, or GPT requests, let
the
user know the error message, either has no more credits, api key is invalid, etc.
- [ ] Improve connected device page UI, including transcription text, and when memory creates after
- [ ] Improve connected device page UI, including transcription text, and when memory creates
after
30 seconds, let the user know
- [ ] Structure the memory asking JSON output `{"title", "summary"}`, in that way we can have better
- [ ] Structure the memory asking JSON output `{"title", "summary"}`, in that way we can have
better
parsed data.
- [ ] Test/Implement [speaker diarization](https://developers.deepgram.com/docs/diarization) to
recognize multiple speakers in transcription, use that for better context when creating the
- [x] Test/Implement [speaker diarization](https://developers.deepgram.com/docs/diarization) to
recognize multiple speakers in transcription, use that for better context when creating the
structured memory.
- [x] Better `AppWithWerable` folders structure.
- [ ] Define flutter code style rules.
- [ ] Include documentation on how to run `AppWithWearable`.
- [ ] If only 1 speaker, set memory prompt creation, explain those are your thoughts, not a
conversation, also, remove Speaker $i in transcript.
- [ ] Allow users who don't have a GCP bucket to store their recordings locally.
- [ ] Improve recordings player.
---

---

- [x] Multilanguage option, implement settings selector, and use that for the deepgram websocket
- [x] Multilanguage option, implement settings selector, and use that for the deepgram websocket
creation
- [ ] Option for storing your transcripts somewhere in the cloud, user inputs their own GCP storage
bucket + auth key, and the files are uploaded there + a reference is stored in the MemoryRecord
- [x] Option for storing your transcripts somewhere in the cloud, user inputs their own GCP
storage
bucket + auth key, and the files are uploaded there + a reference is stored in the MemoryRecord
object.
- [ ] `createWavFile` remove empty sounds without words, and saves that fixed file.

- [ ] ~~ (Idea) Detect a keyword or special order e.g. "Hey Friend" (but not so generic) and
triggers a prompt execution + response. This would require a few hardware updates (could also be a
- [ ] ~~ (Idea) Detect a keyword or special order e.g. "Hey Friend" (but not so generic) and
triggers a prompt execution + response. This would require a few hardware updates (could also be
a
button on the device), and it's way bigger than it seems.
- [ ] ~~ (Idea) Store the location at which the memory was created, and have saved places, like "at
- [ ] ~~ (Idea) Store the location at which the memory was created, and have saved places, like "
at
Home you were chatting about x and y"
- [ ] ~~ (Idea) Speaker detection, use something like the python
library [librosa](https://github.com/librosa/librosa), so that friend recognizes when is you the
one speaking and creates memories better considering that. Maybe even later learns to recognize
- [ ] ~~ (Idea) Speaker detection, use something like the python
library [librosa](https://github.com/librosa/librosa), so that friend recognizes when is you the
one speaking and creates memories better considering that. Maybe even later learns to recognize
other people.
17 changes: 9 additions & 8 deletions apps/AppWithWearable/lib/actions/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import '/backend/api_requests/api_calls.dart';
import '/flutter_flow/flutter_flow_util.dart';

// Perform actions periodically
Future<void> processTranscriptContent(String content) async {
if (content.isNotEmpty) await memoryCreationBlock(content);
Future<void> processTranscriptContent(String content, String? audioFileName) async {
if (content.isNotEmpty) await memoryCreationBlock(content, audioFileName);
}

// Process the creation of memory records
Future<void> memoryCreationBlock(String rawMemory) async {
Future<void> memoryCreationBlock(String rawMemory, String? audioFileName) async {
changeAppStateMemoryCreating();
var structuredMemory = await generateTitleAndSummaryForMemory(rawMemory);
debugPrint('Structured Memory: $structuredMemory');
if (structuredMemory.contains("N/A")) {
await saveFailureMemory(rawMemory, structuredMemory);
changeAppStateMemoryCreating();
} else {
await finalizeMemoryRecord(rawMemory, structuredMemory);
await finalizeMemoryRecord(rawMemory, structuredMemory, audioFileName);
}
}

Expand All @@ -42,20 +42,21 @@ void changeAppStateMemoryCreating() {
}

// Finalize memory record after processing feedback
Future<void> finalizeMemoryRecord(String rawMemory, String structuredMemory) async {
await createMemoryRecord(rawMemory, structuredMemory);
Future<void> finalizeMemoryRecord(String rawMemory, String structuredMemory, String? audioFilePath) async {
await createMemoryRecord(rawMemory, structuredMemory, audioFilePath);
changeAppStateMemoryCreating();
}

// Create memory record
Future<MemoryRecord> createMemoryRecord(String rawMemory, String structuredMemory) async {
Future<MemoryRecord> createMemoryRecord(String rawMemory, String structuredMemory, String? audioFileName) async {
var memory = MemoryRecord(
id: const Uuid().v4(),
date: DateTime.now(),
rawMemory: rawMemory,
structuredMemory: structuredMemory,
isEmpty: rawMemory == '',
isUseless: false);
isUseless: false,
audioFileName: audioFileName);
MemoryStorage.addMemory(memory);
debugPrint('createMemoryRecord added memory: ${memory.id}');
return memory;
Expand Down
93 changes: 93 additions & 0 deletions apps/AppWithWearable/lib/backend/api_requests/cloud_storage.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'package:googleapis_auth/auth_io.dart';
import 'package:path_provider/path_provider.dart';

AuthClient? authClient;

void authenticateGCP() async {
var prefs = await SharedPreferences.getInstance();
var credentialsBase64 = prefs.getString('gcpCredentials') ?? '';
if (credentialsBase64.isEmpty) {
debugPrint('No GCP credentials found');
return;
}
final credentialsBytes = base64Decode(credentialsBase64);
String decodedString = utf8.decode(credentialsBytes);
final credentials = ServiceAccountCredentials.fromJson(jsonDecode(decodedString));
var scopes = ['https://www.googleapis.com/auth/devstorage.full_control'];
authClient = await clientViaServiceAccount(credentials, scopes);
debugPrint('Authenticated');
}

Future<String?> uploadFile(File file) async {
var prefs = await SharedPreferences.getInstance();
String bucketName = prefs.getString('gcpBucketName') ?? '';
if (bucketName.isEmpty) {
debugPrint('No bucket name found');
return null;
}
String fileName = file.path.split('/')[file.path.split('/').length - 1];
String url = 'https://storage.googleapis.com/upload/storage/v1/b/$bucketName/o?uploadType=media&name=$fileName';

try {
var response = await http.post(
Uri.parse(url),
headers: {
'Authorization': 'Bearer ${authClient?.credentials.accessToken.data}',
'Content-Type': 'audio/wav',
},
body: file.readAsBytesSync(),
);

if (response.statusCode == 200) {
// var json = jsonDecode(response.body);
debugPrint('Upload successful');
return fileName;
} else {
debugPrint('Failed to upload');
}
} catch (e) {
debugPrint('Error uploading file: $e');
}
return null;
}

// Download file method
Future<File?> downloadFile(String objectName, String saveFileName) async {
final directory = await getApplicationDocumentsDirectory();
String saveFilePath = '${directory.path}/$saveFileName';
if (File(saveFilePath).existsSync()) {
debugPrint('File already exists: $saveFileName');
return File(saveFilePath);
}

var prefs = await SharedPreferences.getInstance();
String bucketName = prefs.getString('gcpBucketName') ?? '';
if (bucketName.isEmpty) {
debugPrint('No bucket name found');
return null;
}

try {
var response = await http.get(
Uri.parse('https://storage.googleapis.com/storage/v1/b/$bucketName/o/$objectName?alt=media'),
headers: {'Authorization': 'Bearer ${authClient?.credentials.accessToken.data}'},
);

if (response.statusCode == 200) {
final file = File('${directory.path}/$saveFileName');
await file.writeAsBytes(response.bodyBytes);
debugPrint('Download successful: $saveFileName');
return file;
} else {
debugPrint('Failed to download: ${response.body}');
}
} catch (e) {
debugPrint('Error downloading file: $e');
}
return null;
}
6 changes: 6 additions & 0 deletions apps/AppWithWearable/lib/backend/storage/memories.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:friend_private/flutter_flow/flutter_flow_util.dart';
import 'package:shared_preferences/shared_preferences.dart';

Expand All @@ -8,6 +9,8 @@ class MemoryRecord {
String structuredMemory;
bool isEmpty;
bool isUseless;
String? audioFileName;
PlayerState playerState = PlayerState.stopped;

MemoryRecord({
required this.id,
Expand All @@ -16,6 +19,7 @@ class MemoryRecord {
required this.structuredMemory,
required this.isEmpty,
required this.isUseless,
this.audioFileName,
});

Map<String, dynamic> toJson() {
Expand All @@ -26,6 +30,7 @@ class MemoryRecord {
'structuredMemory': structuredMemory,
'isEmpty': isEmpty,
'isUseless': isUseless,
'audioFileName': audioFileName ?? '',
};
}

Expand All @@ -37,6 +42,7 @@ class MemoryRecord {
structuredMemory: json['structuredMemory'],
isEmpty: json['isEmpty'],
isUseless: json['isUseless'],
audioFileName: json['audioFileName'],
);
}

Expand Down

0 comments on commit 05174fc

Please sign in to comment.