//! Templates API handler. use axum::{extract::Query, http::StatusCode, response::IntoResponse, Json}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use crate::llm::templates; // ============================================================================= // Contract Type Templates // ============================================================================= /// Contract type template for API response #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ContractTypeTemplate { /// Template identifier (e.g., "simple", "specification") pub id: String, /// Display name pub name: String, /// Description of the contract type workflow pub description: String, /// Ordered list of phases for this contract type pub phases: Vec, /// Default starting phase pub default_phase: String, /// Whether this is a built-in type (always available) pub is_builtin: bool, } /// Response for listing contract types #[derive(Debug, Serialize, ToSchema)] pub struct ListContractTypesResponse { pub types: Vec, } /// List available contract type templates #[utoipa::path( get, path = "/api/v1/contract-types", responses( (status = 200, description = "Contract types retrieved successfully", body = ListContractTypesResponse) ), tag = "contract-types" )] pub async fn list_contract_types() -> impl IntoResponse { let types = vec![ ContractTypeTemplate { id: "simple".to_string(), name: "Simple".to_string(), description: "Plan \u{2192} Execute: Simple workflow with a plan document".to_string(), phases: vec!["plan".to_string(), "execute".to_string()], default_phase: "plan".to_string(), is_builtin: true, }, ContractTypeTemplate { id: "specification".to_string(), name: "Specification".to_string(), description: "Research \u{2192} Specify \u{2192} Plan \u{2192} Execute \u{2192} Review: Full specification-driven development with TDD".to_string(), phases: vec![ "research".to_string(), "specify".to_string(), "plan".to_string(), "execute".to_string(), "review".to_string(), ], default_phase: "research".to_string(), is_builtin: true, }, ]; ( StatusCode::OK, Json(ListContractTypesResponse { types }), ) .into_response() } /// Query parameters for listing templates #[derive(Debug, Deserialize, ToSchema)] pub struct ListTemplatesQuery { /// Filter by contract phase (research, specify, plan, execute, review) pub phase: Option, } /// Template summary for API response #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TemplateSummary { /// Template identifier pub id: String, /// Display name pub name: String, /// Contract phase this template is designed for pub phase: String, /// Brief description pub description: String, /// Number of body elements in the template pub element_count: usize, } /// Response for listing templates #[derive(Debug, Serialize, ToSchema)] pub struct ListTemplatesResponse { pub templates: Vec, } /// List available file templates #[utoipa::path( get, path = "/api/v1/templates", params( ("phase" = Option, Query, description = "Filter by contract phase") ), responses( (status = 200, description = "Templates retrieved successfully", body = ListTemplatesResponse) ), tag = "templates" )] pub async fn list_templates( Query(query): Query, ) -> impl IntoResponse { let template_list = match query.phase.as_deref() { Some(phase) => templates::templates_for_phase(phase), None => templates::all_templates(), }; let summaries: Vec = template_list .iter() .map(|t| TemplateSummary { id: t.id.clone(), name: t.name.clone(), phase: t.phase.clone(), description: t.description.clone(), element_count: t.suggested_body.len(), }) .collect(); ( StatusCode::OK, Json(ListTemplatesResponse { templates: summaries, }), ) .into_response() } /// Get a specific template by ID #[utoipa::path( get, path = "/api/v1/templates/{id}", params( ("id" = String, Path, description = "Template ID") ), responses( (status = 200, description = "Template retrieved successfully", body = templates::FileTemplate), (status = 404, description = "Template not found") ), tag = "templates" )] pub async fn get_template( axum::extract::Path(id): axum::extract::Path, ) -> impl IntoResponse { let all = templates::all_templates(); let template = all.into_iter().find(|t| t.id == id); match template { Some(t) => (StatusCode::OK, Json(serde_json::json!(t))).into_response(), None => ( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": format!("Template '{}' not found", id) })), ) .into_response(), } }