- New paths, removing compiling errors.
This commit is contained in:
parent
1a7d6ae0e2
commit
ac716981ec
106 changed files with 127 additions and 573 deletions
|
|
@ -1,6 +1,6 @@
|
|||
use crate::models::automation_model::{Automation, TriggerKind};
|
||||
use crate::services::script::ScriptService;
|
||||
use crate::services::state::AppState;
|
||||
use crate::basic::ScriptService;
|
||||
use crate::state::AppState;
|
||||
use chrono::Datelike;
|
||||
use chrono::Timelike;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::services::email::save_email_draft;
|
||||
use crate::services::email::{fetch_latest_sent_to, SaveDraftRequest};
|
||||
use crate::services::state::AppState;
|
||||
use crate::email::save_email_draft;
|
||||
use crate::email::{fetch_latest_sent_to, SaveDraftRequest};
|
||||
use crate::state::AppState;
|
||||
use rhai::Dynamic;
|
||||
use rhai::Engine;
|
||||
|
||||
|
|
@ -7,8 +7,8 @@ use std::fs;
|
|||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::services::state::AppState;
|
||||
use crate::services::utils;
|
||||
use crate::state::AppState;
|
||||
use crate::utils;
|
||||
|
||||
pub fn create_site_keyword(state: &AppState, engine: &mut Engine) {
|
||||
let state_clone = state.clone();
|
||||
|
|
@ -43,7 +43,7 @@ pub fn create_site_keyword(state: &AppState, engine: &mut Engine) {
|
|||
}
|
||||
|
||||
async fn create_site(
|
||||
config: &crate::services::config::AppConfig,
|
||||
config: &crate::config::AppConfig,
|
||||
alias: Dynamic,
|
||||
template_dir: Dynamic,
|
||||
prompt: Dynamic,
|
||||
|
|
@ -4,10 +4,10 @@ use rhai::Engine;
|
|||
use serde_json::{json, Value};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::services::state::AppState;
|
||||
use crate::services::utils;
|
||||
use crate::services::utils::row_to_json;
|
||||
use crate::services::utils::to_array;
|
||||
use crate::state::AppState;
|
||||
use crate::utils;
|
||||
use crate::utils::row_to_json;
|
||||
use crate::utils::to_array;
|
||||
|
||||
pub fn find_keyword(state: &AppState, engine: &mut Engine) {
|
||||
let db = state.db_custom.clone();
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::services::state::AppState;
|
||||
use crate::state::AppState;
|
||||
use log::info;
|
||||
use rhai::Dynamic;
|
||||
use rhai::Engine;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use log::info;
|
||||
|
||||
use crate::services::state::AppState;
|
||||
use crate::state::AppState;
|
||||
use reqwest::{self, Client};
|
||||
use rhai::{Dynamic, Engine};
|
||||
use scraper::{Html, Selector};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::services::{state::AppState, web_automation::BrowserPool};
|
||||
use crate::{state::AppState, web_automation::BrowserPool};
|
||||
use log::info;
|
||||
use rhai::{Dynamic, Engine};
|
||||
use std::error::Error;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use log::info;
|
||||
|
||||
use crate::services::{state::AppState, utils::call_llm};
|
||||
use crate::{state::AppState, utils::call_llm};
|
||||
use rhai::{Dynamic, Engine};
|
||||
|
||||
pub fn llm_keyword(state: &AppState, engine: &mut Engine) {
|
||||
|
|
@ -5,7 +5,7 @@ use serde_json::{json, Value};
|
|||
use sqlx::PgPool;
|
||||
|
||||
use crate::models::automation_model::TriggerKind;
|
||||
use crate::services::state::AppState;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub fn on_keyword(state: &AppState, engine: &mut Engine) {
|
||||
let db = state.db_custom.clone();
|
||||
|
|
@ -2,7 +2,7 @@ use log::info;
|
|||
use rhai::Dynamic;
|
||||
use rhai::Engine;
|
||||
|
||||
use crate::services::state::AppState;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub fn print_keyword(_state: &AppState, engine: &mut Engine) {
|
||||
// PRINT command
|
||||
|
|
@ -5,8 +5,8 @@ use serde_json::{json, Value};
|
|||
use sqlx::PgPool;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::services::state::AppState;
|
||||
use crate::services::utils;
|
||||
use crate::state::AppState;
|
||||
use crate::utils;
|
||||
|
||||
pub fn set_keyword(state: &AppState, engine: &mut Engine) {
|
||||
let db = state.db_custom.clone();
|
||||
|
|
@ -5,7 +5,7 @@ use serde_json::{json, Value};
|
|||
use sqlx::PgPool;
|
||||
|
||||
use crate::models::automation_model::TriggerKind;
|
||||
use crate::services::state::AppState;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub fn set_schedule_keyword(state: &AppState, engine: &mut Engine) {
|
||||
let db = state.db_custom.clone();
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::services::state::AppState;
|
||||
use crate::state::AppState;
|
||||
use log::info;
|
||||
use rhai::{Dynamic, Engine};
|
||||
use std::thread;
|
||||
|
|
@ -1,19 +1,21 @@
|
|||
use crate::services::keywords::create_draft::create_draft_keyword;
|
||||
use crate::services::keywords::create_site::create_site_keyword;
|
||||
use crate::services::keywords::find::find_keyword;
|
||||
use crate::services::keywords::first::first_keyword;
|
||||
use crate::services::keywords::last::last_keyword;
|
||||
use crate::services::keywords::format::format_keyword;
|
||||
use crate::services::keywords::for_next::for_keyword;
|
||||
use crate::services::keywords::get::get_keyword;
|
||||
use crate::services::keywords::get_website::get_website_keyword;
|
||||
use crate::services::keywords::llm_keyword::llm_keyword;
|
||||
use crate::services::keywords::on::on_keyword;
|
||||
use crate::services::keywords::print::print_keyword;
|
||||
use crate::services::keywords::set::set_keyword;
|
||||
use crate::services::keywords::set_schedule::set_schedule_keyword;
|
||||
use crate::services::keywords::wait::wait_keyword;
|
||||
use crate::services::state::AppState;
|
||||
mod keywords;
|
||||
|
||||
use self::keywords::create_draft::create_draft_keyword;
|
||||
use self::keywords::create_site::create_site_keyword;
|
||||
use self::keywords::find::find_keyword;
|
||||
use self::keywords::first::first_keyword;
|
||||
use self::keywords::for_next::for_keyword;
|
||||
use self::keywords::format::format_keyword;
|
||||
use self::keywords::get::get_keyword;
|
||||
use self::keywords::get_website::get_website_keyword;
|
||||
use self::keywords::last::last_keyword;
|
||||
use self::keywords::llm_keyword::llm_keyword;
|
||||
use self::keywords::on::on_keyword;
|
||||
use self::keywords::print::print_keyword;
|
||||
use self::keywords::set::set_keyword;
|
||||
use self::keywords::set_schedule::set_schedule_keyword;
|
||||
use self::keywords::wait::wait_keyword;
|
||||
use crate::shared::AppState;
|
||||
use log::info;
|
||||
use rhai::{Dynamic, Engine, EvalAltResult};
|
||||
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
pub mod channels;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use livekit::{DataPacketKind, Room, RoomOptions};
|
||||
|
|
@ -6,7 +8,7 @@ use std::collections::HashMap;
|
|||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
|
||||
use crate::services::shared::{BotResponse, UserMessage};
|
||||
use crate::shared::{BotResponse, UserMessage};
|
||||
|
||||
#[async_trait]
|
||||
pub trait ChannelAdapter: Send + Sync {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::services::{config::EmailConfig, state::AppState};
|
||||
use crate::{config::EmailConfig, state::AppState};
|
||||
use log::info;
|
||||
|
||||
use actix_web::error::ErrorInternalServerError;
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
|
||||
|
||||
use actix_web::{ web};
|
||||
use actix_web::web;
|
||||
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{post, HttpResponse};
|
||||
use minio::s3::builders::ObjectContent;
|
||||
use minio::s3::types::ToStream;
|
||||
use minio::s3::Client;
|
||||
use std::io::Write;
|
||||
use tempfile::NamedTempFile;
|
||||
use minio::s3::types::ToStream;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
use minio::s3::client::{Client as MinioClient, ClientBuilder as MinioClientBuilder};
|
||||
|
|
@ -16,18 +14,18 @@ use minio::s3::creds::StaticProvider;
|
|||
use minio::s3::http::BaseUrl;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::services::config::{AppConfig};
|
||||
use crate::services::state::AppState;
|
||||
use crate::config::AppConfig;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub async fn init_minio(config: &AppConfig) -> Result<MinioClient, minio::s3::error::Error> {
|
||||
let scheme = if config.minio.use_ssl { "https" } else { "http" };
|
||||
let scheme = if config.minio.use_ssl {
|
||||
"https"
|
||||
} else {
|
||||
"http"
|
||||
};
|
||||
let base_url = format!("{}://{}", scheme, config.minio.server);
|
||||
let base_url = BaseUrl::from_str(&base_url)?;
|
||||
let credentials = StaticProvider::new(
|
||||
&config.minio.access_key,
|
||||
&config.minio.secret_key,
|
||||
None,
|
||||
);
|
||||
let credentials = StaticProvider::new(&config.minio.access_key, &config.minio.secret_key, None);
|
||||
|
||||
let minio_client = MinioClientBuilder::new(base_url)
|
||||
.provider(Some(credentials))
|
||||
|
|
@ -104,8 +102,6 @@ pub async fn upload_file(
|
|||
)))
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[post("/files/list/{folder_path}")]
|
||||
pub async fn list_file(
|
||||
folder_path: web::Path<String>,
|
||||
|
|
@ -124,7 +120,7 @@ pub async fn list_file(
|
|||
.await;
|
||||
|
||||
let mut file_list = Vec::new();
|
||||
|
||||
|
||||
// Use StreamExt::next() to iterate through the stream
|
||||
while let Some(items) = objects_stream.next().await {
|
||||
match items {
|
||||
|
|
@ -132,11 +128,12 @@ pub async fn list_file(
|
|||
for item in result.contents {
|
||||
file_list.push(item.name);
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(actix_web::error::ErrorInternalServerError(
|
||||
format!("Failed to list files in MinIO: {}", e)
|
||||
));
|
||||
return Err(actix_web::error::ErrorInternalServerError(format!(
|
||||
"Failed to list files in MinIO: {}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ use langchain_rust::{
|
|||
template_fstring,
|
||||
};
|
||||
|
||||
use crate::services::{state::AppState, utils::azure_from_config};
|
||||
use crate::{state::AppState, utils::azure_from_config};
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct ChatRequest {
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
pub mod llm_generic;
|
||||
pub mod llm_local;
|
||||
pub mod llm_provider;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use langchain_rust::{
|
||||
|
|
@ -9,7 +13,7 @@ use serde_json::Value;
|
|||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::services::tools::ToolManager;
|
||||
use crate::tools::ToolManager;
|
||||
|
||||
#[async_trait]
|
||||
pub trait LLMProvider: Send + Sync {
|
||||
92
src/main.rs
92
src/main.rs
|
|
@ -1,3 +1,22 @@
|
|||
mod auth;
|
||||
mod automation;
|
||||
mod basic;
|
||||
mod bot;
|
||||
mod channels;
|
||||
mod chart;
|
||||
mod config;
|
||||
mod context;
|
||||
mod email;
|
||||
mod file;
|
||||
mod llm;
|
||||
mod models;
|
||||
mod org;
|
||||
mod session;
|
||||
mod shared;
|
||||
mod tools;
|
||||
mod web_automation;
|
||||
mod whatsapp;
|
||||
|
||||
use actix_web::middleware::Logger;
|
||||
use log::info;
|
||||
use qdrant_client::Qdrant;
|
||||
|
|
@ -7,27 +26,24 @@ use actix_web::{web, App, HttpServer};
|
|||
use dotenv::dotenv;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::services::auth::AuthService;
|
||||
use crate::services::automation::AutomationService;
|
||||
use crate::services::channels::ChannelAdapter;
|
||||
use crate::services::config::AppConfig;
|
||||
use crate::services::email::{
|
||||
use crate::bot::BotOrchestrator;
|
||||
use crate::channels::ChannelAdapter;
|
||||
use crate::config::AppConfig;
|
||||
use crate::email::{
|
||||
get_emails, get_latest_email_from, list_emails, save_click, save_draft, send_email,
|
||||
};
|
||||
use crate::services::file::{list_file, upload_file};
|
||||
use crate::services::llm_generic::generic_chat_completions;
|
||||
use crate::services::llm_local::{
|
||||
use crate::file::{list_file, upload_file};
|
||||
use crate::llm::llm_generic::generic_chat_completions;
|
||||
use crate::llm::llm_local::{
|
||||
chat_completions_local, embeddings_local, ensure_llama_servers_running,
|
||||
};
|
||||
use crate::services::orchestrator::BotOrchestrator;
|
||||
use crate::services::session::SessionManager;
|
||||
use crate::services::state::AppState;
|
||||
use crate::services::tools::{RedisToolExecutor, ToolManager};
|
||||
use crate::services::web_automation::{initialize_browser_pool, BrowserPool};
|
||||
use crate::services::whatsapp::WhatsAppAdapter;
|
||||
|
||||
mod models;
|
||||
mod services;
|
||||
use crate::session::SessionManager;
|
||||
use crate::state::AppState;
|
||||
use crate::tools::{RedisToolExecutor, ToolManager};
|
||||
use crate::web_automation::{initialize_browser_pool, BrowserPool};
|
||||
use crate::whatsapp::WhatsAppAdapter;
|
||||
use crate::AuthService;
|
||||
use crate::BotOrchestrator;
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
|
|
@ -81,28 +97,27 @@ async fn main() -> std::io::Result<()> {
|
|||
let session_manager = SessionManager::new(db.clone(), redis_conn.clone());
|
||||
let auth_service = AuthService::new(db.clone(), redis_conn.clone());
|
||||
|
||||
let llm_provider: Arc<dyn crate::services::llm_local::LLMProvider> =
|
||||
match std::env::var("LLM_PROVIDER")
|
||||
.unwrap_or("mock".to_string())
|
||||
.as_str()
|
||||
{
|
||||
"openai" => Arc::new(crate::services::llm_local::OpenAIClient::new(
|
||||
std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY required"),
|
||||
)),
|
||||
"anthropic" => Arc::new(crate::services::llm_local::AnthropicClient::new(
|
||||
std::env::var("ANTHROPIC_API_KEY").expect("ANTHROPIC_API_KEY required"),
|
||||
)),
|
||||
_ => Arc::new(crate::services::llm_local::MockLLMProvider::new()),
|
||||
};
|
||||
let llm_provider: Arc<dyn crate::llm_local::LLMProvider> = match std::env::var("LLM_PROVIDER")
|
||||
.unwrap_or("mock".to_string())
|
||||
.as_str()
|
||||
{
|
||||
"openai" => Arc::new(crate::llm_local::OpenAIClient::new(
|
||||
std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY required"),
|
||||
)),
|
||||
"anthropic" => Arc::new(crate::llm_local::AnthropicClient::new(
|
||||
std::env::var("ANTHROPIC_API_KEY").expect("ANTHROPIC_API_KEY required"),
|
||||
)),
|
||||
_ => Arc::new(crate::llm_local::MockLLMProvider::new()),
|
||||
};
|
||||
|
||||
let web_adapter = Arc::new(crate::services::channels::WebChannelAdapter::new());
|
||||
let voice_adapter = Arc::new(crate::services::channels::VoiceAdapter::new(
|
||||
let web_adapter = Arc::new(crate::channels::WebChannelAdapter::new());
|
||||
let voice_adapter = Arc::new(crate::channels::VoiceAdapter::new(
|
||||
std::env::var("LIVEKIT_URL").unwrap_or("ws://localhost:7880".to_string()),
|
||||
std::env::var("LIVEKIT_API_KEY").unwrap_or("dev".to_string()),
|
||||
std::env::var("LIVEKIT_API_SECRET").unwrap_or("secret".to_string()),
|
||||
));
|
||||
|
||||
let whatsapp_adapter = Arc::new(crate::services::whatsapp::WhatsAppAdapter::new(
|
||||
let whatsapp_adapter = Arc::new(crate::whatsapp::WhatsAppAdapter::new(
|
||||
std::env::var("META_ACCESS_TOKEN").unwrap_or("".to_string()),
|
||||
std::env::var("META_PHONE_NUMBER_ID").unwrap_or("".to_string()),
|
||||
std::env::var("META_WEBHOOK_VERIFY_TOKEN").unwrap_or("".to_string()),
|
||||
|
|
@ -111,7 +126,7 @@ async fn main() -> std::io::Result<()> {
|
|||
let tool_executor = Arc::new(
|
||||
RedisToolExecutor::new(
|
||||
redis_url.as_str(),
|
||||
web_adapter.clone() as Arc<dyn crate::services::channels::ChannelAdapter>,
|
||||
web_adapter.clone() as Arc<dyn crate::channels::ChannelAdapter>,
|
||||
db.clone(),
|
||||
redis_conn.clone(),
|
||||
)
|
||||
|
|
@ -216,8 +231,8 @@ async fn main() -> std::io::Result<()> {
|
|||
.service(get_emails)
|
||||
.service(list_emails)
|
||||
.service(send_email)
|
||||
.service(crate::services::orchestrator::chat_stream)
|
||||
.service(crate::services::orchestrator::chat)
|
||||
.service(crate::orchestrator::chat_stream)
|
||||
.service(crate::orchestrator::chat)
|
||||
.service(chat_completions_local)
|
||||
.service(save_draft)
|
||||
.service(generic_chat_completions)
|
||||
|
|
@ -232,6 +247,11 @@ async fn main() -> std::io::Result<()> {
|
|||
.service(services::orchestrator::get_sessions)
|
||||
.service(services::orchestrator::get_session_history)
|
||||
.service(services::orchestrator::index)
|
||||
.service(create_organization)
|
||||
.service(get_organization)
|
||||
.service(list_organizations)
|
||||
.service(update_organization)
|
||||
.service(delete_organization)
|
||||
})
|
||||
.bind((config.server.host.clone(), config.server.port))?
|
||||
.run()
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
pub mod automation_model;
|
||||
1
src/models/mod.rs
Normal file
1
src/models/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -1,10 +1,3 @@
|
|||
.service(create_organization)
|
||||
.service(get_organization)
|
||||
.service(list_organizations)
|
||||
.service(update_organization)
|
||||
.service(delete_organization)
|
||||
|
||||
|
||||
use actix_web::{web, HttpResponse, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
pub mod auth;
|
||||
pub mod automation;
|
||||
pub mod bot;
|
||||
pub mod channels;
|
||||
pub mod chart;
|
||||
pub mod config;
|
||||
pub mod context;
|
||||
pub mod email;
|
||||
pub mod file;
|
||||
pub mod keywords;
|
||||
pub mod llm;
|
||||
pub mod session;
|
||||
pub mod shared;
|
||||
pub mod tools;
|
||||
pub mod web_automation;
|
||||
pub mod whatsapp;
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
// .service(create_bot)
|
||||
// .service(get_bot)
|
||||
// .service(list_bots)
|
||||
// .service(update_bot)
|
||||
// .service(delete_bot)
|
||||
// .service(update_bot_status)
|
||||
// .service(execute_bot_command)
|
||||
|
||||
|
||||
|
||||
use crate::services::{config::BotConfig, state::AppState};
|
||||
use actix_web::{
|
||||
delete, get, post, put,
|
||||
web::{self, Data, Json, Path},
|
||||
HttpResponse, Responder, Result,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{postgres::PgQueryResult, FromRow, PgPool};
|
||||
use uuid::Uuid;
|
||||
|
||||
// 1. Core Data Structures
|
||||
|
||||
// 2. Request/Response DTOs
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CreateBotRequest {
|
||||
pub name: String,
|
||||
pub initial_config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UpdateBotRequest {
|
||||
pub name: Option<String>,
|
||||
pub status: Option<BotStatus>,
|
||||
pub config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BotResponse {
|
||||
pub bot_id: Uuid,
|
||||
pub name: String,
|
||||
pub status: BotStatus,
|
||||
pub created_at: chrono::DateTime<Utc>,
|
||||
pub updated_at: chrono::DateTime<Utc>,
|
||||
}
|
||||
|
||||
// 3. Helper Functions
|
||||
impl From<Bot> for BotResponse {
|
||||
fn from(bot: Bot) -> Self {
|
||||
BotResponse {
|
||||
bot_id: bot.bot_id,
|
||||
name: bot.name,
|
||||
status: bot.status,
|
||||
created_at: bot.created_at,
|
||||
updated_at: bot.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_bot(bot_id: Uuid, pool: &PgPool) -> Result<Bot, sqlx::Error> {
|
||||
sqlx::query_as::<_, Bot>("SELECT * FROM bots WHERE bot_id = $1")
|
||||
.bind(bot_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
// 4. API Endpoints
|
||||
#[post("/bots/create")]
|
||||
pub async fn create_bot(
|
||||
payload: Json<CreateBotRequest>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<impl Responder, actix_web::Error> {
|
||||
let new_bot = sqlx::query_as::<_, Bot>(
|
||||
r#"
|
||||
INSERT INTO bots (name, status, config)
|
||||
VALUES ($1, 'active', $2)
|
||||
RETURNING *
|
||||
"#,
|
||||
)
|
||||
.bind(&payload.name)
|
||||
.bind(&payload.initial_config)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to create bot: {}", e);
|
||||
actix_web::error::ErrorInternalServerError("Failed to create bot")
|
||||
})?;
|
||||
|
||||
Ok(HttpResponse::Created().json(BotResponse::from(new_bot)))
|
||||
}
|
||||
|
||||
#[get("/bots/{bot_id}")]
|
||||
pub async fn get_bot(
|
||||
path: Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<impl Responder, actix_web::Error> {
|
||||
let bot_id = path.into_inner();
|
||||
let bot = find_bot(bot_id, &state.db).await.map_err(|e| match e {
|
||||
sqlx::Error::RowNotFound => actix_web::error::ErrorNotFound("Bot not found"),
|
||||
_ => {
|
||||
log::error!("Failed to fetch bot: {}", e);
|
||||
actix_web::error::ErrorInternalServerError("Failed to fetch bot")
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(BotResponse::from(bot)))
|
||||
}
|
||||
|
||||
#[get("/bots")]
|
||||
pub async fn list_bots(state: Data<AppState>) -> Result<impl Responder, actix_web::Error> {
|
||||
let bots = sqlx::query_as::<_, Bot>("SELECT * FROM bots ORDER BY created_at DESC")
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to list bots: {}", e);
|
||||
actix_web::error::ErrorInternalServerError("Failed to list bots")
|
||||
})?;
|
||||
|
||||
let responses: Vec<BotResponse> = bots.into_iter().map(BotResponse::from).collect();
|
||||
Ok(HttpResponse::Ok().json(responses))
|
||||
}
|
||||
|
||||
#[put("/bots/{bot_id}")]
|
||||
pub async fn update_bot(
|
||||
path: Path<Uuid>,
|
||||
payload: Json<UpdateBotRequest>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<impl Responder, actix_web::Error> {
|
||||
let bot_id = path.into_inner();
|
||||
|
||||
let updated_bot = sqlx::query_as::<_, Bot>(
|
||||
r#"
|
||||
UPDATE bots
|
||||
SET
|
||||
name = COALESCE($1, name),
|
||||
status = COALESCE($2, status),
|
||||
config = COALESCE($3, config),
|
||||
updated_at = NOW()
|
||||
WHERE bot_id = $4
|
||||
RETURNING *
|
||||
"#,
|
||||
)
|
||||
.bind(&payload.name)
|
||||
.bind(&payload.status)
|
||||
.bind(&payload.config)
|
||||
.bind(bot_id)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
sqlx::Error::RowNotFound => actix_web::error::ErrorNotFound("Bot not found"),
|
||||
_ => {
|
||||
log::error!("Failed to update bot: {}", e);
|
||||
actix_web::error::ErrorInternalServerError("Failed to update bot")
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(BotResponse::from(updated_bot)))
|
||||
}
|
||||
|
||||
#[delete("/bots/{bot_id}")]
|
||||
pub async fn delete_bot(
|
||||
path: Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<impl Responder, actix_web::Error> {
|
||||
let bot_id = path.into_inner();
|
||||
|
||||
let result = sqlx::query("DELETE FROM bots WHERE bot_id = $1")
|
||||
.bind(bot_id)
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to delete bot: {}", e);
|
||||
actix_web::error::ErrorInternalServerError("Failed to delete bot")
|
||||
})?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
return Err(actix_web::error::ErrorNotFound("Bot not found"));
|
||||
}
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[put("/bots/{bot_id}/status")]
|
||||
pub async fn update_bot_status(
|
||||
path: Path<Uuid>,
|
||||
new_status: Json<BotStatus>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<impl Responder, actix_web::Error> {
|
||||
let bot_id = path.into_inner();
|
||||
|
||||
let updated_bot = sqlx::query_as::<_, Bot>(
|
||||
"UPDATE bots SET status = $1, updated_at = NOW() WHERE bot_id = $2 RETURNING *",
|
||||
)
|
||||
.bind(new_status.into_inner())
|
||||
.bind(bot_id)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
sqlx::Error::RowNotFound => actix_web::error::ErrorNotFound("Bot not found"),
|
||||
_ => {
|
||||
log::error!("Failed to update bot status: {}", e);
|
||||
actix_web::error::ErrorInternalServerError("Failed to update bot status")
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(BotResponse::from(updated_bot)))
|
||||
}
|
||||
|
||||
#[post("/bots/{bot_id}/execute")]
|
||||
pub async fn execute_bot_command(
|
||||
path: Path<Uuid>,
|
||||
command: Json<serde_json::Value>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<impl Responder, actix_web::Error> {
|
||||
let bot_id = path.into_inner();
|
||||
|
||||
// Verify bot exists
|
||||
let _ = find_bot(bot_id, &state.db).await.map_err(|e| match e {
|
||||
sqlx::Error::RowNotFound => actix_web::error::ErrorNotFound("Bot not found"),
|
||||
_ => {
|
||||
log::error!("Failed to fetch bot: {}", e);
|
||||
actix_web::error::ErrorInternalServerError("Failed to fetch bot")
|
||||
}
|
||||
})?;
|
||||
|
||||
// Here you would implement your bot execution logic
|
||||
// For now, we'll just echo back the command
|
||||
Ok(HttpResponse::Ok().json(json!({
|
||||
"bot_id": bot_id,
|
||||
"command": command,
|
||||
"result": "Command executed successfully (simulated)"
|
||||
})))
|
||||
}
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
use actix_web::{web, HttpResponse, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct ChatRequest {
|
||||
input: String,
|
||||
context: Option<AppContext>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct AppContext {
|
||||
view_type: Option<String>,
|
||||
email_context: Option<EmailContext>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct EmailContext {
|
||||
id: String,
|
||||
subject: String,
|
||||
labels: Vec<String>,
|
||||
from: Option<String>,
|
||||
to: Option<Vec<String>>,
|
||||
body: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct ChatResponse {
|
||||
response: String,
|
||||
tool_calls: Option<Vec<ToolCall>>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct ToolCall {
|
||||
tool_name: String,
|
||||
parameters: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct ToolDefinition {
|
||||
name: String,
|
||||
description: String,
|
||||
parameters: serde_json::Value,
|
||||
}
|
||||
|
||||
#[actix_web::post("/chat")]
|
||||
pub async fn chat(
|
||||
web::Json(request): web::Json<ChatRequest>,
|
||||
state: web::Data<AppState>,
|
||||
) -> Result<HttpResponse> {
|
||||
let azure_config = from_config(&state.config.clone().unwrap().ai);
|
||||
let open_ai = OpenAI::new(azure_config);
|
||||
|
||||
// Define available tools based on context
|
||||
let tools = get_available_tools(&request.context);
|
||||
|
||||
// Build the prompt with context and available tools
|
||||
let system_prompt = build_system_prompt(&request.context, &tools);
|
||||
let user_message = format!("{}\n\nUser input: {}", system_prompt, request.input);
|
||||
|
||||
let response = match open_ai.invoke(&user_message).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
error!("Error invoking API: {}", err);
|
||||
return Err(actix_web::error::ErrorInternalServerError(
|
||||
"Failed to invoke OpenAI API",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Parse the response for tool calls
|
||||
let tool_calls = parse_tool_calls(&response);
|
||||
|
||||
let chat_response = ChatResponse {
|
||||
response,
|
||||
tool_calls,
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().json(chat_response))
|
||||
}
|
||||
|
||||
fn get_available_tools(context: &Option<AppContext>) -> Vec<ToolDefinition> {
|
||||
let mut tools = Vec::new();
|
||||
|
||||
if let Some(ctx) = context {
|
||||
if let Some(view_type) = &ctx.view_type {
|
||||
match view_type.as_str() {
|
||||
"email" => {
|
||||
tools.push(ToolDefinition {
|
||||
name: "replyEmail".to_string(),
|
||||
description: "Reply to the current email with generated content".to_string(),
|
||||
parameters: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "The reply content to send"
|
||||
}
|
||||
},
|
||||
"required": ["content"]
|
||||
}),
|
||||
});
|
||||
|
||||
tools.push(ToolDefinition {
|
||||
name: "forwardEmail".to_string(),
|
||||
description: "Forward the current email to specified recipients".to_string(),
|
||||
parameters: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recipients": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Email addresses to forward to"
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "Additional message to include"
|
||||
}
|
||||
},
|
||||
"required": ["recipients"]
|
||||
}),
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tools
|
||||
}
|
||||
|
||||
fn build_system_prompt(context: &Option<AppContext>, tools: &[ToolDefinition]) -> String {
|
||||
let mut prompt = String::new();
|
||||
|
||||
if let Some(ctx) = context {
|
||||
if let Some(view_type) = &ctx.view_type {
|
||||
match view_type.as_str() {
|
||||
"email" => {
|
||||
if let Some(email_ctx) = &ctx.email_context {
|
||||
prompt.push_str(&format!(
|
||||
"You are an email assistant. Current email context:\n\
|
||||
Subject: {}\n\
|
||||
ID: {}\n\
|
||||
Labels: {:?}\n\n",
|
||||
email_ctx.subject, email_ctx.id, email_ctx.labels
|
||||
));
|
||||
|
||||
if let Some(from) = &email_ctx.from {
|
||||
prompt.push_str(&format!("From: {}\n", from));
|
||||
}
|
||||
|
||||
if let Some(body) = &email_ctx.body {
|
||||
prompt.push_str(&format!("Body: {}\n", body));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !tools.is_empty() {
|
||||
prompt.push_str("\nAvailable tools:\n");
|
||||
for tool in tools {
|
||||
prompt.push_str(&format!(
|
||||
"- {}: {}\n Parameters: {}\n\n",
|
||||
tool.name, tool.description, tool.parameters
|
||||
));
|
||||
}
|
||||
|
||||
prompt.push_str(
|
||||
"If you need to use a tool, respond with:\n\
|
||||
TOOL_CALL: tool_name\n\
|
||||
PARAMETERS: {json_parameters}\n\
|
||||
RESPONSE: your_response_text\n\n\
|
||||
Otherwise, just provide a normal response.\n"
|
||||
);
|
||||
}
|
||||
|
||||
prompt
|
||||
}
|
||||
|
||||
fn parse_tool_calls(response: &str) -> Option<Vec<ToolCall>> {
|
||||
if !response.contains("TOOL_CALL:") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut tool_calls = Vec::new();
|
||||
let lines: Vec<&str> = response.lines().collect();
|
||||
|
||||
let mut i = 0;
|
||||
while i < lines.len() {
|
||||
if lines[i].starts_with("TOOL_CALL:") {
|
||||
let tool_name = lines[i].replace("TOOL_CALL:", "").trim().to_string();
|
||||
|
||||
// Look for parameters in the next line
|
||||
if i + 1 < lines.len() && lines[i + 1].starts_with("PARAMETERS:") {
|
||||
let params_str = lines[i + 1].replace("PARAMETERS:", "").trim();
|
||||
|
||||
if let Ok(parameters) = serde_json::from_str::<serde_json::Value>(params_str) {
|
||||
tool_calls.push(ToolCall {
|
||||
tool_name,
|
||||
parameters,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if tool_calls.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(tool_calls)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::services::shared::shared::UserSession;
|
||||
use crate::shared::shared::UserSession;
|
||||
use sqlx::Row;
|
||||
|
||||
use redis::{aio::Connection as ConnectionManager, AsyncCommands};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue