1use crate::templates::{InfraTemplates, MetaTemplates, RustTemplates};
19use anyhow::{Context, Result};
20use std::path::Path;
21
22#[allow(dead_code)]
24const MECHA10_JSON_TEMPLATE: &str = include_str!("../../templates/config/mecha10.json.template");
25const MODEL_JSON_TEMPLATE: &str = include_str!("../../templates/config/model.json.template");
26const ENVIRONMENT_JSON_TEMPLATE: &str = include_str!("../../templates/config/environment.json.template");
27
28const BEHAVIOR_EXECUTOR_CONFIG_DEV: &str = include_str!("../../templates/config/nodes/behavior-executor/config.json");
30const IMAGE_CLASSIFIER_CONFIG_DEV: &str = include_str!("../../templates/config/nodes/image-classifier/config.json");
31const LLM_COMMAND_CONFIG_DEV: &str = include_str!("../../templates/config/nodes/llm-command/config.json");
32const OBJECT_DETECTOR_CONFIG_DEV: &str = include_str!("../../templates/config/nodes/object-detector/config.json");
33const SIMULATION_BRIDGE_CONFIG_DEV: &str = include_str!("../../templates/config/nodes/simulation-bridge/config.json");
34const WEBSOCKET_BRIDGE_CONFIG_DEV: &str = include_str!("../../templates/config/nodes/websocket-bridge/config.json");
35
36const SIMULATION_CONFIG: &str = include_str!("../../templates/config/simulation/config.json");
38
39const AIKO_IMAGE: &[u8] = include_bytes!("../../templates/assets/images/aiko.jpg");
41const PHOEBE_IMAGE: &[u8] = include_bytes!("../../templates/assets/images/phoebe.jpg");
42const CAT_TOWER_IMAGE: &[u8] = include_bytes!("../../templates/assets/cat_tower.jpg");
43
44const BEHAVIOR_BASIC_NAVIGATION: &str = include_str!("../../templates/behaviors/basic_navigation.json");
46const BEHAVIOR_IDLE_WANDER: &str = include_str!("../../templates/behaviors/idle_wander.json");
47const BEHAVIOR_OBSTACLE_AVOIDANCE: &str = include_str!("../../templates/behaviors/obstacle_avoidance.json");
48const BEHAVIOR_PATROL_SIMPLE: &str = include_str!("../../templates/behaviors/patrol_simple.json");
49
50pub struct ProjectTemplateService {
64 rust: RustTemplates,
65 infra: InfraTemplates,
66 meta: MetaTemplates,
67}
68
69impl ProjectTemplateService {
70 pub fn new() -> Self {
72 Self {
73 rust: RustTemplates::new(),
74 infra: InfraTemplates::new(),
75 meta: MetaTemplates::new(),
76 }
77 }
78
79 pub async fn create_readme(&self, path: &Path, project_name: &str) -> Result<()> {
81 self.meta.create_readme(path, project_name).await
82 }
83
84 pub async fn create_gitignore(&self, path: &Path) -> Result<()> {
86 self.meta.create_gitignore(path).await
87 }
88
89 pub async fn create_cargo_config(&self, path: &Path, framework_path: &str) -> Result<()> {
94 self.rust.create_cargo_config(path, framework_path).await
95 }
96
97 pub async fn create_cargo_toml(&self, path: &Path, project_name: &str, dev: bool) -> Result<()> {
105 self.rust.create_cargo_toml(path, project_name, dev).await
106 }
107
108 pub async fn create_main_rs(&self, path: &Path, project_name: &str) -> Result<()> {
110 self.rust.create_main_rs(path, project_name).await
111 }
112
113 pub async fn create_build_rs(&self, path: &Path) -> Result<()> {
118 self.rust.create_build_rs(path).await
119 }
120
121 pub async fn create_env_example(
129 &self,
130 path: &Path,
131 project_name: &str,
132 framework_path: Option<String>,
133 ) -> Result<()> {
134 self.infra.create_env_example(path, project_name, framework_path).await
135 }
136
137 pub async fn create_rustfmt_toml(&self, path: &Path) -> Result<()> {
139 self.rust.create_rustfmt_toml(path).await
140 }
141
142 pub async fn create_docker_compose(&self, path: &Path, project_name: &str) -> Result<()> {
152 self.infra.create_docker_compose(path, project_name).await
153 }
154
155 pub async fn create_package_json(&self, path: &Path, project_name: &str) -> Result<()> {
167 self.meta.create_package_json(path, project_name).await
168 }
169
170 pub async fn create_requirements_txt(&self, path: &Path) -> Result<()> {
179 self.meta.create_requirements_txt(path).await
180 }
181
182 #[allow(dead_code)]
193 pub async fn create_mecha10_json(&self, path: &Path, project_name: &str, template: &Option<String>) -> Result<()> {
194 let platform = template.as_deref().unwrap_or("basic");
195 let project_id = project_name.replace('-', "_");
196
197 let config_content = MECHA10_JSON_TEMPLATE
199 .replace("{{project_name}}", project_name)
200 .replace("{{project_id}}", &project_id)
201 .replace("{{platform}}", platform);
202
203 tokio::fs::write(path.join("mecha10.json"), config_content).await?;
204 Ok(())
205 }
206
207 pub async fn create_simulation_model_json(&self, path: &Path) -> Result<()> {
216 let model_dest = path.join("simulation/models/rover/model.json");
217
218 if let Some(parent) = model_dest.parent() {
220 tokio::fs::create_dir_all(parent).await?;
221 }
222
223 tokio::fs::write(&model_dest, MODEL_JSON_TEMPLATE)
225 .await
226 .context("Failed to write model.json")?;
227
228 Ok(())
229 }
230
231 pub async fn create_simulation_environment_json(&self, path: &Path) -> Result<()> {
240 let env_dest = path.join("simulation/environments/basic_arena/environment.json");
241
242 if let Some(parent) = env_dest.parent() {
244 tokio::fs::create_dir_all(parent).await?;
245 }
246
247 tokio::fs::write(&env_dest, ENVIRONMENT_JSON_TEMPLATE)
249 .await
250 .context("Failed to write environment.json")?;
251
252 Ok(())
253 }
254
255 pub async fn create_node_configs(&self, path: &Path) -> Result<()> {
264 let node_configs: &[(&str, &str)] = &[
265 ("behavior-executor", BEHAVIOR_EXECUTOR_CONFIG_DEV),
266 ("image-classifier", IMAGE_CLASSIFIER_CONFIG_DEV),
267 ("llm-command", LLM_COMMAND_CONFIG_DEV),
268 ("object-detector", OBJECT_DETECTOR_CONFIG_DEV),
269 ("simulation-bridge", SIMULATION_BRIDGE_CONFIG_DEV),
270 ("websocket-bridge", WEBSOCKET_BRIDGE_CONFIG_DEV),
271 ];
272
273 for (node_name, config_content) in node_configs {
274 let dev_dest = path.join("configs/dev/nodes").join(node_name).join("config.json");
275
276 if let Some(parent) = dev_dest.parent() {
277 tokio::fs::create_dir_all(parent).await?;
278 }
279
280 tokio::fs::write(&dev_dest, *config_content)
281 .await
282 .with_context(|| format!("Failed to write {}/config.json", node_name))?;
283 }
284
285 Ok(())
286 }
287
288 pub async fn create_simulation_configs(&self, path: &Path) -> Result<()> {
297 let dev_dest = path.join("configs/dev/simulation/config.json");
299 if let Some(parent) = dev_dest.parent() {
300 tokio::fs::create_dir_all(parent).await?;
301 }
302 tokio::fs::write(&dev_dest, SIMULATION_CONFIG)
303 .await
304 .context("Failed to write configs/dev/simulation/config.json")?;
305
306 Ok(())
307 }
308
309 pub async fn create_simulation_assets(&self, path: &Path) -> Result<()> {
319 let images_dest = path.join("assets/images");
321 tokio::fs::create_dir_all(&images_dest).await?;
322
323 let image_assets: &[(&str, &[u8])] = &[("aiko.jpg", AIKO_IMAGE), ("phoebe.jpg", PHOEBE_IMAGE)];
325
326 for (filename, content) in image_assets {
327 let dest_file = images_dest.join(filename);
328 tokio::fs::write(&dest_file, content)
329 .await
330 .with_context(|| format!("Failed to write assets/images/{}", filename))?;
331 }
332
333 let assets_dest = path.join("assets");
335 tokio::fs::write(assets_dest.join("cat_tower.jpg"), CAT_TOWER_IMAGE)
336 .await
337 .context("Failed to write assets/cat_tower.jpg")?;
338
339 Ok(())
340 }
341
342 pub async fn create_behavior_templates(&self, path: &Path) -> Result<()> {
352 let behaviors_dest = path.join("behaviors");
353 tokio::fs::create_dir_all(&behaviors_dest).await?;
354
355 let behavior_templates: &[(&str, &str)] = &[
356 ("basic_navigation.json", BEHAVIOR_BASIC_NAVIGATION),
357 ("idle_wander.json", BEHAVIOR_IDLE_WANDER),
358 ("obstacle_avoidance.json", BEHAVIOR_OBSTACLE_AVOIDANCE),
359 ("patrol_simple.json", BEHAVIOR_PATROL_SIMPLE),
360 ];
361
362 for (filename, content) in behavior_templates {
363 let dest_file = behaviors_dest.join(filename);
364 tokio::fs::write(&dest_file, *content)
365 .await
366 .with_context(|| format!("Failed to write behaviors/{}", filename))?;
367 }
368
369 Ok(())
370 }
371}
372
373impl Default for ProjectTemplateService {
374 fn default() -> Self {
375 Self::new()
376 }
377}