-
Notifications
You must be signed in to change notification settings - Fork 1
Add Gradio web interface for standards registry #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
eswarchandravidyasagar
commented
Nov 14, 2025
- Add standards_registry.py with full Gradio UI
- Add interactive data tables with horizontal scrolling
- Add search functionality across standards
- Add .gitignore for Python project
- Update README with web interface documentation
- Update pyproject.toml configuration
- Add standards_registry.py with full Gradio UI - Add interactive data tables with horizontal scrolling - Add search functionality across standards - Add .gitignore for Python project - Update README with web interface documentation - Update pyproject.toml configuration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds a Gradio-based web interface for browsing and interacting with the standards registry, along with supporting configuration files and documentation updates.
- Introduces a complete web UI with browse, search, and data viewing capabilities
- Adds Python project configuration with dependency management
- Updates documentation to include web interface usage instructions
Reviewed Changes
Copilot reviewed 3 out of 5 changed files in this pull request and generated 19 comments.
| File | Description |
|---|---|
| standards_registry.py | New Gradio web application providing interactive UI for browsing, searching, and viewing standards with data tables and statistics |
| pyproject.toml | Project configuration file defining dependencies (Gradio, PyYAML, watchdog) and build settings |
| README.md | Documentation updated with web interface installation, usage instructions, and architecture overview |
| .gitignore | Standard Python gitignore patterns added for virtual environments, build artifacts, IDE files, and Gradio-specific directories |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for std in standards: | ||
| stats = registry.get_statistics(std.id) | ||
| total_records += stats.get("total_records", 0) |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The registry overview loads and processes statistics for all standards sequentially within the loop. This can be inefficient if the registry contains many standards with large datasets. Consider caching statistics or loading them in parallel to improve performance:
stats = registry.get_statistics(std.id)
if stats: # Only proceed if stats exist
total_records += stats.get("total_records", 0)| return [] | ||
|
|
||
| data = registry.load_standard_data(standard_id) | ||
| if not data: | ||
| return [] | ||
|
|
||
| records = registry._get_data_records(data) | ||
| if not records or not records[0]: | ||
| return [] | ||
|
|
||
| fields = list(records[0].keys()) | ||
| if not fields: | ||
| return [] |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function get_all_records_table returns an empty list [] for both error cases and when there are truly no records. This makes it impossible for callers to distinguish between different failure modes. Consider returning None for errors or raising exceptions:
def get_all_records_table(standard_id: str, registry: StandardsRegistry):
if not standard_id:
return None # Or raise ValueError("standard_id is required")
# ... rest of the function| return [] | |
| data = registry.load_standard_data(standard_id) | |
| if not data: | |
| return [] | |
| records = registry._get_data_records(data) | |
| if not records or not records[0]: | |
| return [] | |
| fields = list(records[0].keys()) | |
| if not fields: | |
| return [] | |
| return None | |
| data = registry.load_standard_data(standard_id) | |
| if not data: | |
| return None | |
| records = registry._get_data_records(data) | |
| if not records or not records[0]: | |
| return None | |
| fields = list(records[0].keys()) | |
| if not fields: | |
| return None |
| path_parts = standard.path.split("/") | ||
| filename = path_parts[-1] | ||
| dir_path = "/".join(path_parts[:-1]) | ||
|
|
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The path parsing logic using string split/join operations is fragile and platform-dependent. Since standard.path is already being handled as a Path object on line 62, use Path operations consistently:
data_path = self.registry_path / standard.path
dir_path = data_path.parent
filename = data_path.name| path_parts = standard.path.split("/") | |
| filename = path_parts[-1] | |
| dir_path = "/".join(path_parts[:-1]) | |
| filename = data_path.name | |
| dir_path = data_path.parent.relative_to(self.registry_path) | |
| Or using `pip`: | ||
|
|
||
| ```bash | ||
| pip install -r requirements.txt | ||
| ``` | ||
|
|
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The README references requirements.txt in the installation instructions, but this file doesn't exist in the repository. Since the project uses pyproject.toml for dependency management, either create a requirements.txt file or update the documentation to only mention uv and the pyproject.toml approach.
| Or using `pip`: | |
| ```bash | |
| pip install -r requirements.txt | |
| ``` |
| outputs=[data_table, table_status, table_info] | ||
| ) | ||
| table_standard_dropdown.change( | ||
| fn=lambda sid: ("<div class='table-container'><p>Select a standard and click 'Load Table' to view data.</p></div>", f"### Data Table\n\nSelected: {registry.get_standard(sid).title if sid else 'None'}. Click 'Load Table' to view data.", ""), |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential AttributeError when sid is provided but registry.get_standard(sid) returns None. The lambda attempts to access .title on a potentially None value. Add a safety check:
fn=lambda sid: ("<div class='table-container'><p>Select a standard and click 'Load Table' to view data.</p></div>",
f"### Data Table\n\nSelected: {registry.get_standard(sid).title if sid and registry.get_standard(sid) else 'None'}. Click 'Load Table' to view data.", ""),| fn=lambda sid: ("<div class='table-container'><p>Select a standard and click 'Load Table' to view data.</p></div>", f"### Data Table\n\nSelected: {registry.get_standard(sid).title if sid else 'None'}. Click 'Load Table' to view data.", ""), | |
| fn=lambda sid: ( | |
| "<div class='table-container'><p>Select a standard and click 'Load Table' to view data.</p></div>", | |
| f"### Data Table\n\nSelected: {(registry.get_standard(sid).title if sid and registry.get_standard(sid) is not None else 'None')}. Click 'Load Table' to view data.", | |
| "" | |
| ), |
| if std.tags: | ||
| tags_str = ", ".join(f"`{tag}`" for tag in std.tags) | ||
| overview += f"**Tags:** {tags_str}\n" | ||
| overview += f"**Description:** {std.description[:150]}...\n\n" |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ellipsis ... is always added regardless of the description length. This results in unnecessary ellipsis for descriptions shorter than 150 characters. Consider adding it conditionally:
desc = std.description[:150]
overview += f"**Description:** {desc}{'...' if len(std.description) > 150 else ''}\n\n"| overview += f"**Description:** {std.description[:150]}...\n\n" | |
| desc = std.description[:150] | |
| overview += f"**Description:** {desc}{'...' if len(std.description) > 150 else ''}\n\n" |
| signal.signal(signal.SIGINT, signal_handler) | ||
|
|
||
| try: | ||
| app.launch(share=False, server_name="0.0.0.0", server_port=7860) |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The server is configured to bind to all network interfaces (0.0.0.0), making it accessible from any network. This could expose the application to unauthorized access if deployed on a shared network or server. Consider:
- Using
127.0.0.1for local-only access by default - Adding authentication via Gradio's
authparameter - Documenting security considerations in the README
Example:
app.launch(share=False, server_name="127.0.0.1", server_port=7860)| app.launch(share=False, server_name="0.0.0.0", server_port=7860) | |
| app.launch(share=False, server_name="127.0.0.1", server_port=7860) |
| return [] | ||
|
|
||
| query_lower = query.lower() | ||
| return [r for r in records if query_lower in " ".join(str(v) for v in r.values() if v).lower()] |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The search implementation converts all record values to strings and concatenates them for each record on every search, which is inefficient for large datasets. Consider one of these optimizations:
- Pre-build a search index when data is loaded
- Use more efficient string matching algorithms
- Add pagination to limit the number of records processed
Example:
query_lower = query.lower()
results = []
for r in records:
record_text = " ".join(str(v) for v in r.values() if v).lower()
if query_lower in record_text:
results.append(r)
if len(results) >= 1000: # Limit results for performance
break
return results| elif isinstance(value, str): | ||
| row.append(value.strip() or "—") | ||
| elif isinstance(value, (int, float)): | ||
| row.append(f"{value:,}" if isinstance(value, int) else f"{value:.2f}") |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The number formatting for integers uses a comma separator but doesn't account for locale. While this works for English locales, it may not be appropriate for all regions. Consider using the locale module or document this behavior. Additionally, the float formatting always shows 2 decimal places, which may not be appropriate for all numeric data.
| vertical-align: top !important; | ||
| } | ||
| .data-table th { | ||
| background-color: #374151 !important; |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The table header background color #374151 (dark gray) with white text may not provide sufficient contrast for users with visual impairments. Consider using a darker background or ensuring the contrast ratio meets WCAG AA standards (minimum 4.5:1 for normal text). You can verify contrast at https://webaim.org/resources/contrastchecker/
| background-color: #374151 !important; | |
| background-color: #1f2937 !important; |