Prechádzať zdrojové kódy

feat: openai and stabilityai integration (#3439)

* chore: create trait

* test: add tests

* chore: remove log

* chore: disable log
Nathan.fooo 1 rok pred
rodič
commit
6ba7fc0317

+ 1 - 1
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<void> apply(Transaction transaction, EditorState editorState) async {
     final stopwatch = Stopwatch()..start();

+ 0 - 1
frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart

@@ -150,7 +150,6 @@ Future<AppTheme> appTheme(String themeName) async {
     try {
       return await AppTheme.fromName(themeName);
     } catch (e) {
-      Log.error(e);
       return AppTheme.fallback;
     }
   }

+ 1 - 5
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<Either<FlowyError, UserProfilePB>>
       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();
   }
 

+ 1 - 1
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'.

+ 1 - 0
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,
     );
   }
 

+ 173 - 12
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",

+ 184 - 8
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"

+ 5 - 9
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"

+ 16 - 0
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<Self, Error> {
+    let openai_api_key =
+      std::env::var(OPENAI_API_KEY).map_err(|_| anyhow!("Missing OPENAI_API_KEY"))?;
+
+    Ok(Self { openai_api_key })
+  }
+}

+ 0 - 71
frontend/rust-lib/flowy-ai/src/entities.rs

@@ -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<TextCompletionParams> for TextCompletionPayloadPB {
-  type Error = ErrorCode;
-  fn try_into(self) -> Result<TextCompletionParams, Self::Error> {
-    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,
-}

+ 0 - 71
frontend/rust-lib/flowy-ai/src/event_handler.rs

@@ -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<Message>,
-}
-
-#[derive(Deserialize)]
-struct ResponseChoice {
-  index: i32,
-  message: Message,
-}
-
-#[derive(Deserialize)]
-struct ApiResponse {
-  choices: Vec<ResponseChoice>,
-}
-
-pub(crate) async fn request_text_completion(
-  data: AFPluginData<TextCompletionPayloadPB>,
-) -> DataResult<TextCompletionDataPB, FlowyError> {
-  // 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(),
-  })
-}

+ 0 - 18
frontend/rust-lib/flowy-ai/src/event_map.rs

@@ -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,
-}

+ 2 - 4
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;

+ 0 - 21
frontend/rust-lib/flowy-ai/src/notification.rs

@@ -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<OpenAINotification> 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)
-}

+ 1 - 0
frontend/rust-lib/flowy-ai/src/text/entities.rs

@@ -0,0 +1 @@
+

+ 14 - 0
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<Self::Output, Error>;
+}

+ 30 - 0
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<OpenAIConfig>,
+}
+
+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<Self::Output, Error> {
+    let response = self.client.completions().create(params).await?;
+    Ok(response)
+  }
+}

+ 15 - 0
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<Self::Output, Error> {
+    todo!()
+  }
+}

+ 2 - 0
frontend/rust-lib/flowy-ai/tests/main.rs

@@ -0,0 +1,2 @@
+mod text;
+mod util;

+ 18 - 0
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);
+  }
+}

+ 1 - 0
frontend/rust-lib/flowy-ai/tests/text/mod.rs

@@ -0,0 +1 @@
+mod completion_test;

+ 8 - 0
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<OpenAISetting> {
+  dotenv::from_filename(".env").ok()?;
+  OpenAISetting::from_env().ok()
+}

+ 2 - 2
shared-lib/lib-infra/src/box_any.rs

@@ -24,7 +24,7 @@ impl BoxAny {
 
   pub fn unbox_or_error<T>(self) -> Result<T>
   where
-    T: Default + 'static,
+    T: 'static,
   {
     match self.0.downcast::<T>() {
       Ok(value) => Ok(*value),
@@ -38,7 +38,7 @@ impl BoxAny {
 
   pub fn unbox_or_none<T>(self) -> Option<T>
   where
-    T: Default + 'static,
+    T: 'static,
   {
     match self.0.downcast::<T>() {
       Ok(value) => Some(*value),