Soroush commited on
Commit
7a2907b
·
1 Parent(s): fa84149
Files changed (1) hide show
  1. app.py +1327 -0
app.py CHANGED
@@ -0,0 +1,1327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import requests
4
+ from typing import Dict, List, Any, Optional
5
+ from dataclasses import asdict, dataclass
6
+ from langgraph.graph import StateGraph, END
7
+ from langgraph.checkpoint.sqlite import SqliteSaver
8
+ from langchain_core.messages import HumanMessage, AIMessage
9
+ from mistralai import Mistral
10
+ import json
11
+ import base64
12
+ import io
13
+ from io import BytesIO
14
+ from bs4 import BeautifulSoup
15
+ import re
16
+ from PIL import Image
17
+
18
+ # Constants
19
+ MAX_IMAGE_SIZE_KB = 50 # 50KB maximum size for images
20
+ MAX_RESIZE_ATTEMPTS = 3 # Maximum number of resize attempts
21
+ from langchain_core.runnables.graph import MermaidDrawMethod
22
+ from IPython.display import Image
23
+
24
+ from dotenv import load_dotenv
25
+ load_dotenv()
26
+
27
+ # Initialize Mistral client
28
+ client = Mistral(api_key=os.getenv("MISTRAL_API_KEY"))
29
+
30
+ @dataclass
31
+ class SimulationState:
32
+ """Enhanced state management for the simulation workflow"""
33
+ url: str = ""
34
+ content: str = ""
35
+ content_type: str = "" # 'article' or 'image'
36
+ summary: str = ""
37
+ agents: List[Dict[str, Any]] = None
38
+ behaviors: List[str] = None
39
+ emojis: Dict[str, str] = None
40
+ simulation_code: str = ""
41
+ documentation: str = ""
42
+ messages: List = None
43
+ # New fields for enhanced simulation
44
+ environment: Dict[str, Any] = None
45
+ hypothesis: str = ""
46
+ story_narrative: str = ""
47
+ research_question: str = ""
48
+ expected_outcomes: List[str] = None
49
+ agent_interactions: List[Dict[str, Any]] = None
50
+ environmental_factors: List[Dict[str, Any]] = None
51
+ # New fields for code validation
52
+ code_errors: List[str] = None
53
+ validation_attempts: int = 0
54
+ max_validation_attempts: int = 3
55
+ code_is_valid: bool = False
56
+
57
+ def __post_init__(self):
58
+ if self.agents is None:
59
+ self.agents = []
60
+ if self.behaviors is None:
61
+ self.behaviors = []
62
+ if self.emojis is None:
63
+ self.emojis = {}
64
+ if self.messages is None:
65
+ self.messages = []
66
+ if self.environment is None:
67
+ self.environment = {}
68
+ if self.expected_outcomes is None:
69
+ self.expected_outcomes = []
70
+ if self.agent_interactions is None:
71
+ self.agent_interactions = []
72
+ if self.environmental_factors is None:
73
+ self.environmental_factors = []
74
+ if self.code_errors is None:
75
+ self.code_errors = []
76
+
77
+ def to_dict(self):
78
+ return asdict(self)
79
+
80
+ import time
81
+ from tenacity import retry, stop_after_attempt, wait_exponential
82
+
83
+ class MistralModelHub:
84
+ def __init__(self, api_key: str):
85
+ self.client = Mistral(api_key=api_key)
86
+
87
+ # Available Mistral models
88
+ self.models = {
89
+ 'large': MistralLLM(self.client, "mistral-large-latest"),
90
+ 'medium': MistralLLM(self.client, "magistral-small-2506"),
91
+ 'small': MistralLLM(self.client, "ministral-3b-2410"),
92
+ 'codestral': MistralLLM(self.client, "codestral-latest"),
93
+ 'pixtral': MistralLLM(self.client, "pixtral-12b-2409"),
94
+ 'nemo': MistralLLM(self.client, "open-mistral-nemo"),
95
+ 'saba': MistralLLM(self.client, "mistral-saba-latest"),
96
+ }
97
+
98
+ def __getattr__(self, name):
99
+ if name in self.models:
100
+ return self.models[name]
101
+ raise AttributeError(f"Model '{name}' not available")
102
+
103
+ class MistralLLM:
104
+ def __init__(self, client, model: str):
105
+ self.model = model
106
+ self.client = client
107
+
108
+ @retry(
109
+ stop=stop_after_attempt(3),
110
+ wait=wait_exponential(multiplier=1, min=4, max=10)
111
+ )
112
+ def chat(self, messages: List[Dict[str, str]], temperature: float = 0.7, max_tokens: Optional[int] = None) -> str:
113
+ try:
114
+ chat_messages = [
115
+ {"role": msg["role"], "content": msg["content"]}
116
+ for msg in messages
117
+ ]
118
+ print(f"Calling model: {self.model}")
119
+ response = self.client.chat.complete(
120
+ model=self.model,
121
+ messages=chat_messages,
122
+ temperature=temperature,
123
+ max_tokens=max_tokens,
124
+ response_format={"type": "text"}
125
+ )
126
+
127
+ if response.choices and len(response.choices) > 0:
128
+ return response.choices[0].message.content
129
+ return ""
130
+
131
+ except Exception as e:
132
+ print(f"Error calling Mistral API: {str(e)}")
133
+ if hasattr(e, 'status_code') and e.status_code == 429:
134
+ print("Rate limit exceeded. Waiting before retry...")
135
+ time.sleep(5)
136
+ raise
137
+
138
+ # Initialize LLM
139
+ llm_large = MistralModelHub(api_key=os.getenv("MISTRAL_API_KEY")).large
140
+ llm_codestral = MistralModelHub(api_key=os.getenv("MISTRAL_API_KEY")).codestral
141
+ llm_pixtral = MistralModelHub(api_key=os.getenv("MISTRAL_API_KEY")).pixtral
142
+ llm_medium = MistralModelHub(api_key=os.getenv("MISTRAL_API_KEY")).medium
143
+ llm_small = MistralModelHub(api_key=os.getenv("MISTRAL_API_KEY")).small
144
+ llm_saba = MistralModelHub(api_key=os.getenv("MISTRAL_API_KEY")).saba
145
+ llm_nemo = MistralModelHub(api_key=os.getenv("MISTRAL_API_KEY")).nemo
146
+
147
+ def resize_image_to_size_limit(image_data: bytes, content_type: str, max_size_kb: int = MAX_IMAGE_SIZE_KB, max_attempts: int = MAX_RESIZE_ATTEMPTS) -> str:
148
+ """
149
+ Resize image to fit within the specified size limit (in KB)
150
+ Returns base64 encoded image data URL
151
+ """
152
+ def get_size_kb(data):
153
+ return len(data) / 1024 # Convert bytes to KB
154
+
155
+ current_data = image_data
156
+ current_size_kb = get_size_kb(current_data)
157
+
158
+ if current_size_kb <= max_size_kb:
159
+ return f"data:{content_type};base64,{base64.b64encode(current_data).decode()}"
160
+
161
+ # Try resizing up to max_attempts times
162
+ for attempt in range(1, max_attempts + 1):
163
+ try:
164
+ # Open the image
165
+ img = Image.open(BytesIO(current_data))
166
+
167
+ # Calculate new dimensions (reduce by 20% each time)
168
+ new_width = int(img.width * 0.8)
169
+ new_height = int(img.height * 0.8)
170
+
171
+ # Resize the image
172
+ img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
173
+
174
+ # Convert back to bytes
175
+ output = BytesIO()
176
+ format = content_type.split('/')[-1].upper()
177
+ if format not in ['JPEG', 'PNG', 'GIF']: # Default to JPEG
178
+ format = 'JPEG'
179
+ img.save(output, format=format, quality=85, optimize=True)
180
+ current_data = output.getvalue()
181
+
182
+ # Check new size
183
+ current_size_kb = get_size_kb(current_data)
184
+ if current_size_kb <= max_size_kb:
185
+ return f"data:{content_type};base64,{base64.b64encode(current_data).decode()}"
186
+
187
+ except Exception as e:
188
+ print(f"Error resizing image (attempt {attempt}): {str(e)}")
189
+ break
190
+
191
+ # If we get here, we couldn't resize within limits after max_attempts
192
+ raise ValueError(f"Could not resize image below {max_size_kb}KB after {max_attempts} attempts")
193
+
194
+ def extract_content_node(state: SimulationState) -> SimulationState:
195
+ """Extract content from URL - handles both articles and images. Only keep main article text, filter out navigation/copyright/unrelated sections."""
196
+ try:
197
+ response = requests.get(state.url, timeout=10)
198
+ content_type = response.headers.get('content-type', '').lower()
199
+
200
+ if 'image' in content_type:
201
+ state.content_type = 'image'
202
+ # Check image size and resize if needed
203
+ image_data = response.content
204
+ image_size_kb = len(image_data) / 1024 # Convert to KB
205
+ if image_size_kb > MAX_IMAGE_SIZE_KB:
206
+ print(f"Image size ({image_size_kb:.2f}KB) exceeds maximum allowed size ({MAX_IMAGE_SIZE_KB}KB). Resizing...")
207
+ state.content = resize_image_to_size_limit(image_data, content_type)
208
+ else:
209
+ # Convert to base64 if within size limit
210
+ image_data = base64.b64encode(image_data).decode()
211
+ state.content = f"data:{content_type};base64,{image_data}"
212
+ return state
213
+
214
+ # Otherwise, treat as article
215
+ state.content_type = 'article'
216
+ soup = BeautifulSoup(response.text, 'html.parser')
217
+ article_content = ''
218
+
219
+ # Try to extract from <article> tag first
220
+ article_tag = soup.find('article')
221
+ if article_tag:
222
+ article_content = article_tag.get_text(separator=' ', strip=True)
223
+ else:
224
+ # Fallback: find the largest <div> with <p> tags
225
+ divs = soup.find_all('div')
226
+ max_p_div = None
227
+ max_p_count = 0
228
+ for div in divs:
229
+ p_count = len(div.find_all('p'))
230
+ if p_count > max_p_count:
231
+ max_p_count = p_count
232
+ max_p_div = div
233
+ if max_p_div:
234
+ article_content = max_p_div.get_text(separator=' ', strip=True)
235
+
236
+ # Fallback: all paragraphs
237
+ if not article_content:
238
+ paragraphs = soup.find_all('p')
239
+ article_content = ' '.join([p.get_text().strip() for p in paragraphs])
240
+
241
+ # Remove unwanted sections by keywords (navigation, copyright, etc.)
242
+ unwanted_keywords = [
243
+ 'riproduzione riservata', 'copyright', 'da non perdere', 'prevPageLabel', 'nextPageLabel', 'condividi', 'link copiato', 'cookie', 'accetta', 'rifiuta'
244
+ ]
245
+ for keyword in unwanted_keywords:
246
+ article_content = re.sub(rf'(?i){keyword}.*', '', article_content)
247
+
248
+ # Remove extra whitespace and code blocks
249
+ article_content = re.sub(r'\s+', ' ', article_content)
250
+ article_content = re.sub(r'``````', '', article_content, flags=re.DOTALL)
251
+ article_content = article_content.strip()
252
+ state.content = article_content[:5000] # Limit content size
253
+
254
+ except Exception as e:
255
+ state.content = f"Error extracting content: {str(e)}"
256
+ state.content_type = 'error'
257
+ return state
258
+
259
+ def process_image_node(state: SimulationState) -> SimulationState:
260
+ """Process image content using Mistral's vision capabilities"""
261
+
262
+ if state.content_type != 'image':
263
+ return state
264
+
265
+ messages = [
266
+ {
267
+ "role": "system",
268
+ "content": """You are an expert image analyzer. Describe the image in detail, focusing on:
269
+ 1. Main subjects and objects
270
+ 2. Activities or interactions happening
271
+ 3. Social dynamics or relationships visible
272
+ 4. Setting and context
273
+ 5. Any text or symbols present
274
+
275
+ Provide a comprehensive description that could be used to understand social dynamics for simulation purposes."""
276
+ },
277
+ {
278
+ "role": "user",
279
+ "content": f"Please describe this image in detail: {state.content}"
280
+ }
281
+ ]
282
+
283
+ description = llm_pixtral.chat(messages)
284
+ state.content = description
285
+ state.content_type = 'article' # Convert to article for further processing
286
+
287
+ return state
288
+
289
+ def analyze_content_node(state: SimulationState) -> SimulationState:
290
+ """Enhanced analysis to extract comprehensive simulation components"""
291
+
292
+ system_prompt = """You are an expert in complex systems theory, social network analysis, and agent-based modeling.
293
+
294
+ Your task is to conduct a deep scientific analysis of the content to create a meaningful agent-based simulation. Consider:
295
+
296
+ 1. System Dynamics: Identify feedback loops, tipping points, and emergent behaviors
297
+ 2. Social Physics: Power dynamics, influence propagation, coalition formation
298
+ 3. Environmental Context: Physical, social, economic, and cultural environments
299
+ 4. Conflict and Cooperation: Competition for resources, alliance formation, negotiation
300
+ 5. Temporal Dynamics: How relationships and behaviors evolve over time
301
+
302
+ Focus on creating agents with rich internal states and meaningful interactions that produce observable phenomena."""
303
+
304
+ user_prompt = f"""
305
+ Analyze this content for an advanced agent-based simulation:
306
+
307
+ Content: {state.content}
308
+
309
+ Provide a comprehensive JSON response with:
310
+ {{
311
+ "summary": "Deep analytical summary focusing on system dynamics",
312
+ "research_domain": "Primary field of study (sociology, economics, politics, etc.)",
313
+ "environment": {{
314
+ "name": "Environment name",
315
+ "description": "Detailed environment description",
316
+ "resources": ["resource1", "resource2"],
317
+ "constraints": ["constraint1", "constraint2"],
318
+ "spatial_properties": "Physical or abstract space description",
319
+ "temporal_dynamics": "How environment changes over time"
320
+ }},
321
+ "agents": [
322
+ {{
323
+ "name": "Agent name",
324
+ "type": "Agent type (individual, organization, group, institution)",
325
+ "description": "Detailed agent description",
326
+ "attributes": {{
327
+ "influence_level": 1-10,
328
+ "resources": ["resource1", "resource2"],
329
+ "goals": ["goal1", "goal2"],
330
+ "constraints": ["constraint1", "constraint2"],
331
+ "decision_strategy": "How agent makes decisions",
332
+ "memory_span": "How long agent remembers interactions",
333
+ "cooperation_tendency": 1-10,
334
+ "aggression_level": 1-10,
335
+ "adaptation_rate": 1-10
336
+ }},
337
+ "initial_state": {{
338
+ "position": "starting position or status",
339
+ "energy": 1-100,
340
+ "relationships": {{}},
341
+ "knowledge": ["known_fact1", "known_fact2"]
342
+ }}
343
+ }}
344
+ ],
345
+ "agent_interactions": [
346
+ {{
347
+ "interaction_type": "cooperation/competition/negotiation/conflict/information_exchange",
348
+ "participants": ["agent1", "agent2"],
349
+ "conditions": "When this interaction occurs",
350
+ "outcomes": {{
351
+ "positive": "What happens if interaction succeeds",
352
+ "negative": "What happens if interaction fails",
353
+ "environmental_impact": "How this affects the environment"
354
+ }},
355
+ "probability_factors": ["factor1", "factor2"]
356
+ }}
357
+ ],
358
+ "environmental_factors": [
359
+ {{
360
+ "name": "Factor name",
361
+ "type": "resource/constraint/event/pressure",
362
+ "description": "How this factor affects the system",
363
+ "impact_on_agents": "Specific effects on different agent types",
364
+ "temporal_pattern": "constant/periodic/random/triggered"
365
+ }}
366
+ ],
367
+ "system_dynamics": {{
368
+ "feedback_loops": ["description of feedback loop"],
369
+ "tipping_points": ["conditions that cause system change"],
370
+ "equilibrium_states": ["possible stable states"],
371
+ "emergent_behaviors": ["behaviors that emerge from interactions"]
372
+ }}
373
+ }}
374
+ """
375
+
376
+ messages = [
377
+ {"role": "system", "content": system_prompt},
378
+ {"role": "user", "content": user_prompt}
379
+ ]
380
+
381
+ response = llm_medium.chat(messages, temperature=0.6)
382
+
383
+ try:
384
+ json_match = re.search(r'\{.*\}', response, re.DOTALL)
385
+ if json_match:
386
+ data = json.loads(json_match.group())
387
+ state.summary = data.get("summary", "")
388
+ state.environment = data.get("environment", {})
389
+ state.agents = data.get("agents", [])
390
+ state.agent_interactions = data.get("agent_interactions", [])
391
+ state.environmental_factors = data.get("environmental_factors", [])
392
+
393
+ # Extract system dynamics for later use
394
+ system_dynamics = data.get("system_dynamics", {})
395
+ state.behaviors = (
396
+ system_dynamics.get("feedback_loops", []) +
397
+ system_dynamics.get("emergent_behaviors", [])
398
+ )
399
+ else:
400
+ state.summary = response
401
+ except Exception as e:
402
+ state.summary = f"Analysis error: {str(e)}"
403
+
404
+ return state
405
+
406
+ def develop_hypothesis_node(state: SimulationState) -> SimulationState:
407
+ """Develop scientific hypothesis and narrative story for the simulation"""
408
+
409
+ system_prompt = """You are a research scientist developing hypotheses for agent-based social simulations.
410
+
411
+ Create a compelling research hypothesis that can be tested through simulation, along with a narrative story
412
+ that makes the simulation engaging and meaningful. The hypothesis should be:
413
+
414
+ 1. Testable through agent interactions
415
+ 2. Based on established social/behavioral theories
416
+ 3. Relevant to the analyzed content
417
+ 4. Capable of producing measurable outcomes
418
+
419
+ The story should provide context and meaning to make the simulation educational and engaging."""
420
+
421
+ agents_summary = "\n".join([
422
+ f"- {agent['name']}: {agent['description']} (Influence: {agent['attributes']['influence_level']})"
423
+ for agent in state.agents
424
+ ])
425
+
426
+ interactions_summary = "\n".join([
427
+ f"- {interaction['interaction_type']}: {interaction['conditions']}"
428
+ for interaction in state.agent_interactions
429
+ ])
430
+
431
+ user_prompt = f"""
432
+ Develop a research hypothesis and story for this simulation:
433
+
434
+ Summary: {state.summary}
435
+
436
+ Environment: {state.environment.get('description', 'N/A')}
437
+
438
+ Agents:
439
+ {agents_summary}
440
+
441
+ Key Interactions:
442
+ {interactions_summary}
443
+
444
+ Provide a JSON response with:
445
+ {{
446
+ "research_question": "Clear, testable research question",
447
+ "hypothesis": "Scientific hypothesis that can be tested through simulation",
448
+ "theoretical_framework": "Underlying social/behavioral theories",
449
+ "story_narrative": "Engaging narrative that contextualizes the simulation",
450
+ "expected_outcomes": [
451
+ "Specific measurable outcome 1",
452
+ "Specific measurable outcome 2",
453
+ "Specific measurable outcome 3"
454
+ ],
455
+ "success_metrics": [
456
+ "How to measure if hypothesis is supported",
457
+ "Key indicators to track"
458
+ ],
459
+ "variables_to_manipulate": [
460
+ {{
461
+ "name": "Variable name",
462
+ "description": "What this controls",
463
+ "range": "Possible values",
464
+ "impact": "Expected effect on system"
465
+ }}
466
+ ]
467
+ }}
468
+ """
469
+
470
+ messages = [
471
+ {"role": "system", "content": system_prompt},
472
+ {"role": "user", "content": user_prompt}
473
+ ]
474
+
475
+ response = llm_large.chat(messages, temperature=0.7)
476
+
477
+ try:
478
+ json_match = re.search(r'\{.*\}', response, re.DOTALL)
479
+ if json_match:
480
+ data = json.loads(json_match.group())
481
+ state.research_question = data.get("research_question", "")
482
+ state.hypothesis = data.get("hypothesis", "")
483
+ state.story_narrative = data.get("story_narrative", "")
484
+ state.expected_outcomes = data.get("expected_outcomes", [])
485
+ except Exception as e:
486
+ state.hypothesis = f"Hypothesis development error: {str(e)}"
487
+
488
+ return state
489
+
490
+ def generate_emojis_node(state: SimulationState) -> SimulationState:
491
+ """Generate emojis and visual representations for agents"""
492
+
493
+ system_prompt = """You are a creative designer specializing in visual representation of social agents.
494
+ Generate appropriate emojis and visual symbols for each agent that represent their role, characteristics, and function in the social network."""
495
+
496
+ agents_text = "\n".join([f"- {agent['name']}: {agent['description']}" for agent in state.agents])
497
+
498
+ user_prompt = f"""
499
+ Generate emojis for these social network agents:
500
+ {agents_text}
501
+
502
+ Provide a JSON response with emoji assignments:
503
+ {{
504
+ "agent_name": "🎭",
505
+ "agent_name2": "👥"
506
+ }}
507
+
508
+ Choose emojis that best represent each agent's role and characteristics.
509
+ """
510
+
511
+ messages = [
512
+ {"role": "system", "content": system_prompt},
513
+ {"role": "user", "content": user_prompt}
514
+ ]
515
+
516
+ response = llm_small.chat(messages, temperature=0.7)
517
+
518
+ try:
519
+ json_match = re.search(r'\{.*\}', response, re.DOTALL)
520
+ if json_match:
521
+ state.emojis = json.loads(json_match.group())
522
+ except Exception as e:
523
+ # Default emojis if parsing fails
524
+ state.emojis = {agent['name']: "👤" for agent in state.agents}
525
+
526
+ return state
527
+
528
+ def generate_simulation_node(state: SimulationState) -> SimulationState:
529
+ """Generate advanced NetLogo-style simulation with comprehensive controls"""
530
+
531
+ system_prompt = """You are an expert in advanced agent-based modeling and interactive simulations.
532
+
533
+ Create a sophisticated HTML5 simulation with:
534
+
535
+ 1. ADVANCED CONTROLS:
536
+ - Speed control (1-10 scale)
537
+ - Population controls for each agent type
538
+ - Environmental parameter controls
539
+ - Interaction probability controls
540
+ - Resource availability controls
541
+ - Scenario trigger buttons
542
+
543
+ 2. VISUAL FEATURES:
544
+ - Dynamic environment visualization with color coding
545
+ - Agent trails showing movement history
546
+ - Network connections with varying thickness
547
+ - Real-time statistics dashboard
548
+ - Interactive agent information on hover/click
549
+
550
+ 3. SCIENTIFIC FEATURES:
551
+ - Hypothesis testing interface
552
+ - Data collection and export
553
+ - Parameter sensitivity analysis
554
+ - Scenario comparison tools
555
+ - Statistical significance indicators
556
+
557
+ 4. TECHNICAL REQUIREMENTS:
558
+ - Proper object-oriented agent architecture
559
+ - Efficient spatial indexing for large populations
560
+ - Event-driven interaction system
561
+ - Configurable random seed for reproducibility
562
+ - Performance monitoring and optimization
563
+
564
+ FOCUS HEAVILY ON THE IMPLEMENTATION OF THE LOGIC AND THE AGENTS INTERACTIONS. DO NOT SKIP ANY IMPLEMENTAION. THE PROJECT MUST BE COMPLETE.
565
+
566
+ The simulation must test the provided hypothesis and produce measurable outcomes."""
567
+
568
+ agents_detail = "\n".join([
569
+ f"""
570
+ Agent: {agent['name']} ({state.emojis.get(agent['name'], '👤')})
571
+ - Type: {agent['type']}
572
+ - Goals: {agent['attributes']['goals']}
573
+ - Decision Strategy: {agent['attributes']['decision_strategy']}
574
+ - Cooperation: {agent['attributes']['cooperation_tendency']}/10
575
+ - Aggression: {agent['attributes']['aggression_level']}/10
576
+ - Resources: {agent['attributes']['resources']}
577
+ """
578
+ for agent in state.agents
579
+ ])
580
+
581
+ interactions_detail = "\n".join([
582
+ f"""
583
+ Interaction: {interaction['interaction_type']}
584
+ - Participants: {interaction['participants']}
585
+ - Conditions: {interaction['conditions']}
586
+ - Success: {interaction['outcomes']['positive']}
587
+ - Failure: {interaction['outcomes']['negative']}
588
+ - Environmental Impact: {interaction['outcomes']['environmental_impact']}
589
+ """
590
+ for interaction in state.agent_interactions
591
+ ])
592
+
593
+ environment_detail = f"""
594
+ Environment: {state.environment.get('name', 'Unknown')}
595
+ - Description: {state.environment.get('description', '')}
596
+ - Resources: {state.environment.get('resources', [])}
597
+ - Constraints: {state.environment.get('constraints', [])}
598
+ - Spatial Properties: {state.environment.get('spatial_properties', '')}
599
+ """
600
+
601
+ if isinstance(state, dict):
602
+ research_question = state['research_question']
603
+ expected_outcomes = state['expected_outcomes']
604
+ else:
605
+ research_question = state.research_question
606
+ expected_outcomes = state.expected_outcomes
607
+ user_prompt = f"""
608
+ Create an advanced agent-based simulation for:
609
+
610
+ HYPOTHESIS TO TEST: {state.hypothesis}
611
+
612
+ STORY CONTEXT: {state.story_narrative}
613
+
614
+ RESEARCH QUESTION: {research_question}
615
+
616
+ ENVIRONMENT:
617
+ {environment_detail}
618
+
619
+ AGENTS:
620
+ {agents_detail}
621
+
622
+ INTERACTIONS:
623
+ {interactions_detail}
624
+
625
+ EXPECTED OUTCOMES: {expected_outcomes}
626
+
627
+ Generate a complete HTML file with:
628
+
629
+ 1. CONTROL PANEL (left side):
630
+ - Simulation speed slider (1-1000 fps)
631
+ - Play/Pause/Reset/Step buttons
632
+ - Population controls for each agent type (0-100)
633
+ - Environmental parameter sliders based on the environment
634
+ - Interaction probability controls
635
+ - Scenario buttons for testing different conditions
636
+ - Random seed input for reproducibility
637
+
638
+ 2. MAIN VISUALIZATION (center):
639
+ - Large canvas (400x400) showing the environment. dO NOT MAKE THE CANVAS BIGGER THAN 400x400.
640
+ - Dynamic background representing environmental state
641
+ - Agents with emojis, trails, and status indicators
642
+ - Connection lines showing relationships/interactions
643
+ - Spatial zones for different environment areas
644
+ - Ensure using a single emoji for each agent type. The emojis are provided for each agent.
645
+ - Main visualization must not overlap with control panel or statistics panel.
646
+
647
+ 3. STATISTICS PANEL (right side):
648
+ - STATISTIC PANEL MUST BE UPDATED IN REAL-TIME
649
+ - Real-time metrics tracking hypothesis variables
650
+ - Population counts and survival rates
651
+ - Resource distribution graphs
652
+ - Interaction frequency charts
653
+ - Hypothesis testing results
654
+ - Data export functionality
655
+
656
+ 4. ADVANCED FEATURES:
657
+ - Agent inspector showing detailed state on click
658
+ - Heatmaps for resource density and activity
659
+ - Timeline scrubber for replay functionality
660
+ - Parameter sweeping tools
661
+ - Automated experiment runner
662
+
663
+ Technical Implementation:
664
+ - Use requestAnimationFrame for smooth animation
665
+ - Implement spatial hashing for performance
666
+ - Object-oriented agent and environment classes
667
+ - Event system for interactions
668
+ - Proper initialization and cleanup
669
+ - Error handling and validation
670
+ - Responsive design
671
+
672
+ The simulation should automatically start with meaningful default values and immediately show the hypothesis being tested through agent behaviors.
673
+ """
674
+
675
+ messages = [
676
+ {"role": "system", "content": system_prompt},
677
+ {"role": "user", "content": user_prompt}
678
+ ]
679
+
680
+ state.simulation_code = llm_codestral.chat(messages, temperature=0.2, max_tokens=8000)
681
+
682
+ return state
683
+
684
+ def check_simulation_code_node(state: SimulationState) -> SimulationState:
685
+ """Check and validate the generated simulation code using Codestral"""
686
+
687
+ print(f"---CHECKING SIMULATION CODE (Attempt {state.validation_attempts + 1})---")
688
+
689
+ # Increment validation attempts
690
+ state.validation_attempts += 1
691
+
692
+ system_prompt = """You are an expert code reviewer and HTML/JavaScript validator specializing in agent-based simulations.
693
+
694
+ Your task is to:
695
+ 1. Analyze the provided HTML simulation code for syntax errors, logic issues, and functionality problems
696
+ 2. Check for proper implementation of agent behaviors, interactions, and controls
697
+ 3. Verify that all promised features are implemented correctly
698
+ 4. Identify any missing functionality or broken components
699
+ 5. Provide specific fixes and improvements
700
+
701
+ Focus on:
702
+ - HTML/CSS/JavaScript syntax validation
703
+ - Proper agent implementation with movement, interactions, and behaviors
704
+ - Working control panels with functional sliders and buttons
705
+ - Real-time statistics and data display
706
+ - Canvas rendering and animation loops
707
+ - Event handling and user interactions
708
+ - Performance and efficiency issues
709
+ """
710
+
711
+ # Extract HTML code if it's wrapped in markdown
712
+ code_to_check = state.simulation_code
713
+ if '```html' in code_to_check:
714
+ html_match = re.search(r'```html\n(.*?)\n```', code_to_check, re.DOTALL)
715
+ if html_match:
716
+ code_to_check = html_match.group(1)
717
+
718
+ user_prompt = f"""
719
+ Analyze this agent-based simulation code and identify any issues:
720
+
721
+ CODE TO CHECK:
722
+ {code_to_check}
723
+
724
+ EXPECTED FEATURES (check if properly implemented):
725
+ - Hypothesis: {state.hypothesis}
726
+ - Agents: {[agent['name'] for agent in state.agents]}
727
+ - Emojis: {state.emojis}
728
+ - Environment: {state.environment.get('name', 'Unknown')}
729
+ - Interactions: {[interaction['interaction_type'] for interaction in state.agent_interactions]}
730
+
731
+ Previous validation attempts: {state.validation_attempts - 1}
732
+ Previous errors found: {state.code_errors}
733
+
734
+ Provide a comprehensive JSON response:
735
+ {{
736
+ "is_valid": boolean,
737
+ "syntax_errors": ["list of syntax errors found"],
738
+ "logic_errors": ["list of logic/functionality errors"],
739
+ "missing_features": ["list of features that should be implemented but are missing"],
740
+ "performance_issues": ["list of performance problems"],
741
+ "agent_issues": ["specific problems with agent implementation"],
742
+ "interaction_issues": ["problems with agent interactions"],
743
+ "control_issues": ["problems with UI controls"],
744
+ "overall_assessment": "summary of code quality and functionality"
745
+ }}
746
+ """
747
+
748
+ messages = [
749
+ {"role": "system", "content": system_prompt},
750
+ {"role": "user", "content": user_prompt}
751
+ ]
752
+
753
+ response = llm_codestral.chat(messages, temperature=0.3)
754
+
755
+ try:
756
+ json_match = re.search(r'\{.*\}', response, re.DOTALL)
757
+ if json_match:
758
+ validation_result = json.loads(json_match.group())
759
+
760
+ # Collect all errors
761
+ all_errors = (
762
+ validation_result.get("syntax_errors", []) +
763
+ validation_result.get("logic_errors", []) +
764
+ validation_result.get("missing_features", []) +
765
+ validation_result.get("performance_issues", []) +
766
+ validation_result.get("agent_issues", []) +
767
+ validation_result.get("interaction_issues", []) +
768
+ validation_result.get("control_issues", [])
769
+ )
770
+
771
+ state.code_errors = all_errors
772
+ state.code_is_valid = validation_result.get("is_valid", False) and len(all_errors) == 0
773
+
774
+ print(f"Code validation result: Valid={state.code_is_valid}, Errors found: {len(all_errors)}")
775
+
776
+ else:
777
+ state.code_errors = ["Failed to parse validation response"]
778
+ state.code_is_valid = False
779
+
780
+ except Exception as e:
781
+ state.code_errors = [f"Validation error: {str(e)}"]
782
+ state.code_is_valid = False
783
+
784
+ return state
785
+
786
+ def fix_simulation_code_node(state: SimulationState) -> SimulationState:
787
+ """Fix the simulation code based on validation errors"""
788
+
789
+ print(f"---FIXING SIMULATION CODE (Attempt {state.validation_attempts})---")
790
+
791
+ system_prompt = """You are an expert programmer specializing in HTML5 agent-based simulations.
792
+
793
+ Your task is to fix the provided simulation code based on the identified errors and issues.
794
+ Focus on:
795
+ 1. Fixing syntax errors and logic problems
796
+ 2. Implementing missing agent behaviors and interactions
797
+ 3. Ensuring proper canvas rendering and animation
798
+ 4. Making sure all UI controls work correctly
799
+ 5. Implementing real-time statistics updates
800
+ 6. Optimizing performance issues
801
+
802
+ Provide ONLY the corrected HTML code without any explanations or markdown formatting.
803
+ """
804
+
805
+ # Extract HTML code if it's wrapped in markdown
806
+ current_code = state.simulation_code
807
+ if '```html' in current_code:
808
+ html_match = re.search(r'```html\n(.*?)\n```', current_code, re.DOTALL)
809
+ if html_match:
810
+ current_code = html_match.group(1)
811
+
812
+ errors_text = "\n".join([f"- {error}" for error in state.code_errors])
813
+
814
+ user_prompt = f"""
815
+ Fix this agent-based simulation code based on the identified errors:
816
+
817
+ CURRENT CODE:
818
+ {current_code}
819
+
820
+ ERRORS TO FIX:
821
+ {errors_text}
822
+
823
+ REQUIREMENTS TO MAINTAIN:
824
+ - Hypothesis: {state.hypothesis}
825
+ - Agents: {[agent['name'] for agent in state.agents]}
826
+ - Emojis: {state.emojis}
827
+ - Environment: {state.environment.get('name', 'Unknown')}
828
+ - Canvas size: 400x400 maximum
829
+ - Real-time statistics panel
830
+ - Working control panels
831
+ - Proper agent movement and interactions
832
+
833
+ Provide the complete fixed HTML code:
834
+ """
835
+
836
+ messages = [
837
+ {"role": "system", "content": system_prompt},
838
+ {"role": "user", "content": user_prompt}
839
+ ]
840
+
841
+ fixed_code = llm_codestral.chat(messages, temperature=0.2, max_tokens=8000)
842
+
843
+ # Clean up the response to ensure it's proper HTML
844
+ if '```html' in fixed_code:
845
+ html_match = re.search(r'```html\n(.*?)\n```', fixed_code, re.DOTALL)
846
+ if html_match:
847
+ fixed_code = html_match.group(1)
848
+
849
+ state.simulation_code = fixed_code
850
+
851
+ return state
852
+
853
+ def create_workflow() -> StateGraph:
854
+ """Create the enhanced LangGraph workflow with code validation"""
855
+
856
+ workflow = StateGraph(SimulationState)
857
+
858
+ # Add all nodes
859
+ workflow.add_node("extract_content", extract_content_node)
860
+ workflow.add_node("process_image", process_image_node)
861
+ workflow.add_node("analyze_content", analyze_content_node)
862
+ workflow.add_node("develop_hypothesis", develop_hypothesis_node)
863
+ workflow.add_node("generate_emojis", generate_emojis_node)
864
+ workflow.add_node("generate_simulation", generate_simulation_node)
865
+ workflow.add_node("check_simulation_code", check_simulation_code_node)
866
+ workflow.add_node("fix_simulation_code", fix_simulation_code_node)
867
+
868
+ # Define the enhanced flow
869
+ workflow.set_entry_point("extract_content")
870
+
871
+ # Conditional routing based on content type
872
+ def route_content(state: SimulationState) -> str:
873
+ if state.content_type == "image":
874
+ return "process_image"
875
+ else:
876
+ state.content_type = "article"
877
+ return "analyze_content"
878
+
879
+ # Conditional routing for code validation
880
+ def should_fix_code(state: SimulationState) -> str:
881
+ if state.code_is_valid or state.validation_attempts >= state.max_validation_attempts:
882
+ return "end"
883
+ else:
884
+ return "fix_code"
885
+
886
+ workflow.add_conditional_edges(
887
+ "extract_content",
888
+ route_content,
889
+ {
890
+ "process_image": "process_image",
891
+ "analyze_content": "analyze_content"
892
+ }
893
+ )
894
+
895
+ # Enhanced workflow: analyze -> hypothesis -> emojis -> simulation -> validation loop
896
+ workflow.add_edge("process_image", "analyze_content")
897
+ workflow.add_edge("analyze_content", "develop_hypothesis")
898
+ workflow.add_edge("develop_hypothesis", "generate_emojis")
899
+ workflow.add_edge("generate_emojis", "generate_simulation")
900
+ workflow.add_edge("generate_simulation", "check_simulation_code")
901
+
902
+ # Conditional edge for validation loop
903
+ workflow.add_conditional_edges(
904
+ "check_simulation_code",
905
+ should_fix_code,
906
+ {
907
+ "fix_code": "fix_simulation_code",
908
+ "end": END
909
+ }
910
+ )
911
+
912
+ # After fixing code, check again
913
+ workflow.add_edge("fix_simulation_code", "check_simulation_code")
914
+
915
+ return workflow
916
+
917
+ # Create workflow and app
918
+ workflow = create_workflow()
919
+ memory = SqliteSaver.from_conn_string(":memory:")
920
+ app = workflow.compile()
921
+
922
+ def save_graph_as_png() -> bytes:
923
+ """Save the LangGraph mermaid graph as PNG"""
924
+ try:
925
+ return app.get_graph().draw_mermaid_png(
926
+ draw_method=MermaidDrawMethod.API,
927
+ background_color="white",
928
+ padding=10
929
+ )
930
+ except Exception as e:
931
+ print(f"Error saving graph: {e}")
932
+ return None
933
+
934
+ def create_gradio_interface():
935
+ """Create the enhanced Gradio interface"""
936
+
937
+ with gr.Blocks(title="Advanced Social Network Simulation Generator", theme=gr.themes.Soft()) as demo:
938
+ gr.Markdown("# 🌐 AI-Powered Advanced Social Network Simulation Generator")
939
+ gr.Markdown("### 📚 Using Mistral Models:")
940
+ for _, model in MistralModelHub(api_key=os.getenv("MISTRAL_API_KEY")).models.items():
941
+ gr.Markdown(f"- {model.model}")
942
+ gr.Markdown("Generate advanced NetLogo-style social network simulations with scientific hypothesis testing from articles or images using AI agents.")
943
+ gr.Markdown("### ✨ NEW: Code validation with up to 3 retry attempts using Codestral")
944
+
945
+ gr.Markdown("### 📚 Examples:")
946
+ gr.Markdown("- https://www.ansa.it/sito/notizie/mondo/nordamerica")
947
+ gr.Markdown("- https://www.ansa.it/sito/notizie/mondo/nordamerica/2025/06/10/dilagano-le-proteste-negli-usa150-arresti-a-san-francisco_3d6b794b-462c-4dd1-9c46-33ecf2bc5790.html")
948
+ gr.Markdown("- https://theonion.com/trump-issues-executive-order-reversing-all-vasectomies/")
949
+
950
+ with gr.Row():
951
+ with gr.Column(scale=2):
952
+ # Input section
953
+ url_input = gr.Textbox(
954
+ label="🔗 URL",
955
+ placeholder="Enter URL to article or image...",
956
+ lines=1
957
+ )
958
+
959
+ extract_btn = gr.Button("📄 Extract Content", variant="primary")
960
+
961
+ # Content display
962
+ content_display = gr.Markdown(label="📋 Extracted Content")
963
+
964
+ # Editable summary and agents
965
+ with gr.Group():
966
+ gr.Markdown("### ✏️ Edit Summary and Analysis")
967
+ summary_edit = gr.Textbox(
968
+ label="Summary",
969
+ lines=3,
970
+ placeholder="Summary will appear here..."
971
+ )
972
+
973
+ agents_edit = gr.Textbox(
974
+ label="Agents (JSON format - Edit as needed)",
975
+ lines=8,
976
+ placeholder="Agents JSON will appear here...",
977
+ info="Edit the JSON to modify agents before generating simulation"
978
+ )
979
+
980
+ behaviors_edit = gr.Textbox(
981
+ label="Behaviors (JSON format - Edit as needed)",
982
+ lines=4,
983
+ placeholder="Behaviors JSON will appear here...",
984
+ info="Edit the JSON to modify behaviors before generating simulation"
985
+ )
986
+
987
+ # Enhanced fields
988
+ with gr.Group():
989
+ gr.Markdown("### 🔬 Research Hypothesis")
990
+ hypothesis_edit = gr.Textbox(
991
+ label="Research Hypothesis",
992
+ lines=3,
993
+ placeholder="Hypothesis will appear here..."
994
+ )
995
+
996
+ story_edit = gr.Textbox(
997
+ label="Story Narrative",
998
+ lines=4,
999
+ placeholder="Story narrative will appear here..."
1000
+ )
1001
+
1002
+ environment_edit = gr.Textbox(
1003
+ label="Environment (JSON format)",
1004
+ lines=6,
1005
+ placeholder="Environment JSON will appear here..."
1006
+ )
1007
+
1008
+ interactions_edit = gr.Textbox(
1009
+ label="Agent Interactions (JSON format)",
1010
+ lines=8,
1011
+ placeholder="Interactions JSON will appear here..."
1012
+ )
1013
+
1014
+ # Emoji selection
1015
+ with gr.Group():
1016
+ gr.Markdown("### 🎭 Agent Emojis")
1017
+ emojis_edit = gr.Textbox(
1018
+ label="Emojis for Agents (JSON format - Edit as needed)",
1019
+ lines=4,
1020
+ placeholder="Emojis JSON will appear here...",
1021
+ info="Edit the JSON to modify emojis before generating simulation"
1022
+ )
1023
+
1024
+ generate_emojis_btn = gr.Button("🎨 Generate/Regenerate Emojis")
1025
+
1026
+ # Generate simulation
1027
+ confirm_btn = gr.Button("🚀 Generate Advanced Simulation", variant="primary", size="lg")
1028
+
1029
+ with gr.Column(scale=3):
1030
+ # Results section
1031
+ with gr.Tabs():
1032
+ with gr.TabItem("🎮 Simulation"):
1033
+ simulation_frame = gr.HTML(
1034
+ label="Advanced Social Network Simulation",
1035
+ value="<div style='padding: 20px; text-align: center; color: #666;'>Advanced simulation will appear here after generation...</div>"
1036
+ )
1037
+
1038
+ with gr.TabItem("💻 Code Editor"):
1039
+ simulation_code_editor = gr.Code(
1040
+ label="Generated Simulation Code (Editable)",
1041
+ language="html",
1042
+ value="<!-- Advanced simulation code will appear here... -->",
1043
+ lines=20,
1044
+ interactive=True
1045
+ )
1046
+
1047
+ update_simulation_btn = gr.Button("🔄 Update Simulation from Code", variant="secondary")
1048
+
1049
+ with gr.TabItem("📖 Documentation"):
1050
+ documentation_display = gr.Markdown(
1051
+ label="Scientific Simulation Documentation",
1052
+ value="Comprehensive documentation will be generated with the simulation..."
1053
+ )
1054
+
1055
+ with gr.TabItem("🗺️ Workflow Graph"):
1056
+ graph_display = gr.Image(
1057
+ label="Enhanced LangGraph Workflow",
1058
+ value=None
1059
+ )
1060
+
1061
+ # State management
1062
+ state = gr.State(SimulationState())
1063
+
1064
+ # Event handlers
1065
+ def extract_content(url, current_state):
1066
+ current_state = SimulationState(url=url)
1067
+ config = {"configurable": {"thread_id": "simulation_thread"}}
1068
+
1069
+ print("Current state:")
1070
+ print(f"{type(current_state)=}")
1071
+ if isinstance(current_state, SimulationState):
1072
+ current_state_dict = current_state.to_dict()
1073
+ else:
1074
+ current_state_dict = current_state
1075
+ for key, value in current_state_dict.items():
1076
+ print(f"{key}: {str(value)[:50]}")
1077
+
1078
+ # Run extraction and analysis
1079
+ result = app.invoke(current_state, config)
1080
+
1081
+ if not isinstance(result, dict):
1082
+ if isinstance(result, SimulationState):
1083
+ result = result.to_dict()
1084
+ else:
1085
+ raise ValueError(f"Unexpected result type: {type(result)}")
1086
+
1087
+ print("Result:")
1088
+ print(f"{type(result)=}")
1089
+ for key, value in result.items():
1090
+ print(f"{key}: {str(value)[:50]}")
1091
+
1092
+ # Update displays with enhanced content
1093
+ cleaned_content = re.sub(r'\s+', ' ', result['content']).strip()
1094
+ content_md = f"**Content Type:** {result['content_type']}\n\n**Content:**\n{cleaned_content[:1000]}..."
1095
+
1096
+ return (
1097
+ result, # Updated state
1098
+ content_md, # Content display
1099
+ result['summary'], # Summary
1100
+ json.dumps(result['agents'], indent=2), # Agents as JSON string
1101
+ json.dumps(result['behaviors'], indent=2), # Behaviors as JSON string
1102
+ result['hypothesis'], # New: Hypothesis
1103
+ result['story_narrative'], # New: Story
1104
+ json.dumps(result['environment'], indent=2), # New: Environment
1105
+ json.dumps(result['agent_interactions'], indent=2), # New: Interactions
1106
+ json.dumps(result['emojis'], indent=2, ensure_ascii=False) # Emojis as JSON string
1107
+ )
1108
+
1109
+ def generate_emojis_only(current_state, agents_json):
1110
+ # Update current state with potentially edited agents
1111
+ try:
1112
+ current_state.agents = json.loads(agents_json) if agents_json else []
1113
+ except:
1114
+ pass # Keep existing agents if JSON parsing fails
1115
+
1116
+ if not current_state.agents:
1117
+ return current_state, "{}"
1118
+
1119
+ current_state = generate_emojis_node(current_state)
1120
+ return current_state, json.dumps(current_state.emojis, indent=2, ensure_ascii=False)
1121
+
1122
+ def generate_final_simulation(current_state, summary, agents_json, behaviors_json, hypothesis, story, environment_json, interactions_json, emojis_json):
1123
+ # Update state with edited values
1124
+ current_state.summary = summary
1125
+ current_state.hypothesis = hypothesis
1126
+ current_state.story_narrative = story
1127
+
1128
+ try:
1129
+ current_state.agents = json.loads(agents_json) if agents_json else []
1130
+ except json.JSONDecodeError as e:
1131
+ print(f"Error parsing agents JSON: {e}")
1132
+ current_state.agents = []
1133
+
1134
+ try:
1135
+ current_state.behaviors = json.loads(behaviors_json) if behaviors_json else []
1136
+ except json.JSONDecodeError as e:
1137
+ print(f"Error parsing behaviors JSON: {e}")
1138
+ current_state.behaviors = []
1139
+
1140
+ try:
1141
+ current_state.environment = json.loads(environment_json) if environment_json else {}
1142
+ except json.JSONDecodeError as e:
1143
+ print(f"Error parsing environment JSON: {e}")
1144
+ current_state.environment = {}
1145
+
1146
+ try:
1147
+ current_state.agent_interactions = json.loads(interactions_json) if interactions_json else []
1148
+ except json.JSONDecodeError as e:
1149
+ print(f"Error parsing interactions JSON: {e}")
1150
+ current_state.agent_interactions = []
1151
+
1152
+ try:
1153
+ current_state.emojis = json.loads(emojis_json) if emojis_json else {}
1154
+ except json.JSONDecodeError as e:
1155
+ print(f"Error parsing emojis JSON: {e}")
1156
+ current_state.emojis = {}
1157
+
1158
+ # Reset validation state for new generation
1159
+ current_state.validation_attempts = 0
1160
+ current_state.code_errors = []
1161
+ current_state.code_is_valid = False
1162
+
1163
+ # Generate simulation (now includes validation loop)
1164
+ current_state = generate_simulation_node(current_state)
1165
+
1166
+ # The validation loop is now handled in the workflow
1167
+ config = {"configurable": {"thread_id": "validation_thread"}}
1168
+ validated_result = app.invoke(current_state, config)
1169
+
1170
+ if isinstance(validated_result, dict):
1171
+ current_state.simulation_code = validated_result.get('simulation_code', current_state.simulation_code)
1172
+ current_state.validation_attempts = validated_result.get('validation_attempts', 0)
1173
+ current_state.code_errors = validated_result.get('code_errors', [])
1174
+ current_state.code_is_valid = validated_result.get('code_is_valid', False)
1175
+
1176
+ # Generate documentation only after validation is complete
1177
+ if isinstance(current_state, dict):
1178
+ research_question = current_state['research_question']
1179
+ expected_outcomes = current_state['expected_outcomes']
1180
+ else:
1181
+ research_question = current_state.research_question
1182
+ expected_outcomes = current_state.expected_outcomes
1183
+
1184
+ environment_detail = f"""
1185
+ Environment: {current_state.environment.get('name', 'Unknown')}
1186
+ - Description: {current_state.environment.get('description', '')}
1187
+ - Resources: {current_state.environment.get('resources', [])}
1188
+ - Constraints: {current_state.environment.get('constraints', [])}
1189
+ - Spatial Properties: {current_state.environment.get('spatial_properties', '')}
1190
+ """
1191
+
1192
+ agents_detail = "\n".join([
1193
+ f"""
1194
+ Agent: {agent['name']} ({current_state.emojis.get(agent['name'], '👤')})
1195
+ - Type: {agent['type']}
1196
+ - Goals: {agent['attributes']['goals']}
1197
+ """
1198
+ for agent in current_state.agents
1199
+ ])
1200
+
1201
+ interactions_detail = "\n".join([
1202
+ f"""
1203
+ Interaction: {interaction['interaction_type']}
1204
+ - Participants: {interaction['participants']}
1205
+ """
1206
+ for interaction in current_state.agent_interactions
1207
+ ])
1208
+
1209
+ doc_prompt = f"""
1210
+ Create comprehensive documentation for this social network simulation based on:
1211
+
1212
+ HYPOTHESIS: {current_state.hypothesis}
1213
+ STORY: {current_state.story_narrative}
1214
+ RESEARCH QUESTION: {research_question}
1215
+ ENVIRONMENT: {environment_detail}
1216
+ AGENTS: {agents_detail}
1217
+ INTERACTIONS: {interactions_detail}
1218
+ EXPECTED OUTCOMES: {expected_outcomes}
1219
+
1220
+ VALIDATION RESULTS:
1221
+ - Code validation attempts: {current_state.validation_attempts}
1222
+ - Code is valid: {current_state.code_is_valid}
1223
+ - Errors found: {current_state.code_errors}
1224
+
1225
+ Include:
1226
+ 1. Overview and scientific purpose
1227
+ 2. Hypothesis explanation and testing methodology
1228
+ 3. Agent descriptions and behavioral models
1229
+ 4. Environment and interaction explanations
1230
+ 5. How to use advanced controls
1231
+ 6. Parameters and their scientific effects
1232
+ 7. Interpretation of results and statistical measures
1233
+ 8. Educational objectives and learning outcomes
1234
+ 9. Code quality assurance and validation process
1235
+
1236
+ Make it accessible for users while being scientifically rigorous.
1237
+ """
1238
+
1239
+ doc_messages = [
1240
+ {"role": "system", "content": "You are a scientific technical writer creating educational documentation."},
1241
+ {"role": "user", "content": doc_prompt}
1242
+ ]
1243
+
1244
+ current_state.documentation = llm_nemo.chat(doc_messages, temperature=0.4)
1245
+
1246
+ # Extract HTML from markdown for the iframe
1247
+ simulation_html = current_state.simulation_code
1248
+ if '```html' in simulation_html:
1249
+ # Extract content between ``````
1250
+ html_match = re.search(r'```html\n(.*?)\n```', simulation_html, re.DOTALL)
1251
+ if html_match:
1252
+ simulation_html = html_match.group(1)
1253
+ # Use update_preview to wrap the HTML
1254
+ simulation_preview = update_preview(simulation_html)
1255
+
1256
+ # Get workflow graph
1257
+ graph_png = None
1258
+ try:
1259
+ graph_bytes = save_graph_as_png()
1260
+ if graph_bytes:
1261
+ from PIL import Image
1262
+ import io
1263
+ graph_png = Image.open(io.BytesIO(graph_bytes))
1264
+ except Exception as e:
1265
+ print(f"Error processing graph image: {e}")
1266
+ graph_png = None
1267
+
1268
+ return (
1269
+ current_state,
1270
+ simulation_preview, # Clean HTML for iframe (now wrapped)
1271
+ current_state.simulation_code, # Original markdown for code editor
1272
+ current_state.documentation,
1273
+ graph_png
1274
+ )
1275
+
1276
+ def update_preview(code):
1277
+ """Update the iframe preview using srcdoc for in-memory HTML preview."""
1278
+ if not code.strip():
1279
+ return "<div style='text-align:center; padding:50px;'>No code to preview</div>"
1280
+ # Escape quotes for srcdoc
1281
+ safe_code = code.replace('"', '&quot;').replace("'", "&#39;")
1282
+ return f'''<iframe style="width:100%;height:900px;border:1px solid #ccc;" srcdoc="{safe_code}"></iframe>'''
1283
+
1284
+ def update_simulation_from_code(current_state, code):
1285
+ """Update simulation iframe from manually edited code"""
1286
+ current_state.simulation_code = code
1287
+ # Extract HTML from markdown if needed
1288
+
1289
+ simulation_html = code
1290
+ if '```html' in code:
1291
+ html_match = re.search(r'```html\n(.*?)\n```', code, re.DOTALL)
1292
+ if html_match:
1293
+ simulation_html = html_match.group(1)
1294
+ # Use update_preview to wrap the HTML
1295
+ return current_state, update_preview(simulation_html)
1296
+
1297
+ # Wire up events
1298
+ extract_btn.click(
1299
+ extract_content,
1300
+ inputs=[url_input, state],
1301
+ outputs=[state, content_display, summary_edit, agents_edit, behaviors_edit, hypothesis_edit, story_edit, environment_edit, interactions_edit, emojis_edit]
1302
+ )
1303
+
1304
+ generate_emojis_btn.click(
1305
+ generate_emojis_only,
1306
+ inputs=[state, agents_edit],
1307
+ outputs=[state, emojis_edit]
1308
+ )
1309
+
1310
+ confirm_btn.click(
1311
+ generate_final_simulation,
1312
+ inputs=[state, summary_edit, agents_edit, behaviors_edit, hypothesis_edit, story_edit, environment_edit, interactions_edit, emojis_edit],
1313
+ outputs=[state, simulation_frame, simulation_code_editor, documentation_display, graph_display]
1314
+ )
1315
+
1316
+ update_simulation_btn.click(
1317
+ update_simulation_from_code,
1318
+ inputs=[state, simulation_code_editor],
1319
+ outputs=[state, simulation_frame]
1320
+ )
1321
+
1322
+ return demo
1323
+
1324
+ # Launch the application
1325
+ if __name__ == "__main__":
1326
+ demo = create_gradio_interface()
1327
+ demo.launch(share=False, debug=False)