Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persona using omi data and twitter #1775

Merged
merged 20 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added app/assets/images/calendar_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/clone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/instagram_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/linkedin_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/new_background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/notion_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/x_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/x_logo_mini.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions app/lib/backend/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,11 @@ Future<void> updateGivenName(String fullName) async {
await user.updateProfile(displayName: fullName);
}
}

Future<void> signInAnonymously() async {
try {
await FirebaseAuth.instance.signInAnonymously();
} catch (e) {
Logger.handle(e, null, message: 'An error occurred while signing in. Please try again later.');
}
}
135 changes: 135 additions & 0 deletions app/lib/backend/http/api/apps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,138 @@ Future<String> getGenratedDescription(String name, String description) async {
return '';
}
}

Future<bool> createPersonaApp(File file, Map<String, dynamic> personaData) async {
var request = http.MultipartRequest(
'POST',
Uri.parse('${Env.apiBaseUrl}v1/personas'),
);
request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path)));
request.headers.addAll({'Authorization': await getAuthHeader()});
request.fields.addAll({'persona_data': jsonEncode(personaData)});
print(jsonEncode(personaData));
try {
var streamedResponse = await request.send();
var response = await http.Response.fromStream(streamedResponse);

if (response.statusCode == 200) {
debugPrint('createPersonaApp Response body: ${jsonDecode(response.body)}');
return true;
} else {
debugPrint('Failed to submit app. Status code: ${response.statusCode}');
if (response.body.isNotEmpty) {
return false;
} else {
return false;
}
}
} catch (e) {
debugPrint('An error occurred createPersonaApp: $e');
return false;
}
}

Future<bool> updatePersonaApp(File? file, Map<String, dynamic> personaData) async {
var request = http.MultipartRequest(
'PATCH',
Uri.parse('${Env.apiBaseUrl}v1/personas/${personaData['id']}'),
);
if (file != null) {
request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path)));
}
request.headers.addAll({'Authorization': await getAuthHeader()});
request.fields.addAll({'persona_data': jsonEncode(personaData)});
debugPrint(jsonEncode(personaData));
try {
var streamedResponse = await request.send();
var response = await http.Response.fromStream(streamedResponse);

if (response.statusCode == 200) {
debugPrint('updatePersonaApp Response body: ${jsonDecode(response.body)}');
return true;
} else {
debugPrint('Failed to update app. Status code: ${response.statusCode}');
return false;
}
} catch (e) {
debugPrint('An error occurred updatePersonaApp: $e');
return false;
}
}

Future<bool> checkPersonaUsername(String username) async {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/apps/check-username?username=$username',
headers: {},
body: '',
method: 'GET',
);
try {
if (response == null || response.statusCode != 200) return false;
log('checkPersonaUsernames: ${response.body}');
return jsonDecode(response.body)['is_taken'];
} catch (e, stackTrace) {
debugPrint(e.toString());
CrashReporting.reportHandledCrash(e, stackTrace);
return true;
}
}

Future<Map?> getTwitterProfileData(String username) async {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/personas/twitter/profile?username=$username',
headers: {},
body: '',
method: 'GET',
);
try {
if (response == null || response.statusCode != 200) return null;
log('getTwitterProfileData: ${response.body}');
return jsonDecode(response.body);
} catch (e, stackTrace) {
debugPrint(e.toString());
CrashReporting.reportHandledCrash(e, stackTrace);
return null;
}
}

Future<bool> verifyTwitterOwnership(String username, String? personaId) async {
var url = '${Env.apiBaseUrl}v1/personas/twitter/verify-ownership?username=$username';
if (personaId != null) {
url += '&persona_id=$personaId';
}
var response = await makeApiCall(
url: url,
headers: {},
body: '',
method: 'GET',
);
try {
if (response == null || response.statusCode != 200) return false;
log('verifyTwitterOwnership: ${response.body}');
return jsonDecode(response.body)['verified'];
} catch (e, stackTrace) {
debugPrint(e.toString());
CrashReporting.reportHandledCrash(e, stackTrace);
return false;
}
}

Future<App?> getUserPersonaServer() async {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/personas',
headers: {},
body: '',
method: 'GET',
);
try {
if (response == null || response.statusCode != 200) return null;
log('getPersonaProfile: ${response.body}');
var res = jsonDecode(response.body);
return App.fromJson(res);
} catch (e, stackTrace) {
debugPrint(e.toString());
CrashReporting.reportHandledCrash(e, stackTrace);
return null;
}
}
7 changes: 7 additions & 0 deletions app/lib/backend/preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ class SharedPreferencesUtil {

//-------------------------------- Device ----------------------------------//

bool? get hasOmiDevice => _preferences?.getBool('hasOmiDevice');
set hasOmiDevice(bool? value) {
if (value != null) {
_preferences?.setBool('hasOmiDevice', value);
}
}

set btDevice(BtDevice value) {
saveString('btDevice', jsonEncode(value.toJson()));
}
Expand Down
13 changes: 12 additions & 1 deletion app/lib/backend/schema/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ class App {
String description;
String image;
Set<String> capabilities;
List<String> connectedAccounts = [];
Map? twitter;
bool private;
bool approved;
String? conversationPrompt;
Expand All @@ -195,6 +197,7 @@ class App {
String? paymentLink;
List<String> thumbnailIds;
List<String> thumbnailUrls;
String? username;

App({
required this.id,
Expand Down Expand Up @@ -229,6 +232,9 @@ class App {
this.paymentLink,
this.thumbnailIds = const [],
this.thumbnailUrls = const [],
this.username,
this.connectedAccounts = const [],
this.twitter,
});

String? getRatingAvg() => ratingAvg?.toStringAsFixed(1);
Expand All @@ -237,7 +243,9 @@ class App {

bool worksWithMemories() => hasCapability('memories');

bool worksWithChat() => hasCapability('chat');
bool worksWithChat() => hasCapability('chat') || hasCapability('persona');

bool isNotPersona() => !hasCapability('persona');

bool worksExternally() => hasCapability('external_integration');

Expand Down Expand Up @@ -278,6 +286,9 @@ class App {
paymentLink: json['payment_link'],
thumbnailIds: (json['thumbnails'] as List<dynamic>?)?.cast<String>() ?? [],
thumbnailUrls: (json['thumbnail_urls'] as List<dynamic>?)?.cast<String>() ?? [],
username: json['username'],
connectedAccounts: (json['connected_accounts'] as List<dynamic>?)?.cast<String>() ?? [],
twitter: json['twitter'],
);
}

Expand Down
33 changes: 24 additions & 9 deletions app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import 'package:friend_private/pages/apps/app_detail/app_detail.dart';
import 'package:friend_private/pages/apps/providers/add_app_provider.dart';
import 'package:friend_private/pages/home/page.dart';
import 'package:friend_private/pages/conversation_detail/conversation_detail_provider.dart';
import 'package:friend_private/pages/onboarding/device_selection.dart';
import 'package:friend_private/pages/onboarding/wrapper.dart';
import 'package:friend_private/pages/persona/persona_profile.dart';
import 'package:friend_private/pages/persona/persona_provider.dart';
import 'package:friend_private/providers/app_provider.dart';
import 'package:friend_private/providers/auth_provider.dart';
import 'package:friend_private/providers/calendar_provider.dart';
Expand Down Expand Up @@ -205,6 +208,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
(previous?..setAppProvider(value)) ?? AddAppProvider(),
),
ChangeNotifierProvider(create: (context) => PaymentMethodProvider()),
ChangeNotifierProvider(create: (context) => PersonaProvider()),
],
builder: (context, child) {
return WithForegroundTask(
Expand Down Expand Up @@ -325,9 +329,14 @@ class _DeciderWidgetState extends State<DeciderWidget> {
if (context.read<AuthenticationProvider>().user != null ||
(SharedPreferencesUtil().customBackendUrl.isNotEmpty && SharedPreferencesUtil().authToken.isNotEmpty)) {
context.read<HomeProvider>().setupHasSpeakerProfile();
IntercomManager.instance.intercom.loginIdentifiedUser(
userId: SharedPreferencesUtil().uid,
);
try {
await IntercomManager.instance.intercom.loginIdentifiedUser(
userId: SharedPreferencesUtil().uid,
);
} catch (e) {
debugPrint('Failed to login to Intercom: $e');
}

context.read<MessageProvider>().setMessagesFromCache();
context.read<AppProvider>().setAppsFromCache();
context.read<MessageProvider>().refreshMessages();
Expand All @@ -343,13 +352,19 @@ class _DeciderWidgetState extends State<DeciderWidget> {
Widget build(BuildContext context) {
return Consumer<AuthenticationProvider>(
builder: (context, authProvider, child) {
if (SharedPreferencesUtil().onboardingCompleted &&
(authProvider.user != null ||
(SharedPreferencesUtil().customBackendUrl.isNotEmpty &&
SharedPreferencesUtil().authToken.isNotEmpty))) {
return const HomePageWrapper();
if ((authProvider.user != null ||
(SharedPreferencesUtil().customBackendUrl.isNotEmpty && SharedPreferencesUtil().authToken.isNotEmpty))) {
if (SharedPreferencesUtil().hasOmiDevice == false) {
return const PersonaProfilePage();
} else {
if (SharedPreferencesUtil().onboardingCompleted) {
return const HomePageWrapper();
} else {
return const OnboardingWrapper();
}
}
} else {
return const OnboardingWrapper();
return const DeviceSelectionPage();
}
},
);
Expand Down
5 changes: 3 additions & 2 deletions app/lib/pages/apps/add_app.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:friend_private/utils/other/debouncer.dart';
import 'package:shimmer/shimmer.dart';
import 'package:friend_private/backend/preferences.dart';
import 'package:friend_private/pages/apps/widgets/full_screen_image_viewer.dart';
Expand Down Expand Up @@ -29,6 +30,8 @@ class AddAppPage extends StatefulWidget {

class _AddAppPageState extends State<AddAppPage> {
late bool showSubmitAppConfirmation;
final _debouncer = Debouncer(delay: const Duration(milliseconds: 500));

@override
void initState() {
showSubmitAppConfirmation = SharedPreferencesUtil().showSubmitAppConfirmation;
Expand Down Expand Up @@ -114,8 +117,6 @@ class _AddAppPageState extends State<AddAppPage> {
appPricing: provider.isPaid ? 'Paid' : 'Free',
appNameController: provider.appNameController,
appDescriptionController: provider.appDescriptionController,
creatorNameController: provider.creatorNameController,
creatorEmailController: provider.creatorEmailController,
categories: provider.categories,
setAppCategory: provider.setAppCategory,
imageFile: provider.imageFile,
Expand Down
24 changes: 17 additions & 7 deletions app/lib/pages/apps/app_detail/app_detail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,23 @@ class _AppDetailPageState extends State<AppDetailPage> {
),
const SizedBox(width: 24),
],
isLoading
isLoading || app.private
? const SizedBox.shrink()
: GestureDetector(
child: const Icon(Icons.share),
onTap: () {
MixpanelManager().track('App Shared', properties: {'appId': app.id});
Share.share(
'Check out this app on Omi AI: ${app.name} by ${app.author} \n\n${app.description.decodeString}\n\n\nhttps://h.omi.me/apps/${app.id}',
subject: app.name,
);
if (app.isNotPersona()) {
Share.share(
'Check out this app on Omi AI: ${app.name} by ${app.author} \n\n${app.description.decodeString}\n\n\nhttps://h.omi.me/apps/${app.id}',
subject: app.name,
);
} else {
Share.share(
'Check out this Persona on Omi AI: ${app.name} by ${app.author} \n\n${app.description.decodeString}\n\n\nhttps://persona.omi.me/u/${app.username}',
subject: app.name,
);
}
},
),
!context.watch<AppProvider>().isAppOwner
Expand Down Expand Up @@ -722,10 +729,13 @@ class _AppDetailPageState extends State<AppDetailPage> {
onTap: () {
if (app.description.decodeString.characters.length > 200) {
routeToPage(
context, MarkdownViewer(title: 'About the App', markdown: app.description.decodeString));
context,
MarkdownViewer(
title: 'About the ${app.isNotPersona() ? 'App' : 'Persona'}',
markdown: app.description.decodeString));
}
},
title: 'About the App',
title: 'About the ${app.isNotPersona() ? 'App' : 'Persona'}',
description: app.description,
showChips: true,
chips: app
Expand Down
20 changes: 14 additions & 6 deletions app/lib/pages/apps/explore_install_page.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import 'package:flutter/material.dart';
import 'package:friend_private/backend/auth.dart';
import 'package:friend_private/backend/schema/app.dart';
import 'package:friend_private/pages/apps/add_app.dart';
import 'package:friend_private/pages/apps/providers/add_app_provider.dart';
import 'package:friend_private/pages/apps/widgets/app_section_card.dart';
import 'package:friend_private/pages/apps/widgets/filter_sheet.dart';
import 'package:friend_private/pages/apps/list_item.dart';
import 'package:friend_private/providers/app_provider.dart';
import 'package:friend_private/providers/home_provider.dart';
import 'package:friend_private/utils/analytics/mixpanel.dart';
import 'package:friend_private/utils/other/temp.dart';
import 'package:provider/provider.dart';

import '../persona/twitter/social_profile.dart';
import 'widgets/create_options_sheet.dart';

String filterValueToString(dynamic value) {
if (value.runtimeType == String) {
return value;
Expand Down Expand Up @@ -189,9 +191,15 @@ class _ExploreInstallPageState extends State<ExploreInstallPage> with AutomaticK
)),
SliverToBoxAdapter(
child: GestureDetector(
onTap: () {
MixpanelManager().pageOpened('Submit App');
routeToPage(context, const AddAppPage());
onTap: () async {
// routeToPage(context, SocialHandleScreen());
showModalBottomSheet(
context: context,
builder: (context) => const CreateOptionsSheet(),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
);
},
child: Container(
padding: const EdgeInsets.all(12.0),
Expand All @@ -208,7 +216,7 @@ class _ExploreInstallPageState extends State<ExploreInstallPage> with AutomaticK
Icon(Icons.add, color: Colors.white),
SizedBox(width: 8),
Text(
'Create and submit a new app',
'Create your own',
textAlign: TextAlign.center,
),
],
Expand Down
Loading