acmc commited on
Commit
724f8fa
Β·
verified Β·
1 Parent(s): 2e8e2cd

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +571 -0
app.py ADDED
@@ -0,0 +1,571 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Streamlit app for testing machine-generated code detection model with explainability.
3
+
4
+ This app allows users to:
5
+ 1. Input code snippets
6
+ 2. Get predictions on whether the code is human-written or machine-generated
7
+ 3. View feature importance and explanations for the prediction
8
+ """
9
+
10
+ import streamlit as st
11
+ import pandas as pd
12
+ import numpy as np
13
+ import json
14
+ import joblib
15
+ import os
16
+ from typing import Dict, List, Any
17
+ import matplotlib.pyplot as plt
18
+ import seaborn as sns
19
+ import plotly.express as px
20
+ import plotly.graph_objects as go
21
+ from plotly.subplots import make_subplots
22
+
23
+ # Import local modules
24
+ from code_analytics import extract_all_code_analytics, get_analytics_feature_names
25
+ from entropy_weighted_perplexity import EntropyWeightedPerplexity
26
+
27
+ # Try to import SHAP for advanced explainability
28
+ try:
29
+ import shap
30
+ SHAP_AVAILABLE = True
31
+ except ImportError:
32
+ SHAP_AVAILABLE = False
33
+
34
+ st.set_page_config(
35
+ page_title="AI Code Detection Tool",
36
+ page_icon="πŸ€–",
37
+ layout="wide",
38
+ initial_sidebar_state="expanded"
39
+ )
40
+
41
+ class ModelLoader:
42
+ """Handle loading of trained models and metadata."""
43
+
44
+ def __init__(self, model_dir: str = "results"):
45
+ self.model_dir = model_dir
46
+ self.model = None
47
+ self.metadata = None
48
+
49
+ def load_model(self) -> bool:
50
+ """Load the trained model and metadata."""
51
+ try:
52
+ model_path = os.path.join(self.model_dir, "trained_model.pkl")
53
+ metadata_path = os.path.join(self.model_dir, "model_metadata.json")
54
+
55
+ if not os.path.exists(model_path) or not os.path.exists(metadata_path):
56
+ return False
57
+
58
+ self.model = joblib.load(model_path)
59
+
60
+ with open(metadata_path, 'r') as f:
61
+ self.metadata = json.load(f)
62
+
63
+ return True
64
+ except Exception as e:
65
+ st.error(f"Error loading model: {e}")
66
+ return False
67
+
68
+ def get_model_info(self) -> Dict[str, Any]:
69
+ """Get model information for display."""
70
+ if self.metadata is None:
71
+ return {}
72
+
73
+ return {
74
+ "Model Type": self.metadata.get("model_type", "Unknown"),
75
+ "Number of Features": len(self.metadata.get("feature_names", [])),
76
+ "F1 Score": f"{self.metadata.get('metrics', {}).get('f1_macro', 0):.4f}",
77
+ "Accuracy": f"{self.metadata.get('metrics', {}).get('accuracy', 0):.4f}",
78
+ "Features Used": "Code Analytics" if not self.metadata.get('config', {}).get('features', {}).get('use_llm_features', False) else "Code Analytics + LLM"
79
+ }
80
+
81
+ class CodeAnalyzer:
82
+ """Handle code analysis and feature extraction."""
83
+
84
+ def __init__(self, model_loader: ModelLoader):
85
+ self.model_loader = model_loader
86
+
87
+ def extract_features(self, code: str) -> np.ndarray:
88
+ """
89
+ Extract features from code using the same pipeline as training.
90
+ """
91
+ try:
92
+ if self.model_loader.metadata is None:
93
+ raise ValueError("Model metadata not loaded")
94
+
95
+ config = self.model_loader.metadata.get("config", {})
96
+ features_config = config.get("features", {})
97
+
98
+ all_features = []
99
+
100
+ # Extract LLM features if enabled (disabled in fast_config.yaml)
101
+ if features_config.get("use_llm_features", False):
102
+ # Initialize EWP calculator if needed
103
+ ewp_calculator = EntropyWeightedPerplexity(
104
+ model_name=config["model"]["name"],
105
+ entropy_window_size=config["model"]["entropy_window_size"],
106
+ entropy_weight=config["model"]["entropy_weight"],
107
+ perplexity_weight=config["model"]["perplexity_weight"],
108
+ )
109
+
110
+ # Extract LLM features
111
+ llm_features = ewp_calculator.calculate_entropy_weighted_score(code)
112
+ all_features.extend([
113
+ llm_features["entropy_weighted_score"],
114
+ llm_features["mean_entropy"],
115
+ llm_features["mean_windowed_entropy"],
116
+ llm_features["mean_cross_entropy"],
117
+ llm_features["sequence_length"],
118
+ llm_features["entropy_cross_entropy_ratio"],
119
+ llm_features["windowed_raw_entropy_ratio"],
120
+ ])
121
+
122
+ # Extract code analytics features
123
+ if features_config.get("use_code_analytics", True):
124
+ analytics_features = extract_all_code_analytics(code)
125
+ # Get features in the same order as training
126
+ analytics_feature_names = get_analytics_feature_names()
127
+ for feature_name in analytics_feature_names:
128
+ all_features.append(analytics_features.get(feature_name, 0.0))
129
+
130
+ return np.array(all_features)
131
+
132
+ except Exception as e:
133
+ st.error(f"Feature extraction failed: {e}")
134
+ # Return zeros if extraction fails
135
+ n_features = len(self.model_loader.metadata.get("feature_names", []))
136
+ return np.zeros(n_features)
137
+
138
+ def predict(self, code: str) -> Dict[str, Any]:
139
+ """Make prediction and return results with explanations."""
140
+ if self.model_loader.model is None:
141
+ return {"error": "Model not loaded"}
142
+
143
+ try:
144
+ # Extract features
145
+ features = self.extract_features(code)
146
+ features = features.reshape(1, -1)
147
+
148
+ # Make prediction
149
+ prediction = self.model_loader.model.predict(features)[0]
150
+ probability = self.model_loader.model.predict_proba(features)[0]
151
+
152
+ # Get feature importance if available
153
+ feature_importance = self.get_feature_importance(features)
154
+
155
+ return {
156
+ "prediction": prediction,
157
+ "probability": probability,
158
+ "features": features[0],
159
+ "feature_importance": feature_importance,
160
+ "label": self.model_loader.metadata["label_mapping"][str(prediction)]
161
+ }
162
+
163
+ except Exception as e:
164
+ return {"error": f"Prediction failed: {e}"}
165
+
166
+ def get_feature_importance(self, features: np.ndarray) -> Dict[str, float]:
167
+ """Get feature importance for the current prediction."""
168
+ try:
169
+ if hasattr(self.model_loader.model, 'feature_importances_'):
170
+ # For tree-based models
171
+ importances = self.model_loader.model.feature_importances_
172
+ elif hasattr(self.model_loader.model, 'coef_'):
173
+ # For linear models
174
+ importances = np.abs(self.model_loader.model.coef_[0])
175
+ else:
176
+ # For ensemble models, try to get feature importance from base estimators
177
+ if hasattr(self.model_loader.model, 'estimators_'):
178
+ importances = []
179
+ for estimator in self.model_loader.model.estimators_:
180
+ if hasattr(estimator, 'feature_importances_'):
181
+ importances.append(estimator.feature_importances_)
182
+ if importances:
183
+ importances = np.mean(importances, axis=0)
184
+ else:
185
+ importances = np.ones(len(features[0])) / len(features[0])
186
+ else:
187
+ importances = np.ones(len(features[0])) / len(features[0])
188
+
189
+ feature_names = self.model_loader.metadata.get("feature_names",
190
+ [f"Feature_{i}" for i in range(len(features[0]))])
191
+
192
+ return dict(zip(feature_names, importances))
193
+
194
+ except Exception as e:
195
+ st.warning(f"Could not get feature importance: {e}")
196
+ return {}
197
+
198
+ def get_shap_explanation(self, code: str) -> Dict[str, Any]:
199
+ """Get SHAP explanations for the prediction."""
200
+ if not SHAP_AVAILABLE:
201
+ return {"error": "SHAP not available"}
202
+
203
+ try:
204
+ # Extract features for the current code
205
+ features = self.extract_features(code).reshape(1, -1)
206
+
207
+ # Create a SHAP explainer
208
+ if hasattr(self.model_loader.model, 'feature_importances_'):
209
+ # Tree-based model
210
+ explainer = shap.TreeExplainer(self.model_loader.model)
211
+ else:
212
+ # For other models, use KernelExplainer with a background dataset
213
+ # Use a small random background for efficiency
214
+ background_size = min(100, 10) # Small background for speed
215
+ background_features = np.random.normal(
216
+ features.mean(), features.std(), (background_size, features.shape[1])
217
+ )
218
+ explainer = shap.KernelExplainer(
219
+ self.model_loader.model.predict_proba, background_features
220
+ )
221
+
222
+ # Get SHAP values
223
+ shap_values = explainer.shap_values(features)
224
+
225
+ # For binary classification, SHAP returns values for both classes
226
+ if isinstance(shap_values, list):
227
+ shap_values = shap_values[1] # Use positive class
228
+
229
+ feature_names = self.model_loader.metadata.get("feature_names",
230
+ [f"Feature_{i}" for i in range(features.shape[1])])
231
+
232
+ return {
233
+ "shap_values": shap_values[0],
234
+ "feature_names": feature_names,
235
+ "base_value": explainer.expected_value if hasattr(explainer, 'expected_value') else 0.5,
236
+ "feature_values": features[0]
237
+ }
238
+
239
+ except Exception as e:
240
+ return {"error": f"SHAP explanation failed: {e}"}
241
+
242
+ def create_shap_waterfall_plot(shap_explanation: Dict[str, Any], top_n: int = 15):
243
+ """Create a SHAP waterfall-style plot showing feature contributions."""
244
+ if "error" in shap_explanation:
245
+ return None
246
+
247
+ shap_values = shap_explanation["shap_values"]
248
+ feature_names = shap_explanation["feature_names"]
249
+ feature_values = shap_explanation["feature_values"]
250
+ base_value = shap_explanation.get("base_value", 0.5)
251
+
252
+ # Get top contributing features (positive and negative)
253
+ feature_contributions = list(zip(feature_names, shap_values, feature_values))
254
+ feature_contributions.sort(key=lambda x: abs(x[1]), reverse=True)
255
+ top_features = feature_contributions[:top_n]
256
+
257
+ # Create waterfall-style data
258
+ names = [f[0] for f in top_features]
259
+ values = [f[1] for f in top_features]
260
+ colors = ['green' if v > 0 else 'red' for v in values]
261
+
262
+ fig = go.Figure(go.Bar(
263
+ x=values,
264
+ y=names,
265
+ orientation='h',
266
+ marker_color=colors,
267
+ text=[f"{v:.4f}" for v in values],
268
+ textposition="outside"
269
+ ))
270
+
271
+ fig.update_layout(
272
+ title=f"SHAP Feature Contributions (Top {top_n})",
273
+ xaxis_title="SHAP Value (contribution to prediction)",
274
+ yaxis_title="Features",
275
+ height=600,
276
+ yaxis={'categoryorder': 'total ascending'},
277
+ showlegend=False
278
+ )
279
+
280
+ # Add vertical line at 0
281
+ fig.add_vline(x=0, line_dash="dash", line_color="black", opacity=0.5)
282
+
283
+ return fig
284
+
285
+ def create_feature_importance_plot(feature_importance: Dict[str, float], top_n: int = 20):
286
+ """Create feature importance visualization."""
287
+ if not feature_importance:
288
+ return None
289
+
290
+ # Sort by importance
291
+ sorted_features = sorted(feature_importance.items(), key=lambda x: abs(x[1]), reverse=True)
292
+ top_features = sorted_features[:top_n]
293
+
294
+ feature_names = [f[0] for f in top_features]
295
+ importance_values = [f[1] for f in top_features]
296
+
297
+ # Create horizontal bar plot
298
+ fig = go.Figure(go.Bar(
299
+ x=importance_values,
300
+ y=feature_names,
301
+ orientation='h',
302
+ marker_color=px.colors.qualitative.Set3
303
+ ))
304
+
305
+ fig.update_layout(
306
+ title=f"Top {top_n} Most Important Features",
307
+ xaxis_title="Feature Importance",
308
+ yaxis_title="Features",
309
+ height=600,
310
+ yaxis={'categoryorder': 'total ascending'}
311
+ )
312
+
313
+ return fig
314
+
315
+ def create_prediction_gauge(probability: np.ndarray, prediction: int):
316
+ """Create a gauge chart showing prediction confidence."""
317
+ confidence = max(probability)
318
+
319
+ fig = go.Figure(go.Indicator(
320
+ mode="gauge+number+delta",
321
+ value=confidence * 100,
322
+ domain={'x': [0, 1], 'y': [0, 1]},
323
+ title={'text': "Prediction Confidence (%)"},
324
+ gauge={
325
+ 'axis': {'range': [None, 100]},
326
+ 'bar': {'color': "lightgreen" if prediction == 0 else "lightcoral"},
327
+ 'steps': [
328
+ {'range': [0, 50], 'color': "lightgray"},
329
+ {'range': [50, 80], 'color': "yellow"},
330
+ {'range': [80, 100], 'color': "lightgreen"}
331
+ ],
332
+ 'threshold': {
333
+ 'line': {'color': "red", 'width': 4},
334
+ 'thickness': 0.75,
335
+ 'value': 90
336
+ }
337
+ }
338
+ ))
339
+
340
+ fig.update_layout(height=300)
341
+ return fig
342
+
343
+ def main():
344
+ st.title("πŸ€– AI Code Detection Tool")
345
+ st.markdown("### Detect whether code is human-written or machine-generated with explainable AI")
346
+
347
+ # Initialize session state
348
+ if 'model_loader' not in st.session_state:
349
+ st.session_state.model_loader = ModelLoader()
350
+ st.session_state.model_loaded = False
351
+
352
+ # Sidebar
353
+ with st.sidebar:
354
+ st.header("Model Information")
355
+
356
+ # Try to load model if not already loaded
357
+ if not st.session_state.model_loaded:
358
+ if st.button("Load Model"):
359
+ with st.spinner("Loading model..."):
360
+ if st.session_state.model_loader.load_model():
361
+ st.session_state.model_loaded = True
362
+ st.success("Model loaded successfully!")
363
+ else:
364
+ st.error("Failed to load model. Please ensure the model files exist in the 'results' directory.")
365
+
366
+ if st.session_state.model_loaded:
367
+ model_info = st.session_state.model_loader.get_model_info()
368
+ for key, value in model_info.items():
369
+ st.metric(key, value)
370
+
371
+ # Main content
372
+ if not st.session_state.model_loaded:
373
+ st.warning("⚠️ Please load the model first using the sidebar.")
374
+ st.info("Make sure you have trained a model using the main script with `save_model: true` in the config.")
375
+ return
376
+
377
+ # Initialize code analyzer
378
+ analyzer = CodeAnalyzer(st.session_state.model_loader)
379
+
380
+ # Code input
381
+ st.header("πŸ“ Enter Code to Analyze")
382
+
383
+ # Sample code examples
384
+ examples = {
385
+ "Python Function": '''def fibonacci(n):
386
+ if n <= 1:
387
+ return n
388
+ return fibonacci(n-1) + fibonacci(n-2)''',
389
+
390
+ "Simple Loop": '''for i in range(10):
391
+ print(f"Number: {i}")
392
+ if i % 2 == 0:
393
+ print("Even")''',
394
+
395
+ "Class Definition": '''class Calculator:
396
+ def __init__(self):
397
+ self.history = []
398
+
399
+ def add(self, a, b):
400
+ result = a + b
401
+ self.history.append(f"{a} + {b} = {result}")
402
+ return result'''
403
+ }
404
+
405
+ # Example selector
406
+ col1, col2 = st.columns([1, 3])
407
+ with col1:
408
+ selected_example = st.selectbox("Load Example:", [""] + list(examples.keys()))
409
+
410
+ # Code input area with syntax highlighting
411
+ if selected_example:
412
+ code_input = st.text_area("Code:", examples[selected_example], height=200, key="code_input")
413
+ else:
414
+ code_input = st.text_area("Code:", height=200, placeholder="Enter your code here...", key="code_input")
415
+
416
+ # Code validation
417
+ if code_input.strip():
418
+ try:
419
+ import ast
420
+ ast.parse(code_input)
421
+ st.success("βœ… Valid Python syntax")
422
+ except SyntaxError as e:
423
+ st.warning(f"⚠️ Syntax error detected: {e}")
424
+ st.info("Note: The model can still analyze syntactically incorrect code, but results may be less reliable.")
425
+ except Exception:
426
+ st.info("Code validation skipped (not standard Python)")
427
+
428
+ # Analysis options
429
+ with st.expander("Analysis Options"):
430
+ show_all_features = st.checkbox("Show all features in results", value=False)
431
+ use_shap = st.checkbox("Enable SHAP explanations", value=SHAP_AVAILABLE, disabled=not SHAP_AVAILABLE)
432
+ if not SHAP_AVAILABLE:
433
+ st.info("Install SHAP (`pip install shap`) for advanced explanations")
434
+
435
+ # Analysis button
436
+ if st.button("πŸ” Analyze Code", type="primary"):
437
+ if not code_input.strip():
438
+ st.warning("Please enter some code to analyze.")
439
+ return
440
+
441
+ with st.spinner("Analyzing code..."):
442
+ result = analyzer.predict(code_input)
443
+
444
+ if "error" in result:
445
+ st.error(result["error"])
446
+ return
447
+
448
+ # Display results
449
+ st.header("πŸ“Š Analysis Results")
450
+
451
+ col1, col2, col3 = st.columns(3)
452
+
453
+ with col1:
454
+ st.metric(
455
+ "Prediction",
456
+ result["label"],
457
+ delta=f"{max(result['probability']):.1%} confidence"
458
+ )
459
+
460
+ with col2:
461
+ human_prob = result["probability"][0]
462
+ machine_prob = result["probability"][1]
463
+ st.metric("Human-written", f"{human_prob:.1%}")
464
+ st.metric("Machine-generated", f"{machine_prob:.1%}")
465
+
466
+ with col3:
467
+ # Confidence gauge
468
+ gauge_fig = create_prediction_gauge(result["probability"], result["prediction"])
469
+ st.plotly_chart(gauge_fig, use_container_width=True)
470
+
471
+ # Feature importance and SHAP explanations
472
+ if result["feature_importance"]:
473
+ st.header("πŸ” Feature Importance & Explanations")
474
+
475
+ # Create tabs for different explanations
476
+ tabs = ["Global Importance", "Feature Values"]
477
+ if SHAP_AVAILABLE:
478
+ tabs.append("SHAP Explanations")
479
+
480
+ tab_objects = st.tabs(tabs)
481
+
482
+ with tab_objects[0]: # Global Importance
483
+ st.subheader("Model's Overall Feature Importance")
484
+ fig = create_feature_importance_plot(result["feature_importance"], top_n=20)
485
+ if fig:
486
+ st.plotly_chart(fig, use_container_width=True)
487
+
488
+ # Show top features in text
489
+ st.subheader("Top Contributing Features:")
490
+ sorted_features = sorted(result["feature_importance"].items(),
491
+ key=lambda x: abs(x[1]), reverse=True)
492
+
493
+ col1, col2 = st.columns(2)
494
+ with col1:
495
+ st.write("**Most Important:**")
496
+ for i, (feature, importance) in enumerate(sorted_features[:10], 1):
497
+ st.write(f"{i}. **{feature}**: {importance:.4f}")
498
+
499
+ with col2:
500
+ st.write("**Feature Description:**")
501
+ st.info("These are the features the model finds most important globally across all predictions.")
502
+
503
+ with tab_objects[1]: # Feature Values
504
+ st.subheader("Current Code's Feature Values")
505
+
506
+ # Show all features in a dataframe
507
+ feature_names = st.session_state.model_loader.metadata.get("feature_names", [])
508
+ feature_values = result["features"]
509
+
510
+ if len(feature_names) == len(feature_values):
511
+ feature_df = pd.DataFrame({
512
+ "Feature": feature_names,
513
+ "Value": feature_values,
514
+ "Global_Importance": [result["feature_importance"].get(name, 0) for name in feature_names]
515
+ }).sort_values("Global_Importance", ascending=False)
516
+
517
+ st.dataframe(feature_df, height=400)
518
+
519
+ if SHAP_AVAILABLE and len(tab_objects) > 2:
520
+ with tab_objects[2]: # SHAP Explanations
521
+ st.subheader("SHAP Analysis: Why This Prediction?")
522
+
523
+ with st.spinner("Computing SHAP explanations..."):
524
+ shap_result = analyzer.get_shap_explanation(code_input)
525
+
526
+ if "error" not in shap_result:
527
+ shap_fig = create_shap_waterfall_plot(shap_result, top_n=15)
528
+ if shap_fig:
529
+ st.plotly_chart(shap_fig, use_container_width=True)
530
+
531
+ st.info("""
532
+ **How to read SHAP values:**
533
+ - Green bars push the prediction toward "Machine-generated"
534
+ - Red bars push the prediction toward "Human-written"
535
+ - Longer bars = stronger influence on this specific prediction
536
+ - Values show how much each feature contributed to moving the prediction from the baseline
537
+ """)
538
+ else:
539
+ st.warning(f"SHAP analysis failed: {shap_result['error']}")
540
+ st.info("Falling back to global feature importance above.")
541
+ else:
542
+ st.warning("Feature importance not available for this model.")
543
+
544
+ # Footer
545
+ st.markdown("---")
546
+ st.markdown("### About This Tool")
547
+ col1, col2 = st.columns(2)
548
+
549
+ with col1:
550
+ st.info("""
551
+ **Purpose**: This tool helps detect whether code was written by humans or generated by AI.
552
+
553
+ **Method**: Uses static code analysis with machine learning, focusing on patterns in:
554
+ - Code structure and complexity
555
+ - Naming conventions and style
556
+ - Syntactic patterns and AST features
557
+ - Error handling and control flow
558
+ """)
559
+
560
+ with col2:
561
+ st.warning(
562
+ """
563
+ **Limitations**:
564
+ - Works with Python code only
565
+ - Accuracy depends on code length and complexity
566
+ - Results are probabilistic, not definitive
567
+ """
568
+ )
569
+
570
+ if __name__ == "__main__":
571
+ main()