chore: update commit id

This commit is contained in:
nathan 2024-07-08 00:10:59 +08:00
parent ffc75106f3
commit a9b43b6454
17 changed files with 118 additions and 257 deletions

View File

@ -119,7 +119,6 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
final uniqueMessages = {...allMessages, ...messages}.toList() final uniqueMessages = {...allMessages, ...messages}.toList()
..sort((a, b) => b.id.compareTo(a.id)); ..sort((a, b) => b.id.compareTo(a.id));
uniqueMessages.insertAll(0, onetimeMessages); uniqueMessages.insertAll(0, onetimeMessages);
emit( emit(
state.copyWith( state.copyWith(
messages: uniqueMessages, messages: uniqueMessages,
@ -380,7 +379,8 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
} }
Message _createStreamMessage(AnswerStream stream, Int64 questionMessageId) { Message _createStreamMessage(AnswerStream stream, Int64 questionMessageId) {
final streamMessageId = nanoid(); final streamMessageId = (questionMessageId + 1).toString();
lastStreamMessageId = streamMessageId; lastStreamMessageId = streamMessageId;
return TextMessage( return TextMessage(

View File

@ -195,9 +195,9 @@ dependencies = [
] ]
[[package]] [[package]]
name = "appflowy-local-ai-chat" name = "appflowy-local-ai"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=b7f51a3f#b7f51a3fe79142582d89c4e577ccd36957cc2c00" source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=8bb364#8bb364a97fe1c3eec5c5092dd8208883a4e6186b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"appflowy-plugin", "appflowy-plugin",
@ -212,7 +212,7 @@ dependencies = [
[[package]] [[package]]
name = "appflowy-plugin" name = "appflowy-plugin"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=b7f51a3f#b7f51a3fe79142582d89c4e577ccd36957cc2c00" source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=8bb364#8bb364a97fe1c3eec5c5092dd8208883a4e6186b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cfg-if", "cfg-if",
@ -1701,7 +1701,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"allo-isolate", "allo-isolate",
"anyhow", "anyhow",
"appflowy-local-ai-chat", "appflowy-local-ai",
"appflowy-plugin", "appflowy-plugin",
"bytes", "bytes",
"dashmap", "dashmap",

View File

@ -145,5 +145,5 @@ collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFl
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" }
appflowy-local-ai-chat = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "b7f51a3f" } appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "8bb364" }
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "b7f51a3f" } appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "8bb364" }

View File

@ -33,7 +33,7 @@ serde_json = { workspace = true }
anyhow = "1.0.86" anyhow = "1.0.86"
tokio-stream = "0.1.15" tokio-stream = "0.1.15"
parking_lot.workspace = true parking_lot.workspace = true
appflowy-local-ai-chat = { version = "0.1.0", features = ["verbose"] } appflowy-local-ai = { version = "0.1.0", features = ["verbose"] }
appflowy-plugin = { version = "0.1.0", features = ["verbose"] } appflowy-plugin = { version = "0.1.0", features = ["verbose"] }
[dev-dependencies] [dev-dependencies]

View File

@ -1,8 +1,8 @@
use crate::chat_manager::ChatUserService; use crate::chat_manager::ChatUserService;
use crate::chat_service_impl::ChatService;
use crate::entities::{ use crate::entities::{
ChatMessageErrorPB, ChatMessageListPB, ChatMessagePB, RepeatedRelatedQuestionPB, ChatMessageErrorPB, ChatMessageListPB, ChatMessagePB, RepeatedRelatedQuestionPB,
}; };
use crate::middleware::chat_service_mw::ChatService;
use crate::notification::{send_notification, ChatNotification}; use crate::notification::{send_notification, ChatNotification};
use crate::persistence::{insert_chat_messages, select_chat_messages, ChatMessageTable}; use crate::persistence::{insert_chat_messages, select_chat_messages, ChatMessageTable};
use allo_isolate::Isolate; use allo_isolate::Isolate;

View File

@ -1,9 +1,9 @@
use crate::chat::Chat; use crate::chat::Chat;
use crate::chat_service_impl::ChatService;
use crate::entities::{ChatMessageListPB, ChatMessagePB, RepeatedRelatedQuestionPB}; use crate::entities::{ChatMessageListPB, ChatMessagePB, RepeatedRelatedQuestionPB};
use crate::middleware::chat_service_mw::ChatService;
use crate::persistence::{insert_chat, ChatTable}; use crate::persistence::{insert_chat, ChatTable};
use appflowy_local_ai_chat::llm_chat::{LocalChatLLMChat, LocalLLMSetting}; use appflowy_local_ai::llm_chat::{LocalChatLLMChat, LocalLLMSetting};
use appflowy_plugin::manager::SidecarManager; use appflowy_plugin::manager::PluginManager;
use dashmap::DashMap; use dashmap::DashMap;
use flowy_chat_pub::cloud::{ChatCloudService, ChatMessageType}; use flowy_chat_pub::cloud::{ChatCloudService, ChatMessageType};
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
@ -38,7 +38,7 @@ impl ChatManager {
let local_ai_setting = store_preferences let local_ai_setting = store_preferences
.get_object::<LocalLLMSetting>(LOCAL_AI_SETTING_KEY) .get_object::<LocalLLMSetting>(LOCAL_AI_SETTING_KEY)
.unwrap_or_default(); .unwrap_or_default();
let sidecar_manager = Arc::new(SidecarManager::new()); let sidecar_manager = Arc::new(PluginManager::new());
// setup local AI chat plugin // setup local AI chat plugin
let local_llm_ctrl = Arc::new(LocalChatLLMChat::new(sidecar_manager)); let local_llm_ctrl = Arc::new(LocalChatLLMChat::new(sidecar_manager));

View File

@ -1,4 +1,4 @@
use appflowy_local_ai_chat::llm_chat::LocalLLMSetting; use appflowy_local_ai::llm_chat::LocalLLMSetting;
use flowy_chat_pub::cloud::{ use flowy_chat_pub::cloud::{
ChatMessage, RelatedQuestion, RepeatedChatMessage, RepeatedRelatedQuestion, ChatMessage, RelatedQuestion, RepeatedChatMessage, RepeatedRelatedQuestion,
}; };
@ -267,3 +267,19 @@ pub enum CompletionTypePB {
MakeLonger = 4, MakeLonger = 4,
ContinueWriting = 5, ContinueWriting = 5,
} }
#[derive(Default, ProtoBuf, Clone, Debug)]
pub struct ChatStatePB {
#[pb(index = 1)]
pub model_type: ModelTypePB,
#[pb(index = 2)]
pub available: bool,
}
#[derive(Clone, Debug, ProtoBuf_Enum, Default)]
pub enum ModelTypePB {
LocalAI = 0,
#[default]
RemoteAI = 1,
}

View File

@ -3,8 +3,8 @@ pub mod event_map;
mod chat; mod chat;
pub mod chat_manager; pub mod chat_manager;
mod chat_service_impl;
pub mod entities; pub mod entities;
mod middleware;
pub mod notification; pub mod notification;
mod persistence; mod persistence;
mod protobuf; mod protobuf;

View File

@ -1,12 +1,16 @@
use crate::chat_manager::ChatUserService; use crate::chat_manager::ChatUserService;
use crate::entities::{ChatStatePB, ModelTypePB};
use crate::notification::{send_notification, ChatNotification};
use crate::persistence::select_single_message; use crate::persistence::select_single_message;
use appflowy_local_ai_chat::llm_chat::{LocalChatLLMChat, LocalLLMSetting}; use appflowy_local_ai::llm_chat::{LocalChatLLMChat, LocalLLMSetting};
use appflowy_plugin::error::PluginError;
use appflowy_plugin::util::is_apple_silicon;
use flowy_chat_pub::cloud::{ use flowy_chat_pub::cloud::{
ChatCloudService, ChatMessage, ChatMessageType, CompletionType, MessageCursor, ChatCloudService, ChatMessage, ChatMessageType, CompletionType, MessageCursor,
RepeatedChatMessage, RepeatedRelatedQuestion, StreamAnswer, StreamComplete, RepeatedChatMessage, RepeatedRelatedQuestion, StreamAnswer, StreamComplete,
}; };
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use futures::{StreamExt, TryStreamExt}; use futures::{stream, StreamExt, TryStreamExt};
use lib_infra::async_trait::async_trait; use lib_infra::async_trait::async_trait;
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
use parking_lot::RwLock; use parking_lot::RwLock;
@ -31,6 +35,13 @@ impl ChatService {
setup_local_chat(&local_llm_setting, local_llm_ctrl.clone()); setup_local_chat(&local_llm_setting, local_llm_ctrl.clone());
} }
let mut rx = local_llm_ctrl.subscribe_running_state();
tokio::spawn(async move {
while let Ok(state) = rx.recv().await {
info!("[Chat Plugin] state: {:?}", state);
}
});
Self { Self {
user_service, user_service,
cloud_service, cloud_service,
@ -92,6 +103,20 @@ impl ChatService {
Ok(content) Ok(content)
} }
fn handle_plugin_error(&self, err: PluginError) {
if matches!(
err,
PluginError::PluginNotConnected | PluginError::PeerDisconnect
) {
send_notification("appflowy_chat_plugin", ChatNotification::ChatStateUpdated).payload(
ChatStatePB {
model_type: ModelTypePB::LocalAI,
available: false,
},
);
}
}
} }
#[async_trait] #[async_trait]
@ -137,12 +162,17 @@ impl ChatCloudService for ChatService {
) -> Result<StreamAnswer, FlowyError> { ) -> Result<StreamAnswer, FlowyError> {
if self.local_llm_setting.read().enabled { if self.local_llm_setting.read().enabled {
let content = self.get_message_content(message_id)?; let content = self.get_message_content(message_id)?;
let stream = self match self.local_llm_chat.ask_question(chat_id, &content).await {
.local_llm_chat Ok(stream) => Ok(
.ask_question(chat_id, &content) stream
.await? .map_err(|err| FlowyError::local_ai().with_context(err))
.map_err(|err| FlowyError::local_ai().with_context(err)); .boxed(),
Ok(stream.boxed()) ),
Err(err) => {
self.handle_plugin_error(err);
Ok(stream::once(async { Err(FlowyError::local_ai_unavailable()) }).boxed())
},
}
} else { } else {
self self
.cloud_service .cloud_service
@ -159,11 +189,19 @@ impl ChatCloudService for ChatService {
) -> Result<ChatMessage, FlowyError> { ) -> Result<ChatMessage, FlowyError> {
if self.local_llm_setting.read().enabled { if self.local_llm_setting.read().enabled {
let content = self.get_message_content(question_message_id)?; let content = self.get_message_content(question_message_id)?;
let _answer = self match self.local_llm_chat.generate_answer(chat_id, &content).await {
.local_llm_chat Ok(answer) => {
.generate_answer(chat_id, &content) let message = self
.await?; .cloud_service
todo!() .save_answer(workspace_id, chat_id, &answer, question_message_id)
.await?;
Ok(message)
},
Err(err) => {
self.handle_plugin_error(err);
Err(FlowyError::local_ai_unavailable())
},
}
} else { } else {
self self
.cloud_service .cloud_service
@ -223,12 +261,35 @@ impl ChatCloudService for ChatService {
fn setup_local_chat(local_llm_setting: &LocalLLMSetting, llm_chat_ctrl: Arc<LocalChatLLMChat>) { fn setup_local_chat(local_llm_setting: &LocalLLMSetting, llm_chat_ctrl: Arc<LocalChatLLMChat>) {
if local_llm_setting.enabled { if local_llm_setting.enabled {
if let Ok(config) = local_llm_setting.chat_config() { if let Ok(mut config) = local_llm_setting.chat_config() {
tokio::spawn(async move { tokio::spawn(async move {
trace!("[Chat Plugin] setup local chat: {:?}", config); trace!("[Chat Plugin] setup local chat: {:?}", config);
if is_apple_silicon().await.unwrap_or(false) {
config = config.with_device("gpu");
}
if let Err(err) = llm_chat_ctrl.init_chat_plugin(config).await { if cfg!(debug_assertions) {
error!("[Chat Plugin] failed to setup plugin: {:?}", err); config = config.with_verbose(true);
}
match llm_chat_ctrl.init_chat_plugin(config).await {
Ok(_) => {
send_notification("appflowy_chat_plugin", ChatNotification::ChatStateUpdated).payload(
ChatStatePB {
model_type: ModelTypePB::LocalAI,
available: true,
},
);
},
Err(err) => {
send_notification("appflowy_chat_plugin", ChatNotification::ChatStateUpdated).payload(
ChatStatePB {
model_type: ModelTypePB::LocalAI,
available: false,
},
);
error!("[Chat Plugin] failed to setup plugin: {:?}", err);
},
} }
}); });
} }

View File

@ -0,0 +1 @@
pub mod chat_service_mw;

View File

@ -12,6 +12,7 @@ pub enum ChatNotification {
DidReceiveChatMessage = 3, DidReceiveChatMessage = 3,
StreamChatMessageError = 4, StreamChatMessageError = 4,
FinishStreaming = 5, FinishStreaming = 5,
ChatStateUpdated = 6,
} }
impl std::convert::From<ChatNotification> for i32 { impl std::convert::From<ChatNotification> for i32 {
@ -27,6 +28,7 @@ impl std::convert::From<i32> for ChatNotification {
3 => ChatNotification::DidReceiveChatMessage, 3 => ChatNotification::DidReceiveChatMessage,
4 => ChatNotification::StreamChatMessageError, 4 => ChatNotification::StreamChatMessageError,
5 => ChatNotification::FinishStreaming, 5 => ChatNotification::FinishStreaming,
6 => ChatNotification::ChatStateUpdated,
_ => ChatNotification::Unknown, _ => ChatNotification::Unknown,
} }
} }

View File

@ -1,41 +0,0 @@
use crate::util::LocalAITest;
use tokio_stream::StreamExt;
#[tokio::test]
async fn load_chat_model_test() {
if let Ok(test) = LocalAITest::new() {
let plugin_id = test.init_chat_plugin().await;
let chat_id = uuid::Uuid::new_v4().to_string();
let resp = test
.send_chat_message(&chat_id, plugin_id, "hello world")
.await;
eprintln!("chat response: {:?}", resp);
let embedding_plugin_id = test.init_embedding_plugin().await;
let score = test.calculate_similarity(embedding_plugin_id, &resp, "Hello! How can I help you today? Is there something specific you would like to know or discuss").await;
assert!(score > 0.9, "score: {}", score);
// let questions = test.related_question(&chat_id, plugin_id).await;
// assert_eq!(questions.len(), 3);
// eprintln!("related questions: {:?}", questions);
}
}
#[tokio::test]
async fn stream_local_model_test() {
if let Ok(test) = LocalAITest::new() {
let plugin_id = test.init_chat_plugin().await;
let chat_id = uuid::Uuid::new_v4().to_string();
let mut resp = test
.stream_chat_message(&chat_id, plugin_id, "hello world")
.await;
let mut list = vec![];
while let Some(s) = resp.next().await {
list.push(String::from_utf8(s.unwrap().to_vec()).unwrap());
}
let answer = list.join("");
eprintln!("chat response: {:?}", answer);
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
}
}

View File

@ -1,2 +0,0 @@
pub mod chat_test;
pub mod util;

View File

@ -1,180 +0,0 @@
use anyhow::Result;
use bytes::Bytes;
use flowy_sidecar::manager::SidecarManager;
use serde_json::json;
use std::path::PathBuf;
use std::sync::Once;
use tokio_stream::wrappers::ReceiverStream;
use flowy_chat::local_ai::chat_plugin::ChatPluginOperation;
use flowy_chat::local_ai::embedding_plugin::EmbeddingPluginOperation;
use flowy_sidecar::core::plugin::{PluginId, PluginInfo};
use flowy_sidecar::error::SidecarError;
use simsimd::SpatialSimilarity;
use std::f64;
use tracing_subscriber::fmt::Subscriber;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
pub struct LocalAITest {
config: LocalAIConfiguration,
manager: SidecarManager,
}
impl LocalAITest {
pub fn new() -> Result<Self> {
let config = LocalAIConfiguration::new()?;
let manager = SidecarManager::new();
Ok(Self { config, manager })
}
pub async fn init_chat_plugin(&self) -> PluginId {
let info = PluginInfo {
name: "chat".to_string(),
exec_path: self.config.chat_bin_path.clone(),
};
let plugin_id = self.manager.create_plugin(info).await.unwrap();
self
.manager
.init_plugin(
plugin_id,
json!({
"absolute_chat_model_path":self.config.chat_model_absolute_path(),
}),
)
.unwrap();
plugin_id
}
pub async fn init_embedding_plugin(&self) -> PluginId {
let info = PluginInfo {
name: "embedding".to_string(),
exec_path: self.config.embedding_bin_path.clone(),
};
let plugin_id = self.manager.create_plugin(info).await.unwrap();
let embedding_model_path = self.config.embedding_model_absolute_path();
self
.manager
.init_plugin(
plugin_id,
json!({
"absolute_model_path":embedding_model_path,
}),
)
.unwrap();
plugin_id
}
pub async fn send_chat_message(
&self,
chat_id: &str,
plugin_id: PluginId,
message: &str,
) -> String {
let plugin = self.manager.get_plugin(plugin_id).await.unwrap();
let operation = ChatPluginOperation::new(plugin);
operation.send_message(chat_id, message).await.unwrap()
}
pub async fn stream_chat_message(
&self,
chat_id: &str,
plugin_id: PluginId,
message: &str,
) -> ReceiverStream<Result<Bytes, SidecarError>> {
let plugin = self.manager.get_plugin(plugin_id).await.unwrap();
let operation = ChatPluginOperation::new(plugin);
operation.stream_message(chat_id, message).await.unwrap()
}
pub async fn related_question(
&self,
chat_id: &str,
plugin_id: PluginId,
) -> Vec<serde_json::Value> {
let plugin = self.manager.get_plugin(plugin_id).await.unwrap();
let operation = ChatPluginOperation::new(plugin);
operation.get_related_questions(chat_id).await.unwrap()
}
pub async fn calculate_similarity(
&self,
plugin_id: PluginId,
message1: &str,
message2: &str,
) -> f64 {
let plugin = self.manager.get_plugin(plugin_id).await.unwrap();
let operation = EmbeddingPluginOperation::new(plugin);
let left = operation.get_embeddings(message1).await.unwrap();
let right = operation.get_embeddings(message2).await.unwrap();
let actual_embedding_flat = flatten_vec(left);
let expected_embedding_flat = flatten_vec(right);
let distance = f64::cosine(&actual_embedding_flat, &expected_embedding_flat)
.expect("Vectors must be of the same length");
distance.cos()
}
}
// Function to flatten Vec<Vec<f64>> into Vec<f64>
fn flatten_vec(vec: Vec<Vec<f64>>) -> Vec<f64> {
vec.into_iter().flatten().collect()
}
pub struct LocalAIConfiguration {
model_dir: String,
chat_bin_path: PathBuf,
chat_model_name: String,
embedding_bin_path: PathBuf,
embedding_model_name: String,
}
impl LocalAIConfiguration {
pub fn new() -> Result<Self> {
dotenv::dotenv().ok();
setup_log();
// load from .env
let model_dir = dotenv::var("LOCAL_AI_MODEL_DIR")?;
let chat_bin_path = PathBuf::from(dotenv::var("CHAT_BIN_PATH")?);
let chat_model_name = dotenv::var("LOCAL_AI_CHAT_MODEL_NAME")?;
let embedding_bin_path = PathBuf::from(dotenv::var("EMBEDDING_BIN_PATH")?);
let embedding_model_name = dotenv::var("LOCAL_AI_EMBEDDING_MODEL_NAME")?;
Ok(Self {
model_dir,
chat_bin_path,
chat_model_name,
embedding_bin_path,
embedding_model_name,
})
}
pub fn chat_model_absolute_path(&self) -> String {
format!("{}/{}", self.model_dir, self.chat_model_name)
}
pub fn embedding_model_absolute_path(&self) -> String {
format!("{}/{}", self.model_dir, self.embedding_model_name)
}
}
pub fn setup_log() {
static START: Once = Once::new();
START.call_once(|| {
let level = "trace";
let mut filters = vec![];
filters.push(format!("flowy_sidecar={}", level));
std::env::set_var("RUST_LOG", filters.join(","));
let subscriber = Subscriber::builder()
.with_env_filter(EnvFilter::from_default_env())
.with_line_number(true)
.with_ansi(true)
.finish();
subscriber.try_init().unwrap();
});
}

View File

@ -57,8 +57,8 @@ pub fn create_log_filter(
filters.push(format!("lib_infra={}", level)); filters.push(format!("lib_infra={}", level));
filters.push(format!("flowy_search={}", level)); filters.push(format!("flowy_search={}", level));
filters.push(format!("flowy_chat={}", level)); filters.push(format!("flowy_chat={}", level));
filters.push(format!("flowy_chat={}", level)); filters.push(format!("appflowy_local_ai={}", level));
filters.push(format!("flowy_sidecar={}", level)); filters.push(format!("appflowy_plugin={}", level));
filters.push(format!("flowy_ai={}", level)); filters.push(format!("flowy_ai={}", level));
// Enable the frontend logs. DO NOT DISABLE. // Enable the frontend logs. DO NOT DISABLE.
// These logs are essential for debugging and verifying frontend behavior. // These logs are essential for debugging and verifying frontend behavior.

View File

@ -283,6 +283,9 @@ pub enum ErrorCode {
#[error("Local AI error")] #[error("Local AI error")]
LocalAIError = 98, LocalAIError = 98,
#[error("Local AI unavailable")]
LocalAIUnavailable = 99,
} }
impl ErrorCode { impl ErrorCode {

View File

@ -119,6 +119,7 @@ impl FlowyError {
); );
static_flowy_error!(workspace_data_not_match, ErrorCode::WorkspaceDataNotMatch); static_flowy_error!(workspace_data_not_match, ErrorCode::WorkspaceDataNotMatch);
static_flowy_error!(local_ai, ErrorCode::LocalAIError); static_flowy_error!(local_ai, ErrorCode::LocalAIError);
static_flowy_error!(local_ai_unavailable, ErrorCode::LocalAIUnavailable);
} }
impl std::convert::From<ErrorCode> for FlowyError { impl std::convert::From<ErrorCode> for FlowyError {