use anyhow::{Context, Result};
use dialoguer::{theme::ColorfulTheme, Input, Select};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tracing::{error, info, instrument, warn};
use crate::{
providers::{
codex::CodexProvider, openai::OpenAIProvider, LLMProvider, ProviderRegistry,
WebSearchProvider,
},
stages::{CrateContext, GenerationPipeline},
utils::{
cache::CacheManager,
config::{AppConfig, CodexConfig, ConfigManager, OpenAIConfig, OpenCratesConfig},
fastapi_integration::FastApiConfig,
health::{HealthInfo, HealthManager},
logging,
metrics::MetricRegistry,
project::ProjectAnalysis,
templates::TemplateManager,
},
};
pub use crate::utils::templates::{CrateSpec, CrateType};
use serde::{Deserialize, Serialize};
pub mod state;
pub use crate::utils::project::ProjectAnalysis as AnalysisResult;
pub use state::AppState;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrateSummary {
pub name: String,
pub description: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestResult {
pub passed: bool,
pub output: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrateMetadata {
pub name: String,
pub description: String,
pub version: String,
pub authors: Vec<String>,
pub license: Option<String>,
pub crate_type: CrateType,
pub dependencies: std::collections::HashMap<String, String>,
pub dev_dependencies: std::collections::HashMap<String, String>,
pub features: Vec<String>,
pub keywords: Vec<String>,
pub categories: Vec<String>,
pub repository: Option<String>,
pub homepage: Option<String>,
pub documentation: Option<String>,
pub readme: Option<String>,
pub rust_version: Option<String>,
pub edition: String,
pub publish: bool,
pub author: Option<String>,
pub template: Option<String>,
}
#[derive(Clone)]
pub struct OpenCrates {
pub config: Arc<OpenCratesConfig>,
pub providers: Arc<ProviderRegistry>,
pub template_manager: Arc<TemplateManager>,
pub metric_registry: Arc<MetricRegistry>,
pub health_manager: Arc<HealthManager>,
pub config_manager: Arc<ConfigManager>,
pub websearch_provider: Arc<WebSearchProvider>,
pub cache_manager: Arc<CacheManager>,
}
impl Default for OpenCratesConfig {
fn default() -> Self {
OpenCratesConfig {
environment: "development".to_string(),
debug: true,
name: "OpenCrates".to_string(),
version: "3.0.0".to_string(),
description: "Default OpenCrates configuration".to_string(),
openai_api_key: std::env::var("OPENAI_API_KEY").unwrap_or_default(),
default_model: "gpt-4".to_string(),
default_output_dir: ".".to_string(),
include_tests_by_default: true,
include_docs_by_default: true,
database: crate::utils::config::DatabaseConfig {
url: "sqlite:./opencrates.db".to_string(),
pool_size: Some(10),
timeout: Some(30),
enable_logging: false,
run_migrations: true,
max_connections: 10,
min_connections: 1,
connection_timeout: Duration::from_secs(30),
idle_timeout: Duration::from_secs(30),
max_lifetime: Duration::from_secs(30),
migration_path: "migrations".to_string(),
},
redis: crate::utils::config::RedisConfig {
url: "redis://127.0.0.1/".to_string(),
max_connections: 10,
connection_timeout: Duration::from_secs(5),
response_timeout: Duration::from_secs(5),
retry_attempts: 3,
retry_delay: Duration::from_millis(500),
enable_cluster: false,
cluster_nodes: vec![],
},
server: crate::utils::config::ServerConfig {
host: "localhost".to_string(),
port: 8080,
workers: num_cpus::get(),
max_connections: Some(1000),
timeout: Some(30),
cors_origins: vec!["*".to_string()],
rate_limit: crate::utils::config::RateLimitConfig {
requests_per_minute: 100,
enabled: true,
burst_size: 20,
},
enable_swagger: true,
enable_metrics: true,
enable_health_checks: true,
enable_tracing: true,
log_level: Some("info".to_string()),
enable_cors: true,
enable_compression: true,
max_payload_size: Some(10 * 1024 * 1024), keep_alive: 60,
request_timeout: 30,
body_limit: "10MB".to_string(),
enable_request_id: true,
cors: crate::utils::config::CorsConfig {
allow_origins: vec!["*".to_string()],
allow_methods: vec!["GET".to_string(), "POST".to_string()],
allow_headers: vec!["Content-Type".to_string()],
expose_headers: vec![],
max_age: 3600,
allow_credentials: false,
},
tls: crate::utils::config::TlsConfig {
enabled: false,
cert_path: String::new(),
key_path: String::new(),
},
},
metrics: crate::utils::config::MetricsConfig {
enabled: true,
port: 9090,
path: "/metrics".to_string(),
include_golang_metrics: false,
include_process_metrics: true,
},
logging: crate::utils::config::LoggingConfig {
level: "info".to_string(),
format: "pretty".to_string(),
enable_json: false,
enable_timestamps: true,
enable_file_info: true,
enable_thread_ids: true,
outputs: crate::utils::config::LoggingOutputs {
stdout: true,
file: None,
syslog: false,
},
rotation: crate::utils::config::LogRotationConfig {
enabled: false,
max_size: "100MB".to_string(),
max_age: 7,
max_backups: 10,
compress: false,
},
},
tracing: crate::utils::config::TracingConfig {
enabled: false,
service_name: "opencrates".to_string(),
endpoint: String::new(),
sample_rate: 1.0,
propagation: "w3c".to_string(),
},
cache: crate::utils::config::CacheConfig::default(),
search: crate::utils::config::SearchConfig {
enabled: true,
provider: "duckduckgo".to_string(),
timeout: 5,
max_results: 10,
safe_search: true,
region: "us-en".to_string(),
cache: crate::utils::config::SearchCacheConfig {
enabled: true,
ttl: 3600,
},
},
ai: crate::utils::config::AiConfig::default(),
features: crate::utils::config::FeaturesConfig {
enable_web_ui: true,
enable_api: true,
enable_cli: true,
enable_webhooks: false,
enable_notifications: false,
enable_analytics: false,
},
security: crate::utils::config::SecurityConfig {
enable_auth: false,
enable_api_keys: false,
enable_rate_limiting: true,
enable_ip_whitelist: false,
ip_whitelist: vec![],
jwt: crate::utils::config::JwtConfig {
secret: "default-secret".to_string(),
expiration: 3600,
refresh_expiration: 86400,
},
},
storage: crate::utils::config::StorageConfig {
backend: "file".to_string(),
path: "storage".to_string(),
max_file_size: "1GB".to_string(),
allowed_extensions: vec![],
s3: crate::utils::config::S3Config {
enabled: false,
bucket: String::new(),
region: String::new(),
access_key: String::new(),
secret_key: String::new(),
},
},
notifications: crate::utils::config::NotificationsConfig {
enabled: false,
providers: vec![],
},
webhooks: crate::utils::config::WebhooksConfig {
enabled: false,
endpoints: vec![],
timeout: 10,
retry_attempts: 3,
},
monitoring: crate::utils::config::MonitoringConfig {
health_check_interval: 60,
enable_profiling: false,
enable_debugging: false,
enabled: true,
endpoint: Some("/metrics".to_string()),
interval: Some(15),
retention: Some(3600),
alert_thresholds: Some(HashMap::new()),
},
experimental: crate::utils::config::ExperimentalConfig {
enable_wasm_plugins: false,
enable_gpu_acceleration: false,
enable_distributed_cache: false,
},
health: crate::utils::config::HealthConfig::default(),
templates: crate::utils::config::TemplatesConfig {
directory: "templates".to_string(),
custom_directory: String::new(),
cache_compiled: true,
auto_reload: false,
},
codex: crate::utils::config::CodexConfig::default(),
}
}
}
impl OpenCrates {
pub async fn new() -> Result<Self> {
let config = OpenCratesConfig::default();
Self::new_with_config(config).await
}
pub async fn new_with_config(config: OpenCratesConfig) -> Result<Self> {
info!("Initializing OpenCrates with custom configuration");
let mut providers = ProviderRegistry::new();
if !config.openai_api_key.is_empty() {
let openai_config = config.to_openai_config();
let openai_provider = OpenAIProvider::new_with_config(&openai_config).await?;
providers.register("openai", Box::new(openai_provider))?;
}
let codex_provider = CodexProvider::new(config.codex.clone()).await?;
providers.register("codex", Box::new(codex_provider))?;
let template_manager = Arc::new(TemplateManager::new().await?);
let metric_registry = Arc::new(MetricRegistry::new());
let health_manager = Arc::new(HealthManager::new().await?);
let config_manager = Arc::new(ConfigManager::new(config.clone())?);
let websearch_config = crate::providers::websearch::WebSearchConfig::default();
let websearch_provider = Arc::new(WebSearchProvider::with_config(websearch_config));
let cache_manager = Arc::new(CacheManager::new());
Ok(Self {
config: Arc::new(config),
providers: Arc::new(providers),
template_manager,
metric_registry,
health_manager,
config_manager,
websearch_provider,
cache_manager,
})
}
pub async fn analyze_crate(&self, path: &std::path::Path) -> Result<ProjectAnalysis> {
info!("Analyzing crate at path: {:?}", path);
let analyzer = crate::utils::project::ProjectAnalyzer::new();
analyzer.analyze(path).await
}
pub async fn health_check(&self) -> Result<HealthInfo> {
Ok(self.health_manager.get_health_info().await)
}
pub async fn get_config_for_server(&self) -> Result<OpenCratesConfig> {
Ok(self.config.as_ref().clone())
}
pub async fn get_system_status(&self) -> Result<crate::utils::health::SystemStatus> {
let health_info = self.health_check().await?;
let healthy = health_info.overall_status == crate::utils::health::HealthStatus::Healthy;
let data = serde_json::json!({
"status": health_info.overall_status.to_string(),
"uptime": health_info.uptime.as_secs(),
"checks": health_info.checks
});
if healthy {
Ok(crate::utils::health::SystemStatus::healthy(data))
} else {
Ok(crate::utils::health::SystemStatus::unhealthy())
}
}
pub async fn generate_crate(
&self,
name: &str,
description: &str,
features: Vec<String>,
output_path: PathBuf,
) -> Result<()> {
self.template_manager
.generate_crate(name, description, features, output_path, None)
.await
}
pub async fn optimize_crate(&self, path: PathBuf) -> Result<()> {
info!("Optimizing crate at {:?}", path);
Ok(())
}
pub async fn search_crates(&self, term: String, limit: usize) -> Result<Vec<CrateSummary>> {
info!("Searching for crates with term: {}, limit: {}", term, limit);
Ok(vec![CrateSummary {
name: "example-crate".to_string(),
description: "An example crate".to_string(),
version: "0.1.0".to_string(),
}])
}
pub async fn test_crate(&self, path: PathBuf, quick: bool) -> Result<TestResult> {
info!("Testing crate at {:?}, quick: {}", path, quick);
Ok(TestResult {
passed: true,
output: "All tests passed".to_string(),
})
}
pub async fn preview_crate_generation(&self, spec: &CrateSpec) -> Result<String> {
info!("Previewing crate generation for: {}", spec.name);
Ok(format!("Preview for crate: {}", spec.name))
}
pub async fn init_project(&self, path: PathBuf) -> Result<()> {
info!("Initializing project at {:?}", path);
std::fs::create_dir_all(&path).context("Failed to create project directory")?;
let config_path = path.join("config.toml");
let config_content = r#"# OpenCrates Project Configuration
[project]
name = "my-project"
version = "0.1.0"
description = "A new OpenCrates project"
[opencrates]
environment = "development"
debug = true
[ai]
provider = "openai"
model = "gpt-4o"
temperature = 0.7
max_tokens = 4096
"#;
std::fs::write(&config_path, config_content).context("Failed to write config.toml")?;
info!("Created config.toml at {:?}", config_path);
Ok(())
}
pub async fn get_default_ai_model_name(&self) -> String {
"gpt-4".to_string()
}
#[must_use]
pub fn metric_registry(&self) -> Arc<MetricRegistry> {
self.metric_registry.clone()
}
#[must_use]
pub fn providers(&self) -> Arc<ProviderRegistry> {
self.providers.clone()
}
#[must_use]
pub fn websearch_provider(&self) -> Arc<WebSearchProvider> {
self.websearch_provider.clone()
}
#[must_use]
pub fn get_health_manager(&self) -> Option<Arc<HealthManager>> {
Some(self.health_manager.clone())
}
}
impl std::fmt::Debug for OpenCrates {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OpenCrates")
.field("config", &"<Arc<OpenCratesConfig>>")
.field("providers", &"<Arc<ProviderRegistry>>")
.field("template_manager", &"<Arc<TemplateManager>>")
.field("metric_registry", &"<Arc<MetricRegistry>>")
.field("health_manager", &"<Arc<HealthManager>>")
.field("config_manager", &"<Arc<ConfigManager>>")
.field("websearch_provider", &"<Arc<WebSearchProvider>>")
.field("cache_manager", &"<Arc<CacheManager>>")
.finish()
}
}