Bhanumani12 commited on
Commit
61ea1a4
·
verified ·
1 Parent(s): e9e0e45

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +44 -29
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import os
2
  import re
3
  import json
@@ -16,7 +17,7 @@ SF_USERNAME = os.getenv("SF_USERNAME")
16
  SF_PASSWORD = os.getenv("SF_PASSWORD")
17
  SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN")
18
 
19
- # ---------- Label Mapping (kept; now used as fallback) ----------
20
  label_to_issue_type = {
21
  "LABEL_0": "Performance",
22
  "LABEL_1": "Error",
@@ -27,7 +28,7 @@ label_to_issue_type = {
27
  suggestions = {
28
  "Performance": "Consider optimizing loops and database access. Use collections to reduce SOQL/DML calls, avoid SOQL/DML inside loops, and add selective WHERE clauses.",
29
  "Error": "Add proper error handling and null checks. Wrap DML in try/catch and use Database methods for partial success.",
30
- "Security": "Avoid dynamic SOQL. Use bind variables, withSharing, and field-level security checks where applicable.",
31
  "Best Practice": "Refactor for readability and bulk-safety (Batchable/Queueable where needed). Limit fields and records in queries."
32
  }
33
 
@@ -39,14 +40,14 @@ severities = {
39
  }
40
 
41
  # ---------- Hugging Face Models (Hugging Face only, per BRD/SDD) ----------
42
- # Lightweight BLOOMZ for natural language support
43
  try:
44
  nlp_pipeline = pipeline("text-generation", model="bigscience/bloomz-560m")
45
  except Exception as e:
46
  nlp_pipeline = None
47
  print(f"⚠️ Could not load BLOOMZ model: {e}")
48
 
49
- # Optional: simple classifier (kept minimal; not strictly required)
50
  try:
51
  clf_pipeline = pipeline("text-classification", model="microsoft/codebert-base")
52
  except Exception as e:
@@ -71,21 +72,21 @@ except Exception as e:
71
  sf = None
72
  print(f"❌ Failed to connect to Salesforce: {e}")
73
 
74
- # ---------- Heuristic Rules for Apex/LWC ----------
75
  SOQL_PATTERN = re.compile(r"\b(?:Database\.query|SELECT\s+[\s\S]+?FROM\b)", re.IGNORECASE)
76
  DML_PATTERN = re.compile(r"\b(insert|update|upsert|delete|undelete|merge)\b", re.IGNORECASE)
77
  LOOP_PATTERN = re.compile(r"\b(for\s*\(|while\s*\()", re.IGNORECASE)
78
  DEBUG_PATTERN = re.compile(r"\bSystem\.debug\s*\(", re.IGNORECASE)
79
  DYNAMIC_SOQL_PATTERN = re.compile(r"['\"].*SELECT.*FROM.*['\"]\s*\+\s*", re.IGNORECASE)
80
- UNBOUNDED_QUERY_PATTERN = re.compile(r"SELECT\s+\*\s+FROM", re.IGNORECASE) # LWC/JS cases
81
- NULL_GUARD_PATTERN = re.compile(r"\b(\w+)\.(\w+)\(", re.IGNORECASE) # very rough
82
 
83
  def analyze_code_rules(code: str):
84
  issues = []
85
 
86
  # SOQL/DML inside loops
87
  for loop in LOOP_PATTERN.finditer(code):
88
- loop_block = code[loop.start(): loop.start()+400] # shallow lookahead
89
  if SOQL_PATTERN.search(loop_block):
90
  issues.append(("Performance", "SOQL query inside a loop detected. Move query outside the loop or use collections."))
91
  if DML_PATTERN.search(loop_block):
@@ -105,15 +106,14 @@ def analyze_code_rules(code: str):
105
  issues.append(("Performance", "Unbounded SELECT * detected. Query only required fields."))
106
 
107
  # (Very) rough null guard hint
108
- # Suggest using null-checks where chained dereferences are visible
109
  dot_calls = len(NULL_GUARD_PATTERN.findall(code))
110
  if dot_calls > 15:
111
  issues.append(("Error", "Multiple chained calls detected. Ensure null checks and guard clauses to avoid NullPointerExceptions."))
112
 
113
- # If classifier is available, add its hint as a final tag
114
  if clf_pipeline:
115
  try:
116
- pred = clf_pipeline(code[:1000])[0] # keep it small
117
  mapped = label_to_issue_type.get(pred.get("label"), "Best Practice")
118
  issues.append((mapped, f"Model hint: {mapped} issue likely. Confidence ~{pred.get('score', 0):.2f}"))
119
  except Exception:
@@ -135,11 +135,10 @@ def pick_primary(issues):
135
  return ("Best Practice", suggestions["Best Practice"], severities["Best Practice"])
136
  issues_sorted = sorted(issues, key=lambda x: prio.get(x[0], 0), reverse=True)
137
  top_type = issues_sorted[0][0]
138
- # Merge messages into one suggestion
139
  merged = "; ".join(msg for _, msg in issues_sorted[:3])
140
  return (top_type, merged or suggestions[top_type], severities[top_type])
141
 
142
- # ---------- Code Analyzer ----------
143
  def analyze_code(code):
144
  if not code or not code.strip():
145
  return "No code provided.", "", ""
@@ -171,7 +170,7 @@ def analyze_code(code):
171
 
172
  return issue_type, suggestion_text, severity
173
 
174
- # ---------- Metadata Validator ----------
175
  def validate_metadata(metadata, admin_id=None):
176
  if not metadata or not metadata.strip():
177
  return "No metadata provided.", "", ""
@@ -184,7 +183,7 @@ def validate_metadata(metadata, admin_id=None):
184
  root = ET.fromstring(metadata)
185
  # 1) Description present?
186
  has_description = any(elem.tag.lower().endswith('description') and (elem.text or '').strip() for elem in root.iter())
187
- # 2) Duplicate <fullName> or field names?
188
  names = []
189
  duplicates = set()
190
  for elem in root.iter():
@@ -199,10 +198,7 @@ def validate_metadata(metadata, admin_id=None):
199
  missing_help = []
200
  for f in root.iter():
201
  if f.tag.lower().endswith('fields'):
202
- # look for nested field fullName
203
- fname = None
204
- fdesc = None
205
- fhelp = None
206
  for ch in f:
207
  t = ch.tag.lower()
208
  if t.endswith('fullname') and ch.text:
@@ -261,8 +257,18 @@ def validate_metadata(metadata, admin_id=None):
261
 
262
  return mtype, issue, recommendation
263
 
264
- # ---------- Salesforce Chatbot (BLOOMZ) ----------
265
- conversation_history = []
 
 
 
 
 
 
 
 
 
 
266
 
267
  def salesforce_chatbot(query, history=[]):
268
  global conversation_history
@@ -273,8 +279,7 @@ def salesforce_chatbot(query, history=[]):
273
  "apex", "soql", "trigger", "lwc", "aura", "visualforce", "salesforce", "governor limits",
274
  "dml", "metadata", "batch apex", "queueable", "future method", "api", "sfdc", "heap", "limits"
275
  ]
276
-
277
- if not any(keyword.lower() in query.lower() for keyword in salesforce_keywords):
278
  return "Please ask a Salesforce-related question."
279
 
280
  history_summary = "\n".join([f"User: {q}\nAssistant: {a}" for q, a in conversation_history[-4:]])
@@ -282,28 +287,38 @@ def salesforce_chatbot(query, history=[]):
282
  system_prompt = (
283
  "You are a certified Salesforce developer and architect. Answer with correct, production-safe guidance. "
284
  "When relevant, mention governor limits (e.g., 100 SOQL queries per transaction, 150 DML statements). "
285
- "Use bullets or code snippets. Prefer bulk-safe patterns and official docs."
 
286
  )
 
287
  prompt = f"{system_prompt}\n\nConversation History:\n{history_summary}\n\nUser: {query.strip()}\nAssistant:"
288
 
289
  try:
290
  if nlp_pipeline:
291
- out = nlp_pipeline(prompt, max_new_tokens=220, do_sample=False)[0]["generated_text"].strip()
 
 
 
 
 
292
  else:
293
- out = "Governor limits matter (e.g., 100 SOQL queries/tx, 150 DML). Use bulk patterns, selective queries, and proper error handling."
294
 
295
- # Keep answer reasonable length
296
- if len(out.split()) < 15:
297
- out += "\n\nTip: Use Database.insert with allOrNone=false for partial success and check Limits class."
298
 
299
  conversation_history.append((query, out))
300
  conversation_history = conversation_history[-6:]
301
  log_to_console({"Question": query, "Answer": out}, "Chatbot Query")
302
  return out
 
303
  except Exception as e:
304
  return f"⚠️ Error generating response: {str(e)}"
305
 
306
  # ---------- Gradio UI ----------
 
 
307
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
308
  gr.Markdown("# 🤖 Advanced Salesforce AI Code Review & Chatbot")
309
 
 
1
+ # app.py
2
  import os
3
  import re
4
  import json
 
17
  SF_PASSWORD = os.getenv("SF_PASSWORD")
18
  SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN")
19
 
20
+ # ---------- Label Mapping (kept for model hint mapping) ----------
21
  label_to_issue_type = {
22
  "LABEL_0": "Performance",
23
  "LABEL_1": "Error",
 
28
  suggestions = {
29
  "Performance": "Consider optimizing loops and database access. Use collections to reduce SOQL/DML calls, avoid SOQL/DML inside loops, and add selective WHERE clauses.",
30
  "Error": "Add proper error handling and null checks. Wrap DML in try/catch and use Database methods for partial success.",
31
+ "Security": "Avoid dynamic SOQL. Use bind variables, with sharing, and field-level security checks where applicable.",
32
  "Best Practice": "Refactor for readability and bulk-safety (Batchable/Queueable where needed). Limit fields and records in queries."
33
  }
34
 
 
40
  }
41
 
42
  # ---------- Hugging Face Models (Hugging Face only, per BRD/SDD) ----------
43
+ # NLP for chatbot
44
  try:
45
  nlp_pipeline = pipeline("text-generation", model="bigscience/bloomz-560m")
46
  except Exception as e:
47
  nlp_pipeline = None
48
  print(f"⚠️ Could not load BLOOMZ model: {e}")
49
 
50
+ # Optional classifier for a small hint in code analysis (not required)
51
  try:
52
  clf_pipeline = pipeline("text-classification", model="microsoft/codebert-base")
53
  except Exception as e:
 
72
  sf = None
73
  print(f"❌ Failed to connect to Salesforce: {e}")
74
 
75
+ # ---------- Heuristic Rules for Apex/LWC (governor, security, best-practice) ----------
76
  SOQL_PATTERN = re.compile(r"\b(?:Database\.query|SELECT\s+[\s\S]+?FROM\b)", re.IGNORECASE)
77
  DML_PATTERN = re.compile(r"\b(insert|update|upsert|delete|undelete|merge)\b", re.IGNORECASE)
78
  LOOP_PATTERN = re.compile(r"\b(for\s*\(|while\s*\()", re.IGNORECASE)
79
  DEBUG_PATTERN = re.compile(r"\bSystem\.debug\s*\(", re.IGNORECASE)
80
  DYNAMIC_SOQL_PATTERN = re.compile(r"['\"].*SELECT.*FROM.*['\"]\s*\+\s*", re.IGNORECASE)
81
+ UNBOUNDED_QUERY_PATTERN = re.compile(r"SELECT\s+\*\s+FROM", re.IGNORECASE) # JS/LWC anti-pattern
82
+ NULL_GUARD_PATTERN = re.compile(r"\b(\w+)\.(\w+)\(", re.IGNORECASE) # rough chained-call detector
83
 
84
  def analyze_code_rules(code: str):
85
  issues = []
86
 
87
  # SOQL/DML inside loops
88
  for loop in LOOP_PATTERN.finditer(code):
89
+ loop_block = code[loop.start(): loop.start() + 400] # shallow lookahead
90
  if SOQL_PATTERN.search(loop_block):
91
  issues.append(("Performance", "SOQL query inside a loop detected. Move query outside the loop or use collections."))
92
  if DML_PATTERN.search(loop_block):
 
106
  issues.append(("Performance", "Unbounded SELECT * detected. Query only required fields."))
107
 
108
  # (Very) rough null guard hint
 
109
  dot_calls = len(NULL_GUARD_PATTERN.findall(code))
110
  if dot_calls > 15:
111
  issues.append(("Error", "Multiple chained calls detected. Ensure null checks and guard clauses to avoid NullPointerExceptions."))
112
 
113
+ # Optional classifier hint
114
  if clf_pipeline:
115
  try:
116
+ pred = clf_pipeline(code[:1000])[0] # short context
117
  mapped = label_to_issue_type.get(pred.get("label"), "Best Practice")
118
  issues.append((mapped, f"Model hint: {mapped} issue likely. Confidence ~{pred.get('score', 0):.2f}"))
119
  except Exception:
 
135
  return ("Best Practice", suggestions["Best Practice"], severities["Best Practice"])
136
  issues_sorted = sorted(issues, key=lambda x: prio.get(x[0], 0), reverse=True)
137
  top_type = issues_sorted[0][0]
 
138
  merged = "; ".join(msg for _, msg in issues_sorted[:3])
139
  return (top_type, merged or suggestions[top_type], severities[top_type])
140
 
141
+ # ---------- Code Analyzer (UI callback) ----------
142
  def analyze_code(code):
143
  if not code or not code.strip():
144
  return "No code provided.", "", ""
 
170
 
171
  return issue_type, suggestion_text, severity
172
 
173
+ # ---------- Metadata Validator (UI callback) ----------
174
  def validate_metadata(metadata, admin_id=None):
175
  if not metadata or not metadata.strip():
176
  return "No metadata provided.", "", ""
 
183
  root = ET.fromstring(metadata)
184
  # 1) Description present?
185
  has_description = any(elem.tag.lower().endswith('description') and (elem.text or '').strip() for elem in root.iter())
186
+ # 2) Duplicate <fullName> or generic <name> values?
187
  names = []
188
  duplicates = set()
189
  for elem in root.iter():
 
198
  missing_help = []
199
  for f in root.iter():
200
  if f.tag.lower().endswith('fields'):
201
+ fname, fdesc, fhelp = None, None, None
 
 
 
202
  for ch in f:
203
  t = ch.tag.lower()
204
  if t.endswith('fullname') and ch.text:
 
257
 
258
  return mtype, issue, recommendation
259
 
260
+ # ---------- Chatbot helpers (no hardcoded answers; model-only) ----------
261
+ def _clean_llm_reply(generated: str) -> str:
262
+ """Strip prompt echoing and keep only the assistant's part."""
263
+ text = generated or ""
264
+ # Keep only content after the last 'Assistant:'
265
+ if "Assistant:" in text:
266
+ text = text.split("Assistant:")[-1]
267
+ # Remove any lines that start with 'User:' to avoid echo
268
+ lines = [line for line in text.splitlines() if not line.strip().startswith("User:")]
269
+ cleaned = "\n".join(lines).strip()
270
+ cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
271
+ return cleaned
272
 
273
  def salesforce_chatbot(query, history=[]):
274
  global conversation_history
 
279
  "apex", "soql", "trigger", "lwc", "aura", "visualforce", "salesforce", "governor limits",
280
  "dml", "metadata", "batch apex", "queueable", "future method", "api", "sfdc", "heap", "limits"
281
  ]
282
+ if not any(k in query.lower() for k in salesforce_keywords):
 
283
  return "Please ask a Salesforce-related question."
284
 
285
  history_summary = "\n".join([f"User: {q}\nAssistant: {a}" for q, a in conversation_history[-4:]])
 
287
  system_prompt = (
288
  "You are a certified Salesforce developer and architect. Answer with correct, production-safe guidance. "
289
  "When relevant, mention governor limits (e.g., 100 SOQL queries per transaction, 150 DML statements). "
290
+ "Use bullets or code snippets. Prefer bulk-safe patterns and official docs. "
291
+ "Do NOT repeat the user's question in your answer."
292
  )
293
+
294
  prompt = f"{system_prompt}\n\nConversation History:\n{history_summary}\n\nUser: {query.strip()}\nAssistant:"
295
 
296
  try:
297
  if nlp_pipeline:
298
+ gen = nlp_pipeline(
299
+ prompt,
300
+ max_new_tokens=220,
301
+ do_sample=False
302
+ )[0]["generated_text"]
303
+ out = _clean_llm_reply(gen)
304
  else:
305
+ out = "⚠️ NLP model not available. Please check Hugging Face pipeline."
306
 
307
+ # Ensure non-trivial response
308
+ if len(out.split()) < 12:
309
+ out += "\n\nRefer to the official docs: https://developer.salesforce.com/docs"
310
 
311
  conversation_history.append((query, out))
312
  conversation_history = conversation_history[-6:]
313
  log_to_console({"Question": query, "Answer": out}, "Chatbot Query")
314
  return out
315
+
316
  except Exception as e:
317
  return f"⚠️ Error generating response: {str(e)}"
318
 
319
  # ---------- Gradio UI ----------
320
+ conversation_history = []
321
+
322
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
323
  gr.Markdown("# 🤖 Advanced Salesforce AI Code Review & Chatbot")
324