mockforge_intelligence/ai_studio/
nl_mock_generator.rs1use crate::ai_studio::artifact_freezer::{ArtifactFreezer, FreezeMetadata};
8use crate::ai_studio::config::DeterministicModeConfig;
9use crate::intelligent_behavior::IntelligentBehaviorConfig;
10use crate::voice::{command_parser::VoiceCommandParser, spec_generator::VoiceSpecGenerator};
11use mockforge_foundation::Result;
12use mockforge_openapi::OpenApiSpec;
13use serde::{Deserialize, Serialize};
14use sha2::{Digest, Sha256};
15use std::collections::hash_map::DefaultHasher;
16use std::hash::{Hash, Hasher};
17
18pub struct MockGenerator {
20 parser: VoiceCommandParser,
22 spec_generator: VoiceSpecGenerator,
24 config: IntelligentBehaviorConfig,
26}
27
28impl MockGenerator {
29 pub fn new() -> Self {
31 let config = IntelligentBehaviorConfig::default();
32 Self {
33 parser: VoiceCommandParser::new(config.clone()),
34 spec_generator: VoiceSpecGenerator::new(),
35 config,
36 }
37 }
38
39 pub fn with_config(config: IntelligentBehaviorConfig) -> Self {
41 Self {
42 parser: VoiceCommandParser::new(config.clone()),
43 spec_generator: VoiceSpecGenerator::new(),
44 config,
45 }
46 }
47
48 pub async fn generate(
70 &self,
71 description: &str,
72 _workspace_id: Option<&str>,
73 ai_mode: Option<crate::ai_studio::config::AiMode>,
74 deterministic_config: Option<&DeterministicModeConfig>,
75 ) -> Result<MockGenerationResult> {
76 if ai_mode == Some(crate::ai_studio::config::AiMode::GenerateOnceFreeze) {
78 let freezer = ArtifactFreezer::new();
79
80 let mut hasher = DefaultHasher::new();
82 description.hash(&mut hasher);
83 let description_hash = format!("{:x}", hasher.finish());
84
85 if let Some(frozen) = freezer.load_frozen("mock", Some(&description_hash)).await? {
87 let mut spec = frozen.content.clone();
89 if let Some(obj) = spec.as_object_mut() {
90 obj.remove("_frozen_metadata");
91 }
92
93 return Ok(MockGenerationResult {
94 spec: Some(spec),
95 message: format!(
96 "Loaded frozen mock artifact from {} (deterministic mode)",
97 frozen.path
98 ),
99 parsed_command: None,
100 frozen_artifact: Some(frozen),
101 });
102 }
103 }
104
105 let parsed = self.parser.parse_command(description).await?;
107
108 let spec = self.spec_generator.generate_spec(&parsed).await?;
110
111 let spec_json = serde_json::to_value(&spec.spec)?;
113
114 let frozen_artifact = if let Some(config) = deterministic_config {
116 if config.enabled && config.is_auto_freeze_enabled() {
117 let freezer = ArtifactFreezer::new();
118
119 let mut hasher = Sha256::new();
121 hasher.update(description.as_bytes());
122 let prompt_hash = format!("{:x}", hasher.finalize());
123
124 let metadata = if config.track_metadata {
126 Some(FreezeMetadata {
127 llm_provider: Some(self.config.behavior_model.llm_provider.clone()),
128 llm_model: Some(self.config.behavior_model.model.clone()),
129 llm_version: None, prompt_hash: Some(prompt_hash),
131 output_hash: None, original_prompt: Some(description.to_string()),
133 })
134 } else {
135 None
136 };
137
138 let freeze_request = crate::ai_studio::artifact_freezer::FreezeRequest {
139 artifact_type: "mock".to_string(),
140 content: spec_json.clone(),
141 format: config.freeze_format.clone(),
142 path: None,
143 metadata,
144 };
145
146 freezer.auto_freeze_if_enabled(&freeze_request, config).await?
147 } else {
148 None
149 }
150 } else {
151 None
152 };
153
154 mockforge_foundation::pillar_tracking::record_ai_usage(
156 _workspace_id.map(String::from),
157 None,
158 "ai_generation",
159 serde_json::json!({
160 "type": "mock",
161 "endpoints": parsed.endpoints.len(),
162 "models": parsed.models.len(),
163 }),
164 )
165 .await;
166
167 Ok(MockGenerationResult {
168 spec: Some(spec_json),
169 message: format!(
170 "Successfully generated API '{}' with {} endpoints and {} models{}",
171 parsed.title,
172 parsed.endpoints.len(),
173 parsed.models.len(),
174 if frozen_artifact.is_some() {
175 " (auto-frozen)"
176 } else {
177 ""
178 }
179 ),
180 parsed_command: Some(parsed),
181 frozen_artifact,
182 })
183 }
184
185 pub async fn generate_with_context(
189 &self,
190 description: &str,
191 existing_spec: Option<&OpenApiSpec>,
192 ) -> Result<MockGenerationResult> {
193 let parsed = self.parser.parse_command(description).await?;
195
196 let spec = if let Some(existing) = existing_spec {
198 self.spec_generator.merge_spec(existing, &parsed).await?
200 } else {
201 self.spec_generator.generate_spec(&parsed).await?
203 };
204
205 let spec_json = serde_json::to_value(&spec.spec)?;
207
208 let metric_name = if existing_spec.is_some() {
210 "ai_refinement"
211 } else {
212 "ai_generation"
213 };
214 mockforge_foundation::pillar_tracking::record_ai_usage(
215 None,
216 None,
217 metric_name,
218 serde_json::json!({
219 "type": "mock",
220 "endpoints": parsed.endpoints.len(),
221 "models": parsed.models.len(),
222 }),
223 )
224 .await;
225
226 Ok(MockGenerationResult {
227 spec: Some(spec_json),
228 message: format!(
229 "Successfully {} API '{}' with {} endpoints and {} models",
230 if existing_spec.is_some() {
231 "updated"
232 } else {
233 "generated"
234 },
235 parsed.title,
236 parsed.endpoints.len(),
237 parsed.models.len()
238 ),
239 parsed_command: Some(parsed),
240 frozen_artifact: None,
241 })
242 }
243}
244
245impl Default for MockGenerator {
246 fn default() -> Self {
247 Self::new()
248 }
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct MockGenerationResult {
254 pub spec: Option<serde_json::Value>,
256
257 pub message: String,
259
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub parsed_command: Option<crate::voice::command_parser::ParsedCommand>,
263
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub frozen_artifact: Option<crate::ai_studio::artifact_freezer::FrozenArtifact>,
267}