mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: AI chat (#5383)
* chore: ai type * chore: use patch to fix version issue * chore: update * chore: update * chore: integrate client api * chore: add schema * chore: setup event * chore: add event test * chore: add test * chore: update test * chore: load chat message * chore: load chat message * chore: chat ui * chore: disable create chat * chore: update client api * chore: disable chat * chore: ui theme * chore: ui theme * chore: copy message * chore: fix test * chore: show error * chore: update bloc * chore: update test * chore: lint * chore: icon * chore: hover * chore: show unsupported page * chore: adjust mobile ui * chore: adjust view title bar * chore: return related question * chore: error page * chore: error page * chore: code format * chore: prompt * chore: fix test * chore: ui adjust * chore: disable create chat * chore: add loading page * chore: fix test * chore: disable chat action * chore: add maximum text limit
This commit is contained in:
@ -31,6 +31,8 @@ diesel.workspace = true
|
||||
uuid.workspace = true
|
||||
flowy-storage = { workspace = true }
|
||||
client-api.workspace = true
|
||||
flowy-chat = { workspace = true }
|
||||
flowy-chat-pub = { workspace = true }
|
||||
|
||||
tracing.workspace = true
|
||||
futures-core = { version = "0.3", default-features = false }
|
||||
@ -61,6 +63,7 @@ dart = [
|
||||
"flowy-search/dart",
|
||||
"flowy-folder/dart",
|
||||
"flowy-database2/dart",
|
||||
"flowy-chat/dart",
|
||||
]
|
||||
ts = [
|
||||
"flowy-user/tauri_ts",
|
||||
@ -68,6 +71,7 @@ ts = [
|
||||
"flowy-search/tauri_ts",
|
||||
"flowy-database2/ts",
|
||||
"flowy-config/tauri_ts",
|
||||
"flowy-chat/tauri_ts",
|
||||
]
|
||||
openssl_vendored = ["flowy-sqlite/openssl_vendored"]
|
||||
|
||||
|
47
frontend/rust-lib/flowy-core/src/deps_resolve/chat_deps.rs
Normal file
47
frontend/rust-lib/flowy-core/src/deps_resolve/chat_deps.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use flowy_chat::manager::{ChatManager, ChatUserService};
|
||||
use flowy_chat_pub::cloud::ChatCloudService;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_sqlite::DBConnection;
|
||||
use flowy_user::services::authenticate_user::AuthenticateUser;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
pub struct ChatDepsResolver;
|
||||
|
||||
impl ChatDepsResolver {
|
||||
pub fn resolve(
|
||||
authenticate_user: Weak<AuthenticateUser>,
|
||||
cloud_service: Arc<dyn ChatCloudService>,
|
||||
) -> Arc<ChatManager> {
|
||||
let user_service = ChatUserServiceImpl(authenticate_user);
|
||||
Arc::new(ChatManager::new(cloud_service, user_service))
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatUserServiceImpl(Weak<AuthenticateUser>);
|
||||
impl ChatUserServiceImpl {
|
||||
fn upgrade_user(&self) -> Result<Arc<AuthenticateUser>, FlowyError> {
|
||||
let user = self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?;
|
||||
Ok(user)
|
||||
}
|
||||
}
|
||||
|
||||
impl ChatUserService for ChatUserServiceImpl {
|
||||
fn user_id(&self) -> Result<i64, FlowyError> {
|
||||
self.upgrade_user()?.user_id()
|
||||
}
|
||||
|
||||
fn device_id(&self) -> Result<String, FlowyError> {
|
||||
self.upgrade_user()?.device_id()
|
||||
}
|
||||
|
||||
fn workspace_id(&self) -> Result<String, FlowyError> {
|
||||
self.upgrade_user()?.workspace_id()
|
||||
}
|
||||
|
||||
fn sqlite_connection(&self, uid: i64) -> Result<DBConnection, FlowyError> {
|
||||
self.upgrade_user()?.get_sqlite_connection(uid)
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
use bytes::Bytes;
|
||||
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use collab_integrate::CollabKVDB;
|
||||
use flowy_chat::manager::ChatManager;
|
||||
use flowy_database2::entities::DatabaseLayoutPB;
|
||||
use flowy_database2::services::share::csv::CSVFormat;
|
||||
use flowy_database2::template::{make_default_board, make_default_calendar, make_default_grid};
|
||||
@ -9,10 +10,11 @@ use flowy_document::entities::DocumentDataPB;
|
||||
use flowy_document::manager::DocumentManager;
|
||||
use flowy_document::parser::json::parser::JsonToDocumentParser;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_folder::entities::ViewLayoutPB;
|
||||
use flowy_folder::manager::{FolderManager, FolderUser};
|
||||
use flowy_folder::share::ImportType;
|
||||
use flowy_folder::view_operation::{FolderOperationHandler, FolderOperationHandlers, View};
|
||||
use flowy_folder::view_operation::{
|
||||
FolderOperationHandler, FolderOperationHandlers, View, ViewData,
|
||||
};
|
||||
use flowy_folder::ViewLayout;
|
||||
use flowy_folder_pub::folder_builder::NestedViewBuilder;
|
||||
use flowy_search::folder::indexer::FolderIndexManagerImpl;
|
||||
@ -35,12 +37,17 @@ impl FolderDepsResolver {
|
||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||
server_provider: Arc<ServerProvider>,
|
||||
folder_indexer: Arc<FolderIndexManagerImpl>,
|
||||
chat_manager: &Arc<ChatManager>,
|
||||
) -> Arc<FolderManager> {
|
||||
let user: Arc<dyn FolderUser> = Arc::new(FolderUserImpl {
|
||||
authenticate_user: authenticate_user.clone(),
|
||||
});
|
||||
|
||||
let handlers = folder_operation_handlers(document_manager.clone(), database_manager.clone());
|
||||
let handlers = folder_operation_handlers(
|
||||
document_manager.clone(),
|
||||
database_manager.clone(),
|
||||
chat_manager.clone(),
|
||||
);
|
||||
Arc::new(
|
||||
FolderManager::new(
|
||||
user.clone(),
|
||||
@ -58,6 +65,7 @@ impl FolderDepsResolver {
|
||||
fn folder_operation_handlers(
|
||||
document_manager: Arc<DocumentManager>,
|
||||
database_manager: Arc<DatabaseManager>,
|
||||
chat_manager: Arc<ChatManager>,
|
||||
) -> FolderOperationHandlers {
|
||||
let mut map: HashMap<ViewLayout, Arc<dyn FolderOperationHandler + Send + Sync>> = HashMap::new();
|
||||
|
||||
@ -65,9 +73,11 @@ fn folder_operation_handlers(
|
||||
map.insert(ViewLayout::Document, document_folder_operation);
|
||||
|
||||
let database_folder_operation = Arc::new(DatabaseFolderOperation(database_manager));
|
||||
let chat_folder_operation = Arc::new(ChatFolderOperation(chat_manager));
|
||||
map.insert(ViewLayout::Board, database_folder_operation.clone());
|
||||
map.insert(ViewLayout::Grid, database_folder_operation.clone());
|
||||
map.insert(ViewLayout::Calendar, database_folder_operation);
|
||||
map.insert(ViewLayout::Chat, chat_folder_operation);
|
||||
Arc::new(map)
|
||||
}
|
||||
|
||||
@ -315,7 +325,15 @@ impl FolderOperationHandler for DatabaseFolderOperation {
|
||||
},
|
||||
Some(params) => {
|
||||
let database_manager = self.0.clone();
|
||||
let layout = layout_type_from_view_layout(layout.into());
|
||||
|
||||
let layout = match layout {
|
||||
ViewLayout::Board => DatabaseLayoutPB::Board,
|
||||
ViewLayout::Calendar => DatabaseLayoutPB::Calendar,
|
||||
ViewLayout::Grid => DatabaseLayoutPB::Grid,
|
||||
ViewLayout::Document | ViewLayout::Chat => {
|
||||
return FutureResult::new(async move { Err(FlowyError::not_support()) });
|
||||
},
|
||||
};
|
||||
let name = name.to_string();
|
||||
let database_view_id = view_id.to_string();
|
||||
|
||||
@ -351,6 +369,10 @@ impl FolderOperationHandler for DatabaseFolderOperation {
|
||||
Err(FlowyError::internal().with_context(format!("Can't handle {:?} layout type", layout)))
|
||||
});
|
||||
},
|
||||
ViewLayout::Chat => {
|
||||
// TODO(nathan): AI
|
||||
todo!("AI")
|
||||
},
|
||||
};
|
||||
FutureResult::new(async move {
|
||||
let result = database_manager.create_database_with_params(data).await;
|
||||
@ -413,7 +435,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
|
||||
|
||||
fn did_update_view(&self, old: &View, new: &View) -> FutureResult<(), FlowyError> {
|
||||
let database_layout = match new.layout {
|
||||
ViewLayout::Document => {
|
||||
ViewLayout::Document | ViewLayout::Chat => {
|
||||
return FutureResult::new(async {
|
||||
Err(FlowyError::internal().with_context("Can't handle document layout type"))
|
||||
});
|
||||
@ -450,11 +472,83 @@ impl CreateDatabaseExtParams {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout_type_from_view_layout(layout: ViewLayoutPB) -> DatabaseLayoutPB {
|
||||
match layout {
|
||||
ViewLayoutPB::Grid => DatabaseLayoutPB::Grid,
|
||||
ViewLayoutPB::Board => DatabaseLayoutPB::Board,
|
||||
ViewLayoutPB::Calendar => DatabaseLayoutPB::Calendar,
|
||||
ViewLayoutPB::Document => DatabaseLayoutPB::Grid,
|
||||
struct ChatFolderOperation(Arc<ChatManager>);
|
||||
impl FolderOperationHandler for ChatFolderOperation {
|
||||
fn open_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.open_chat(&view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.close_chat(&view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn delete_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.delete_chat(&view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn duplicate_view(&self, _view_id: &str) -> FutureResult<ViewData, FlowyError> {
|
||||
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||
}
|
||||
|
||||
fn create_view_with_view_data(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
_view_id: &str,
|
||||
_name: &str,
|
||||
_data: Vec<u8>,
|
||||
_layout: ViewLayout,
|
||||
_meta: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||
}
|
||||
|
||||
fn create_built_in_view(
|
||||
&self,
|
||||
user_id: i64,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
_layout: ViewLayout,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.create_chat(&user_id, &view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn import_from_bytes(
|
||||
&self,
|
||||
_uid: i64,
|
||||
_view_id: &str,
|
||||
_name: &str,
|
||||
_import_type: ImportType,
|
||||
_bytes: Vec<u8>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||
}
|
||||
|
||||
fn import_from_file_path(
|
||||
&self,
|
||||
_view_id: &str,
|
||||
_name: &str,
|
||||
_path: String,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub use chat_deps::*;
|
||||
pub use collab_deps::*;
|
||||
pub use database_deps::*;
|
||||
pub use document_deps::*;
|
||||
@ -9,6 +10,7 @@ mod collab_deps;
|
||||
mod document_deps;
|
||||
mod folder_deps;
|
||||
|
||||
mod chat_deps;
|
||||
mod database_deps;
|
||||
mod search_deps;
|
||||
mod user_deps;
|
||||
|
@ -52,6 +52,7 @@ pub fn create_log_filter(level: String, with_crates: Vec<String>, platform: Plat
|
||||
filters.push(format!("flowy_notification={}", "info"));
|
||||
filters.push(format!("lib_infra={}", level));
|
||||
filters.push(format!("flowy_search={}", level));
|
||||
filters.push(format!("flowy_chat={}", level));
|
||||
// Enable the frontend logs. DO NOT DISABLE.
|
||||
// These logs are essential for debugging and verifying frontend behavior.
|
||||
filters.push(format!("dart_ffi={}", level));
|
||||
|
@ -3,8 +3,10 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Error;
|
||||
use client_api::collab_sync::{SinkConfig, SyncObject, SyncPlugin};
|
||||
|
||||
use client_api::entity::ai_dto::RepeatedRelatedQuestion;
|
||||
use client_api::entity::ChatMessageType;
|
||||
use collab::core::origin::{CollabClient, CollabOrigin};
|
||||
|
||||
use collab::preclude::CollabPlugin;
|
||||
use collab_entity::CollabType;
|
||||
use collab_plugins::cloud_storage::postgres::SupabaseDBPlugin;
|
||||
@ -14,6 +16,9 @@ use tracing::debug;
|
||||
use collab_integrate::collab_builder::{
|
||||
CollabCloudPluginProvider, CollabPluginProviderContext, CollabPluginProviderType,
|
||||
};
|
||||
use flowy_chat_pub::cloud::{
|
||||
ChatCloudService, ChatMessage, ChatMessageStream, MessageCursor, RepeatedChatMessage,
|
||||
};
|
||||
use flowy_database_pub::cloud::{
|
||||
CollabDocStateByOid, DatabaseCloudService, DatabaseSnapshot, SummaryRowContent,
|
||||
};
|
||||
@ -28,6 +33,7 @@ use flowy_server_pub::supabase_config::SupabaseConfiguration;
|
||||
use flowy_storage::ObjectValue;
|
||||
use flowy_user_pub::cloud::{UserCloudService, UserCloudServiceProvider};
|
||||
use flowy_user_pub::entities::{Authenticator, UserTokenState};
|
||||
use lib_infra::async_trait::async_trait;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use crate::integrate::server::{Server, ServerProvider};
|
||||
@ -372,7 +378,12 @@ impl CollabCloudPluginProvider for ServerProvider {
|
||||
collab_object.uid,
|
||||
collab_object.device_id.clone(),
|
||||
));
|
||||
let sync_object = SyncObject::from(collab_object);
|
||||
let sync_object = SyncObject::new(
|
||||
&collab_object.object_id,
|
||||
&collab_object.workspace_id,
|
||||
collab_object.collab_type,
|
||||
&collab_object.device_id,
|
||||
);
|
||||
let (sink, stream) = (channel.sink(), channel.stream());
|
||||
let sink_config = SinkConfig::new().send_timeout(8);
|
||||
let sync_plugin = SyncPlugin::new(
|
||||
@ -427,3 +438,93 @@ impl CollabCloudPluginProvider for ServerProvider {
|
||||
*self.user_enable_sync.read()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ChatCloudService for ServerProvider {
|
||||
fn create_chat(
|
||||
&self,
|
||||
uid: &i64,
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let server = self.get_server();
|
||||
let chat_id = chat_id.to_string();
|
||||
let uid = *uid;
|
||||
FutureResult::new(async move {
|
||||
server?
|
||||
.chat_service()
|
||||
.create_chat(&uid, &workspace_id, &chat_id)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
async fn send_chat_message(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
message: &str,
|
||||
message_type: ChatMessageType,
|
||||
) -> Result<ChatMessageStream, FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let chat_id = chat_id.to_string();
|
||||
let message = message.to_string();
|
||||
let server = self.get_server()?;
|
||||
server
|
||||
.chat_service()
|
||||
.send_chat_message(&workspace_id, &chat_id, &message, message_type)
|
||||
.await
|
||||
}
|
||||
|
||||
fn get_chat_messages(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
offset: MessageCursor,
|
||||
limit: u64,
|
||||
) -> FutureResult<RepeatedChatMessage, FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let chat_id = chat_id.to_string();
|
||||
let server = self.get_server();
|
||||
FutureResult::new(async move {
|
||||
server?
|
||||
.chat_service()
|
||||
.get_chat_messages(&workspace_id, &chat_id, offset, limit)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
fn get_related_message(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
message_id: i64,
|
||||
) -> FutureResult<RepeatedRelatedQuestion, FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let chat_id = chat_id.to_string();
|
||||
let server = self.get_server();
|
||||
FutureResult::new(async move {
|
||||
server?
|
||||
.chat_service()
|
||||
.get_related_message(&workspace_id, &chat_id, message_id)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_answer(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
question_message_id: i64,
|
||||
) -> FutureResult<ChatMessage, FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let chat_id = chat_id.to_string();
|
||||
let server = self.get_server();
|
||||
FutureResult::new(async move {
|
||||
server?
|
||||
.chat_service()
|
||||
.generate_answer(&workspace_id, &chat_id, question_message_id)
|
||||
.await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use tokio::sync::RwLock;
|
||||
use tracing::{debug, error, event, info, instrument};
|
||||
|
||||
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabPluginProviderType};
|
||||
use flowy_chat::manager::ChatManager;
|
||||
use flowy_database2::DatabaseManager;
|
||||
use flowy_document::manager::DocumentManager;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -57,6 +58,7 @@ pub struct AppFlowyCore {
|
||||
pub task_dispatcher: Arc<RwLock<TaskDispatcher>>,
|
||||
pub store_preference: Arc<StorePreferences>,
|
||||
pub search_manager: Arc<SearchManager>,
|
||||
pub chat_manager: Arc<ChatManager>,
|
||||
}
|
||||
|
||||
impl AppFlowyCore {
|
||||
@ -137,6 +139,7 @@ impl AppFlowyCore {
|
||||
document_manager,
|
||||
collab_builder,
|
||||
search_manager,
|
||||
chat_manager,
|
||||
) = async {
|
||||
/// The shared collab builder is used to build the [Collab] instance. The plugins will be loaded
|
||||
/// on demand based on the [CollabPluginConfig].
|
||||
@ -164,6 +167,8 @@ impl AppFlowyCore {
|
||||
Arc::downgrade(&(server_provider.clone() as Arc<dyn ObjectStorageService>)),
|
||||
);
|
||||
|
||||
let chat_manager =
|
||||
ChatDepsResolver::resolve(Arc::downgrade(&authenticate_user), server_provider.clone());
|
||||
let folder_indexer = Arc::new(FolderIndexManagerImpl::new(None));
|
||||
let folder_manager = FolderDepsResolver::resolve(
|
||||
Arc::downgrade(&authenticate_user),
|
||||
@ -172,6 +177,7 @@ impl AppFlowyCore {
|
||||
collab_builder.clone(),
|
||||
server_provider.clone(),
|
||||
folder_indexer.clone(),
|
||||
&chat_manager,
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -195,6 +201,7 @@ impl AppFlowyCore {
|
||||
document_manager,
|
||||
collab_builder,
|
||||
search_manager,
|
||||
chat_manager,
|
||||
)
|
||||
}
|
||||
.await;
|
||||
@ -230,6 +237,7 @@ impl AppFlowyCore {
|
||||
Arc::downgrade(&user_manager),
|
||||
Arc::downgrade(&document_manager),
|
||||
Arc::downgrade(&search_manager),
|
||||
Arc::downgrade(&chat_manager),
|
||||
),
|
||||
));
|
||||
|
||||
@ -244,6 +252,7 @@ impl AppFlowyCore {
|
||||
task_dispatcher,
|
||||
store_preference,
|
||||
search_manager,
|
||||
chat_manager,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
use flowy_chat::manager::ChatManager;
|
||||
use std::sync::Weak;
|
||||
|
||||
use flowy_database2::DatabaseManager;
|
||||
@ -13,6 +14,7 @@ pub fn make_plugins(
|
||||
user_session: Weak<UserManager>,
|
||||
document_manager2: Weak<DocumentManager2>,
|
||||
search_manager: Weak<SearchManager>,
|
||||
chat_manager: Weak<ChatManager>,
|
||||
) -> Vec<AFPlugin> {
|
||||
let store_preferences = user_session
|
||||
.upgrade()
|
||||
@ -25,6 +27,7 @@ pub fn make_plugins(
|
||||
let config_plugin = flowy_config::event_map::init(store_preferences);
|
||||
let date_plugin = flowy_date::event_map::init();
|
||||
let search_plugin = flowy_search::event_map::init(search_manager);
|
||||
let chat_plugin = flowy_chat::event_map::init(chat_manager);
|
||||
vec![
|
||||
user_plugin,
|
||||
folder_plugin,
|
||||
@ -33,5 +36,6 @@ pub fn make_plugins(
|
||||
config_plugin,
|
||||
date_plugin,
|
||||
search_plugin,
|
||||
chat_plugin,
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user