1use axum::{
7 extract::{Json, State},
8 http::StatusCode,
9 response::Json as ResponseJson,
10};
11use mockforge_core::intelligent_behavior::IntelligentBehaviorConfig;
12use mockforge_core::voice::{
13 command_parser::{ParsedWorkspaceCreation, VoiceCommandParser},
14 hook_transpiler::HookTranspiler,
15 spec_generator::VoiceSpecGenerator,
16 workspace_builder::WorkspaceBuilder,
17 workspace_scenario_generator::{GeneratedWorkspaceScenario, WorkspaceScenarioGenerator},
18};
19use serde::{Deserialize, Serialize};
20use serde_json::Value;
21
22use crate::handlers::workspaces::WorkspaceState;
23use crate::models::ApiResponse;
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ProcessVoiceCommandRequest {
28 pub command: String,
30 #[serde(default)]
32 pub conversation_id: Option<String>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct ProcessVoiceCommandResponse {
38 pub command: String,
40 pub parsed: ParsedCommandData,
42 pub spec: Option<Value>,
44 pub error: Option<String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ParsedCommandData {
51 pub api_type: String,
53 pub title: String,
55 pub description: String,
57 pub endpoints: Vec<Value>,
59 pub models: Vec<Value>,
61}
62
63pub async fn process_voice_command(
67 Json(request): Json<ProcessVoiceCommandRequest>,
68) -> Result<ResponseJson<ApiResponse<ProcessVoiceCommandResponse>>, StatusCode> {
69 if request.command.trim().is_empty() {
70 return Err(StatusCode::BAD_REQUEST);
71 }
72
73 let config = IntelligentBehaviorConfig::default();
75 let parser = VoiceCommandParser::new(config);
76
77 let parsed = match parser.parse_command(&request.command).await {
79 Ok(parsed) => parsed,
80 Err(e) => {
81 return Ok(ResponseJson(ApiResponse::error(format!("Failed to parse command: {}", e))));
82 }
83 };
84
85 let spec_generator = VoiceSpecGenerator::new();
87 let spec_result = spec_generator.generate_spec(&parsed).await;
88 let spec = match spec_result {
89 Ok(spec) => {
90 let mut spec_json = serde_json::to_value(&spec.spec).unwrap_or(Value::Null);
92 if let Value::Object(ref mut obj) = spec_json {
94 if let Some(Value::Object(ref mut info)) = obj.get_mut("info") {
95 if !info.contains_key("title") {
97 info.insert("title".to_string(), Value::String(parsed.title.clone()));
98 }
99 if !info.contains_key("version") {
100 info.insert("version".to_string(), Value::String("1.0.0".to_string()));
101 }
102 }
103 }
104 Some(spec_json)
105 }
106 Err(e) => {
107 return Ok(ResponseJson(ApiResponse::error(format!("Failed to generate spec: {}", e))));
108 }
109 };
110
111 let parsed_data = ParsedCommandData {
113 api_type: parsed.api_type.clone(),
114 title: parsed.title.clone(),
115 description: parsed.description.clone(),
116 endpoints: parsed
117 .endpoints
118 .iter()
119 .map(|e| serde_json::to_value(e).unwrap_or(Value::Null))
120 .collect(),
121 models: parsed
122 .models
123 .iter()
124 .map(|m| serde_json::to_value(m).unwrap_or(Value::Null))
125 .collect(),
126 };
127
128 let response = ProcessVoiceCommandResponse {
129 command: request.command,
130 parsed: parsed_data,
131 spec,
132 error: None,
133 };
134
135 Ok(ResponseJson(ApiResponse::success(response)))
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct TranspileHookRequest {
141 pub description: String,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct TranspileHookResponse {
148 pub description: String,
150 pub hook_yaml: Option<String>,
152 pub hook_json: Option<Value>,
154 pub error: Option<String>,
156}
157
158pub async fn transpile_hook(
162 Json(request): Json<TranspileHookRequest>,
163) -> Result<ResponseJson<ApiResponse<TranspileHookResponse>>, StatusCode> {
164 if request.description.trim().is_empty() {
165 return Err(StatusCode::BAD_REQUEST);
166 }
167
168 let config = IntelligentBehaviorConfig::default();
170 let transpiler = HookTranspiler::new(config);
171
172 let hook = match transpiler.transpile(&request.description).await {
174 Ok(hook) => hook,
175 Err(e) => {
176 return Ok(ResponseJson(ApiResponse::error(format!(
177 "Failed to transpile hook: {}",
178 e
179 ))));
180 }
181 };
182
183 let hook_yaml = match serde_yaml::to_string(&hook) {
185 Ok(yaml) => Some(yaml),
186 Err(e) => {
187 return Ok(ResponseJson(ApiResponse::error(format!(
188 "Failed to serialize hook to YAML: {}",
189 e
190 ))));
191 }
192 };
193
194 let hook_json = match serde_json::to_value(&hook) {
195 Ok(json) => Some(json),
196 Err(e) => {
197 return Ok(ResponseJson(ApiResponse::error(format!(
198 "Failed to serialize hook to JSON: {}",
199 e
200 ))));
201 }
202 };
203
204 let response = TranspileHookResponse {
205 description: request.description,
206 hook_yaml,
207 hook_json,
208 error: None,
209 };
210
211 Ok(ResponseJson(ApiResponse::success(response)))
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct CreateWorkspaceScenarioRequest {
217 pub description: String,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct CreateWorkspaceScenarioResponse {
224 pub description: String,
226 pub scenario: Option<GeneratedWorkspaceScenario>,
228 pub error: Option<String>,
230}
231
232pub async fn create_workspace_scenario(
236 Json(request): Json<CreateWorkspaceScenarioRequest>,
237) -> Result<ResponseJson<ApiResponse<CreateWorkspaceScenarioResponse>>, StatusCode> {
238 if request.description.trim().is_empty() {
239 return Err(StatusCode::BAD_REQUEST);
240 }
241
242 let config = IntelligentBehaviorConfig::default();
244 let parser = VoiceCommandParser::new(config);
245
246 let parsed = match parser.parse_workspace_scenario_command(&request.description).await {
248 Ok(parsed) => parsed,
249 Err(e) => {
250 return Ok(ResponseJson(ApiResponse::error(format!(
251 "Failed to parse scenario description: {}",
252 e
253 ))));
254 }
255 };
256
257 let generator = WorkspaceScenarioGenerator::new();
259 let scenario = match generator.generate_scenario(&parsed).await {
260 Ok(scenario) => Some(scenario),
261 Err(e) => {
262 return Ok(ResponseJson(ApiResponse::error(format!(
263 "Failed to generate workspace scenario: {}",
264 e
265 ))));
266 }
267 };
268
269 let response = CreateWorkspaceScenarioResponse {
270 description: request.description,
271 scenario,
272 error: None,
273 };
274
275 Ok(ResponseJson(ApiResponse::success(response)))
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct CreateWorkspaceRequest {
281 pub description: String,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct CreateWorkspacePreviewResponse {
288 pub description: String,
290 pub parsed: ParsedWorkspaceCreation,
292 pub error: Option<String>,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct ConfirmCreateWorkspaceRequest {
299 pub parsed: ParsedWorkspaceCreation,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct CreateWorkspaceResponse {
306 pub workspace_id: String,
308 pub name: String,
310 pub creation_log: Vec<String>,
312 pub endpoint_count: usize,
314 pub persona_count: usize,
316 pub scenario_count: usize,
318 pub has_reality_continuum: bool,
320 pub has_drift_budget: bool,
322 pub error: Option<String>,
324}
325
326pub async fn create_workspace_preview(
330 Json(request): Json<CreateWorkspaceRequest>,
331) -> Result<ResponseJson<ApiResponse<CreateWorkspacePreviewResponse>>, StatusCode> {
332 if request.description.trim().is_empty() {
333 return Err(StatusCode::BAD_REQUEST);
334 }
335
336 let config = IntelligentBehaviorConfig::default();
338 let parser = VoiceCommandParser::new(config);
339
340 let parsed = match parser.parse_workspace_creation_command(&request.description).await {
342 Ok(parsed) => parsed,
343 Err(e) => {
344 return Ok(ResponseJson(ApiResponse::error(format!(
345 "Failed to parse workspace creation command: {}",
346 e
347 ))));
348 }
349 };
350
351 let response = CreateWorkspacePreviewResponse {
352 description: request.description,
353 parsed,
354 error: None,
355 };
356
357 Ok(ResponseJson(ApiResponse::success(response)))
358}
359
360pub async fn create_workspace_confirm(
364 State(state): State<WorkspaceState>,
365 Json(request): Json<ConfirmCreateWorkspaceRequest>,
366) -> Result<ResponseJson<ApiResponse<CreateWorkspaceResponse>>, StatusCode> {
367 let mut builder = WorkspaceBuilder::new();
369
370 let mut registry = state.registry.write().await;
372
373 let built = match builder.build_workspace(&mut registry, &request.parsed).await {
375 Ok(built) => built,
376 Err(e) => {
377 return Ok(ResponseJson(ApiResponse::error(format!(
378 "Failed to create workspace: {}",
379 e
380 ))));
381 }
382 };
383
384 let endpoint_count = built
385 .openapi_spec
386 .as_ref()
387 .map(|s| s.all_paths_and_operations().len())
388 .unwrap_or(0);
389
390 let response = CreateWorkspaceResponse {
391 workspace_id: built.workspace_id,
392 name: built.name,
393 creation_log: built.creation_log,
394 endpoint_count,
395 persona_count: built.personas.len(),
396 scenario_count: built.scenarios.len(),
397 has_reality_continuum: built.reality_continuum.is_some(),
398 has_drift_budget: built.drift_budget.is_some(),
399 error: None,
400 };
401
402 Ok(ResponseJson(ApiResponse::success(response)))
403}