From 6ba7fc0317f1971d100cfc565501c95fcf170b0c Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Thu, 21 Sep 2023 12:41:52 +0800 Subject: [PATCH] feat: openai and stabilityai integration (#3439) * chore: create trait * test: add tests * chore: remove log * chore: disable log --- .../editor_transaction_adapter.dart | 2 +- .../lib/startup/tasks/app_widget.dart | 1 - .../lib/user/application/user_service.dart | 6 +- .../presentation/screens/splash_screen.dart | 2 +- .../packages/appflowy_backend/lib/log.dart | 1 + frontend/appflowy_tauri/src-tauri/Cargo.lock | 185 +++++++++++++++-- frontend/rust-lib/Cargo.lock | 192 +++++++++++++++++- frontend/rust-lib/flowy-ai/Cargo.toml | 14 +- frontend/rust-lib/flowy-ai/src/config.rs | 16 ++ frontend/rust-lib/flowy-ai/src/entities.rs | 71 ------- .../rust-lib/flowy-ai/src/event_handler.rs | 71 ------- frontend/rust-lib/flowy-ai/src/event_map.rs | 18 -- frontend/rust-lib/flowy-ai/src/lib.rs | 6 +- .../rust-lib/flowy-ai/src/notification.rs | 21 -- .../rust-lib/flowy-ai/src/text/entities.rs | 1 + frontend/rust-lib/flowy-ai/src/text/mod.rs | 14 ++ .../rust-lib/flowy-ai/src/text/open_ai.rs | 30 +++ .../flowy-ai/src/text/stability_ai.rs | 15 ++ frontend/rust-lib/flowy-ai/tests/main.rs | 2 + .../flowy-ai/tests/text/completion_test.rs | 18 ++ frontend/rust-lib/flowy-ai/tests/text/mod.rs | 1 + frontend/rust-lib/flowy-ai/tests/util/mod.rs | 8 + shared-lib/lib-infra/src/box_any.rs | 4 +- 23 files changed, 475 insertions(+), 224 deletions(-) create mode 100644 frontend/rust-lib/flowy-ai/src/config.rs delete mode 100644 frontend/rust-lib/flowy-ai/src/entities.rs delete mode 100644 frontend/rust-lib/flowy-ai/src/event_handler.rs delete mode 100644 frontend/rust-lib/flowy-ai/src/event_map.rs delete mode 100644 frontend/rust-lib/flowy-ai/src/notification.rs create mode 100644 frontend/rust-lib/flowy-ai/src/text/entities.rs create mode 100644 frontend/rust-lib/flowy-ai/src/text/mod.rs create mode 100644 frontend/rust-lib/flowy-ai/src/text/open_ai.rs create mode 100644 frontend/rust-lib/flowy-ai/src/text/stability_ai.rs create mode 100644 frontend/rust-lib/flowy-ai/tests/main.rs create mode 100644 frontend/rust-lib/flowy-ai/tests/text/completion_test.rs create mode 100644 frontend/rust-lib/flowy-ai/tests/text/mod.rs create mode 100644 frontend/rust-lib/flowy-ai/tests/util/mod.rs diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart b/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart index bf7ed031ff..11084ae424 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart @@ -34,7 +34,7 @@ class TransactionAdapter { final DocumentService documentService; final String documentId; - final bool _enableDebug = true; + final bool _enableDebug = false; Future apply(Transaction transaction, EditorState editorState) async { final stopwatch = Stopwatch()..start(); diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart index b9d0032e80..46b41b32fd 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart @@ -150,7 +150,6 @@ Future appTheme(String themeName) async { try { return await AppTheme.fromName(themeName); } catch (e) { - Log.error(e); return AppTheme.fallback; } } diff --git a/frontend/appflowy_flutter/lib/user/application/user_service.dart b/frontend/appflowy_flutter/lib/user/application/user_service.dart index 90df9096a6..c498d0b8c5 100644 --- a/frontend/appflowy_flutter/lib/user/application/user_service.dart +++ b/frontend/appflowy_flutter/lib/user/application/user_service.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:dartz/dartz.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; @@ -17,10 +16,7 @@ class UserBackendService { static Future> getCurrentUserProfile() async { - final result = await UserEventGetUserProfile().send().then((value) { - value.fold((l) => null, (r) => Log.info(r)); - return value; - }); + final result = await UserEventGetUserProfile().send(); return result.swap(); } diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/splash_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/splash_screen.dart index 3633cf301e..3fc09102c9 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/splash_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/splash_screen.dart @@ -104,7 +104,7 @@ class SplashScreen extends StatelessWidget { } void _handleUnauthenticated(BuildContext context, Unauthenticated result) { - Log.debug( + Log.trace( '_handleUnauthenticated -> Supabase is enabled: $isSupabaseEnabled', ); // if the env is not configured, we will skip to the 'skip login screen'. diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart b/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart index 0cb8e8ca7f..d892f903b3 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart +++ b/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart @@ -21,6 +21,7 @@ class Log { printEmojis: true, // Print an emoji for each log message printTime: false // Should each log print contain a timestamp ), + level: kDebugMode ? Level.verbose : Level.info, ); } diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 4d4b640f12..f7e0329369 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -172,6 +172,38 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "async-convert" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d416feee97712e43152cd42874de162b8f9b77295b1c85e5d92725cc8310bae" +dependencies = [ + "async-trait", +] + +[[package]] +name = "async-openai" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7150fb5d9cc4eb0184af43ce75a89620dc3747d3c816e8b0ba200682d0155c05" +dependencies = [ + "async-convert", + "backoff", + "base64 0.21.2", + "derive_builder", + "futures", + "rand 0.8.5", + "reqwest", + "reqwest-eventsource", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -250,6 +282,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "futures-core", + "getrandom 0.2.10", + "instant", + "pin-project-lite", + "rand 0.8.5", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.67" @@ -362,7 +408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive", - "hashbrown 0.12.3", + "hashbrown 0.13.2", ] [[package]] @@ -1215,14 +1261,38 @@ dependencies = [ "cipher", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + [[package]] name = "darling" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.1", + "darling_macro 0.20.1", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", ] [[package]] @@ -1239,13 +1309,24 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + [[package]] name = "darling_macro" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ - "darling_core", + "darling_core 0.20.1", "quote", "syn 2.0.29", ] @@ -1274,6 +1355,37 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -1364,6 +1476,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "dotenvy" version = "0.15.7" @@ -1482,6 +1600,17 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom 7.1.3", + "pin-project-lite", +] + [[package]] name = "faccess" version = "0.2.4" @@ -1573,17 +1702,14 @@ dependencies = [ name = "flowy-ai" version = "0.1.0" dependencies = [ - "bytes", - "flowy-derive", - "flowy-error", - "flowy-notification", - "lib-dispatch", + "anyhow", + "async-openai", + "dotenv", "lib-infra", - "protobuf", "reqwest", "serde", "serde_json", - "strum_macros 0.21.1", + "tokio", ] [[package]] @@ -2160,6 +2286,12 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.28" @@ -4515,6 +4647,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls", + "rustls-native-certs", "rustls-pemfile", "serde", "serde_json", @@ -4533,6 +4666,22 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "reqwest-eventsource" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f03f570355882dd8d15acc3a313841e6e90eddbc76a93c748fd82cc13ba9f51" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom 7.1.3", + "pin-project-lite", + "reqwest", + "thiserror", +] + [[package]] name = "ring" version = "0.16.20" @@ -4661,6 +4810,18 @@ dependencies = [ "sct", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.2" @@ -4911,7 +5072,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" dependencies = [ - "darling", + "darling 0.20.1", "proc-macro2", "quote", "syn 2.0.29", diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index dcd4bd295a..414bdb1647 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -152,6 +152,38 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-convert" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d416feee97712e43152cd42874de162b8f9b77295b1c85e5d92725cc8310bae" +dependencies = [ + "async-trait", +] + +[[package]] +name = "async-openai" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7150fb5d9cc4eb0184af43ce75a89620dc3747d3c816e8b0ba200682d0155c05" +dependencies = [ + "async-convert", + "backoff", + "base64 0.21.3", + "derive_builder", + "futures", + "rand 0.8.5", + "reqwest", + "reqwest-eventsource", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -257,6 +289,20 @@ dependencies = [ "tower-service", ] +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "futures-core", + "getrandom 0.2.10", + "instant", + "pin-project-lite", + "rand 0.8.5", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -375,7 +421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive", - "hashbrown 0.12.3", + "hashbrown 0.13.2", ] [[package]] @@ -1048,6 +1094,41 @@ dependencies = [ "cipher", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "dart-ffi" version = "0.1.0" @@ -1104,6 +1185,37 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + [[package]] name = "deunicode" version = "0.4.4" @@ -1248,6 +1360,17 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom 7.1.3", + "pin-project-lite", +] + [[package]] name = "faccess" version = "0.2.4" @@ -1321,17 +1444,14 @@ dependencies = [ name = "flowy-ai" version = "0.1.0" dependencies = [ - "bytes", - "flowy-derive", - "flowy-error", - "flowy-notification", - "lib-dispatch", + "anyhow", + "async-openai", + "dotenv", "lib-infra", - "protobuf", "reqwest", "serde", "serde_json", - "strum_macros 0.21.1", + "tokio", ] [[package]] @@ -1971,6 +2091,12 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.28" @@ -2340,6 +2466,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -2403,6 +2535,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipnet" version = "2.8.0" @@ -3737,6 +3878,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls", + "rustls-native-certs", "rustls-pemfile", "serde", "serde_json", @@ -3755,6 +3897,22 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest-eventsource" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f03f570355882dd8d15acc3a313841e6e90eddbc76a93c748fd82cc13ba9f51" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom 7.1.3", + "pin-project-lite", + "reqwest", + "thiserror", +] + [[package]] name = "ring" version = "0.16.20" @@ -3871,6 +4029,18 @@ dependencies = [ "sct", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.3" @@ -4323,6 +4493,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum" version = "0.25.0" diff --git a/frontend/rust-lib/flowy-ai/Cargo.toml b/frontend/rust-lib/flowy-ai/Cargo.toml index 5d44a3fbdd..a8526484c7 100644 --- a/frontend/rust-lib/flowy-ai/Cargo.toml +++ b/frontend/rust-lib/flowy-ai/Cargo.toml @@ -6,15 +6,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -flowy-derive = { path = "../../../shared-lib/flowy-derive" } -flowy-notification = { path = "../flowy-notification" } -flowy-error = { path = "../flowy-error", features = ["impl_from_serde", "impl_from_dispatch_error"] } -lib-dispatch = { path = "../lib-dispatch" } -lib-infra = { path = "../../../shared-lib/lib-infra" } - -protobuf = {version = "2.28.0"} -bytes = { version = "1.4" } -strum_macros = "0.21" reqwest = { version = "0.11", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +anyhow = "1.0.75" +lib-infra = { path = "../../../shared-lib/lib-infra" } +async-openai = "0.14.2" +tokio = { version = "1.12", features = ["rt", "sync"] } +dotenv = "0.15.0" \ No newline at end of file diff --git a/frontend/rust-lib/flowy-ai/src/config.rs b/frontend/rust-lib/flowy-ai/src/config.rs new file mode 100644 index 0000000000..a9564f2aa6 --- /dev/null +++ b/frontend/rust-lib/flowy-ai/src/config.rs @@ -0,0 +1,16 @@ +use anyhow::{anyhow, Error}; + +pub struct OpenAISetting { + pub openai_api_key: String, +} + +const OPENAI_API_KEY: &str = "OPENAI_API_KEY"; + +impl OpenAISetting { + pub fn from_env() -> Result { + let openai_api_key = + std::env::var(OPENAI_API_KEY).map_err(|_| anyhow!("Missing OPENAI_API_KEY"))?; + + Ok(Self { openai_api_key }) + } +} diff --git a/frontend/rust-lib/flowy-ai/src/entities.rs b/frontend/rust-lib/flowy-ai/src/entities.rs deleted file mode 100644 index bc42019650..0000000000 --- a/frontend/rust-lib/flowy-ai/src/entities.rs +++ /dev/null @@ -1,71 +0,0 @@ -use flowy_error::ErrorCode; - -/* - model="text-davinci-003", - prompt="Write a tagline for an ice cream shop." -*/ -#[derive(Default)] -pub struct TextCompletionPayloadPB { - pub request_id: String, - - // Model: Either text-davinci-003 or gpt-3.5-turbo - pub model: String, - - // Prompt to query gpt - pub prompt: String, - - // User open_ai_key for authentication - pub open_ai_key: String, -} - -pub struct TextCompletionParams { - pub request_id: String, - pub model: String, - pub prompt: String, - pub open_ai_key: String, -} - -impl TryInto for TextCompletionPayloadPB { - type Error = ErrorCode; - fn try_into(self) -> Result { - Ok(TextCompletionParams { - request_id: self.request_id, - model: self.model, - prompt: self.prompt, - open_ai_key: self.open_ai_key, - }) - } -} - -/* -{ - "id": "chatcmpl-123", - "object": "chat.completion", - "created": 1677652288, - "model": "gpt-3.5-turbo-0613", - "choices": [{ - "index": 0, - "message": { - "role": "assistant", - "content": "\n\nHello there, how may I assist you today?", - }, - "finish_reason": "stop" - }], - "usage": { - "prompt_tokens": 9, - "completion_tokens": 12, - "total_tokens": 21 - } -} - -*/ -#[derive(Default)] -pub struct TextCompletionDataPB { - pub request_id: String, - - pub model: String, - - pub index: i32, - - pub content: String, -} diff --git a/frontend/rust-lib/flowy-ai/src/event_handler.rs b/frontend/rust-lib/flowy-ai/src/event_handler.rs deleted file mode 100644 index ac2b83bcff..0000000000 --- a/frontend/rust-lib/flowy-ai/src/event_handler.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::entities::{TextCompletionDataPB, TextCompletionParams, TextCompletionPayloadPB}; -use flowy_error::FlowyError; -use lib_dispatch::prelude::{data_result_ok, AFPluginData, DataResult}; -use reqwest; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -struct Message { - role: String, - content: String, -} - -#[derive(Serialize)] -struct RequestBody { - model: String, - messages: Vec, -} - -#[derive(Deserialize)] -struct ResponseChoice { - index: i32, - message: Message, -} - -#[derive(Deserialize)] -struct ApiResponse { - choices: Vec, -} - -pub(crate) async fn request_text_completion( - data: AFPluginData, -) -> DataResult { - // Set up the request body - let body = RequestBody { - model: "gpt-3.5-turbo".to_string(), - messages: vec![ - Message { - role: "system".to_string(), - content: "You are a helpful assistant.".to_string(), - }, - Message { - role: "user".to_string(), - content: data.prompt.to_string(), - }, - ], - }; - - // Make the API call - let client = reqwest::Client::new(); - let response: ApiResponse = client - .post("https://api.openai.com/v1/chat/completions") - .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", data.open_ai_key)) - .json(&body) - .send() - .await? - .json() - .await?; - - // Extract index and content - let _choice = &response.choices[0]; - - let params: TextCompletionParams = data.into_inner().try_into()?; - - data_result_ok(TextCompletionDataPB { - request_id: params.request_id, - model: params.model, - index: response.choices[0].index, - content: response.choices[0].message.content.to_string(), - }) -} diff --git a/frontend/rust-lib/flowy-ai/src/event_map.rs b/frontend/rust-lib/flowy-ai/src/event_map.rs deleted file mode 100644 index 52d1f34df2..0000000000 --- a/frontend/rust-lib/flowy-ai/src/event_map.rs +++ /dev/null @@ -1,18 +0,0 @@ -use flowy_derive::{Flowy_Event, ProtoBuf_Enum}; -use lib_dispatch::prelude::AFPlugin; -use strum_macros::Display; - -use crate::event_handler::request_text_completion; - -pub fn init() -> AFPlugin { - AFPlugin::new() - .name(env!("CARGO_PKG_NAME")) - .event(OpenAIEvent::RequestTextCompletion, request_text_completion) -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, ProtoBuf_Enum, Flowy_Event)] -#[event_err = "FlowyError"] -pub enum OpenAIEvent { - #[event(input = "TextCompletionPayloadPB", output = "TextCompletionDataPB")] - RequestTextCompletion = 0, -} diff --git a/frontend/rust-lib/flowy-ai/src/lib.rs b/frontend/rust-lib/flowy-ai/src/lib.rs index 7446a897b9..fd772a64bb 100644 --- a/frontend/rust-lib/flowy-ai/src/lib.rs +++ b/frontend/rust-lib/flowy-ai/src/lib.rs @@ -1,4 +1,2 @@ -// pub mod entities; -// pub mod event_handler; -// pub mod event_map; -// pub mod notification; +pub mod config; +pub mod text; diff --git a/frontend/rust-lib/flowy-ai/src/notification.rs b/frontend/rust-lib/flowy-ai/src/notification.rs deleted file mode 100644 index 48bf159846..0000000000 --- a/frontend/rust-lib/flowy-ai/src/notification.rs +++ /dev/null @@ -1,21 +0,0 @@ -use flowy_derive::ProtoBuf_Enum; -use flowy_notification::NotificationBuilder; - -const OPEN_AI_NOTIFICATION: &str = "OpenAI"; - -#[derive(ProtoBuf_Enum, Debug, Default)] -pub(crate) enum OpenAINotification { - #[default] - Unknown = 0, -} - -impl std::convert::From for i32 { - fn from(notification: OpenAINotification) -> Self { - notification as i32 - } -} - -#[allow(dead_code)] -pub(crate) fn send_notification(id: &str, ty: OpenAINotification) -> NotificationBuilder { - NotificationBuilder::new(id, ty, OPEN_AI_NOTIFICATION) -} diff --git a/frontend/rust-lib/flowy-ai/src/text/entities.rs b/frontend/rust-lib/flowy-ai/src/text/entities.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/frontend/rust-lib/flowy-ai/src/text/entities.rs @@ -0,0 +1 @@ + diff --git a/frontend/rust-lib/flowy-ai/src/text/mod.rs b/frontend/rust-lib/flowy-ai/src/text/mod.rs new file mode 100644 index 0000000000..26bf90966b --- /dev/null +++ b/frontend/rust-lib/flowy-ai/src/text/mod.rs @@ -0,0 +1,14 @@ +use anyhow::Error; +use lib_infra::async_trait::async_trait; + +mod entities; +pub mod open_ai; +pub mod stability_ai; + +#[async_trait] +pub trait TextCompletion: Send + Sync { + type Input: Send + 'static; + type Output; + + async fn text_completion(&self, params: Self::Input) -> Result; +} diff --git a/frontend/rust-lib/flowy-ai/src/text/open_ai.rs b/frontend/rust-lib/flowy-ai/src/text/open_ai.rs new file mode 100644 index 0000000000..5138be23ac --- /dev/null +++ b/frontend/rust-lib/flowy-ai/src/text/open_ai.rs @@ -0,0 +1,30 @@ +use crate::text::TextCompletion; +use anyhow::Error; +use async_openai::config::OpenAIConfig; +use async_openai::types::{CreateCompletionRequest, CreateCompletionResponse}; +use async_openai::Client; +use lib_infra::async_trait::async_trait; + +pub struct OpenAITextCompletion { + client: Client, +} + +impl OpenAITextCompletion { + pub fn new(api_key: &str) -> Self { + // https://docs.rs/async-openai/latest/async_openai/struct.Completions.html + let config = OpenAIConfig::new().with_api_key(api_key); + let client = Client::with_config(config); + Self { client } + } +} + +#[async_trait] +impl TextCompletion for OpenAITextCompletion { + type Input = CreateCompletionRequest; + type Output = CreateCompletionResponse; + + async fn text_completion(&self, params: Self::Input) -> Result { + let response = self.client.completions().create(params).await?; + Ok(response) + } +} diff --git a/frontend/rust-lib/flowy-ai/src/text/stability_ai.rs b/frontend/rust-lib/flowy-ai/src/text/stability_ai.rs new file mode 100644 index 0000000000..aa342d2961 --- /dev/null +++ b/frontend/rust-lib/flowy-ai/src/text/stability_ai.rs @@ -0,0 +1,15 @@ +use crate::text::TextCompletion; +use anyhow::Error; +use lib_infra::async_trait::async_trait; + +pub struct StabilityAITextCompletion {} + +#[async_trait] +impl TextCompletion for StabilityAITextCompletion { + type Input = (); + type Output = (); + + async fn text_completion(&self, _params: Self::Input) -> Result { + todo!() + } +} diff --git a/frontend/rust-lib/flowy-ai/tests/main.rs b/frontend/rust-lib/flowy-ai/tests/main.rs new file mode 100644 index 0000000000..78e95f65e4 --- /dev/null +++ b/frontend/rust-lib/flowy-ai/tests/main.rs @@ -0,0 +1,2 @@ +mod text; +mod util; diff --git a/frontend/rust-lib/flowy-ai/tests/text/completion_test.rs b/frontend/rust-lib/flowy-ai/tests/text/completion_test.rs new file mode 100644 index 0000000000..c466f160d6 --- /dev/null +++ b/frontend/rust-lib/flowy-ai/tests/text/completion_test.rs @@ -0,0 +1,18 @@ +use crate::util::get_openai_config; +use async_openai::types::CreateCompletionRequestArgs; +use flowy_ai::text::open_ai::OpenAITextCompletion; +use flowy_ai::text::TextCompletion; + +#[tokio::test] +async fn text_completion_test() { + if let Some(config) = get_openai_config() { + let client = OpenAITextCompletion::new(&config.openai_api_key); + let params = CreateCompletionRequestArgs::default() + .model("text-davinci-003") + .prompt("Write a rust function to calculate the sum of two numbers") + .build() + .unwrap(); + let resp = client.text_completion(params).await.unwrap(); + dbg!("{:?}", resp); + } +} diff --git a/frontend/rust-lib/flowy-ai/tests/text/mod.rs b/frontend/rust-lib/flowy-ai/tests/text/mod.rs new file mode 100644 index 0000000000..6e0de3680e --- /dev/null +++ b/frontend/rust-lib/flowy-ai/tests/text/mod.rs @@ -0,0 +1 @@ +mod completion_test; diff --git a/frontend/rust-lib/flowy-ai/tests/util/mod.rs b/frontend/rust-lib/flowy-ai/tests/util/mod.rs new file mode 100644 index 0000000000..2d4377b4a7 --- /dev/null +++ b/frontend/rust-lib/flowy-ai/tests/util/mod.rs @@ -0,0 +1,8 @@ +use flowy_ai::config::OpenAISetting; + +// To run the OpenAI test, you need to create a .env file in the flowy-ai folder. +// Use the format: OPENAI_API_KEY=your_api_key +pub fn get_openai_config() -> Option { + dotenv::from_filename(".env").ok()?; + OpenAISetting::from_env().ok() +} diff --git a/shared-lib/lib-infra/src/box_any.rs b/shared-lib/lib-infra/src/box_any.rs index 25562fda43..1822cd1a23 100644 --- a/shared-lib/lib-infra/src/box_any.rs +++ b/shared-lib/lib-infra/src/box_any.rs @@ -24,7 +24,7 @@ impl BoxAny { pub fn unbox_or_error(self) -> Result where - T: Default + 'static, + T: 'static, { match self.0.downcast::() { Ok(value) => Ok(*value), @@ -38,7 +38,7 @@ impl BoxAny { pub fn unbox_or_none(self) -> Option where - T: Default + 'static, + T: 'static, { match self.0.downcast::() { Ok(value) => Some(*value),