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:
128
frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs
Normal file
128
frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use crate::af_cloud::AFServer;
|
||||
use client_api::entity::ai_dto::RepeatedRelatedQuestion;
|
||||
use client_api::entity::{
|
||||
CreateChatMessageParams, CreateChatParams, MessageCursor, RepeatedChatMessage,
|
||||
};
|
||||
use flowy_chat_pub::cloud::{ChatCloudService, ChatMessage, ChatMessageStream, ChatMessageType};
|
||||
use flowy_error::FlowyError;
|
||||
use futures_util::StreamExt;
|
||||
use lib_infra::async_trait::async_trait;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
pub(crate) struct AFCloudChatCloudServiceImpl<T> {
|
||||
pub inner: T,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T> ChatCloudService for AFCloudChatCloudServiceImpl<T>
|
||||
where
|
||||
T: AFServer,
|
||||
{
|
||||
fn create_chat(
|
||||
&self,
|
||||
_uid: &i64,
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let chat_id = chat_id.to_string();
|
||||
let try_get_client = self.inner.try_get_client();
|
||||
|
||||
FutureResult::new(async move {
|
||||
let params = CreateChatParams {
|
||||
chat_id,
|
||||
name: "".to_string(),
|
||||
rag_ids: vec![],
|
||||
};
|
||||
try_get_client?
|
||||
.create_chat(&workspace_id, params)
|
||||
.await
|
||||
.map_err(FlowyError::from)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
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 try_get_client = self.inner.try_get_client();
|
||||
let params = CreateChatMessageParams {
|
||||
content: message,
|
||||
message_type,
|
||||
};
|
||||
let stream = try_get_client?
|
||||
.create_chat_message(&workspace_id, &chat_id, params)
|
||||
.await
|
||||
.map_err(FlowyError::from)?;
|
||||
|
||||
Ok(stream.boxed())
|
||||
}
|
||||
|
||||
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 try_get_client = self.inner.try_get_client();
|
||||
|
||||
FutureResult::new(async move {
|
||||
let resp = try_get_client?
|
||||
.get_chat_messages(&workspace_id, &chat_id, offset, limit)
|
||||
.await
|
||||
.map_err(FlowyError::from)?;
|
||||
|
||||
Ok(resp)
|
||||
})
|
||||
}
|
||||
|
||||
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 try_get_client = self.inner.try_get_client();
|
||||
|
||||
FutureResult::new(async move {
|
||||
let resp = try_get_client?
|
||||
.get_chat_related_question(&workspace_id, &chat_id, message_id)
|
||||
.await
|
||||
.map_err(FlowyError::from)?;
|
||||
|
||||
Ok(resp)
|
||||
})
|
||||
}
|
||||
|
||||
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 try_get_client = self.inner.try_get_client();
|
||||
|
||||
FutureResult::new(async move {
|
||||
let resp = try_get_client?
|
||||
.generate_question_answer(&workspace_id, &chat_id, question_message_id)
|
||||
.await
|
||||
.map_err(FlowyError::from)?;
|
||||
Ok(resp)
|
||||
})
|
||||
}
|
||||
}
|
@ -42,10 +42,7 @@ where
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
workspace_id: workspace_id.clone(),
|
||||
inner: QueryCollab {
|
||||
object_id: object_id.clone(),
|
||||
collab_type: collab_type.clone(),
|
||||
},
|
||||
inner: QueryCollab::new(object_id.clone(), collab_type.clone()),
|
||||
};
|
||||
match try_get_client?.get_collab(params).await {
|
||||
Ok(data) => {
|
||||
@ -81,10 +78,7 @@ where
|
||||
let client = try_get_client?;
|
||||
let params = object_ids
|
||||
.into_iter()
|
||||
.map(|object_id| QueryCollab {
|
||||
object_id,
|
||||
collab_type: object_ty.clone(),
|
||||
})
|
||||
.map(|object_id| QueryCollab::new(object_id, object_ty.clone()))
|
||||
.collect();
|
||||
let results = client.batch_get_collab(&workspace_id, params).await?;
|
||||
check_request_workspace_id_is_match(
|
||||
|
@ -37,10 +37,7 @@ where
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
workspace_id: workspace_id.clone(),
|
||||
inner: QueryCollab {
|
||||
object_id: document_id.to_string(),
|
||||
collab_type: CollabType::Document,
|
||||
},
|
||||
inner: QueryCollab::new(document_id.to_string(), CollabType::Document),
|
||||
};
|
||||
let doc_state = try_get_client?
|
||||
.get_collab(params)
|
||||
@ -82,10 +79,7 @@ where
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
workspace_id: workspace_id.clone(),
|
||||
inner: QueryCollab {
|
||||
object_id: document_id.clone(),
|
||||
collab_type: CollabType::Document,
|
||||
},
|
||||
inner: QueryCollab::new(document_id.clone(), CollabType::Document),
|
||||
};
|
||||
let doc_state = try_get_client?
|
||||
.get_collab(params)
|
||||
|
@ -93,10 +93,7 @@ where
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
workspace_id: workspace_id.clone(),
|
||||
inner: QueryCollab {
|
||||
object_id: workspace_id.clone(),
|
||||
collab_type: CollabType::Folder,
|
||||
},
|
||||
inner: QueryCollab::new(workspace_id.clone(), CollabType::Folder),
|
||||
};
|
||||
let doc_state = try_get_client?
|
||||
.get_collab(params)
|
||||
@ -140,10 +137,7 @@ where
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
workspace_id: workspace_id.clone(),
|
||||
inner: QueryCollab {
|
||||
object_id,
|
||||
collab_type,
|
||||
},
|
||||
inner: QueryCollab::new(object_id, collab_type),
|
||||
};
|
||||
let doc_state = try_get_client?
|
||||
.get_collab(params)
|
||||
@ -167,10 +161,12 @@ where
|
||||
FutureResult::new(async move {
|
||||
let params = objects
|
||||
.into_iter()
|
||||
.map(|object| CollabParams {
|
||||
object_id: object.object_id,
|
||||
encoded_collab_v1: object.encoded_collab_v1,
|
||||
collab_type: object.collab_type,
|
||||
.map(|object| {
|
||||
CollabParams::new(
|
||||
object.object_id,
|
||||
object.collab_type,
|
||||
object.encoded_collab_v1,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
try_get_client?
|
||||
|
@ -1,9 +1,11 @@
|
||||
pub(crate) use chat::*;
|
||||
pub(crate) use database::*;
|
||||
pub(crate) use document::*;
|
||||
pub(crate) use file_storage::*;
|
||||
pub(crate) use folder::*;
|
||||
pub(crate) use user::*;
|
||||
|
||||
mod chat;
|
||||
mod database;
|
||||
mod document;
|
||||
mod file_storage;
|
||||
|
@ -348,10 +348,7 @@ where
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
workspace_id: workspace_id.clone(),
|
||||
inner: QueryCollab {
|
||||
object_id,
|
||||
collab_type: CollabType::UserAwareness,
|
||||
},
|
||||
inner: QueryCollab::new(object_id, CollabType::UserAwareness),
|
||||
};
|
||||
let resp = try_get_client?.get_collab(params).await?;
|
||||
check_request_workspace_id_is_match(
|
||||
@ -381,10 +378,10 @@ where
|
||||
FutureResult::new(async move {
|
||||
let client = try_get_client?;
|
||||
let params = CreateCollabParams {
|
||||
workspace_id: collab_object.workspace_id.clone(),
|
||||
object_id: collab_object.object_id.clone(),
|
||||
workspace_id: collab_object.workspace_id,
|
||||
object_id: collab_object.object_id,
|
||||
collab_type: collab_object.collab_type,
|
||||
encoded_collab_v1: data,
|
||||
collab_type: collab_object.collab_type.clone(),
|
||||
};
|
||||
client.create_collab(params).await?;
|
||||
Ok(())
|
||||
@ -401,10 +398,12 @@ where
|
||||
FutureResult::new(async move {
|
||||
let params = objects
|
||||
.into_iter()
|
||||
.map(|object| CollabParams {
|
||||
object_id: object.object_id,
|
||||
encoded_collab_v1: object.encoded_collab,
|
||||
collab_type: object.collab_type,
|
||||
.map(|object| {
|
||||
CollabParams::new(
|
||||
object.object_id,
|
||||
u8::from(object.collab_type).into(),
|
||||
object.encoded_collab,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
try_get_client?
|
||||
|
@ -89,6 +89,7 @@ pub fn from_af_workspace_member(member: AFWorkspaceMember) -> WorkspaceMember {
|
||||
email: member.email,
|
||||
role: from_af_role(member.role),
|
||||
name: member.name,
|
||||
avatar_url: member.avatar_url,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ use client_api::ws::{
|
||||
ConnectState, WSClient, WSClientConfig, WSConnectStateReceiver, WebSocketChannel,
|
||||
};
|
||||
use client_api::{Client, ClientConfiguration};
|
||||
use flowy_chat_pub::cloud::ChatCloudService;
|
||||
use flowy_storage::ObjectStorageService;
|
||||
use rand::Rng;
|
||||
use semver::Version;
|
||||
@ -31,9 +32,10 @@ use flowy_user_pub::entities::UserTokenState;
|
||||
use lib_dispatch::prelude::af_spawn;
|
||||
|
||||
use crate::af_cloud::impls::{
|
||||
AFCloudDatabaseCloudServiceImpl, AFCloudDocumentCloudServiceImpl, AFCloudFileStorageServiceImpl,
|
||||
AFCloudFolderCloudServiceImpl, AFCloudUserAuthServiceImpl,
|
||||
AFCloudChatCloudServiceImpl, AFCloudDatabaseCloudServiceImpl, AFCloudDocumentCloudServiceImpl,
|
||||
AFCloudFileStorageServiceImpl, AFCloudFolderCloudServiceImpl, AFCloudUserAuthServiceImpl,
|
||||
};
|
||||
|
||||
use crate::AppFlowyServer;
|
||||
|
||||
pub(crate) type AFCloudClient = Client;
|
||||
@ -214,6 +216,13 @@ impl AppFlowyServer for AppFlowyCloudServer {
|
||||
})
|
||||
}
|
||||
|
||||
fn chat_service(&self) -> Arc<dyn ChatCloudService> {
|
||||
let server = AFServerImpl {
|
||||
client: self.get_client(),
|
||||
};
|
||||
Arc::new(AFCloudChatCloudServiceImpl { inner: server })
|
||||
}
|
||||
|
||||
fn subscribe_ws_state(&self) -> Option<WSConnectStateReceiver> {
|
||||
Some(self.ws_client.subscribe_connect_state())
|
||||
}
|
||||
|
66
frontend/rust-lib/flowy-server/src/default_impl.rs
Normal file
66
frontend/rust-lib/flowy-server/src/default_impl.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use client_api::entity::ai_dto::RepeatedRelatedQuestion;
|
||||
use client_api::entity::{ChatMessageType, MessageCursor, RepeatedChatMessage};
|
||||
use flowy_chat_pub::cloud::{ChatCloudService, ChatMessage, ChatMessageStream};
|
||||
use flowy_error::FlowyError;
|
||||
use lib_infra::async_trait::async_trait;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
pub(crate) struct DefaultChatCloudServiceImpl;
|
||||
|
||||
#[async_trait]
|
||||
impl ChatCloudService for DefaultChatCloudServiceImpl {
|
||||
fn create_chat(
|
||||
&self,
|
||||
_uid: &i64,
|
||||
_workspace_id: &str,
|
||||
_chat_id: &str,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async move {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
})
|
||||
}
|
||||
|
||||
async fn send_chat_message(
|
||||
&self,
|
||||
_workspace_id: &str,
|
||||
_chat_id: &str,
|
||||
_message: &str,
|
||||
_message_type: ChatMessageType,
|
||||
) -> Result<ChatMessageStream, FlowyError> {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
}
|
||||
|
||||
fn get_chat_messages(
|
||||
&self,
|
||||
_workspace_id: &str,
|
||||
_chat_id: &str,
|
||||
_offset: MessageCursor,
|
||||
_limit: u64,
|
||||
) -> FutureResult<RepeatedChatMessage, FlowyError> {
|
||||
FutureResult::new(async move {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
})
|
||||
}
|
||||
|
||||
fn get_related_message(
|
||||
&self,
|
||||
_workspace_id: &str,
|
||||
_chat_id: &str,
|
||||
_message_id: i64,
|
||||
) -> FutureResult<RepeatedRelatedQuestion, FlowyError> {
|
||||
FutureResult::new(async move {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_answer(
|
||||
&self,
|
||||
_workspace_id: &str,
|
||||
_chat_id: &str,
|
||||
_question_message_id: i64,
|
||||
) -> FutureResult<ChatMessage, FlowyError> {
|
||||
FutureResult::new(async move {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
})
|
||||
}
|
||||
}
|
@ -8,4 +8,5 @@ mod server;
|
||||
#[cfg(feature = "enable_supabase")]
|
||||
pub mod supabase;
|
||||
|
||||
mod default_impl;
|
||||
pub mod util;
|
||||
|
@ -6,11 +6,13 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Error;
|
||||
use client_api::collab_sync::ServerCollabMessage;
|
||||
use flowy_chat_pub::cloud::ChatCloudService;
|
||||
use parking_lot::RwLock;
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
#[cfg(feature = "enable_supabase")]
|
||||
use {collab_entity::CollabObject, collab_plugins::cloud_storage::RemoteCollabStorage};
|
||||
|
||||
use crate::default_impl::DefaultChatCloudServiceImpl;
|
||||
use flowy_database_pub::cloud::DatabaseCloudService;
|
||||
use flowy_document_pub::cloud::DocumentCloudService;
|
||||
use flowy_folder_pub::cloud::FolderCloudService;
|
||||
@ -94,6 +96,10 @@ pub trait AppFlowyServer: Send + Sync + 'static {
|
||||
/// An `Arc` wrapping the `DocumentCloudService` interface.
|
||||
fn document_service(&self) -> Arc<dyn DocumentCloudService>;
|
||||
|
||||
fn chat_service(&self) -> Arc<dyn ChatCloudService> {
|
||||
Arc::new(DefaultChatCloudServiceImpl)
|
||||
}
|
||||
|
||||
/// Manages collaborative objects within a remote storage system. This includes operations such as
|
||||
/// checking storage status, retrieving updates and snapshots, and dispatching updates. The service
|
||||
/// also provides subscription capabilities for real-time updates.
|
||||
|
Reference in New Issue
Block a user