diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index b571fc33..347ca952 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -89,6 +89,8 @@ jobs: sed -i 's|APPFLOWY_MAILER_SMTP_USERNAME=.*|APPFLOWY_MAILER_SMTP_USERNAME=${{ secrets.CI_GOTRUE_SMTP_USER }}|' .env sed -i 's|APPFLOWY_MAILER_SMTP_PASSWORD=.*|APPFLOWY_MAILER_SMTP_PASSWORD=${{ secrets.CI_GOTRUE_SMTP_PASS }}|' .env sed -i 's|APPFLOWY_AI_OPENAI_API_KEY=.*|APPFLOWY_AI_OPENAI_API_KEY=${{ secrets.CI_OPENAI_API_KEY }}|' .env + sed -i "s|LOCAL_AI_AWS_ACCESS_KEY_ID=.*|LOCAL_AI_AWS_ACCESS_KEY_ID=${{ secrets.LOCAL_AI_AWS_ACCESS_KEY_ID }}|" .env + sed -i "s|LOCAL_AI_AWS_SECRET_ACCESS_KEY=.*|LOCAL_AI_AWS_SECRET_ACCESS_KEY=${{ secrets.LOCAL_AI_AWS_SECRET_ACCESS_KEY }}|" .env sed -i 's|APPFLOWY_INDEXER_REDIS_URL=.*|APPFLOWY_INDEXER_REDIS_URL=redis://localhost:6379|' .env sed -i 's|APPFLOWY_INDEXER_DATABASE_URL=.*|APPFLOWY_INDEXER_DATABASE_URL=postgres://postgres:password@localhost:5432/postgres|' .env shell: bash diff --git a/Cargo.toml b/Cargo.toml index ed1a6e4a..0b138caa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -287,4 +287,4 @@ collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev [features] history = [] -ai-test-enabled = [] +ai-test-enabled = ["client-api-test/ai-test-enabled"] diff --git a/deploy.env b/deploy.env index 5ce77fd2..8ee278c3 100644 --- a/deploy.env +++ b/deploy.env @@ -112,6 +112,7 @@ APPFLOWY_AI_OPENAI_API_KEY= APPFLOWY_AI_SERVER_PORT=5001 APPFLOWY_AI_SERVER_HOST=ai APPFLOWY_AI_DATABASE_URL=postgresql+psycopg://postgres:password@postgres:5432/postgres +APPFLOWY_LOCAL_AI_TEST_ENABLED=false # AppFlowy History APPFLOWY_HISTORY_URL=http://localhost:50051 diff --git a/dev.env b/dev.env index 0452f633..421bfb84 100644 --- a/dev.env +++ b/dev.env @@ -103,6 +103,7 @@ APPFLOWY_AI_OPENAI_API_KEY= APPFLOWY_AI_SERVER_PORT=5001 APPFLOWY_AI_SERVER_HOST=localhost APPFLOWY_AI_DATABASE_URL=postgresql+psycopg://postgres:password@postgres:5432/postgres +APPFLOWY_LOCAL_AI_TEST_ENABLED=false # AppFlowy History APPFLOWY_HISTORY_URL=http://localhost:50051 diff --git a/libs/appflowy-ai-client/src/client.rs b/libs/appflowy-ai-client/src/client.rs index af301c68..c042870b 100644 --- a/libs/appflowy-ai-client/src/client.rs +++ b/libs/appflowy-ai-client/src/client.rs @@ -291,8 +291,19 @@ impl AppFlowyAIClient { .into_data() } - pub async fn get_local_ai_config(&self, platform: &str) -> Result { - let url = format!("{}/local_ai/config?platform={platform}", self.url); + pub async fn get_local_ai_config( + &self, + platform: &str, + app_version: Option, + ) -> Result { + // Start with the base URL including the platform parameter + let mut url = format!("{}/local_ai/config?platform={}", self.url, platform); + + // If app_version is provided, append it as a query parameter + if let Some(version) = app_version { + url = format!("{}&app_version={}", url, version); + } + let resp = self.http_client(Method::GET, &url)?.send().await?; AIResponse::::from_response(resp) .await? diff --git a/libs/appflowy-ai-client/tests/chat_test/qa_test.rs b/libs/appflowy-ai-client/tests/chat_test/qa_test.rs index 6b372ba1..a9bf1f6b 100644 --- a/libs/appflowy-ai-client/tests/chat_test/qa_test.rs +++ b/libs/appflowy-ai-client/tests/chat_test/qa_test.rs @@ -82,7 +82,10 @@ async fn download_package_test() { #[tokio::test] async fn get_local_ai_config_test() { let client = appflowy_ai_client(); - let config = client.get_local_ai_config("macos").await.unwrap(); + let config = client + .get_local_ai_config("macos", Some("0.6.10".to_string())) + .await + .unwrap(); assert!(!config.models.is_empty()); assert!(!config.models[0].embedding_model.download_url.is_empty()); diff --git a/libs/client-api-test/Cargo.toml b/libs/client-api-test/Cargo.toml index 8ccff831..7f95b817 100644 --- a/libs/client-api-test/Cargo.toml +++ b/libs/client-api-test/Cargo.toml @@ -41,4 +41,5 @@ web-sys = { version = "0.3", features = ["console"] } [features] -collab-sync = ["client-api/collab-sync"] \ No newline at end of file +collab-sync = ["client-api/collab-sync"] +ai-test-enabled = [] \ No newline at end of file diff --git a/libs/client-api-test/src/log.rs b/libs/client-api-test/src/log.rs index f58ab6df..49bd22f1 100644 --- a/libs/client-api-test/src/log.rs +++ b/libs/client-api-test/src/log.rs @@ -1,3 +1,4 @@ +use tracing::trace; #[cfg(not(target_arch = "wasm32"))] use { std::sync::Once, @@ -16,6 +17,31 @@ fn get_bool_from_env_var(env_var_name: &str) -> bool { } } +pub fn load_env() { + // load once + static START: Once = Once::new(); + START.call_once(|| { + dotenvy::dotenv().ok(); + }); +} + +pub fn local_ai_test_enabled() -> bool { + // In appflowy GitHub CI, we enable 'ai-test-enabled' feature by default, so even if the env var is not set, + // we still enable the local ai test. + if cfg!(feature = "ai-test-enabled") { + return true; + } + + load_env(); + + // local ai test is disable by default + let enabled = get_bool_from_env_var("APPFLOWY_LOCAL_AI_TEST_ENABLED"); + if enabled { + trace!("Local AI test is enabled"); + } + enabled +} + #[cfg(not(target_arch = "wasm32"))] pub fn setup_log() { if get_bool_from_env_var("DISABLE_CI_TEST_LOG") { diff --git a/libs/client-api-test/src/test_client.rs b/libs/client-api-test/src/test_client.rs index 4afbbac9..c7d73261 100644 --- a/libs/client-api-test/src/test_client.rs +++ b/libs/client-api-test/src/test_client.rs @@ -1,4 +1,4 @@ -use crate::{localhost_client_with_device_id, setup_log}; +use crate::{load_env, localhost_client_with_device_id, setup_log}; use anyhow::{anyhow, Error}; use assert_json_diff::{ assert_json_eq, assert_json_include, assert_json_matches_no_panic, CompareMode, Config, @@ -56,6 +56,7 @@ pub struct TestCollab { } impl TestClient { pub async fn new(registered_user: User, start_ws_conn: bool) -> Self { + load_env(); setup_log(); let device_id = Uuid::new_v4().to_string(); Self::new_with_device_id(&device_id, registered_user, start_ws_conn).await diff --git a/libs/client-api/src/http.rs b/libs/client-api/src/http.rs index 638abfaa..f3d80066 100644 --- a/libs/client-api/src/http.rs +++ b/libs/client-api/src/http.rs @@ -126,15 +126,15 @@ impl Client { ) -> Self { let reqwest_client = reqwest::Client::new(); let client_version = Version::parse(client_id).unwrap_or_else(|_| { - warn!("Failed to parse client version, defaulting to 0.6.3"); - Version::new(0, 6, 3) + warn!("Failed to parse client version, defaulting to 0.6.6"); + Version::new(0, 6, 6) }); - // The latest version of appflowy frontend application is 0.6.3. - // Ensure the client version is at least 0.6.3. Just in case client passes a lower version. - let min_version = Version::new(0, 6, 3); + // The latest version of appflowy frontend application is 0.6.6. + // Ensure the client version is at least 0.6.6. Just in case client passes a lower version. + let min_version = Version::new(0, 6, 6); let client_version = if client_version < min_version { - warn!("Client version is less than 0.6.3, setting it to 0.6.3"); + warn!("Client version is less than 0.6.6, setting it to 0.6.6"); min_version } else { client_version diff --git a/libs/client-api/src/http_ai.rs b/libs/client-api/src/http_ai.rs index 1bdd16c0..74da345b 100644 --- a/libs/client-api/src/http_ai.rs +++ b/libs/client-api/src/http_ai.rs @@ -80,8 +80,9 @@ impl Client { workspace_id: &str, platform: &str, ) -> Result { + let client_version = self.client_version.to_string(); let url = format!( - "{}/api/ai/{}/local/config?platform={platform}", + "{}/api/ai/{}/local/config?platform={platform}&app_version={client_version}", self.base_url, workspace_id ); let resp = self diff --git a/src/api/ai.rs b/src/api/ai.rs index 3ea0e90b..99fe8d4a 100644 --- a/src/api/ai.rs +++ b/src/api/ai.rs @@ -9,13 +9,14 @@ use appflowy_ai_client::dto::{ }; use futures_util::{stream, TryStreamExt}; + use serde::Deserialize; use shared_entity::dto::ai_dto::{ CompleteTextParams, SummarizeRowData, SummarizeRowParams, SummarizeRowResponse, }; use shared_entity::response::{AppResponse, JsonAppResponse}; -use tracing::{error, instrument}; +use tracing::{error, instrument, trace}; pub fn ai_completion_scope() -> Scope { web::scope("/api/ai/{workspace_id}") @@ -128,17 +129,19 @@ async fn translate_row_handler( } } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] struct ConfigQuery { platform: String, + app_version: Option, } #[instrument(level = "debug", skip_all, err)] async fn local_ai_config_handler( state: web::Data, query: web::Query, - // req: HttpRequest, ) -> actix_web::Result>> { - let platform = match query.into_inner().platform.as_str() { + let query = query.into_inner(); + trace!("query ai configuration: {:?}", query); + let platform = match query.platform.as_str() { "macos" => "macos", "linux" => "ubuntu", "ubuntu" => "ubuntu", @@ -150,22 +153,8 @@ async fn local_ai_config_handler( let config = state .ai_client - .get_local_ai_config(platform) + .get_local_ai_config(platform, query.app_version) .await .map_err(|err| AppError::AIServiceUnavailable(err.to_string()))?; Ok(AppResponse::Ok().with_data(config).into()) } - -// fn device_info_from_headers(headers: &HeaderMap) -> std::result::Result { -// headers -// .get("device_id") -// .ok_or(AppError::InvalidRequest( -// "Missing device_id header".to_string(), -// )) -// .and_then(|header| { -// header -// .to_str() -// .map_err(|err| AppError::InvalidRequest(format!("Failed to parse device_id: {}", err))) -// }) -// .map(|s| s.to_string()) -// } diff --git a/tests/ai_test/chat_test.rs b/tests/ai_test/chat_test.rs index 67be339d..daa28a48 100644 --- a/tests/ai_test/chat_test.rs +++ b/tests/ai_test/chat_test.rs @@ -2,7 +2,7 @@ use crate::ai_test::util::read_text_from_asset; use appflowy_ai_client::dto::{ChatContextLoader, CreateTextChatContext}; use assert_json_diff::assert_json_eq; use client_api::entity::QuestionStreamValue; -use client_api_test::TestClient; +use client_api_test::{local_ai_test_enabled, TestClient}; use database_entity::dto::{ ChatMessage, ChatMessageMetadata, ChatMetadataData, CreateChatMessageParams, CreateChatParams, MessageCursor, @@ -12,6 +12,10 @@ use serde_json::json; #[tokio::test] async fn create_chat_and_create_messages_test() { + if !local_ai_test_enabled() { + return; + } + let test_client = TestClient::new_user_without_ws_conn().await; let workspace_id = test_client.workspace_id().await; @@ -101,6 +105,9 @@ async fn create_chat_and_create_messages_test() { #[tokio::test] async fn chat_qa_test() { + if !local_ai_test_enabled() { + return; + } let test_client = TestClient::new_user_without_ws_conn().await; let workspace_id = test_client.workspace_id().await; let chat_id = uuid::Uuid::new_v4().to_string(); @@ -174,6 +181,9 @@ async fn chat_qa_test() { #[tokio::test] async fn generate_chat_message_answer_test() { + if !local_ai_test_enabled() { + return; + } let test_client = TestClient::new_user_without_ws_conn().await; let workspace_id = test_client.workspace_id().await; let chat_id = uuid::Uuid::new_v4().to_string(); @@ -216,6 +226,9 @@ async fn generate_chat_message_answer_test() { #[tokio::test] async fn generate_stream_answer_test() { + if !local_ai_test_enabled() { + return; + } let test_client = TestClient::new_user_without_ws_conn().await; let workspace_id = test_client.workspace_id().await; let chat_id = uuid::Uuid::new_v4().to_string(); @@ -271,6 +284,9 @@ async fn generate_stream_answer_test() { #[tokio::test] async fn create_chat_context_test() { + if !local_ai_test_enabled() { + return; + } let test_client = TestClient::new_user_without_ws_conn().await; let workspace_id = test_client.workspace_id().await; let chat_id = uuid::Uuid::new_v4().to_string(); diff --git a/tests/ai_test/complete_text.rs b/tests/ai_test/complete_text.rs index ac451dab..8930f58c 100644 --- a/tests/ai_test/complete_text.rs +++ b/tests/ai_test/complete_text.rs @@ -1,9 +1,12 @@ use appflowy_ai_client::dto::{AIModel, CompletionType}; -use client_api_test::TestClient; +use client_api_test::{local_ai_test_enabled, TestClient}; use shared_entity::dto::ai_dto::CompleteTextParams; #[tokio::test] async fn improve_writing_test() { + if !local_ai_test_enabled() { + return; + } let test_client = TestClient::new_user().await; test_client.api_client.set_ai_model(AIModel::GPT4o); diff --git a/tests/ai_test/local_ai_test.rs b/tests/ai_test/local_ai_test.rs index c95c782f..06ff8983 100644 --- a/tests/ai_test/local_ai_test.rs +++ b/tests/ai_test/local_ai_test.rs @@ -1,7 +1,10 @@ -use client_api_test::TestClient; +use client_api_test::{local_ai_test_enabled, TestClient}; #[tokio::test] async fn get_local_ai_config_test() { + if !local_ai_test_enabled() { + return; + } let test_client = TestClient::new_user().await; let workspace_id = test_client.workspace_id().await; let config = test_client diff --git a/tests/ai_test/summarize_row.rs b/tests/ai_test/summarize_row.rs index c8e141cb..e493d897 100644 --- a/tests/ai_test/summarize_row.rs +++ b/tests/ai_test/summarize_row.rs @@ -1,9 +1,12 @@ -use client_api_test::TestClient; +use client_api_test::{local_ai_test_enabled, TestClient}; use serde_json::json; use shared_entity::dto::ai_dto::{SummarizeRowData, SummarizeRowParams}; #[tokio::test] async fn summarize_row_test() { + if !local_ai_test_enabled() { + return; + } let test_client = TestClient::new_user().await; let workspace_id = test_client.workspace_id().await; diff --git a/tests/main.rs b/tests/main.rs index 302b8a5c..3224a624 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "ai-test-enabled")] mod ai_test; mod collab; mod collab_history;