use axum::{
extract::{Json, State},
http::StatusCode,
response::Json as ResponseJson,
};
use mockforge_core::intelligent_behavior::IntelligentBehaviorConfig;
use mockforge_core::voice::{
command_parser::{ParsedWorkspaceCreation, VoiceCommandParser},
hook_transpiler::HookTranspiler,
spec_generator::VoiceSpecGenerator,
workspace_builder::WorkspaceBuilder,
workspace_scenario_generator::{GeneratedWorkspaceScenario, WorkspaceScenarioGenerator},
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::handlers::workspaces::WorkspaceState;
use crate::models::ApiResponse;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessVoiceCommandRequest {
pub command: String,
#[serde(default)]
pub conversation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessVoiceCommandResponse {
pub command: String,
pub parsed: ParsedCommandData,
pub spec: Option<Value>,
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParsedCommandData {
pub api_type: String,
pub title: String,
pub description: String,
pub endpoints: Vec<Value>,
pub models: Vec<Value>,
}
pub async fn process_voice_command(
Json(request): Json<ProcessVoiceCommandRequest>,
) -> Result<ResponseJson<ApiResponse<ProcessVoiceCommandResponse>>, StatusCode> {
if request.command.trim().is_empty() {
return Err(StatusCode::BAD_REQUEST);
}
let config = IntelligentBehaviorConfig::default();
let parser = VoiceCommandParser::new(config);
let parsed = match parser.parse_command(&request.command).await {
Ok(parsed) => parsed,
Err(e) => {
return Ok(ResponseJson(ApiResponse::error(format!("Failed to parse command: {}", e))));
}
};
let spec_generator = VoiceSpecGenerator::new();
let spec_result = spec_generator.generate_spec(&parsed).await;
let spec = match spec_result {
Ok(spec) => {
let mut spec_json = serde_json::to_value(&spec.spec).unwrap_or(Value::Null);
if let Value::Object(ref mut obj) = spec_json {
if let Some(Value::Object(ref mut info)) = obj.get_mut("info") {
if !info.contains_key("title") {
info.insert("title".to_string(), Value::String(parsed.title.clone()));
}
if !info.contains_key("version") {
info.insert("version".to_string(), Value::String("1.0.0".to_string()));
}
}
}
Some(spec_json)
}
Err(e) => {
return Ok(ResponseJson(ApiResponse::error(format!("Failed to generate spec: {}", e))));
}
};
let parsed_data = ParsedCommandData {
api_type: parsed.api_type.clone(),
title: parsed.title.clone(),
description: parsed.description.clone(),
endpoints: parsed
.endpoints
.iter()
.map(|e| serde_json::to_value(e).unwrap_or(Value::Null))
.collect(),
models: parsed
.models
.iter()
.map(|m| serde_json::to_value(m).unwrap_or(Value::Null))
.collect(),
};
let response = ProcessVoiceCommandResponse {
command: request.command,
parsed: parsed_data,
spec,
error: None,
};
Ok(ResponseJson(ApiResponse::success(response)))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TranspileHookRequest {
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TranspileHookResponse {
pub description: String,
pub hook_yaml: Option<String>,
pub hook_json: Option<Value>,
pub error: Option<String>,
}
pub async fn transpile_hook(
Json(request): Json<TranspileHookRequest>,
) -> Result<ResponseJson<ApiResponse<TranspileHookResponse>>, StatusCode> {
if request.description.trim().is_empty() {
return Err(StatusCode::BAD_REQUEST);
}
let config = IntelligentBehaviorConfig::default();
let transpiler = HookTranspiler::new(config);
let hook = match transpiler.transpile(&request.description).await {
Ok(hook) => hook,
Err(e) => {
return Ok(ResponseJson(ApiResponse::error(format!(
"Failed to transpile hook: {}",
e
))));
}
};
let hook_yaml = match serde_yaml::to_string(&hook) {
Ok(yaml) => Some(yaml),
Err(e) => {
return Ok(ResponseJson(ApiResponse::error(format!(
"Failed to serialize hook to YAML: {}",
e
))));
}
};
let hook_json = match serde_json::to_value(&hook) {
Ok(json) => Some(json),
Err(e) => {
return Ok(ResponseJson(ApiResponse::error(format!(
"Failed to serialize hook to JSON: {}",
e
))));
}
};
let response = TranspileHookResponse {
description: request.description,
hook_yaml,
hook_json,
error: None,
};
Ok(ResponseJson(ApiResponse::success(response)))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateWorkspaceScenarioRequest {
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateWorkspaceScenarioResponse {
pub description: String,
pub scenario: Option<GeneratedWorkspaceScenario>,
pub error: Option<String>,
}
pub async fn create_workspace_scenario(
Json(request): Json<CreateWorkspaceScenarioRequest>,
) -> Result<ResponseJson<ApiResponse<CreateWorkspaceScenarioResponse>>, StatusCode> {
if request.description.trim().is_empty() {
return Err(StatusCode::BAD_REQUEST);
}
let config = IntelligentBehaviorConfig::default();
let parser = VoiceCommandParser::new(config);
let parsed = match parser.parse_workspace_scenario_command(&request.description).await {
Ok(parsed) => parsed,
Err(e) => {
return Ok(ResponseJson(ApiResponse::error(format!(
"Failed to parse scenario description: {}",
e
))));
}
};
let generator = WorkspaceScenarioGenerator::new();
let scenario = match generator.generate_scenario(&parsed).await {
Ok(scenario) => Some(scenario),
Err(e) => {
return Ok(ResponseJson(ApiResponse::error(format!(
"Failed to generate workspace scenario: {}",
e
))));
}
};
let response = CreateWorkspaceScenarioResponse {
description: request.description,
scenario,
error: None,
};
Ok(ResponseJson(ApiResponse::success(response)))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateWorkspaceRequest {
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateWorkspacePreviewResponse {
pub description: String,
pub parsed: ParsedWorkspaceCreation,
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfirmCreateWorkspaceRequest {
pub parsed: ParsedWorkspaceCreation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateWorkspaceResponse {
pub workspace_id: String,
pub name: String,
pub creation_log: Vec<String>,
pub endpoint_count: usize,
pub persona_count: usize,
pub scenario_count: usize,
pub has_reality_continuum: bool,
pub has_drift_budget: bool,
pub error: Option<String>,
}
pub async fn create_workspace_preview(
Json(request): Json<CreateWorkspaceRequest>,
) -> Result<ResponseJson<ApiResponse<CreateWorkspacePreviewResponse>>, StatusCode> {
if request.description.trim().is_empty() {
return Err(StatusCode::BAD_REQUEST);
}
let config = IntelligentBehaviorConfig::default();
let parser = VoiceCommandParser::new(config);
let parsed = match parser.parse_workspace_creation_command(&request.description).await {
Ok(parsed) => parsed,
Err(e) => {
return Ok(ResponseJson(ApiResponse::error(format!(
"Failed to parse workspace creation command: {}",
e
))));
}
};
let response = CreateWorkspacePreviewResponse {
description: request.description,
parsed,
error: None,
};
Ok(ResponseJson(ApiResponse::success(response)))
}
pub async fn create_workspace_confirm(
State(state): State<WorkspaceState>,
Json(request): Json<ConfirmCreateWorkspaceRequest>,
) -> Result<ResponseJson<ApiResponse<CreateWorkspaceResponse>>, StatusCode> {
let mut builder = WorkspaceBuilder::new();
let mut registry = state.registry.write().await;
let built = match builder.build_workspace(&mut registry, &request.parsed).await {
Ok(built) => built,
Err(e) => {
return Ok(ResponseJson(ApiResponse::error(format!(
"Failed to create workspace: {}",
e
))));
}
};
let endpoint_count = built
.openapi_spec
.as_ref()
.map(|s| s.all_paths_and_operations().len())
.unwrap_or(0);
let response = CreateWorkspaceResponse {
workspace_id: built.workspace_id,
name: built.name,
creation_log: built.creation_log,
endpoint_count,
persona_count: built.personas.len(),
scenario_count: built.scenarios.len(),
has_reality_continuum: built.reality_continuum.is_some(),
has_drift_budget: built.drift_budget.is_some(),
error: None,
};
Ok(ResponseJson(ApiResponse::success(response)))
}