JawadBenali commited on
Commit
b10623f
·
1 Parent(s): 8fb64cb

new commit

Browse files
app.py CHANGED
@@ -170,43 +170,173 @@ def extract_file_list(zip_path):
170
  )
171
 
172
  # --- TAB 1: SINGLE FILE ANALYSIS ---
173
- def process_code_snippet(code_snippet: str, enrich_types: bool = False):
174
- """Analyze single Python code snippet and generate UML diagram"""
 
 
 
 
 
175
  if not code_snippet.strip():
176
- return "⚠️ Please enter some code.", None, gr.update(visible=True, value="⚠️ No Input")
 
 
 
 
 
 
 
 
177
 
178
  try:
179
- # Parse code with AST
180
  tree = ast.parse(code_snippet)
181
  visitor = ArchitectureVisitor()
182
  visitor.visit(tree)
183
 
184
  if not visitor.structure:
185
- return "⚠️ No classes/functions found.", None, gr.update(visible=True, value="⚠️ No Structure")
 
 
 
 
 
 
 
 
186
 
187
- # Optional AI type enrichment
 
188
  if enrich_types:
 
 
 
 
 
 
 
 
 
 
189
  try:
190
- llm = _llm_singleton.get_client(preferred_provider="openai", temperature=0.0)
191
- if llm:
192
- enricher = FastTypeEnricher(llm)
193
- visitor.structure = enricher.enrich(code_snippet, visitor.structure)
194
- logger.info("✓ Type enrichment complete")
195
  except Exception as e:
196
  logger.warning(f"Type enrichment failed: {e}")
197
 
198
- # Convert to PlantUML
199
  converter = DeterministicPlantUMLConverter()
200
- puml_text = converter.convert(visitor.structure)
201
- text, image = render_plantuml(puml_text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
- return text, image, gr.update(visible=True, value="✅ Analysis Complete!")
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  except SyntaxError as se:
206
- return f"❌ Syntax Error: {se}", None, gr.update(visible=True, value="❌ Syntax Error")
 
 
 
 
 
 
 
 
207
  except Exception as e:
208
  logger.error(f"Code analysis error: {e}")
209
- return f"❌ Error: {e}", None, gr.update(visible=True, value="❌ Failed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
  # --- TAB 2: PROJECT MAP ---
212
 
@@ -1147,49 +1277,123 @@ with gr.Blocks(
1147
  with gr.Tabs():
1148
 
1149
  # TAB 1: Single File
1150
- with gr.Tab("📄 Single File Analysis" , id = 0):
1151
- gr.HTML('<div class="info-card"><strong>💡 Quick Analysis:</strong> Paste Python code to generate instant UML class diagram with optional AI-powered type enrichment.</div>')
1152
-
1153
- with gr.Row():
1154
- # LEFT COLUMN - Inputs
1155
- with gr.Column(scale=1):
1156
- code_input = gr.Code(
1157
- language="python",
1158
- label="Python Code",
1159
- lines=20,
1160
- elem_classes=["code-input"]
1161
- )
 
 
 
 
 
1162
  enrich_checkbox = gr.Checkbox(
1163
- label="✨ AI Type Enrichment",
1164
  value=False,
1165
- info="Use AI to infer missing type hints"
1166
  )
1167
- analyze_btn = gr.Button(
1168
- "🚀 Analyze Code",
1169
- variant="primary",
1170
- size="lg",
1171
- elem_classes=["primary-button"]
1172
  )
 
 
 
 
 
 
 
1173
 
1174
- # RIGHT COLUMN - Results
1175
- with gr.Column(scale=2):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1176
  with gr.Group(elem_classes=["output-card"]):
1177
- status_banner_1 = gr.Markdown(visible=False, elem_classes=["banner"])
1178
- img_output_1 = gr.Image(
1179
- label="📊 Class Diagram",
1180
  type="pil",
1181
  elem_classes=["diagram-container"]
1182
  )
 
 
1183
 
1184
- with gr.Accordion("📝 PlantUML Source", open=False):
1185
- text_output_1 = gr.Code(language="markdown", lines=10)
1186
-
 
 
 
 
 
 
 
 
 
1187
  analyze_btn.click(
1188
- fn=process_code_snippet,
1189
- inputs=[code_input, enrich_checkbox],
1190
- outputs=[text_output_1, img_output_1, status_banner_1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1191
  )
1192
-
1193
 
1194
  # TAB 2: Project Map
1195
  with gr.Tab("📂 Project Map", id=1):
 
170
  )
171
 
172
  # --- TAB 1: SINGLE FILE ANALYSIS ---
173
+ def process_code_snippet_with_patterns(code_snippet: str, enrich_types: bool = False, provider: str = "sambanova"):
174
+ """
175
+ Analyze single Python code snippet:
176
+ 1. Detect design patterns and recommendations
177
+ 2. Generate current UML diagram
178
+ 3. Generate before/after diagrams for recommendations
179
+ """
180
  if not code_snippet.strip():
181
+ return (
182
+ "⚠️ Please enter some code.",
183
+ None, None,
184
+ gr.update(visible=True, value="⚠️ No Input"),
185
+ gr.update(visible=False),
186
+ gr.update(visible=False),
187
+ gr.update(choices=[]),
188
+ None, None, "", ""
189
+ )
190
 
191
  try:
192
+ # STEP 1: Parse code with AST
193
  tree = ast.parse(code_snippet)
194
  visitor = ArchitectureVisitor()
195
  visitor.visit(tree)
196
 
197
  if not visitor.structure:
198
+ return (
199
+ "⚠️ No classes/functions found.",
200
+ None, None,
201
+ gr.update(visible=True, value="⚠️ No Structure"),
202
+ gr.update(visible=False),
203
+ gr.update(visible=False),
204
+ gr.update(choices=[]),
205
+ None, None, "", ""
206
+ )
207
 
208
+ # STEP 2: Pattern Detection
209
+ llm = None
210
  if enrich_types:
211
+ llm = _llm_singleton.get_client(preferred_provider=provider, temperature=0.0)
212
+
213
+ service = PatternDetectionService(llm=llm)
214
+ pattern_result = service.analyze_code(code_snippet, enrich=enrich_types)
215
+
216
+ # Format pattern report
217
+ pattern_report = service.format_report(pattern_result)
218
+
219
+ # STEP 3: Generate current UML diagram
220
+ if enrich_types and llm:
221
  try:
222
+ enricher = FastTypeEnricher(llm)
223
+ visitor.structure = enricher.enrich(code_snippet, visitor.structure)
224
+ logger.info("✓ Type enrichment complete")
 
 
225
  except Exception as e:
226
  logger.warning(f"Type enrichment failed: {e}")
227
 
 
228
  converter = DeterministicPlantUMLConverter()
229
+ current_puml = converter.convert(visitor.structure)
230
+ _, current_image = render_plantuml(current_puml)
231
+
232
+ # STEP 4: Generate before/after UML for recommendations
233
+ recommendation_choices = []
234
+ first_before_img = None
235
+ first_after_img = None
236
+ first_before_uml = ""
237
+ first_after_uml = ""
238
+
239
+ if pattern_result['recommendations']:
240
+ for i, rec_dict in enumerate(pattern_result['recommendations']):
241
+ rec = PatternRecommendation(**rec_dict)
242
+ recommendation_choices.append(f"{i+1}. {rec.pattern} - {rec.location}")
243
+
244
+ # Generate UML for first recommendation
245
+ if i == 0:
246
+ recommender = service.recommender
247
+ before_uml, after_uml = recommender.generate_recommendation_uml(rec, visitor.structure, code_snippet)
248
+
249
+ first_before_uml = before_uml
250
+ first_after_uml = after_uml
251
+
252
+ # Render to images
253
+ _, first_before_img = render_plantuml(before_uml)
254
+ _, first_after_img = render_plantuml(after_uml)
255
+
256
+ # Show pattern sections if recommendations exist
257
+ show_patterns = len(pattern_result['recommendations']) > 0
258
 
259
+ status_msg = f"✅ Found {pattern_result['summary']['total_patterns']} pattern(s) • {pattern_result['summary']['total_recommendations']} recommendation(s)"
260
 
261
+ return (
262
+ pattern_report,
263
+ current_puml,
264
+ current_image,
265
+ gr.update(visible=True, value=status_msg),
266
+ gr.update(visible=show_patterns), # Pattern section
267
+ gr.update(visible=show_patterns), # Comparison section
268
+ gr.update(choices=recommendation_choices, value=recommendation_choices[0] if recommendation_choices else None),
269
+ first_before_img,
270
+ first_after_img,
271
+ first_before_uml,
272
+ first_after_uml
273
+ )
274
+
275
  except SyntaxError as se:
276
+ return (
277
+ f"❌ Syntax Error: {se}",
278
+ None, None,
279
+ gr.update(visible=True, value="❌ Syntax Error"),
280
+ gr.update(visible=False),
281
+ gr.update(visible=False),
282
+ gr.update(choices=[]),
283
+ None, None, "", ""
284
+ )
285
  except Exception as e:
286
  logger.error(f"Code analysis error: {e}")
287
+ import traceback
288
+ error_detail = traceback.format_exc()
289
+ return (
290
+ f"❌ Error: {e}\n\nDetails:\n{error_detail[:500]}",
291
+ None, None,
292
+ gr.update(visible=True, value="❌ Failed"),
293
+ gr.update(visible=False),
294
+ gr.update(visible=False),
295
+ gr.update(choices=[]),
296
+ None, None, "", ""
297
+ )
298
+
299
+ def update_single_file_recommendation(selected_rec, code, enrich, provider):
300
+ """Update UML diagrams when user selects different recommendation in single file tab"""
301
+ if not selected_rec or not code:
302
+ return None, None, "", ""
303
+
304
+ try:
305
+ # Parse selection
306
+ rec_index = int(selected_rec.split(".")[0]) - 1
307
+
308
+ # Re-analyze
309
+ llm = None
310
+ if enrich:
311
+ llm = _llm_singleton.get_client(preferred_provider=provider, temperature=0.0)
312
+
313
+ # Parse structure
314
+ tree = ast.parse(code)
315
+ visitor = ArchitectureVisitor()
316
+ visitor.visit(tree)
317
+
318
+ service = PatternDetectionService(llm=llm)
319
+ result = service.analyze_code(code, enrich=False)
320
+
321
+ if rec_index < len(result['recommendations']):
322
+ rec_dict = result['recommendations'][rec_index]
323
+ rec = PatternRecommendation(**rec_dict)
324
+
325
+ # Generate UML
326
+ recommender = service.recommender
327
+ before_uml, after_uml = recommender.generate_recommendation_uml(rec, visitor.structure, code)
328
+
329
+ # Render to images
330
+ _, before_img = render_plantuml(before_uml)
331
+ _, after_img = render_plantuml(after_uml)
332
+
333
+ return before_img, after_img, before_uml, after_uml
334
+
335
+ except Exception as e:
336
+ logger.error(f"Recommendation update error: {e}")
337
+
338
+ return None, None, "", ""
339
+
340
 
341
  # --- TAB 2: PROJECT MAP ---
342
 
 
1277
  with gr.Tabs():
1278
 
1279
  # TAB 1: Single File
1280
+ with gr.Tab("📄 Single File Analysis", id=0):
1281
+ gr.HTML('<div class="info-card"><strong>💡 Smart Analysis:</strong> Paste Python code to detect design patterns, get recommendations, and see before/after UML visualizations.</div>')
1282
+
1283
+ # Store code for recommendation updates
1284
+ stored_code = gr.State("")
1285
+
1286
+ # --- SECTION 1: CODE INPUT ---
1287
+ with gr.Group(elem_classes=["output-card"]):
1288
+ gr.Markdown("### 💻 Source Code")
1289
+ code_input = gr.Code(
1290
+ language="python",
1291
+ label=None,
1292
+ lines=15,
1293
+ elem_classes=["code-input"],
1294
+ )
1295
+
1296
+ with gr.Row():
1297
  enrich_checkbox = gr.Checkbox(
1298
+ label="✨ AI Enhancement",
1299
  value=False,
1300
+ info="Use AI for type hints"
1301
  )
1302
+ provider_choice = gr.Dropdown(
1303
+ choices=["sambanova", "gemini", "nebius", "openai"],
1304
+ value="sambanova",
1305
+ label="LLM Provider",
1306
+ scale=1
1307
  )
1308
+
1309
+ analyze_btn = gr.Button(
1310
+ "🔍 Analyze Code",
1311
+ variant="primary",
1312
+ size="lg",
1313
+ elem_classes=["primary-button"]
1314
+ )
1315
 
1316
+ # --- SECTION 2: READ ME / RECOMMENDATIONS ---
1317
+ gr.Markdown("### 📖 Analysis & Recommendations")
1318
+ with gr.Group(elem_classes=["output-card"]):
1319
+ status_banner_1 = gr.Markdown(visible=False, elem_classes=["banner"])
1320
+ pattern_report_single = gr.Markdown(
1321
+ value="*Analysis report will appear here after clicking Analyze...*"
1322
+ )
1323
+
1324
+ # --- SECTION 3: UML CODE & DIAGRAM ---
1325
+ gr.Markdown("### 🎨 UML Visualization")
1326
+ with gr.Group(elem_classes=["output-card"]):
1327
+ img_output_1 = gr.Image(
1328
+ label="Class Structure",
1329
+ type="pil",
1330
+ elem_classes=["diagram-container"]
1331
+ )
1332
+ with gr.Accordion("📝 PlantUML Source Code", open=False):
1333
+ text_output_1 = gr.Code(language="markdown", lines=10, label="UML Code")
1334
+
1335
+ # --- HIDDEN SECTION: PATTERN VISUALIZATION (Appears on demand) ---
1336
+ with gr.Row(visible=False) as pattern_uml_section_single:
1337
+ gr.HTML('<div class="info-card" style="margin-top: 2rem;"><strong>💡 Recommended Improvements:</strong> Visual comparison showing how design patterns can improve your code structure.</div>')
1338
+
1339
+ with gr.Row(visible=False) as pattern_selector_section_single:
1340
+ recommendation_dropdown_single = gr.Dropdown(
1341
+ label="📋 Select Recommendation to Visualize",
1342
+ choices=[],
1343
+ interactive=True
1344
+ )
1345
+
1346
+ with gr.Row(visible=False) as pattern_comparison_section_single:
1347
+ with gr.Column(scale=1):
1348
+ gr.Markdown("#### ⚠️ Before (Current Structure)")
1349
  with gr.Group(elem_classes=["output-card"]):
1350
+ pattern_before_img_single = gr.Image(
1351
+ label="Current Design",
 
1352
  type="pil",
1353
  elem_classes=["diagram-container"]
1354
  )
1355
+ with gr.Accordion("📝 PlantUML Code", open=False):
1356
+ pattern_before_uml_single = gr.Code(language="markdown", lines=8)
1357
 
1358
+ with gr.Column(scale=1):
1359
+ gr.Markdown("#### ✅ After (Recommended Pattern)")
1360
+ with gr.Group(elem_classes=["output-card"]):
1361
+ pattern_after_img_single = gr.Image(
1362
+ label="Improved Design",
1363
+ type="pil",
1364
+ elem_classes=["diagram-container"]
1365
+ )
1366
+ with gr.Accordion("📝 PlantUML Code", open=False):
1367
+ pattern_after_uml_single = gr.Code(language="markdown", lines=8)
1368
+
1369
+ # Event handlers
1370
  analyze_btn.click(
1371
+ fn=process_code_snippet_with_patterns,
1372
+ inputs=[code_input, enrich_checkbox, provider_choice],
1373
+ outputs=[
1374
+ pattern_report_single,
1375
+ text_output_1,
1376
+ img_output_1,
1377
+ status_banner_1,
1378
+ pattern_uml_section_single,
1379
+ pattern_comparison_section_single,
1380
+ recommendation_dropdown_single,
1381
+ pattern_before_img_single,
1382
+ pattern_after_img_single,
1383
+ pattern_before_uml_single,
1384
+ pattern_after_uml_single
1385
+ ]
1386
+ ).then(
1387
+ fn=lambda x: x,
1388
+ inputs=code_input,
1389
+ outputs=stored_code
1390
+ )
1391
+
1392
+ recommendation_dropdown_single.change(
1393
+ fn=update_single_file_recommendation,
1394
+ inputs=[recommendation_dropdown_single, stored_code, enrich_checkbox, provider_choice],
1395
+ outputs=[pattern_before_img_single, pattern_after_img_single, pattern_before_uml_single, pattern_after_uml_single]
1396
  )
 
1397
 
1398
  # TAB 2: Project Map
1399
  with gr.Tab("📂 Project Map", id=1):
core/__pycache__/llm_providers.cpython-313.pyc CHANGED
Binary files a/core/__pycache__/llm_providers.cpython-313.pyc and b/core/__pycache__/llm_providers.cpython-313.pyc differ
 
services/pattern_detector.py CHANGED
@@ -340,7 +340,7 @@ class PatternDetectorAST(ast.NodeVisitor):
340
  class PatternRecommender:
341
  """
342
  Analyzes code structure to recommend where patterns would help.
343
- Uses code smells and structural analysis.
344
  """
345
 
346
  def __init__(self, llm=None):
@@ -360,153 +360,117 @@ class PatternRecommender:
360
 
361
  def generate_recommendation_uml(self, recommendation: PatternRecommendation, structure: List[Dict], code: str) -> tuple[str, str]:
362
  """
363
- Generate before/after UML for a recommendation.
364
  Returns: (before_uml, after_uml)
365
  """
366
  if self.llm:
 
367
  return self._generate_ai_uml(recommendation, structure, code)
368
  else:
369
- return self._generate_template_uml(recommendation, structure)
 
370
 
371
- def _generate_template_uml(self, rec: PatternRecommendation, structure: List[Dict]) -> tuple[str, str]:
372
- """Generate template-based UML for recommendations"""
373
 
374
- if rec.pattern == "Strategy":
375
- before = self._strategy_before_uml(rec, structure)
376
- after = self._strategy_after_uml(rec)
377
- return before, after
378
-
379
- elif rec.pattern == "Factory":
380
- before = self._factory_before_uml(rec, structure)
381
- after = self._factory_after_uml(rec)
382
- return before, after
383
-
384
- elif rec.pattern == "Singleton":
385
- before = self._singleton_before_uml(rec, structure)
386
- after = self._singleton_after_uml(rec)
387
- return before, after
388
 
389
- elif rec.pattern == "Observer":
390
- before = self._observer_before_uml(rec, structure)
391
- after = self._observer_after_uml(rec)
392
- return before, after
393
 
394
- return "", ""
395
-
396
- def _strategy_before_uml(self, rec: PatternRecommendation, structure: List[Dict]) -> str:
397
- """Generate BEFORE UML for Strategy pattern recommendation"""
398
- class_name = rec.location
399
 
400
- return f"""@startuml
401
- title Before: {class_name} with Conditionals
 
402
  skinparam classAttributeIconSize 0
403
 
404
  class {class_name} {{
405
- + process(type, data)
406
- - handle_type_a()
407
- - handle_type_b()
408
- - handle_type_c()
409
- }}
 
410
 
411
  note right of {class_name}
412
- Problem: Multiple if/else branches
413
- ❌ Hard to add new types
414
- ❌ Violates Open/Closed Principle
 
 
 
 
 
 
415
  end note
416
 
417
  @enduml"""
418
-
419
- def _strategy_after_uml(self, rec: PatternRecommendation) -> str:
420
- """Generate AFTER UML for Strategy pattern recommendation"""
421
- class_name = rec.location
422
 
423
- return f"""@startuml
424
- title After: Strategy Pattern Applied
 
425
  skinparam classAttributeIconSize 0
426
 
427
- interface ProcessingStrategy {{
428
- + process(data)
429
- }}
430
-
431
- class {class_name} {{
432
- - strategy: ProcessingStrategy
433
- + set_strategy(s: ProcessingStrategy)
434
- + execute(data)
435
- }}
436
-
437
- class StrategyA {{
438
- + process(data)
439
- }}
440
-
441
- class StrategyB {{
442
- + process(data)
443
- }}
444
-
445
- class StrategyC {{
446
- + process(data)
447
- }}
448
-
449
- ProcessingStrategy <|.. StrategyA
450
- ProcessingStrategy <|.. StrategyB
451
- ProcessingStrategy <|.. StrategyC
452
- {class_name} o-- ProcessingStrategy
453
-
454
- note right of ProcessingStrategy
455
- ✅ Easy to add new strategies
456
- ✅ Each strategy is independent
457
- �� Follows Open/Closed Principle
458
- ✅ ~{rec.complexity_reduction}% complexity reduction
459
  end note
460
 
461
  @enduml"""
 
 
462
 
463
- def _factory_before_uml(self, rec: PatternRecommendation, structure: List[Dict]) -> str:
464
- """Generate BEFORE UML for Factory pattern recommendation"""
465
 
466
- return f"""@startuml
467
- title Before: Scattered Object Creation
468
- skinparam classAttributeIconSize 0
469
-
470
- class ClientCode {{
471
- + method1()
472
- + method2()
473
- + method3()
474
  }}
475
 
476
- class ProductA {{
 
 
 
477
  }}
478
 
479
- class ProductB {{
 
480
  }}
481
 
482
- class ProductC {{
 
483
  }}
484
 
485
- ClientCode ..> ProductA : creates directly
486
- ClientCode ..> ProductB : creates directly
487
- ClientCode ..> ProductC : creates directly
488
-
489
- note right of ClientCode
490
- ❌ Object creation scattered everywhere
491
- ❌ Hard to change instantiation logic
492
- ❌ Tight coupling to concrete classes
493
- end note
494
-
495
- @enduml"""
496
-
497
- def _factory_after_uml(self, rec: PatternRecommendation) -> str:
498
- """Generate AFTER UML for Factory pattern recommendation"""
499
 
500
- return f"""@startuml
501
- title After: Factory Pattern Applied
502
- skinparam classAttributeIconSize 0
503
-
504
- class ProductFactory {{
505
- + create_product(type: str): Product
506
  }}
507
 
508
- interface Product {{
509
- + operation()
510
  }}
511
 
512
  class ProductA {{
@@ -517,244 +481,208 @@ class ProductB {{
517
  + operation()
518
  }}
519
 
520
- class ProductC {{
521
- + operation()
522
- }}
523
-
524
- class ClientCode {{
525
- - factory: ProductFactory
526
- + use_product(type: str)
527
- }}
528
-
529
  Product <|.. ProductA
530
  Product <|.. ProductB
531
- Product <|.. ProductC
532
- ProductFactory ..> Product : creates
533
- ClientCode o-- ProductFactory
534
-
535
- note right of ProductFactory
536
- ✅ Centralized creation logic
537
- ✅ Easy to add new products
538
- ✅ Client decoupled from concrete classes
539
- ✅ ~{rec.complexity_reduction}% complexity reduction
540
- end note
541
-
542
- @enduml"""
543
-
544
- def _singleton_before_uml(self, rec: PatternRecommendation, structure: List[Dict]) -> str:
545
- """Generate BEFORE UML for Singleton pattern recommendation"""
546
-
547
- return f"""@startuml
548
- title Before: Multiple Instances Possible
549
- skinparam classAttributeIconSize 0
550
-
551
- class SharedResource {{
552
- + config: Dict
553
- + connect()
554
- + disconnect()
555
- }}
556
-
557
- class ClientA {{
558
- - resource: SharedResource
559
- }}
560
-
561
- class ClientB {{
562
- - resource: SharedResource
563
- }}
564
-
565
- class ClientC {{
566
- - resource: SharedResource
567
- }}
568
-
569
- ClientA ..> SharedResource : creates new instance
570
- ClientB ..> SharedResource : creates new instance
571
- ClientC ..> SharedResource : creates new instance
572
-
573
- note right of SharedResource
574
- ❌ Multiple instances created
575
- ❌ Inconsistent state
576
- ❌ Resource waste
577
- end note
578
-
579
- @enduml"""
580
-
581
- def _singleton_after_uml(self, rec: PatternRecommendation) -> str:
582
- """Generate AFTER UML for Singleton pattern recommendation"""
583
-
584
- return """@startuml
585
- title After: Singleton Pattern Applied
586
- skinparam classAttributeIconSize 0
587
-
588
- class SharedResource {{
589
- - {static} _instance: SharedResource
590
- - config: Dict
591
- + {static} get_instance(): SharedResource
592
- + connect()
593
- + disconnect()
594
- }}
595
-
596
- class ClientA {{
597
- }}
598
-
599
- class ClientB {{
600
- }}
601
-
602
- class ClientC {{
603
- }}
604
-
605
- ClientA ..> SharedResource : uses singleton
606
- ClientB ..> SharedResource : uses singleton
607
- ClientC ..> SharedResource : uses singleton
608
-
609
- note right of SharedResource
610
- ✅ Single instance guaranteed
611
- ✅ Global access point
612
- ✅ Consistent state
613
- ✅ ~{rec.complexity_reduction}% complexity reduction
614
- end note
615
-
616
- @enduml"""
617
-
618
- def _observer_before_uml(self, rec: PatternRecommendation, structure: List[Dict]) -> str:
619
- """Generate BEFORE UML for Observer pattern recommendation"""
620
 
621
- return f"""@startuml
622
- title Before: Tight Coupling for Notifications
623
- skinparam classAttributeIconSize 0
624
-
625
- class EventSource {{
626
- + notify_a()
627
- + notify_b()
628
- + notify_c()
629
- }}
630
-
631
- class ListenerA {{
632
- + update()
633
- }}
634
-
635
- class ListenerB {{
636
- + update()
637
- }}
638
-
639
- class ListenerC {{
640
- + update()
641
- }}
642
-
643
- EventSource --> ListenerA
644
- EventSource --> ListenerB
645
- EventSource --> ListenerC
646
-
647
- note right of EventSource
648
- ❌ Tightly coupled to all listeners
649
- ❌ Hard to add/remove listeners
650
- ❌ Manual notification logic
651
- end note
652
-
653
- @enduml"""
654
-
655
- def _observer_after_uml(self, rec: PatternRecommendation) -> str:
656
- """Generate AFTER UML for Observer pattern recommendation"""
657
 
658
- return f"""@startuml
659
- title After: Observer Pattern Applied
660
- skinparam classAttributeIconSize 0
661
-
662
- interface Observer {{
663
  + update(event)
664
  }}
665
 
666
- class Subject {{
667
  - observers: List<Observer>
668
  + attach(o: Observer)
669
  + detach(o: Observer)
670
  + notify()
671
  }}
672
 
673
- class ListenerA {{
674
  + update(event)
675
  }}
676
 
677
- class ListenerB {{
678
  + update(event)
679
  }}
680
 
681
- class ListenerC {{
682
- + update(event)
683
- }}
684
-
685
- Observer <|.. ListenerA
686
- Observer <|.. ListenerB
687
- Observer <|.. ListenerC
688
- Subject o-- Observer
689
-
690
- note right of Subject
691
- ✅ Loosely coupled
692
- ✅ Dynamic listener management
693
- ✅ Automatic notifications
694
- ✅ ~{rec.complexity_reduction}% complexity reduction
695
- end note
696
-
697
- @enduml"""
698
 
699
  def _generate_ai_uml(self, rec: PatternRecommendation, structure: List[Dict], code: str) -> tuple[str, str]:
700
- """Generate AI-powered UML for recommendations"""
 
 
 
 
 
701
  try:
702
- prompt = f"""
703
- Generate two PlantUML class diagrams for a design pattern recommendation.
704
-
705
- Pattern to apply: {rec.pattern}
706
- Location: {rec.location}
707
- Reason: {rec.reason}
708
-
709
- Current code structure:
710
- {json.dumps(structure[:3], indent=2)}
711
-
712
- Generate:
713
- 1. BEFORE diagram: Show current problematic structure
714
- 2. AFTER diagram: Show improved structure with {rec.pattern} pattern
 
 
 
 
 
 
 
 
 
 
715
 
716
- Output as JSON:
717
- {{
718
- "before": "@startuml...@enduml",
719
- "after": "@startuml...@enduml"
720
- }}
721
 
722
- Keep diagrams simple (4-6 classes max). Include notes explaining problems/benefits.
723
- OUTPUT ONLY VALID JSON.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  """
725
 
726
  messages = [
727
- SystemMessage(content="You are a UML diagram expert. Output only valid JSON."),
728
  HumanMessage(content=prompt)
729
  ]
730
 
 
731
  response = self.llm.invoke(messages)
732
  content = response.content.strip()
733
 
734
- # Clean JSON
735
  if "```json" in content:
736
  content = content.split("```json")[1].split("```")[0].strip()
737
  elif "```" in content:
738
  content = content.split("```")[1].split("```")[0].strip()
739
 
 
 
 
 
 
 
 
740
  result = json.loads(content)
741
- return result.get("before", ""), result.get("after", "")
 
 
 
 
 
 
 
 
 
 
742
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
743
  except Exception as e:
744
- logger.warning(f"AI UML generation failed: {e}, using templates")
745
- return self._generate_template_uml(rec, structure)
 
 
 
 
 
 
 
 
746
 
 
747
  def _recommend_strategy(self, structure: List[Dict], code: str):
748
  """Recommend Strategy pattern for complex conditionals"""
749
- # Look for classes with many if-else statements
750
  for cls in structure:
751
  if cls.get("type") == "module":
752
  continue
753
 
754
- # Count conditionals in methods (simplified heuristic)
755
  conditional_count = code.count("if ") + code.count("elif ")
756
 
757
- if conditional_count > 5: # Threshold
758
  self.recommendations.append(PatternRecommendation(
759
  pattern="Strategy",
760
  location=cls["name"],
@@ -770,7 +698,6 @@ OUTPUT ONLY VALID JSON.
770
  if cls.get("type") == "module":
771
  continue
772
 
773
- # Look for multiple object instantiations
774
  if "(" in code and code.count("= ") > 3:
775
  self.recommendations.append(PatternRecommendation(
776
  pattern="Factory",
@@ -783,7 +710,6 @@ OUTPUT ONLY VALID JSON.
783
 
784
  def _recommend_singleton(self, structure: List[Dict], code: str):
785
  """Recommend Singleton for shared resources"""
786
- # Look for global variables or module-level instances
787
  if "global " in code or code.count("_instance") > 0:
788
  self.recommendations.append(PatternRecommendation(
789
  pattern="Singleton",
@@ -796,7 +722,6 @@ OUTPUT ONLY VALID JSON.
796
 
797
  def _recommend_observer(self, structure: List[Dict], code: str):
798
  """Recommend Observer for event handling"""
799
- # Look for callback patterns or manual notification
800
  if "callback" in code.lower() or "notify" in code.lower():
801
  self.recommendations.append(PatternRecommendation(
802
  pattern="Observer",
@@ -807,7 +732,6 @@ OUTPUT ONLY VALID JSON.
807
  implementation_hint="Create Subject class with attach/detach/notify methods"
808
  ))
809
 
810
-
811
  class PatternEnricher:
812
  """
813
  Uses AI to enrich pattern detections with:
 
340
  class PatternRecommender:
341
  """
342
  Analyzes code structure to recommend where patterns would help.
343
+ Uses AI to generate custom before/after UML diagrams based on actual code.
344
  """
345
 
346
  def __init__(self, llm=None):
 
360
 
361
  def generate_recommendation_uml(self, recommendation: PatternRecommendation, structure: List[Dict], code: str) -> tuple[str, str]:
362
  """
363
+ Generate before/after UML for a recommendation using AI analysis of actual code.
364
  Returns: (before_uml, after_uml)
365
  """
366
  if self.llm:
367
+ logger.info(f"🤖 Using AI to analyze {recommendation.pattern} pattern for {recommendation.location}")
368
  return self._generate_ai_uml(recommendation, structure, code)
369
  else:
370
+ logger.info(f"📊 Using structure-based UML for {recommendation.pattern} pattern")
371
+ return self._generate_structure_based_uml(recommendation, structure, code)
372
 
373
+ def _generate_structure_based_uml(self, rec: PatternRecommendation, structure: List[Dict], code: str) -> tuple[str, str]:
374
+ """Generate UML from actual code structure when AI is not available"""
375
 
376
+ # Extract actual class from structure
377
+ target_class = None
378
+ for item in structure:
379
+ if item.get("name") == rec.location or rec.location in item.get("name", ""):
380
+ target_class = item
381
+ break
 
 
 
 
 
 
 
 
382
 
383
+ if not target_class:
384
+ # Fallback to first class if location not found
385
+ target_class = structure[0] if structure else {"name": rec.location, "methods": []}
 
386
 
387
+ class_name = target_class.get("name", rec.location)
388
+ methods = target_class.get("methods", [])
 
 
 
389
 
390
+ # Generate BEFORE diagram with actual code
391
+ before = f"""@startuml
392
+ title Before: {class_name} - Current Implementation
393
  skinparam classAttributeIconSize 0
394
 
395
  class {class_name} {{
396
+ """
397
+ # Add actual methods from the class
398
+ for method in methods[:6]: # Show up to 6 methods
399
+ before += f" + {method}()\n"
400
+
401
+ before += f"""}}
402
 
403
  note right of {class_name}
404
+ Current Issue:
405
+ {rec.reason}
406
+
407
+ ❌ Problems:
408
+ - Hard to extend
409
+ - High complexity
410
+ - Maintenance burden
411
+
412
+ Location: {rec.location}
413
  end note
414
 
415
  @enduml"""
 
 
 
 
416
 
417
+ # Generate AFTER diagram with pattern applied
418
+ after = f"""@startuml
419
+ title After: {rec.pattern} Pattern Applied to {class_name}
420
  skinparam classAttributeIconSize 0
421
 
422
+ {self._generate_pattern_structure(rec.pattern, class_name, methods)}
423
+
424
+ note bottom
425
+ ✅ Benefits:
426
+ {rec.benefit}
427
+
428
+ Improvements:
429
+ - ~{rec.complexity_reduction}% complexity reduction
430
+ - Better separation of concerns
431
+ - Easier to extend and maintain
432
+
433
+ Pattern: {rec.pattern}
434
+ Implementation: {rec.implementation_hint}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  end note
436
 
437
  @enduml"""
438
+
439
+ return before, after
440
 
441
+ def _generate_pattern_structure(self, pattern: str, class_name: str, methods: List[str]) -> str:
442
+ """Generate pattern-specific structure using actual class name"""
443
 
444
+ if pattern == "Strategy":
445
+ return f"""interface Strategy {{
446
+ + execute()
 
 
 
 
 
447
  }}
448
 
449
+ class {class_name} {{
450
+ - strategy: Strategy
451
+ + set_strategy(s: Strategy)
452
+ + execute()
453
  }}
454
 
455
+ class StrategyA {{
456
+ + execute()
457
  }}
458
 
459
+ class StrategyB {{
460
+ + execute()
461
  }}
462
 
463
+ Strategy <|.. StrategyA
464
+ Strategy <|.. StrategyB
465
+ {class_name} o-- Strategy"""
 
 
 
 
 
 
 
 
 
 
 
466
 
467
+ elif pattern == "Factory":
468
+ return f"""interface Product {{
469
+ + operation()
 
 
 
470
  }}
471
 
472
+ class {class_name}Factory {{
473
+ + create_product(type: str): Product
474
  }}
475
 
476
  class ProductA {{
 
481
  + operation()
482
  }}
483
 
 
 
 
 
 
 
 
 
 
484
  Product <|.. ProductA
485
  Product <|.. ProductB
486
+ {class_name}Factory ..> Product
487
+ {class_name}Factory ..> ProductA
488
+ {class_name}Factory ..> ProductB"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
 
490
+ elif pattern == "Singleton":
491
+ return f"""class {class_name} <<Singleton>> {{
492
+ - {{static}} _instance: {class_name}
493
+ - __init__()
494
+ + {{static}} get_instance(): {class_name}
495
+ """
496
+ + "\n".join(f" + {m}()" for m in methods[:4]) + "\n}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
 
498
+ elif pattern == "Observer":
499
+ return f"""interface Observer {{
 
 
 
500
  + update(event)
501
  }}
502
 
503
+ class {class_name} {{
504
  - observers: List<Observer>
505
  + attach(o: Observer)
506
  + detach(o: Observer)
507
  + notify()
508
  }}
509
 
510
+ class ObserverA {{
511
  + update(event)
512
  }}
513
 
514
+ class ObserverB {{
515
  + update(event)
516
  }}
517
 
518
+ Observer <|.. ObserverA
519
+ Observer <|.. ObserverB
520
+ {class_name} o-- Observer"""
521
+
522
+ else:
523
+ # Generic pattern structure
524
+ return f"""class {class_name}Improved {{
525
+ """
526
+ + "\n".join(f" + {m}()" for m in methods[:4]) + "\n}"
 
 
 
 
 
 
 
 
527
 
528
  def _generate_ai_uml(self, rec: PatternRecommendation, structure: List[Dict], code: str) -> tuple[str, str]:
529
+ """
530
+ 🤖 AI-POWERED UML GENERATION
531
+
532
+ This is the key method that makes diagrams show YOUR actual code,
533
+ not generic templates.
534
+ """
535
  try:
536
+ # Extract relevant code section for the target class
537
+ relevant_code = self._extract_relevant_code(rec.location, code)
538
+
539
+ # Get actual class details from structure
540
+ target_class = None
541
+ for item in structure:
542
+ if item.get("name") == rec.location:
543
+ target_class = item
544
+ break
545
+
546
+ # Prepare context for AI
547
+ context = {
548
+ "class_name": rec.location,
549
+ "pattern": rec.pattern,
550
+ "reason": rec.reason,
551
+ "benefit": rec.benefit,
552
+ "complexity_reduction": rec.complexity_reduction,
553
+ "code_snippet": relevant_code[:1500],
554
+ "class_details": target_class if target_class else {},
555
+ "all_classes": [item.get("name") for item in structure if item.get("type") == "class"][:10]
556
+ }
557
+
558
+ prompt = f"""You are an expert software architect analyzing REAL Python code.
559
 
560
+ **ACTUAL CODE TO ANALYZE:**
561
+ ```python
562
+ {context['code_snippet']}
563
+ ```
 
564
 
565
+ **CODE CONTEXT:**
566
+ - Target class: `{context['class_name']}`
567
+ - All classes in project: {', '.join(context['all_classes'])}
568
+ - Current problem: {context['reason']}
569
+ - Recommended pattern: {context['pattern']}
570
+
571
+ **YOUR TASK:**
572
+ Generate TWO PlantUML class diagrams showing before/after applying {context['pattern']} pattern.
573
+
574
+ **CRITICAL REQUIREMENTS:**
575
+ 1. Use ACTUAL class names from the code above (e.g., `{context['class_name']}`, not "ClientCode")
576
+ 2. Use ACTUAL method names you see in the code
577
+ 3. Show REAL relationships between classes
578
+ 4. BEFORE diagram: Show current problematic structure
579
+ 5. AFTER diagram: Show improved structure with {context['pattern']} pattern
580
+
581
+ **DIAGRAM GUIDELINES:**
582
+ - Keep simple: 4-6 classes maximum
583
+ - Use actual names (no "ProductA", "ServiceA" generic names)
584
+ - Add notes explaining problems (BEFORE) and benefits (AFTER)
585
+ - Include complexity reduction: ~{context['complexity_reduction']}%
586
+
587
+ **OUTPUT FORMAT (CRITICAL):**
588
+ Return ONLY this JSON structure, nothing else:
589
+ {{{{
590
+ "before": "@startuml\\ntitle Before: [use actual class name]\\nskinparam classAttributeIconSize 0\\n...\\n@enduml",
591
+ "after": "@startuml\\ntitle After: {context['pattern']} Pattern Applied\\nskinparam classAttributeIconSize 0\\n...\\n@enduml"
592
+ }}}}
593
+
594
+ DO NOT include ```json or ``` markers.
595
+ DO NOT add any explanation text.
596
+ OUTPUT ONLY THE JSON OBJECT.
597
  """
598
 
599
  messages = [
600
+ SystemMessage(content="You are a UML expert. Analyze actual code and generate precise diagrams using real class/method names. Output only valid JSON, no markdown."),
601
  HumanMessage(content=prompt)
602
  ]
603
 
604
+ logger.info(f"🤖 Sending code to AI for analysis...")
605
  response = self.llm.invoke(messages)
606
  content = response.content.strip()
607
 
608
+ # Clean up response - remove markdown if present
609
  if "```json" in content:
610
  content = content.split("```json")[1].split("```")[0].strip()
611
  elif "```" in content:
612
  content = content.split("```")[1].split("```")[0].strip()
613
 
614
+ # Find JSON object boundaries
615
+ start = content.find("{")
616
+ end = content.rfind("}") + 1
617
+ if start >= 0 and end > start:
618
+ content = content[start:end]
619
+
620
+ # Parse JSON
621
  result = json.loads(content)
622
+ before = result.get("before", "")
623
+ after = result.get("after", "")
624
+
625
+ # Validate UML syntax
626
+ if "@startuml" not in before or "@enduml" not in before:
627
+ logger.warning("❌ AI returned invalid BEFORE UML")
628
+ raise ValueError("Invalid BEFORE UML from AI")
629
+
630
+ if "@startuml" not in after or "@enduml" not in after:
631
+ logger.warning("❌ AI returned invalid AFTER UML")
632
+ raise ValueError("Invalid AFTER UML from AI")
633
 
634
+ logger.info(f"✅ AI successfully generated custom UML for {rec.pattern} pattern")
635
+ logger.info(f" BEFORE diagram: {len(before)} chars")
636
+ logger.info(f" AFTER diagram: {len(after)} chars")
637
+
638
+ return before, after
639
+
640
+ except json.JSONDecodeError as e:
641
+ logger.error(f"❌ Failed to parse AI response as JSON: {e}")
642
+ logger.error(f" Response preview: {content[:200] if 'content' in locals() else 'N/A'}")
643
+ return self._generate_structure_based_uml(rec, structure, code)
644
+
645
+ except Exception as e:
646
+ logger.error(f"❌ AI UML generation failed: {e}")
647
+ import traceback
648
+ logger.error(traceback.format_exc())
649
+ logger.info("⚠️ Falling back to structure-based generation")
650
+ return self._generate_structure_based_uml(rec, structure, code)
651
+
652
+ def _extract_relevant_code(self, class_name: str, code: str) -> str:
653
+ """Extract the code section relevant to the class being analyzed"""
654
+ try:
655
+ tree = ast.parse(code)
656
+ for node in ast.walk(tree):
657
+ if isinstance(node, ast.ClassDef) and node.name == class_name:
658
+ lines = code.split("\n")
659
+ start = node.lineno - 1
660
+ # Get reasonable chunk of code (not too much)
661
+ end = node.end_lineno if hasattr(node, 'end_lineno') else start + 30
662
+ end = min(end, start + 40) # Max 40 lines
663
+ return "\n".join(lines[start:end])
664
  except Exception as e:
665
+ logger.warning(f"Failed to extract class code: {e}")
666
+
667
+ # Fallback: search for class definition manually
668
+ lines = code.split("\n")
669
+ for i, line in enumerate(lines):
670
+ if f"class {class_name}" in line:
671
+ return "\n".join(lines[i:min(i+30, len(lines))])
672
+
673
+ # Last resort: return first chunk of code
674
+ return code[:800]
675
 
676
+ # Keep the existing _recommend_* methods unchanged
677
  def _recommend_strategy(self, structure: List[Dict], code: str):
678
  """Recommend Strategy pattern for complex conditionals"""
 
679
  for cls in structure:
680
  if cls.get("type") == "module":
681
  continue
682
 
 
683
  conditional_count = code.count("if ") + code.count("elif ")
684
 
685
+ if conditional_count > 5:
686
  self.recommendations.append(PatternRecommendation(
687
  pattern="Strategy",
688
  location=cls["name"],
 
698
  if cls.get("type") == "module":
699
  continue
700
 
 
701
  if "(" in code and code.count("= ") > 3:
702
  self.recommendations.append(PatternRecommendation(
703
  pattern="Factory",
 
710
 
711
  def _recommend_singleton(self, structure: List[Dict], code: str):
712
  """Recommend Singleton for shared resources"""
 
713
  if "global " in code or code.count("_instance") > 0:
714
  self.recommendations.append(PatternRecommendation(
715
  pattern="Singleton",
 
722
 
723
  def _recommend_observer(self, structure: List[Dict], code: str):
724
  """Recommend Observer for event handling"""
 
725
  if "callback" in code.lower() or "notify" in code.lower():
726
  self.recommendations.append(PatternRecommendation(
727
  pattern="Observer",
 
732
  implementation_hint="Create Subject class with attach/detach/notify methods"
733
  ))
734
 
 
735
  class PatternEnricher:
736
  """
737
  Uses AI to enrich pattern detections with: