diff --git a/api/libs/agents/src/agents/buster_multi_agent.rs b/api/libs/agents/src/agents/buster_multi_agent.rs index 47e4ae5a6..3ea9de629 100644 --- a/api/libs/agents/src/agents/buster_multi_agent.rs +++ b/api/libs/agents/src/agents/buster_multi_agent.rs @@ -118,7 +118,7 @@ impl BusterMultiAgent { // Create agent, passing the provider let agent = Arc::new(Agent::new( - "o4-mini".to_string(), // Initial model (can be overridden by first mode) + "o3-mini".to_string(), // Initial model (can be overridden by first mode) user_id, session_id, "buster_multi_agent".to_string(), diff --git a/api/libs/agents/src/agents/modes/analysis.rs b/api/libs/agents/src/agents/modes/analysis.rs index 4b22c8be6..751bf2b63 100644 --- a/api/libs/agents/src/agents/modes/analysis.rs +++ b/api/libs/agents/src/agents/modes/analysis.rs @@ -30,7 +30,7 @@ pub fn get_configuration(agent_data: &ModeAgentData) -> ModeConfiguration { // Note: This prompt doesn't use {DATASETS} // 2. Define the model for this mode (Using default based on original MODEL = None) - let model = "o4-mini".to_string(); + let model = "o3-mini".to_string(); // 3. Define the tool loader closure let tool_loader: Box< diff --git a/api/libs/agents/src/agents/modes/follow_up_initialization.rs b/api/libs/agents/src/agents/modes/follow_up_initialization.rs index b42e63935..d394eb307 100644 --- a/api/libs/agents/src/agents/modes/follow_up_initialization.rs +++ b/api/libs/agents/src/agents/modes/follow_up_initialization.rs @@ -42,7 +42,7 @@ pub fn get_configuration(agent_data: &ModeAgentData) -> ModeConfiguration { .replace("{TODAYS_DATE}", &agent_data.todays_date); // 2. Define the model for this mode (Using a default, adjust if needed) - let model = "o4-mini".to_string(); // Assuming default based on original MODEL = None + let model = "o3-mini".to_string(); // Assuming default based on original MODEL = None // 3. Define the tool loader closure let tool_loader: Box) -> Pin> + Send>> + Send + Sync> = diff --git a/api/libs/agents/src/agents/modes/initialization.rs b/api/libs/agents/src/agents/modes/initialization.rs index d278af901..74e67b67c 100644 --- a/api/libs/agents/src/agents/modes/initialization.rs +++ b/api/libs/agents/src/agents/modes/initialization.rs @@ -26,8 +26,8 @@ pub fn get_configuration(agent_data: &ModeAgentData) -> ModeConfiguration { // 2. Define the model for this mode (Using a default, adjust if needed) // Since the original MODEL was None, we might use the agent's default - // or specify a standard one like "o4-mini". Let's use "o4-mini". - let model = "o4-mini".to_string(); + // or specify a standard one like "o3-mini". Let's use "o3-mini". + let model = "o3-mini".to_string(); // 3. Define the tool loader closure let tool_loader: Box< diff --git a/api/libs/agents/src/agents/modes/mod.rs b/api/libs/agents/src/agents/modes/mod.rs index b295f9b29..71e7913df 100644 --- a/api/libs/agents/src/agents/modes/mod.rs +++ b/api/libs/agents/src/agents/modes/mod.rs @@ -31,7 +31,7 @@ pub struct ModeAgentData { pub struct ModeConfiguration { /// The system prompt to use for the LLM call in this mode. pub prompt: String, - /// The specific LLM model identifier (e.g., "o4-mini") to use for this mode. + /// The specific LLM model identifier (e.g., "o3-mini") to use for this mode. pub model: String, /// An async function/closure responsible for clearing existing tools /// and loading the specific tools required for this mode onto the agent. diff --git a/api/libs/agents/src/agents/modes/planning.rs b/api/libs/agents/src/agents/modes/planning.rs index 3b5590085..7001a5d9c 100644 --- a/api/libs/agents/src/agents/modes/planning.rs +++ b/api/libs/agents/src/agents/modes/planning.rs @@ -28,7 +28,7 @@ pub fn get_configuration(agent_data: &ModeAgentData) -> ModeConfiguration { .replace("{DATASETS}", &agent_data.dataset_names.join(", ")); // 2. Define the model for this mode (Using default based on original MODEL = None) - let model = "o4-mini".to_string(); + let model = "o3-mini".to_string(); // 3. Define the tool loader closure let tool_loader: Box< diff --git a/api/libs/agents/src/tools/categories/file_tools/common.rs b/api/libs/agents/src/tools/categories/file_tools/common.rs index 25e457267..ac96af7cc 100644 --- a/api/libs/agents/src/tools/categories/file_tools/common.rs +++ b/api/libs/agents/src/tools/categories/file_tools/common.rs @@ -206,7 +206,7 @@ properties: description: Visualization settings (must include selectedChartType, columnLabelFormats, and ONE chart-specific block) allOf: # Base requirements for ALL chart types - $ref: '#/definitions/base_chart_config' - oneOf: # Specific block required based on type + oneOf: # Specific block required based on type - $ref: #/definitions/bar_line_chart_config - $ref: #/definitions/scatter_chart_config - $ref: #/definitions/pie_chart_config diff --git a/api/libs/agents/src/tools/categories/file_tools/filter_dashboards.rs b/api/libs/agents/src/tools/categories/file_tools/filter_dashboards.rs index 12530854a..fe4c5c07b 100644 --- a/api/libs/agents/src/tools/categories/file_tools/filter_dashboards.rs +++ b/api/libs/agents/src/tools/categories/file_tools/filter_dashboards.rs @@ -673,7 +673,7 @@ mod tests { fn test_tool_parameter_validation() { let tool = FilterDashboardsTool { agent: Arc::new(Agent::new( - "o4-mini".to_string(), + "o3-mini".to_string(), HashMap::new(), Uuid::new_v4(), Uuid::new_v4(), diff --git a/api/libs/handlers/src/lib.rs b/api/libs/handlers/src/lib.rs index 6ec40a8f5..83af35fa8 100644 --- a/api/libs/handlers/src/lib.rs +++ b/api/libs/handlers/src/lib.rs @@ -8,6 +8,7 @@ pub mod messages; pub mod metrics; pub mod organizations; pub mod search; +pub mod users; pub mod utils; // Re-export commonly used types and functions diff --git a/api/libs/handlers/src/users/invite_user_handler.rs b/api/libs/handlers/src/users/invite_user_handler.rs new file mode 100644 index 000000000..5374f9514 --- /dev/null +++ b/api/libs/handlers/src/users/invite_user_handler.rs @@ -0,0 +1,90 @@ +use anyhow::{Context, Result}; +use chrono::Utc; +use database::{ + self, + enums::{UserOrganizationRole, UserOrganizationStatus, SharingSetting}, + models::{User, UserToOrganization}, + schema::{users, users_to_organizations}, +}; +use diesel::prelude::*; +use diesel_async::{AsyncPgConnection, RunQueryDsl}; +use middleware::AuthenticatedUser; +use serde_json::json; +use uuid::Uuid; + +/// Invites multiple users by creating user records and adding them to the inviter's organization. +/// This function requires an active database transaction. +pub async fn invite_user_handler( + inviting_user: &AuthenticatedUser, + emails: Vec, + conn: &mut AsyncPgConnection, // Accept the connection directly +) -> Result<()> { + let organization_id = inviting_user + .organizations + .get(0) // Accessing the vector of organizations + .map(|m| m.id) // Use .id as confirmed by search + .context("Inviting user is not associated with any organization")?; + + let inviter_id = inviting_user.id; // For created_by/updated_by + let now = Utc::now(); + + for email in emails { + // 1. Generate ID and construct attributes first + let new_user_id = Uuid::new_v4(); + let user_email = email.clone(); // Clone email for ownership + let assigned_role = UserOrganizationRole::RestrictedQuerier; + let user_attributes = json!({ + "user_id": new_user_id.to_string(), + "user_email": user_email, + "organization_id": organization_id.to_string(), + "organization_role": format!("{:?}", assigned_role) // Use variable for role + }); + + // 2. Create User struct instance using the generated ID and attributes + let user_to_insert = User { + id: new_user_id, // Use the generated ID + email: email.clone(), // Use the original email variable again or the cloned one + name: None, + config: json!({}), + created_at: now, + updated_at: now, + attributes: user_attributes, // Use the constructed attributes + avatar_url: None, + }; + + // 3. Insert user + diesel::insert_into(users::table) + .values(&user_to_insert) + .execute(conn) + .await + .context("Failed to insert new user")?; + + // 4. Create UserToOrganization struct instance + let user_org_to_insert = UserToOrganization { + user_id: new_user_id, // Use the generated ID + organization_id, + role: assigned_role, // Use the role variable + sharing_setting: SharingSetting::None, // Default setting + edit_sql: false, // Default permission + upload_csv: false, // Default permission + export_assets: false, // Default permission + email_slack_enabled: false, // Default setting + created_at: now, + updated_at: now, + deleted_at: None, + created_by: inviter_id, + updated_by: inviter_id, + deleted_by: None, + status: UserOrganizationStatus::Active, // Default status + }; + + // 5. Insert user organization mapping + diesel::insert_into(users_to_organizations::table) + .values(&user_org_to_insert) + .execute(conn) + .await + .context("Failed to map user to organization")?; + } + + Ok(()) +} diff --git a/api/libs/handlers/src/users/mod.rs b/api/libs/handlers/src/users/mod.rs new file mode 100644 index 000000000..77f3c202b --- /dev/null +++ b/api/libs/handlers/src/users/mod.rs @@ -0,0 +1 @@ +pub mod invite_user_handler; \ No newline at end of file diff --git a/api/server/src/routes/rest/routes/users/invite_users.rs b/api/server/src/routes/rest/routes/users/invite_users.rs new file mode 100644 index 000000000..cdaab884f --- /dev/null +++ b/api/server/src/routes/rest/routes/users/invite_users.rs @@ -0,0 +1,20 @@ +use anyhow::Result; +use axum::{Extension, Json}; + +use crate::routes::rest::ApiResponse; +use axum::http::StatusCode; +use database::enums::UserOrganizationRole; +use middleware::AuthenticatedUser; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct InviteUsersRequest { + pub emails: Vec, +} + +pub async fn invite_users( + Extension(user): Extension, + Json(body): Json, +) -> Result, (StatusCode, &'static str)> { + Ok(ApiResponse::NoContent) +} diff --git a/api/server/src/routes/rest/routes/users/mod.rs b/api/server/src/routes/rest/routes/users/mod.rs index 3d8c84623..3be12e530 100644 --- a/api/server/src/routes/rest/routes/users/mod.rs +++ b/api/server/src/routes/rest/routes/users/mod.rs @@ -1,13 +1,13 @@ use axum::{ - routing::{get, put}, + routing::{get, post, put}, Router, }; - mod assets; mod favorites; mod get_user; mod get_user_by_id; +mod invite_users; mod update_user; pub fn router() -> Router { @@ -16,6 +16,7 @@ pub fn router() -> Router { .route("/:user_id", put(update_user::update_user)) .route("/:user_id", get(get_user_by_id::get_user_by_id)) .nest("/:user_id", assets::router()) - .nest("/favorites", favorites::router()), + .nest("/favorites", favorites::router()) + .route("/invite", post(invite_users::invite_users)), ) }