2025-07-20 00:03:37 -03:00
use rhai ::Dynamic ;
use rhai ::Engine ;
2025-07-31 00:36:10 -03:00
use std ::error ::Error ;
2025-07-21 12:16:17 -03:00
use std ::fs ;
2025-08-02 00:07:54 -03:00
use std ::path ::{ PathBuf } ;
use std ::io ::Read ;
2025-07-20 00:03:37 -03:00
use crate ::services ::state ::AppState ;
2025-07-31 00:36:10 -03:00
use crate ::services ::utils ;
2025-07-20 00:03:37 -03:00
2025-07-31 00:36:10 -03:00
pub fn create_site_keyword ( state : & AppState , engine : & mut Engine ) {
let state_clone = state . clone ( ) ;
2025-07-20 00:03:37 -03:00
engine
. register_custom_syntax (
& [
2025-08-02 00:07:54 -03:00
" CREATE_SITE " , " $expr$ " , " , " , " $expr$ " , " , " , " $expr$ " ,
2025-07-20 00:03:37 -03:00
] ,
2025-08-02 00:07:54 -03:00
true ,
2025-07-21 12:16:17 -03:00
move | context , inputs | {
2025-08-02 00:07:54 -03:00
if inputs . len ( ) < 3 {
2025-07-20 00:03:37 -03:00
return Err ( " Not enough arguments for CREATE SITE " . into ( ) ) ;
}
2025-08-02 00:07:54 -03:00
let alias = context . eval_expression_tree ( & inputs [ 0 ] ) ? ;
let template_dir = context . eval_expression_tree ( & inputs [ 1 ] ) ? ;
let prompt = context . eval_expression_tree ( & inputs [ 2 ] ) ? ;
let config = state_clone . config . as_ref ( ) . expect ( " Config must be initialized " ) . clone ( ) ;
let fut = create_site ( & config , alias , template_dir , prompt ) ;
2025-07-31 00:36:10 -03:00
let result =
tokio ::task ::block_in_place ( | | tokio ::runtime ::Handle ::current ( ) . block_on ( fut ) )
2025-08-02 00:07:54 -03:00
. map_err ( | e | format! ( " Site creation failed: {} " , e ) ) ? ;
2025-07-20 00:03:37 -03:00
2025-07-31 00:36:10 -03:00
Ok ( Dynamic ::from ( result ) )
2025-07-20 00:03:37 -03:00
} ,
)
. unwrap ( ) ;
2025-07-31 00:36:10 -03:00
}
async fn create_site (
2025-08-02 00:07:54 -03:00
config : & crate ::services ::config ::AppConfig ,
alias : Dynamic ,
template_dir : Dynamic ,
2025-07-31 00:36:10 -03:00
prompt : Dynamic ,
2025-08-02 00:07:54 -03:00
) -> Result < String , Box < dyn Error + Send + Sync > > {
// Convert paths to platform-specific format
let base_path = PathBuf ::from ( & config . site_path ) ;
let template_path = base_path . join ( template_dir . to_string ( ) ) ;
let alias_path = base_path . join ( alias . to_string ( ) ) ;
// Create destination directory
fs ::create_dir_all ( & alias_path ) . map_err ( | e | e . to_string ( ) ) ? ;
2025-07-31 00:36:10 -03:00
2025-08-02 00:07:54 -03:00
// Process all HTML files in template directory
let mut combined_content = String ::new ( ) ;
for entry in fs ::read_dir ( & template_path ) . map_err ( | e | e . to_string ( ) ) ? {
let entry = entry . map_err ( | e | e . to_string ( ) ) ? ;
let path = entry . path ( ) ;
if path . extension ( ) . map_or ( false , | ext | ext = = " html " ) {
let mut file = fs ::File ::open ( & path ) . map_err ( | e | e . to_string ( ) ) ? ;
let mut contents = String ::new ( ) ;
file . read_to_string ( & mut contents ) . map_err ( | e | e . to_string ( ) ) ? ;
combined_content . push_str ( & contents ) ;
combined_content . push_str ( " \n \n --- TEMPLATE SEPARATOR --- \n \n " ) ;
}
}
2025-07-31 00:36:10 -03:00
2025-08-02 00:07:54 -03:00
// Combine template content with prompt
let full_prompt = format! (
" TEMPLATE FILES: \n {} \n \n PROMPT: {} \n \n Generate a new HTML file cloning all previous TEMPLATE (keeping only the local _assets libraries use, no external resources), but turning this into this prompt: " ,
combined_content ,
prompt . to_string ( )
) ;
2025-07-31 00:36:10 -03:00
2025-08-02 00:07:54 -03:00
// Call LLM with the combined prompt
println! ( " Asking LLM to create site. " ) ;
let llm_result = utils ::call_llm ( & full_prompt , & config . ai ) . await ? ;
2025-07-31 00:36:10 -03:00
2025-08-02 00:07:54 -03:00
// Write the generated HTML file
let index_path = alias_path . join ( " index.html " ) ;
2025-07-31 00:36:10 -03:00
fs ::write ( index_path , llm_result ) . map_err ( | e | e . to_string ( ) ) ? ;
2025-08-02 00:07:54 -03:00
println! ( " Site created at: {} " , alias_path . display ( ) ) ;
Ok ( alias_path . to_string_lossy ( ) . into_owned ( ) )
}