usmansafdarktk commited on
Commit
ff9e41b
·
1 Parent(s): 63b19ff

fix(UI): improve dropdowns and update UI

Browse files
Files changed (3) hide show
  1. app.py +49 -36
  2. utils/data_generator.py +3 -53
  3. utils/template_discovery.py +44 -31
app.py CHANGED
@@ -4,6 +4,8 @@ from utils.data_generator import generate_examples
4
  from llm_client import ask_llm
5
  from utils.evaluation import compute_similarity, validate_answer
6
  import asyncio
 
 
7
 
8
  print("Scanning for all available Engchain templates...")
9
  ALL_TEMPLATES = discover_templates()
@@ -109,29 +111,31 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
109
  background: var(--panel-background-fill);
110
  }
111
  """) as demo:
 
112
  gr.Markdown(
113
  """
114
  # 📈 Engchain Template Playground
115
- Select a domain, a file, and a template to generate 10 unique Q&A examples.
116
  Compare your solutions against a live LLM and see similarity + correctness checks.
117
  """
118
  )
119
 
120
  with gr.Group(elem_classes="dropdown-box"):
121
  with gr.Row():
122
- domain_dropdown = gr.Dropdown(
123
- label="1. Select Engineering Domain",
124
- choices=[(format_name(d), d) for d in sorted(list(ALL_TEMPLATES.keys()))],
125
  )
126
- file_dropdown = gr.Dropdown(label="2. Select Specific Area / File", interactive=False)
127
- template_dropdown = gr.Dropdown(label="3. Select Specific Tool / Template", interactive=False)
 
128
 
129
  generate_button = gr.Button("Generate 10 Examples", variant="primary")
130
 
131
  # Pre-create a slot for template heading
132
  template_heading = gr.Markdown("", visible=False, elem_classes="template-heading")
133
 
134
- # Pre-create 10 slots
135
  example_blocks = []
136
  for i in range(10):
137
  with gr.Group(visible=False, elem_classes="example-card") as grp:
@@ -145,57 +149,70 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
145
 
146
  example_blocks.append((grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md))
147
 
148
- # Bind button → LLM response + evaluation
149
  async def reveal_llm(q, s, llm_group=llm_group, eval_md=eval_md, llm_response_md=llm_response_md):
150
- # temporary loading message
151
- await asyncio.sleep(0) # ensures Gradio sees async
152
  yield gr.update(visible=True), gr.update(value=""), gr.update(value="⏳ Fetching LLM response...")
153
-
154
- # call the LLM asynchronously
155
- response = await asyncio.to_thread(call_llm, q) # run blocking LLM call in thread
156
-
157
  sim = compute_similarity(s, response)
158
  match_flag = validate_answer(s, response)
159
-
160
  eval_html = (
161
  f"<div class='badge badge-blue'><span>Textual Similarity:</span> {sim}%</div>"
162
  f"<div class='badge {'badge-green' if match_flag else 'badge-red'}'>"
163
  f"<span>Final Answer Validation:</span> {'✅ Yes' if match_flag else '❌ No'}</div>"
164
  )
165
-
166
  yield gr.update(visible=True), gr.update(value=eval_html), gr.update(value=response)
167
 
168
  btn.click(reveal_llm, inputs=[q_md, s_md], outputs=[llm_group, eval_md, llm_response_md])
169
 
170
  # Dropdown update logic
171
- def update_file_dropdown(domain):
172
- if not domain:
 
 
 
 
 
 
 
 
 
 
 
 
173
  return (
174
  gr.update(choices=[], value=None, interactive=False),
175
  gr.update(choices=[], value=None, interactive=False),
176
  )
177
- files = sorted(list(ALL_TEMPLATES.get(domain, {}).keys()))
178
- formatted_files = [(format_name(f), f) for f in files]
 
 
 
 
 
179
  return (
180
  gr.update(choices=formatted_files, value=None, interactive=True),
181
  gr.update(choices=[], value=None, interactive=False),
182
  )
183
 
184
- def update_template_dropdown(domain, filename):
185
- if not domain or not filename:
 
186
  return gr.update(choices=[], value=None, interactive=False)
187
- templates = sorted(ALL_TEMPLATES.get(domain, {}).get(filename, []))
188
  formatted_templates = [(format_name(t), t) for t in templates]
189
  return gr.update(choices=formatted_templates, value=None, interactive=True)
190
 
191
- # Populate the pre-created slots
192
- def render_examples(domain, filename, template_name):
193
- # If any selection is missing, clear screen and show friendly error
194
- if not domain or not filename or not template_name:
195
- heading_text = "<div style='margin-top:24px; font-size: 20px; color: red;'>⚠️ Please select a domain, file, and template/tool before generating examples.</div>"
 
 
 
196
  template_heading_update = gr.update(value=heading_text, visible=True)
197
 
198
- # Clear all example slots
199
  updates = []
200
  for grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md in example_blocks:
201
  updates.extend([
@@ -209,9 +226,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
209
  ])
210
  return [template_heading_update] + updates
211
 
212
- # Normal behavior: generate examples
213
- examples = generate_examples(domain, filename, template_name)
214
-
215
  heading_text = f"<div style='margin-top:24px; font-size: 24px;'>Generated Examples for Template: <b>{format_name(template_name)}</b></div>"
216
  template_heading_update = gr.update(value=heading_text, visible=True)
217
 
@@ -240,12 +255,9 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
240
  ])
241
  return [template_heading_update] + updates
242
 
243
-
244
- domain_dropdown.change(update_file_dropdown, domain_dropdown, [file_dropdown, template_dropdown])
245
- file_dropdown.change(update_template_dropdown, [domain_dropdown, file_dropdown], template_dropdown)
246
  generate_button.click(
247
  render_examples,
248
- [domain_dropdown, file_dropdown, template_dropdown],
249
  [template_heading] + sum(([grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md]
250
  for (grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md) in example_blocks), [])
251
  )
@@ -254,3 +266,4 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
254
  if __name__ == "__main__":
255
  print("Launching Gradio app...")
256
  demo.launch()
 
 
4
  from llm_client import ask_llm
5
  from utils.evaluation import compute_similarity, validate_answer
6
  import asyncio
7
+ import os
8
+
9
 
10
  print("Scanning for all available Engchain templates...")
11
  ALL_TEMPLATES = discover_templates()
 
111
  background: var(--panel-background-fill);
112
  }
113
  """) as demo:
114
+
115
  gr.Markdown(
116
  """
117
  # 📈 Engchain Template Playground
118
+ Select a branch, domain, file, and template to generate 10 unique Q&A examples.
119
  Compare your solutions against a live LLM and see similarity + correctness checks.
120
  """
121
  )
122
 
123
  with gr.Group(elem_classes="dropdown-box"):
124
  with gr.Row():
125
+ branch_dropdown = gr.Dropdown(
126
+ label="1. Select Branch",
127
+ choices=[(format_name(b), b) for b in sorted(list(ALL_TEMPLATES.keys()))],
128
  )
129
+ domain_dropdown = gr.Dropdown(label="2. Select Engineering Domain", interactive=False)
130
+ file_dropdown = gr.Dropdown(label="3. Select Specific Area / File", interactive=False)
131
+ template_dropdown = gr.Dropdown(label="4. Select Specific Tool / Template", interactive=False)
132
 
133
  generate_button = gr.Button("Generate 10 Examples", variant="primary")
134
 
135
  # Pre-create a slot for template heading
136
  template_heading = gr.Markdown("", visible=False, elem_classes="template-heading")
137
 
138
+ # Pre-create 10 example slots
139
  example_blocks = []
140
  for i in range(10):
141
  with gr.Group(visible=False, elem_classes="example-card") as grp:
 
149
 
150
  example_blocks.append((grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md))
151
 
 
152
  async def reveal_llm(q, s, llm_group=llm_group, eval_md=eval_md, llm_response_md=llm_response_md):
153
+ await asyncio.sleep(0)
 
154
  yield gr.update(visible=True), gr.update(value=""), gr.update(value="⏳ Fetching LLM response...")
155
+ response = await asyncio.to_thread(call_llm, q)
 
 
 
156
  sim = compute_similarity(s, response)
157
  match_flag = validate_answer(s, response)
 
158
  eval_html = (
159
  f"<div class='badge badge-blue'><span>Textual Similarity:</span> {sim}%</div>"
160
  f"<div class='badge {'badge-green' if match_flag else 'badge-red'}'>"
161
  f"<span>Final Answer Validation:</span> {'✅ Yes' if match_flag else '❌ No'}</div>"
162
  )
 
163
  yield gr.update(visible=True), gr.update(value=eval_html), gr.update(value=response)
164
 
165
  btn.click(reveal_llm, inputs=[q_md, s_md], outputs=[llm_group, eval_md, llm_response_md])
166
 
167
  # Dropdown update logic
168
+ def update_domain_dropdown(branch):
169
+ if not branch:
170
+ return gr.update(choices=[], value=None, interactive=False), \
171
+ gr.update(choices=[], value=None, interactive=False), \
172
+ gr.update(choices=[], value=None, interactive=False)
173
+ domains = sorted(list(ALL_TEMPLATES.get(branch, {}).keys()))
174
+ formatted_domains = [(format_name(d), d) for d in domains]
175
+ return gr.update(choices=formatted_domains, value=None, interactive=True), \
176
+ gr.update(choices=[], value=None, interactive=False), \
177
+ gr.update(choices=[], value=None, interactive=False)
178
+
179
+
180
+ def update_file_dropdown(branch, domain):
181
+ if not branch or not domain:
182
  return (
183
  gr.update(choices=[], value=None, interactive=False),
184
  gr.update(choices=[], value=None, interactive=False),
185
  )
186
+ # get all files for this branch+domain
187
+ files_dict = ALL_TEMPLATES.get(branch, {}).get(domain, {})
188
+ files = sorted(list(files_dict.keys()))
189
+
190
+ # Use only the last part of the path as label
191
+ formatted_files = [(format_name(os.path.basename(f)), f) for f in files]
192
+
193
  return (
194
  gr.update(choices=formatted_files, value=None, interactive=True),
195
  gr.update(choices=[], value=None, interactive=False),
196
  )
197
 
198
+
199
+ def update_template_dropdown(branch, domain, filename):
200
+ if not branch or not domain or not filename:
201
  return gr.update(choices=[], value=None, interactive=False)
202
+ templates = sorted(ALL_TEMPLATES.get(branch, {}).get(domain, {}).get(filename, []))
203
  formatted_templates = [(format_name(t), t) for t in templates]
204
  return gr.update(choices=formatted_templates, value=None, interactive=True)
205
 
206
+ branch_dropdown.change(update_domain_dropdown, branch_dropdown, [domain_dropdown, file_dropdown, template_dropdown])
207
+ domain_dropdown.change(update_file_dropdown, [branch_dropdown, domain_dropdown], [file_dropdown, template_dropdown])
208
+ file_dropdown.change(update_template_dropdown, [branch_dropdown, domain_dropdown, file_dropdown], template_dropdown)
209
+
210
+ # Populate example slots
211
+ def render_examples(branch, domain, filename, template_name):
212
+ if not branch or not domain or not filename or not template_name:
213
+ heading_text = "<div style='margin-top:24px; font-size: 20px; color: red;'>⚠️ Please select a branch, domain, file, and template/tool before generating examples.</div>"
214
  template_heading_update = gr.update(value=heading_text, visible=True)
215
 
 
216
  updates = []
217
  for grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md in example_blocks:
218
  updates.extend([
 
226
  ])
227
  return [template_heading_update] + updates
228
 
229
+ examples = generate_examples(branch, domain, filename, template_name)
 
 
230
  heading_text = f"<div style='margin-top:24px; font-size: 24px;'>Generated Examples for Template: <b>{format_name(template_name)}</b></div>"
231
  template_heading_update = gr.update(value=heading_text, visible=True)
232
 
 
255
  ])
256
  return [template_heading_update] + updates
257
 
 
 
 
258
  generate_button.click(
259
  render_examples,
260
+ [branch_dropdown, domain_dropdown, file_dropdown, template_dropdown],
261
  [template_heading] + sum(([grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md]
262
  for (grp, q_md, s_md, btn, llm_group, eval_md, llm_response_md) in example_blocks), [])
263
  )
 
266
  if __name__ == "__main__":
267
  print("Launching Gradio app...")
268
  demo.launch()
269
+
utils/data_generator.py CHANGED
@@ -61,21 +61,12 @@ def discover_templates(root_dir="data/templates/branches"):
61
 
62
  return discovered
63
 
64
-
65
- def generate_examples(domain, filename, template_name):
66
  """
67
  Dynamically imports and runs a selected template function multiple times
68
  to generate a list of {question, solution} objects.
69
-
70
- Args:
71
- domain (str): The name of the domain (e.g. 'chemical_engineering').
72
- filename (str): Relative path under branches/ (e.g. 'chemical_engineering/reaction_kinetics/mole_balances.py').
73
- template_name (str): The template function to call (e.g. 'template_mole_balance').
74
-
75
- Returns:
76
- list[dict]: [{"question": ..., "solution": ...}, ...]
77
  """
78
- if not all([domain, filename, template_name]):
79
  print("Missing arguments to generate_examples()")
80
  return []
81
 
@@ -84,7 +75,6 @@ def generate_examples(domain, filename, template_name):
84
  filename_no_ext = filename.replace(".py", "").replace(os.sep, ".")
85
  module_path = f"data.templates.branches.{filename_no_ext}"
86
 
87
- # Import module dynamically
88
  module = importlib.import_module(module_path)
89
  template_function = getattr(module, template_name)
90
 
@@ -96,48 +86,8 @@ def generate_examples(domain, filename, template_name):
96
  examples.append({"question": question, "solution": solution})
97
  else:
98
  print(f"Warning: {template_name} did not return (question, solution) tuple.")
99
-
100
  return examples
101
 
102
- except ImportError as e:
103
- print(f"ImportError in {domain}/{filename}: {e}")
104
- return []
105
- except AttributeError as e:
106
- print(f"AttributeError in {domain}/{filename}: {e}")
107
- return []
108
  except Exception as e:
109
- print(f"Error running template '{template_name}' from {filename}: {e}")
110
  return []
111
-
112
-
113
- if __name__ == "__main__":
114
- """
115
- Run this file directly to test the discovery and generation system.
116
- """
117
- print("Discovering templates...\n")
118
- templates = discover_templates()
119
-
120
- if not templates:
121
- print("No templates found.")
122
- else:
123
- for domain, files in templates.items():
124
- print(f"\nDomain: {domain}")
125
- for file_path, funcs in files.items():
126
- print(f" {file_path}")
127
- for func in funcs:
128
- print(f" {func}")
129
-
130
- # Example test: run one of the templates
131
- print("\n\n Testing template generation...")
132
- # pick first available template
133
- first_domain = next(iter(templates))
134
- first_file, first_funcs = next(iter(templates[first_domain].items()))
135
- first_func = first_funcs[0]
136
-
137
- examples = generate_examples(first_domain, first_file, first_func)
138
- print(f"\n Generated {len(examples)} examples from {first_func} in {first_file}:")
139
- for ex in examples[:3]:
140
- print("Q:", ex["question"])
141
- print("A:", ex["solution"])
142
- print("---")
143
-
 
61
 
62
  return discovered
63
 
64
+ def generate_examples(branch, domain, filename, template_name):
 
65
  """
66
  Dynamically imports and runs a selected template function multiple times
67
  to generate a list of {question, solution} objects.
 
 
 
 
 
 
 
 
68
  """
69
+ if not all([branch, domain, filename, template_name]):
70
  print("Missing arguments to generate_examples()")
71
  return []
72
 
 
75
  filename_no_ext = filename.replace(".py", "").replace(os.sep, ".")
76
  module_path = f"data.templates.branches.{filename_no_ext}"
77
 
 
78
  module = importlib.import_module(module_path)
79
  template_function = getattr(module, template_name)
80
 
 
86
  examples.append({"question": question, "solution": solution})
87
  else:
88
  print(f"Warning: {template_name} did not return (question, solution) tuple.")
 
89
  return examples
90
 
 
 
 
 
 
 
91
  except Exception as e:
92
+ print(f"Error running template '{template_name}' from {branch}/{domain}/{filename}: {e}")
93
  return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/template_discovery.py CHANGED
@@ -4,7 +4,15 @@ import ast
4
  def discover_templates(root_dir="data/templates/branches"):
5
  """
6
  Recursively scans directory structure to find all template functions
7
- under each domain (supports nested folders like branches/chemical_engineering/...).
 
 
 
 
 
 
 
 
8
  """
9
  discovered = {}
10
 
@@ -12,35 +20,40 @@ def discover_templates(root_dir="data/templates/branches"):
12
  print(f"Error: Directory '{root_dir}' not found.")
13
  return discovered
14
 
15
- # Walk one level down (e.g., chemical_engineering, mechanical_engineering, etc.)
16
- for domain in sorted(os.listdir(root_dir)):
17
- domain_path = os.path.join(root_dir, domain)
18
- if os.path.isdir(domain_path):
19
- domain_templates = {}
20
-
21
- # Recursively walk through all .py files under this domain
22
- for dirpath, _, filenames in os.walk(domain_path):
23
- for filename in sorted(filenames):
24
- if filename.endswith(".py"):
25
- file_path = os.path.join(dirpath, filename)
26
- try:
27
- with open(file_path, "r", encoding="utf-8") as f:
28
- file_content = f.read()
29
-
30
- tree = ast.parse(file_content)
31
- template_functions = [
32
- node.name for node in ast.walk(tree)
33
- if isinstance(node, ast.FunctionDef) and node.name.startswith("template_")
34
- ]
35
-
36
- if template_functions:
37
- rel_path = os.path.relpath(file_path, root_dir)
38
- domain_templates[rel_path] = sorted(template_functions)
39
- except Exception as e:
40
- print(f"Error parsing {file_path}: {e}")
41
-
42
- if domain_templates:
43
- discovered[domain] = domain_templates
 
 
 
 
 
 
44
 
45
  return discovered
46
-
 
4
  def discover_templates(root_dir="data/templates/branches"):
5
  """
6
  Recursively scans directory structure to find all template functions
7
+ under each branch/domain/file.
8
+ Returns:
9
+ dict: {
10
+ branch: {
11
+ domain: {
12
+ file_path: [template_function_names]
13
+ }
14
+ }
15
+ }
16
  """
17
  discovered = {}
18
 
 
20
  print(f"Error: Directory '{root_dir}' not found.")
21
  return discovered
22
 
23
+ # Top-level folders = branches
24
+ for branch in sorted(os.listdir(root_dir)):
25
+ branch_path = os.path.join(root_dir, branch)
26
+ if os.path.isdir(branch_path):
27
+ branch_dict = {}
28
+
29
+ # Domains under branch
30
+ for domain in sorted(os.listdir(branch_path)):
31
+ domain_path = os.path.join(branch_path, domain)
32
+ if os.path.isdir(domain_path):
33
+ domain_templates = {}
34
+
35
+ # Walk all Python files under domain
36
+ for dirpath, _, filenames in os.walk(domain_path):
37
+ for filename in sorted(filenames):
38
+ if filename.endswith(".py"):
39
+ file_path = os.path.join(dirpath, filename)
40
+ try:
41
+ with open(file_path, "r", encoding="utf-8") as f:
42
+ tree = ast.parse(f.read())
43
+ template_functions = [
44
+ node.name for node in ast.walk(tree)
45
+ if isinstance(node, ast.FunctionDef) and node.name.startswith("template_")
46
+ ]
47
+ if template_functions:
48
+ rel_path = os.path.relpath(file_path, root_dir)
49
+ domain_templates[rel_path] = sorted(template_functions)
50
+ except Exception as e:
51
+ print(f"Error parsing {file_path}: {e}")
52
+
53
+ if domain_templates:
54
+ branch_dict[domain] = domain_templates
55
+
56
+ if branch_dict:
57
+ discovered[branch] = branch_dict
58
 
59
  return discovered