a1c00l's picture
Upload result.html
c7cebcc verified
raw
history blame
61 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI SBOM Generated</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
line-height: 1.6;
color: #333;
background-color: #f9f9f9;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 0 20px;
}
/* Header styling */
.header {
background-color: #ffffff;
padding: 15px 20px;
border-bottom: 1px solid #e9ecef;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30px;
}
.header-left {
display: flex;
align-items: center;
}
.header img {
height: 60px;
margin-right: 15px;
}
.header-content {
display: flex;
flex-direction: column;
}
.header h1 {
margin: 0;
font-size: 28px;
color: #2c3e50;
font-weight: 600;
margin-bottom: 5px;
}
.header-right {
display: flex;
gap: 10px;
}
.generate-another-btn {
padding: 12px 20px;
background-color: #3498db;
color: white;
text-decoration: none;
border-radius: 6px;
font-weight: 500;
font-size: 15px;
transition: background-color 0.3s;
}
.generate-another-btn:hover {
background-color: #2980b9;
text-decoration: none;
}
.success-message {
text-align: left;
padding: 15px;
background-color: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 8px;
margin-bottom: 20px;
}
.success-message h2 {
margin: 0;
font-size: 18px;
color: #155724;
font-weight: 500;
}
.model-name {
font-weight: 600;
color: #2c3e50;
}
/* Footer styling */
.footer {
text-align: center;
padding: 20px;
color: #7f8c8d;
font-size: 14px;
margin-top: 30px;
}
/* Content styling */
.content-section {
background-color: #ffffff;
border-radius: 8px;
padding: 25px;
margin-bottom: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.content-section h2 {
color: #2c3e50;
margin-top: 0;
margin-bottom: 20px;
font-size: 22px;
border-bottom: 2px solid #f0f0f0;
padding-bottom: 10px;
}
.content-section h3 {
color: #2c3e50;
margin-top: 0;
margin-bottom: 15px;
font-size: 20px;
}
.content-section p {
margin-bottom: 20px;
font-size: 16px;
line-height: 1.7;
color: #555;
}
/* Button styling */
.button {
display: inline-block;
padding: 12px 20px;
background-color: #7f8c8d;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
font-weight: 500;
text-decoration: none;
transition: background-color 0.3s;
margin-bottom: 20px;
}
.button:hover {
background-color: #95a5a6;
text-decoration: none;
}
button {
padding: 12px 20px;
background-color: #3498db;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
font-weight: 500;
transition: background-color 0.3s;
}
button:hover {
background-color: #2980b9;
}
/* Table styling */
table {
border-collapse: collapse;
width: 100%;
margin-top: 15px;
margin-bottom: 20px;
}
th, td {
border: 1px solid #e9ecef;
padding: 12px;
}
th {
background-color: #f8f9fa;
color: #2c3e50;
font-weight: 600;
}
/* Styling for field checklist items */
.check-mark { color: #27ae60; } /* Green color for check marks */
.x-mark { color: #e74c3c; } /* Red color for x marks */
.field-name { color: #000; } /* Black color for field names */
.field-stars { color: #000; } /* Black color for importance stars */
.improvement {
color: #2c3e50;
background-color: #ecf0f1;
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
border-left: 4px solid #3498db;
}
.improvement-value { color: #27ae60; font-weight: bold; }
.ai-badge {
background-color: #3498db;
color: white;
padding: 3px 8px;
border-radius: 3px;
font-size: 0.8em;
margin-left: 10px;
}
/* Styles for human-friendly viewer */
.aibom-viewer {
margin: 20px 0;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
background-color: #f9f9f9;
}
.aibom-section {
margin-bottom: 20px;
padding: 20px;
border-radius: 8px;
background-color: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.aibom-section h4 {
margin-top: 0;
color: #2c3e50;
border-bottom: 2px solid #f0f0f0;
padding-bottom: 10px;
margin-bottom: 15px;
font-size: 18px;
}
.aibom-property {
display: flex;
margin: 10px 0;
}
.property-name {
font-weight: bold;
width: 200px;
color: #34495e;
}
.property-value {
flex: 1;
color: #555;
line-height: 1.6;
}
.aibom-tabs {
display: flex;
border-bottom: 1px solid #e9ecef;
margin-bottom: 20px;
}
.aibom-tab {
padding: 12px 20px;
cursor: pointer;
background-color: #f8f9fa;
margin-right: 5px;
border-radius: 8px 8px 0 0;
font-weight: 500;
transition: all 0.3s ease;
}
.aibom-tab.active {
background-color: #6c7a89;
color: white;
}
.aibom-tab:hover:not(.active) {
background-color: #e9ecef;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.json-view {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
overflow: auto;
max-height: 500px;
font-family: monospace;
line-height: 1.5;
}
.collapsible {
cursor: pointer;
position: relative;
transition: all 0.3s ease;
}
.collapsible:after {
content: '+';
position: absolute;
right: 10px;
font-weight: bold;
}
.collapsible.active:after {
content: '-';
}
.collapsible-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.collapsible-content.active {
max-height: 500px;
}
.tag {
display: inline-block;
background-color: #e9ecef;
padding: 4px 10px;
border-radius: 16px;
margin: 3px;
font-size: 0.9em;
}
.key-info {
background-color: #e3f2fd;
border-left: 4px solid #2196F3;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
}
/* Progress bar styles */
.progress-container {
width: 100%;
background-color: #f1f1f1;
border-radius: 8px;
margin: 8px 0;
overflow: hidden;
}
.progress-bar {
height: 24px;
border-radius: 8px;
text-align: center;
line-height: 24px;
color: white;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
transition: width 0.5s ease;
}
.progress-excellent {
background-color: #4CAF50; /* Green */
}
.progress-good {
background-color: #2196F3; /* Blue */
}
.progress-fair {
background-color: #FF9800; /* Orange */
}
.progress-poor {
background-color: #f44336; /* Red */
}
.score-table {
width: 100%;
margin-bottom: 20px;
}
.score-table th {
text-align: left;
padding: 12px;
background-color: #f8f9fa;
}
.score-table td {
padding: 12px;
}
/* Adjust column widths for better progress bar display */
.score-table th:nth-child(1), .score-table td:nth-child(1) {
width: 25%; /* Category column */
}
.score-table th:nth-child(2), .score-table td:nth-child(2) {
width: 20%; /* Fields Present column */
}
.score-table th:nth-child(3), .score-table td:nth-child(3) {
width: 15%; /* Score column */
}
.score-table th:nth-child(4), .score-table td:nth-child(4) {
width: 40%; /* Progress column - wider for better display */
}
.score-weight {
font-size: 0.9em;
color: #666;
margin-left: 5px;
}
.score-label {
display: inline-block;
padding: 3px 8px;
border-radius: 4px;
color: white;
font-size: 0.9em;
margin-left: 5px;
background-color: transparent; /* Make background transparent */
}
.total-score-container {
display: flex;
align-items: center;
margin-bottom: 25px;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.total-score {
font-size: 28px;
font-weight: bold;
margin-right: 20px;
color: #2c3e50;
}
.total-progress {
flex: 1;
}
/* Styles for improved user understanding */
.tooltip {
position: relative;
display: inline-block;
cursor: help;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 300px;
background-color: #34495e;
color: #fff;
text-align: left;
border-radius: 6px;
padding: 12px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -150px;
opacity: 0;
transition: opacity 0.3s;
font-size: 0.9em;
line-height: 1.5;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.tooltip .tooltiptext::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #34495e transparent transparent transparent;
}
.missing-fields {
background-color: #ffebee;
border-left: 4px solid #f44336;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
}
.missing-fields h4 {
margin-top: 0;
color: #d32f2f;
margin-bottom: 15px;
}
.missing-fields ul {
margin-bottom: 0;
padding-left: 20px;
}
.recommendations {
background-color: #e8f5e9;
border-left: 4px solid #4caf50;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
}
.recommendations h4 {
margin-top: 0;
color: #2e7d32;
margin-bottom: 15px;
}
.recommendations ul {
margin-bottom: 0;
padding-left: 20px;
}
.importance-indicator {
display: inline-block;
margin-left: 5px;
}
.high-importance {
color: #d32f2f;
}
.medium-importance {
color: #ff9800;
}
.low-importance {
color: #2196f3;
}
.scoring-rubric {
background-color: #e3f2fd;
border-left: 4px solid #2196f3;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
}
.scoring-rubric h4 {
margin-top: 0;
color: #1565c0;
margin-bottom: 15px;
}
.scoring-rubric table {
width: 100%;
margin-top: 15px;
}
.scoring-rubric th, .scoring-rubric td {
padding: 10px;
text-align: left;
}
.note-box {
background-color: #fffbea; /* Lighter yellow background */
border-left: 4px solid #ffc107;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
}
.download-section {
margin: 20px 0;
display: flex;
align-items: center;
}
.download-section p {
margin: 0;
margin-right: 15px;
}
/* Styles for completeness profile */
.completeness-profile {
background-color: #f6f5f5;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
border-left: 4px solid #7f7c7c;
}
.profile-badge {
display: inline-block;
padding: 5px 12px;
border-radius: 20px;
color: white;
font-weight: bold;
margin-right: 10px;
}
.profile-basic {
background-color: #ff9800;
}
.profile-standard {
background-color: #2196f3;
}
.profile-advanced {
background-color: #4caf50;
}
/* Contrast for profile status */
.profile-incomplete {
background-color: #f44336;
color: white; /* Ensure text is visible on red background */
}
.field-tier {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 5px;
}
.tier-critical {
background-color: #d32f2f;
}
.tier-important {
background-color: #ff9800;
}
.tier-supplementary {
background-color: #2196f3;
}
.tier-legend {
display: flex;
margin: 15px 0;
font-size: 0.9em;
}
.tier-legend-item {
display: flex;
align-items: center;
margin-right: 20px;
}
/* Style for validation penalty explanation */
.validation-penalty-info {
background-color: #fff3e0;
border-left: 4px solid #ff9800;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
font-size: 0.95em;
}
.validation-penalty-info h4 {
margin-top: 0;
color: #e65100;
margin-bottom: 15px;
}
/* Validation warning box styling */
.validation-warning-box {
background-color: #fff3e0;
border: 1px solid #ff9800;
border-left: 4px solid #ff9800;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
box-shadow: 0 2px 10px rgba(255, 152, 0, 0.1);
}
.validation-warning-box h4 {
margin-top: 0;
color: #e65100;
margin-bottom: 15px;
display: flex;
align-items: center;
}
.validation-warning-box .warning-icon {
margin-right: 10px;
font-size: 1.2em;
}
.validation-warning-box .issue-summary {
margin-bottom: 15px;
line-height: 1.6;
}
.validation-warning-box .issue-details {
margin-bottom: 15px;
}
.validation-warning-box .issue-list {
margin: 10px 0;
padding-left: 20px;
}
.validation-warning-box .issue-list li {
margin-bottom: 8px;
line-height: 1.5;
}
.validation-warning-box .call-to-action {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #ffcc80;
}
.validation-warning-box .call-to-action p {
margin-bottom: 10px;
}
.issue-tracker-link {
display: inline-block;
padding: 8px 16px;
background-color: #3498db;
color: white;
text-decoration: none;
border-radius: 4px;
font-weight: 500;
transition: background-color 0.3s;
}
.issue-tracker-link:hover {
background-color: #2980b9;
text-decoration: none;
}
/* Category table styling */
.category-table {
margin-bottom: 30px;
}
.category-table h4 {
color: #2c3e50;
margin-bottom: 10px;
font-size: 18px;
}
.category-result {
background-color: #f8f9fa;
padding: 10px;
border-radius: 4px;
margin-top: 10px;
font-weight: bold;
}
.field-type-legend {
background-color: #e3f2fd;
border-left: 4px solid #2196f3;
padding: 15px;
margin: 20px 0;
border-radius: 8px;
font-size: 0.9em;
}
.field-type-legend h4 {
margin-top: 0;
color: #1565c0;
margin-bottom: 10px;
}
.legend-item {
display: inline-block;
margin-right: 20px;
margin-bottom: 5px;
}
/* Support section styling */
.support-section {
background-color: #f8f9fa;
border-left: 4px solid #6c757d;
padding: 20px;
margin: 30px 0;
border-radius: 8px;
text-align: center;
}
.support-section h3 {
margin-top: 0;
color: #495057;
margin-bottom: 15px;
}
.support-section p {
margin-bottom: 15px;
color: #6c757d;
}
.github-button {
display: inline-block;
padding: 12px 20px;
background-color: #3498db;
color: white;
text-decoration: none;
border-radius: 6px;
font-weight: 500;
transition: background-color 0.3s;
}
.github-button:hover {
background-color: #2980b9;
text-decoration: none;
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<div class="header-left">
<a href="https://aetheris.ai/" target="_blank">
<img src="https://huggingface.co/spaces/aetheris-ai/aibom-generator/resolve/main/templates/images/AetherisAI-logo.png" alt="Aetheris AI Logo">
</a>
<div class="header-content">
<h1>AI SBOM Generator</h1>
</div>
</div>
<div class="header-right">
<!-- <a href="https://aetheris-ai-aibom-generator.hf.space/" class="generate-another-btn">πŸš€ Generate Another AI SBOM</a> -->
<a href="/" class="generate-another-btn">πŸš€ Generate Another AI SBOM</a>
</div>
</div>
<!-- Success Message
<div class="success-message">
<h2>βœ… AI SBOM is Generated Successfully for <span class="model-name">{{ model_id }}</span></h2>
</div> -->
<!-- Key Information -->
<div class="key-info">
<h3>πŸ“‹ AI SBOM Summary for model <span class="model-name">{{ model_id }}</span></h3>
<div class="aibom-property">
<span class="property-name">Model:</span>
<span class="property-value">{{ model_id }}</span>
</div>
<div class="aibom-property">
<span class="property-name">Generated:</span>
<span class="property-value">{{ aibom.metadata.timestamp }}</span>
</div>
<div class="aibom-property">
<span class="property-name">SBOM Format:</span>
<span class="property-value">{{ aibom.bomFormat }} {{ aibom.specVersion }}</span>
</div>
<div class="aibom-property">
<span class="property-name">Serial Number:</span>
<span class="property-value">{{ aibom.serialNumber }}</span>
</div>
</div>
<!-- Download Section -->
<div class="download-section">
<p><strong>Download your AI SBOM:</strong></p>
<button onclick="downloadJSON()">πŸ“₯ Download JSON</button>
</div>
<!-- Completeness Profile -->
{% if completeness_score.completeness_profile %}
<div class="completeness-profile">
<h3>πŸ“Š Completeness Assessment</h3>
<div>
<span class="profile-badge profile-{{ completeness_score.completeness_profile.name|lower }}">
{{ completeness_score.completeness_profile.name }}
</span>
<span>{{ completeness_score.completeness_profile.description }}</span>
</div>
</div>
{% endif %}
<!-- Tabbed Content -->
<div class="aibom-viewer">
<div class="aibom-tabs">
<div class="aibom-tab active" onclick="switchTab('human-view')">Human-Friendly View</div>
<div class="aibom-tab" onclick="switchTab('field-checklist')">Field Checklist</div>
<div class="aibom-tab" onclick="switchTab('score-view')">Score Report</div>
<div class="aibom-tab" onclick="switchTab('json-view')">JSON View</div>
</div>
<!-- Human-Friendly View Tab -->
<div id="human-view" class="tab-content active">
<div class="aibom-section">
<h4>πŸ€– AI Model Information</h4>
<div class="aibom-property">
<span class="property-name">Name:</span>
<span class="property-value">{{ aibom.components[0].name if aibom.components else 'Not specified' }}</span>
</div>
<div class="aibom-property">
<span class="property-name">Type:</span>
<span class="property-value">{{ aibom.components[0].type if aibom.components else 'Not specified' }}</span>
</div>
<div class="aibom-property">
<span class="property-name">Version:</span>
<span class="property-value">{{ aibom.components[0].version if aibom.components else 'Not specified' }}</span>
</div>
<div class="aibom-property">
<span class="property-name">Description:</span>
<span class="property-value">{{ aibom.components[0].description if aibom.components and aibom.components[0].description else 'Not specified' }}</span>
</div>
<div class="aibom-property">
<span class="property-name">PURL:</span>
<span class="property-value">{{ aibom.components[0].purl if aibom.components and aibom.components[0].purl else 'Not specified' }}</span>
</div>
{% if aibom.components and aibom.components[0].licenses %}
<div class="aibom-property">
<span class="property-name">Licenses:</span>
<span class="property-value">
{% for license in aibom.components[0].licenses %}
<span class="tag">{{ license.license.id if license.license else 'Unknown' }}</span>
{% endfor %}
</span>
</div>
{% endif %}
</div>
{% if aibom.components and aibom.components[0].modelCard %}
<div class="aibom-section">
<h4>πŸ“Š Model Card</h4>
{% if aibom.components[0].modelCard.modelParameters %}
<div class="aibom-property">
<span class="property-name">Architecture:</span>
<span class="property-value">{{ aibom.components[0].modelCard.modelParameters.modelArchitecture if aibom.components[0].modelCard.modelParameters.modelArchitecture else 'Not specified' }}</span>
</div>
<div class="aibom-property">
<span class="property-name">Task:</span>
<span class="property-value">{{ aibom.components[0].modelCard.modelParameters.task if aibom.components[0].modelCard.modelParameters.task else 'Not specified' }}</span>
</div>
{% endif %}
{% if aibom.components[0].modelCard.properties %}
<div class="aibom-property">
<span class="property-name">Additional Properties:</span>
<span class="property-value">
{% for prop in aibom.components[0].modelCard.properties %}
<span class="tag">{{ prop.name }}: {{ prop.value }}</span>
{% endfor %}
</span>
</div>
{% endif %}
</div>
{% endif %}
{% if aibom.externalReferences %}
<div class="aibom-section">
<h4>πŸ”— External References</h4>
{% for ref in aibom.externalReferences %}
<div class="aibom-property">
<span class="property-name">{{ ref.type|title }}:</span>
<span class="property-value"><a href="{{ ref.url }}" target="_blank">{{ ref.url }}</a></span>
</div>
{% endfor %}
</div>
{% endif %}
<div class="aibom-section">
<h4>πŸ› οΈ Generation Metadata</h4>
<div class="aibom-property">
<span class="property-name">Generated by:</span>
<span class="property-value">{{ aibom.metadata.tools.components[0].name if aibom.metadata.tools and aibom.metadata.tools.components else 'Unknown' }}</span>
</div>
<div class="aibom-property">
<span class="property-name">Timestamp:</span>
<span class="property-value">{{ aibom.metadata.timestamp }}</span>
</div>
{% if aibom.metadata.component %}
<div class="aibom-property">
<span class="property-name">Component Ref:</span>
<span class="property-value">{{ aibom.metadata.component['bom-ref'] }}</span>
</div>
{% endif %}
</div>
</div>
<!-- Field Checklist Tab -->
<div id="field-checklist" class="tab-content">
<div class="content-section">
<h3>Field Checklist & Mapping</h3>
<!-- Field Type Legend -->
<div class="field-type-legend">
<h4>Legend</h4>
<div class="legend-item">
<span class="field-tier tier-critical"></span>
<span>Critical</span>
</div>
<div class="legend-item">
<span class="field-tier tier-important"></span>
<span>Important</span>
</div>
<div class="legend-item">
<span class="field-tier tier-supplementary"></span>
<span>Supplementary</span>
</div>
<div class="legend-item">
<strong>CDX</strong> = CycloneDX Standard
</div>
<div class="legend-item">
<strong>AI</strong> = AI-Specific Extension
</div>
</div>
<p>This breakdown outlines field categories and statuses in the AI SBOM generated for model <strong>{{ model_id }}</strong>, showing how each field impacts the completeness score.</p>
{% if completeness_score.field_checklist %}
<!-- Required Fields Category -->
<div class="category-table">
<h4>Required Fields Category</h4>
<table>
<thead>
<tr>
<th>Status</th>
<th>Field Name</th>
<th>Actual Location</th>
<th>Tier</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{% set required_fields = ['bomFormat', 'specVersion', 'serialNumber', 'version'] %}
{% for field in required_fields %}
<tr>
<td>
{% if completeness_score.field_checklist.get(field, '').startswith('βœ”') %}
<span class="check-mark">βœ”</span>
{% else %}
<span class="x-mark">✘</span>
{% endif %}
</td>
<td>{{ field }}</td>
<td>
{% if completeness_score.field_checklist.get(field, '').startswith('βœ”') %}
$.{{ field }}
{% else %}
Not found
{% endif %}
</td>
<td><span class="field-tier tier-critical"></span> Critical</td>
<td>CDX</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="category-result">
Result: {{ completeness_score.category_details.required_fields.present_fields if completeness_score.category_details else 'N/A' }}/{{ completeness_score.category_details.required_fields.total_fields if completeness_score.category_details else 'N/A' }} present
({{ completeness_score.category_details.required_fields.percentage if completeness_score.category_details else 'N/A' }}%) =
{{ completeness_score.section_scores.required_fields if completeness_score.section_scores else 'N/A' }}/20 points
</div>
</div>
<!-- Metadata Category -->
<div class="category-table">
<h4>Metadata Category</h4>
<table>
<thead>
<tr>
<th>Status</th>
<th>Field Name</th>
<th>Actual Location</th>
<th>Tier</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{% set metadata_fields = [
('primaryPurpose', 'Critical'),
('suppliedBy', 'Critical'),
('standardCompliance', 'Supplementary'),
('domain', 'Supplementary'),
('autonomyType', 'Supplementary')
] %}
{% for field, tier in metadata_fields %}
<tr>
<td>
{% if completeness_score.field_checklist.get(field, '').startswith('βœ”') %}
<span class="check-mark">βœ”</span>
{% else %}
<span class="x-mark">✘</span>
{% endif %}
</td>
<td>{{ field }}</td>
<td>
{% if completeness_score.field_checklist.get(field, '').startswith('βœ”') %}
$.metadata.properties[name="{{ field }}"]
{% else %}
Not found
{% endif %}
</td>
<td><span class="field-tier tier-{{ tier.lower() }}"></span> {{ tier }}</td>
<td>AI</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="category-result">
Result: {{ completeness_score.category_details.metadata.present_fields if completeness_score.category_details else 'N/A' }}/{{ completeness_score.category_details.metadata.total_fields if completeness_score.category_details else 'N/A' }} present
({{ completeness_score.category_details.metadata.percentage if completeness_score.category_details else 'N/A' }}%) =
{{ completeness_score.section_scores.metadata if completeness_score.section_scores else 'N/A' }}/20 points
</div>
</div>
<!-- Component Basic Category -->
<div class="category-table">
<h4>Component Basic Category</h4>
<table>
<thead>
<tr>
<th>Status</th>
<th>Field Name</th>
<th>Actual Location</th>
<th>Tier</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{% set component_basic_fields = [
('name', 'Critical'),
('type', 'Important'),
('purl', 'Important'),
('description', 'Important'),
('licenses', 'Important')
] %}
{% for field, tier in component_basic_fields %}
<tr>
<td>
{% if completeness_score.field_checklist.get(field, '').startswith('βœ”') %}
<span class="check-mark">βœ”</span>
{% else %}
<span class="x-mark">✘</span>
{% endif %}
</td>
<td>{{ field }}</td>
<td>
{% if completeness_score.field_checklist.get(field, '').startswith('βœ”') %}
$.components[0].{{ field }}
{% else %}
{% if field == 'description' %}
Not found in component level
{% else %}
Not found
{% endif %}
{% endif %}
</td>
<td><span class="field-tier tier-{{ tier.lower() }}"></span> {{ tier }}</td>
<td>CDX</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="category-result">
Result: {{ completeness_score.category_details.component_basic.present_fields if completeness_score.category_details else 'N/A' }}/{{ completeness_score.category_details.component_basic.total_fields if completeness_score.category_details else 'N/A' }} present
({{ completeness_score.category_details.component_basic.percentage if completeness_score.category_details else 'N/A' }}%) =
{{ completeness_score.section_scores.component_basic if completeness_score.section_scores else 'N/A' }}/20 points
</div>
</div>
<!-- Component Model Card Category -->
<div class="category-table">
<h4>Component Model Card Category</h4>
<table>
<thead>
<tr>
<th>Status</th>
<th>Field Name</th>
<th>Actual Location</th>
<th>Tier</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{% set model_card_fields = [
('energyConsumption', 'Important'),
('hyperparameter', 'Important'),
('limitation', 'Important'),
('safetyRiskAssessment', 'Important'),
('typeOfModel', 'Important'),
('modelExplainability', 'Supplementary'),
('energyQuantity', 'Supplementary'),
('energyUnit', 'Supplementary'),
('informationAboutTraining', 'Supplementary'),
('informationAboutApplication', 'Supplementary'),
('metric', 'Supplementary'),
('metricDecisionThreshold', 'Supplementary'),
('modelDataPreprocessing', 'Supplementary'),
('useSensitivePersonalInformation', 'Supplementary')
] %}
{% for field, tier in model_card_fields %}
<tr>
<td>
{% if completeness_score.field_checklist.get(field, '').startswith('βœ”') %}
<span class="check-mark">βœ”</span>
{% else %}
<span class="x-mark">✘</span>
{% endif %}
</td>
<td>{{ field }}</td>
<td>
{% if completeness_score.field_checklist.get(field, '').startswith('βœ”') %}
{% if field == 'typeOfModel' %}
$.metadata.properties[name="{{ field }}"]
{% else %}
$.components[0].modelCard.{{ field }}
{% endif %}
{% else %}
Not found
{% endif %}
</td>
<td><span class="field-tier tier-{{ tier.lower() }}"></span> {{ tier }}</td>
<td>AI</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="category-result">
Result: {{ completeness_score.category_details.component_model_card.present_fields if completeness_score.category_details else 'N/A' }}/{{ completeness_score.category_details.component_model_card.total_fields if completeness_score.category_details else 'N/A' }} present
({{ completeness_score.category_details.component_model_card.percentage if completeness_score.category_details else 'N/A' }}%) =
{{ completeness_score.section_scores.component_model_card if completeness_score.section_scores else 'N/A' }}/30 points
</div>
</div>
<!-- External References Category -->
<div class="category-table">
<h4>External References Category</h4>
<table>
<thead>
<tr>
<th>Status</th>
<th>Field Name</th>
<th>Actual Location</th>
<th>Tier</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{% set external_ref_fields = [('downloadLocation', 'Critical')] %}
{% for field, tier in external_ref_fields %}
<tr>
<td>
{% if completeness_score.field_checklist.get(field, '').startswith('βœ”') %}
<span class="check-mark">βœ”</span>
{% else %}
<span class="x-mark">✘</span>
{% endif %}
</td>
<td>{{ field }}</td>
<td>
{% if completeness_score.field_checklist.get(field, '').startswith('βœ”') %}
$.externalReferences[type="distribution"]
{% else %}
Not found
{% endif %}
</td>
<td><span class="field-tier tier-{{ tier.lower() }}"></span> {{ tier }}</td>
<td>CDX</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="category-result">
Result: {{ completeness_score.category_details.external_references.present_fields if completeness_score.category_details else 'N/A' }}/{{ completeness_score.category_details.external_references.total_fields if completeness_score.category_details else 'N/A' }} present
({{ completeness_score.category_details.external_references.percentage if completeness_score.category_details else 'N/A' }}%) =
{{ completeness_score.section_scores.external_references if completeness_score.section_scores else 'N/A' }}/10 points
</div>
</div>
{% else %}
<p>Field checklist data not available.</p>
{% endif %}
</div>
</div>
<!-- Score Report Tab -->
<div id="score-view" class="tab-content">
<div class="content-section">
<h3>πŸ“Š Completeness Score Report</h3>
<!-- Total Score Display -->
<div class="total-score-container">
<div class="total-score">{{ (completeness_score.total_score if completeness_score.total_score != "Undefined" else 0)|round(1) }}/100</div>
<div class="total-progress">
<div class="progress-container">
{% set score_percent = completeness_score.total_score %}
{% if score_percent >= 90 %}
{% set score_class = 'progress-excellent' %}
{% set score_label = 'Excellent' %}
{% elif score_percent >= 70 %}
{% set score_class = 'progress-good' %}
{% set score_label = 'Good' %}
{% elif score_percent >= 50 %}
{% set score_class = 'progress-fair' %}
{% set score_label = 'Fair' %}
{% else %}
{% set score_class = 'progress-poor' %}
{% set score_label = 'Poor' %}
{% endif %}
<div class="progress-bar {{ score_class }}" style="width: {{ score_percent }}%">
{{ score_percent|int }}% {{ score_label }}
</div>
</div>
</div>
</div>
<!-- Specific Breakdown for This SBOM -->
<div class="note-box">
<h4>Your AI SBOM Breakdown</h4>
<p><strong>Model:</strong> {{ model_id }}</p>
<table class="score-table">
<thead>
<tr>
<th>Category</th>
<th>Fields Present</th>
<th>Score</th>
<th>Progress</th>
</tr>
</thead>
<tbody>
{% if completeness_score.category_details and completeness_score.section_scores %}
{% set categories = [
('Required Fields', 'required_fields', 20),
('Metadata', 'metadata', 20),
('Component Basic', 'component_basic', 20),
('Model Card', 'component_model_card', 30),
('External References', 'external_references', 10)
] %}
{% for display_name, key, max_score in categories %}
<tr>
<td>{{ display_name }}</td>
<td>{{ completeness_score.category_details[key].present_fields }}/{{ completeness_score.category_details[key].total_fields }}</td>
<td>{{ completeness_score.section_scores[key]|round(1) }}/{{ max_score }}</td>
<td>
<div class="progress-container">
{% set percentage = completeness_score.category_details[key].percentage %}
{% if percentage >= 80 %}
{% set progress_class = "progress-excellent" %}
{% elif percentage >= 60 %}
{% set progress_class = "progress-good" %}
{% elif percentage >= 40 %}
{% set progress_class = "progress-fair" %}
{% else %}
{% set progress_class = "progress-poor" %}
{% endif %}
<div class="progress-bar {{ progress_class }}" style="width: {{ percentage }}%">{{ percentage|round(0) }}%</div>
</div>
</td>
</tr>
{% endfor %}
{% else %}
<tr><td colspan="4">Breakdown data not available</td></tr>
{% endif %}
</tbody>
</table>
</div>
<p><strong>Calculation:</strong></p>
<p>Subtotal:
{% if completeness_score.section_scores %}
{% for category, score in completeness_score.section_scores.items() %}
{{ score|round(1) }}{% if not loop.last %} + {% endif %}
{% endfor %}
= <strong>{{ completeness_score.subtotal_score|round(1) }}/100</strong>
{% else %}
<strong>{{ completeness_score.subtotal_score|round(1) }}/100</strong>
{% endif %}
</p>
{% if completeness_score.penalty_applied %}
<p>Penalty Applied: <strong>-{{ completeness_score.penalty_percentage }}%</strong> ({{ completeness_score.penalty_reason }})</p>
<p>Final Score: {{ completeness_score.subtotal_score|round(1) }} Γ— {{ completeness_score.penalty_factor }} = <strong>{{ completeness_score.total_score|round(1) }}/100</strong></p>
{% else %}
<p>No penalties applied</p>
<p>Final Score: <strong>{{ completeness_score.total_score|round(1) }}/100</strong></p>
{% endif %}
</div>
<!-- Missing Fields Analysis -->
{% if completeness_score.missing_counts %}
<div class="missing-fields">
<h4>Missing Fields Summary</h4>
<ul>
<li><strong>Critical:</strong> {{ completeness_score.missing_counts.critical }} missing</li>
<li><strong>Important:</strong> {{ completeness_score.missing_counts.important }} missing</li>
<li><strong>Supplementary:</strong> {{ completeness_score.missing_counts.supplementary }} missing</li>
</ul>
{% if completeness_score.missing_counts.important >= 5 %}
<p><strong>Impact:</strong> Missing multiple critical and/or important fields will incur penalties according to the Penalty Structure.</p>
{% endif %}
</div>
{% endif %}
<!-- Recommendations -->
{% if completeness_score.recommendations %}
<div class="recommendations">
<h4>General Recommendations to Improve AI SBOM Completeness</h4>
<ul>
<li><strong>Required Fields:</strong> Ensure the model is published with a clear name, version, and hosting platform information to allow proper SBOM structuring.</li>
<li><strong>Metadata:</strong> Include author or organization name, purpose of the model, and relevant timestamps in the model repository or card.</li>
<li><strong>Component Basic:</strong> Provide a descriptive model title, a meaningful description, a valid license, and a consistent version reference (e.g., tags or commits).</li>
<li><strong>Model Card:</strong> Fill out structured sections for model parameters, evaluation metrics, limitations, and ethical considerations to enable full transparency.</li>
<li><strong>External References:</strong> Add links to source code, datasets, documentation, and versioned download locations to support traceability and reproducibility.</li>
</ul>
</div>
<!-- Generic Scoring Explanation -->
<div class="scoring-rubric">
<h4>How AI SBOM Completeness is Scored</h4>
<p>The completeness score evaluates how well your AI SBOM documents the model across five key categories:</p>
<ul>
<li><strong>Required Fields (20 points):</strong> Basic SBOM structure mandated by CycloneDX</li>
<li><strong>Metadata (20 points):</strong> Information about the SBOM generation and model purpose</li>
<li><strong>Component Basic (20 points):</strong> Essential model identification and licensing</li>
<li><strong>Model Card (30 points):</strong> Detailed AI-specific documentation for transparency</li>
<li><strong>External References (10 points):</strong> Links to model resources and documentation</li>
</ul>
<p><strong>Calculation Method:</strong></p>
<p>Each category score = (Present Fields Γ· Total Fields) Γ— Maximum Points</p>
<p>Subtotal = Sum of all category scores</p>
<p>Final Score = Subtotal Γ— Penalty Factor (if applicable)</p>
<h4>Penalty Structure:</h4>
<p><strong>Critical Fields Missing:</strong></p>
<ul>
<li>0-1 missing: No penalty</li>
<li>2-3 missing: 10% penalty (Γ—0.9)</li>
<li>4+ missing: 20% penalty (Γ—0.8)</li>
</ul>
<p><strong>Important Fields Missing:</strong></p>
<ul>
<li>0-4 missing: No penalty</li>
<li>5+ missing: 5% penalty (Γ—0.95)</li>
</ul>
<p><strong>Note:</strong> Penalties are cumulative and applied to the subtotal. For example, if you have 3 critical fields missing AND 5 important fields missing, both penalties apply: Subtotal Γ— 0.9 Γ— 0.95 = Final Score.</p>
</div>
{% endif %}
</div>
</div>
<!-- JSON View Tab -->
<div id="json-view" class="tab-content">
<div class="content-section">
<h3>πŸ“„ Raw JSON View</h3>
<p>This is the complete AI SBOM in CycloneDX JSON format:</p>
<div class="json-view">
<pre>{{ aibom|tojson(indent=2) }}</pre>
</div>
</div>
</div>
</div>
<!-- Support Section -->
<div class="support-section">
<h3>πŸ› οΈ Need Help or Found an Issue?</h3>
<p>If you encountered any problems, found a bug, or have suggestions for improvement, we'd love to hear from you!</p>
<a href="https://github.com/aetheris-ai/aibom-generator/issues" target="_blank" class="github-button">
Report Issue on GitHub
</a>
</div>
<!-- Help us spread the word section -->
<div class="content-section" style="text-align: center;">
<h3>πŸ—£οΈ Help Us Spread the Word</h3>
<p>If you find this tool useful, share it with your network! <a href="https://sbom.aetheris.ai" target="_blank" rel="noopener noreferrer">https://sbom.aetheris.ai</a></p>
<a href="https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fsbom.aetheris.ai" target="_blank" rel="noopener noreferrer" style="text-decoration: none;">
<button style="background-color: #0077b5;">πŸ”— Share on LinkedIn</button>
</a>
<p style="margin-top: 10px; font-size: 14px;">
Follow us for updates:
<a href="https://www.linkedin.com/company/aetheris-ai" target="_blank" rel="noopener noreferrer">@Aetheris AI</a>
</p>
</div>
<!-- Info Section -->
<div class="content-section" style="text-align: center;">
<!-- Display the SBOM count -->
<div class="sbom-count">πŸš€ Generated AI SBOMs using this tool: <strong>{{ sbom_count if sbom_count else 'N/A' }}</strong></div>
</div>
<!-- Footer -->
<div class="footer">
<p>Β© 2025 AI SBOM Generator | Powered by Aetheris AI</p>
</div>
</div>
<script>
function switchTab(tabId) {
// Hide all tab contents
var tabContents = document.getElementsByClassName('tab-content');
for (var i = 0; i < tabContents.length; i++) {
tabContents[i].classList.remove('active');
}
// Deactivate all tabs
var tabs = document.getElementsByClassName('aibom-tab');
for (var i = 0; i < tabs.length; i++) {
tabs[i].classList.remove('active');
}
// Activate the selected tab and content
document.getElementById(tabId).classList.add('active');
var selectedTab = document.querySelector('.aibom-tab[onclick="switchTab(\'' + tabId + '\')"]');
selectedTab.classList.add('active');
}
function toggleCollapsible(element) {
element.classList.toggle('active');
var content = element.nextElementSibling;
content.classList.toggle('active');
if (content.classList.contains('active')) {
content.style.maxHeight = content.scrollHeight + 'px';
} else {
content.style.maxHeight = '0';
}
}
function downloadJSON() {
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify({{ aibom|tojson }}, null, 2));
var downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", "{{ model_id|replace('/', '_') }}_aibom.json");
document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click();
downloadAnchorNode.remove();
}
// Initialize collapsible sections
document.addEventListener('DOMContentLoaded', function() {
var collapsibles = document.getElementsByClassName('collapsible');
for (var i = 0; i < collapsibles.length; i++) {
toggleCollapsible(collapsibles[i]);
}
});
</script>
</body>
</html>