Spaces:
Running
Running
<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; | |
margin-bottom: 30px; | |
} | |
.header img { | |
height: 60px; | |
margin-right: 15px; | |
} | |
.header h1 { | |
margin: 0; | |
font-size: 28px; | |
color: #2c3e50; | |
font-weight: 600; | |
} | |
/* header-content div for layout */ | |
.header .header-content { | |
display: flex; | |
flex-direction: column; /* Stack title and count */ | |
} | |
.header h1 { | |
margin: 0; | |
font-size: 28px; | |
color: #2c3e50; | |
font-weight: 600; | |
margin-bottom: 5px; /* Space between title and count */ | |
} | |
/* Added style for sbom-count */ | |
.header .sbom-count { | |
font-size: 14px; | |
color: #555; | |
font-weight: 500; | |
} | |
/* 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; | |
} | |
.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; | |
} | |
.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: #e8f5e9; | |
border-radius: 8px; | |
padding: 20px; | |
margin: 20px 0; | |
border-left: 4px solid #4caf50; | |
} | |
.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; | |
} | |
/* Section for score calculation explanation */ | |
.score-calculation { | |
margin-top: 30px; | |
padding: 25px; | |
background-color: #ffffff; | |
border-radius: 8px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.05); | |
} | |
.score-calculation h3 { | |
margin-top: 0; | |
color: #2c3e50; | |
border-bottom: 2px solid #f0f0f0; | |
padding-bottom: 10px; | |
margin-bottom: 20px; | |
} | |
.calculation-section { | |
margin-bottom: 25px; | |
} | |
/* Footer styling */ | |
.footer { | |
text-align: center; | |
padding: 20px; | |
color: #7f8c8d; | |
font-size: 14px; | |
margin-top: 30px; | |
} | |
/* Responsive adjustments */ | |
@media (max-width: 768px) { | |
.aibom-property { | |
flex-direction: column; | |
} | |
.property-name { | |
width: 100%; | |
margin-bottom: 5px; | |
} | |
.total-score-container { | |
flex-direction: column; | |
align-items: flex-start; | |
} | |
.total-score { | |
margin-bottom: 10px; | |
} | |
.aibom-tabs { | |
flex-wrap: wrap; | |
} | |
.aibom-tab { | |
margin-bottom: 5px; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<!-- Header with logo, title, and SBOM count --> | |
<div class="header"> | |
<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> | |
<!-- Header-content div --> | |
<div class="header-content"> | |
<h1>AI SBOM Generator</h1> | |
</div> | |
</div> | |
<div class="container"> | |
<div class="content-section"> | |
<h2>AI SBOM Generated for {{ model_id }}</h2> | |
<a href="/" class="button">Generate another AI SBOM</a> | |
<div class="download-section"> | |
<p>Download generated AI SBOM in CycloneDX format</p> | |
<button onclick="downloadJSON()">Download JSON</button> | |
</div> | |
{% if enhancement_report and enhancement_report.ai_enhanced %} | |
<div class="improvement"> | |
<h3>AI Enhancement Results</h3> | |
<p>This AI SBOM was enhanced using <strong>{{ enhancement_report.ai_model }}</strong></p> | |
<p>Original Score: {{ enhancement_report.original_score.total_score|round(1) }}/100</p> | |
<p>Enhanced Score: {{ enhancement_report.final_score.total_score|round(1) }}/100</p> | |
<p>Improvement: <span class="improvement-value">+{{ enhancement_report.improvement|round(1) }} points</span></p> | |
</div> | |
{% endif %} | |
</div> | |
<!-- Human-friendly AI SBOM Viewer --> | |
<div class="note-box"> | |
<p><strong>Note:</strong> This page displays the AI SBOM in a human-friendly format for easier readability. | |
The downloaded JSON file follows the standard CycloneDX format required for interoperability with other tools.</p> | |
</div> | |
<div class="aibom-tabs"> | |
<div class="aibom-tab active" onclick="switchTab('human-view')">Human-Friendly View</div> | |
<div class="aibom-tab" onclick="switchTab('json-view')">JSON 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> | |
<div id="human-view" class="tab-content active"> | |
<div class="aibom-viewer"> | |
<!-- Key Information Section --> | |
<div class="aibom-section key-info"> | |
<h4>Key Information</h4> | |
<div class="aibom-property"> | |
<div class="property-name">Model Name:</div> | |
<div class="property-value">{{ aibom.components[0].name if aibom.components and aibom.components[0].name else 'Not specified' }}</div> | |
</div> | |
<div class="aibom-property"> | |
<div class="property-name">Type:</div> | |
<div class="property-value">{{ aibom.components[0].type if aibom.components and aibom.components[0].type else 'Not specified' }}</div> | |
</div> | |
<div class="aibom-property"> | |
<div class="property-name">Version:</div> | |
<div class="property-value">{{ aibom.components[0].version if aibom.components and aibom.components[0].version else 'Not specified' }}</div> | |
</div> | |
<div class="aibom-property"> | |
<div class="property-name">PURL:</div> | |
<div class="property-value">{{ aibom.components[0].purl if aibom.components and aibom.components[0].purl else 'Not specified' }}</div> | |
</div> | |
{% if aibom.components and aibom.components[0].description %} | |
<div class="aibom-property"> | |
<div class="property-name">Description:</div> | |
<div class="property-value">{{ aibom.components[0].description }}</div> | |
</div> | |
{% endif %} | |
</div> | |
<!-- Model Card Section --> | |
{% if aibom.components and aibom.components[0].modelCard %} | |
<div class="aibom-section"> | |
<h4 class="collapsible" onclick="toggleCollapsible(this)">Model Card</h4> | |
<div class="collapsible-content"> | |
{% if aibom.components[0].modelCard.modelParameters %} | |
<div class="aibom-property"> | |
<div class="property-name">Model Parameters:</div> | |
<div class="property-value"> | |
<ul> | |
{% for key, value in aibom.components[0].modelCard.modelParameters.items() %} | |
<li><strong>{{ key }}:</strong> {{ value }}</li> | |
{% endfor %} | |
</ul> | |
</div> | |
</div> | |
{% endif %} | |
{% if aibom.components[0].modelCard.considerations %} | |
<div class="aibom-property"> | |
<div class="property-name">Considerations:</div> | |
<div class="property-value"> | |
<ul> | |
{% for key, value in aibom.components[0].modelCard.considerations.items() %} | |
<li><strong>{{ key }}:</strong> {{ value }}</li> | |
{% endfor %} | |
</ul> | |
</div> | |
</div> | |
{% endif %} | |
</div> | |
</div> | |
{% endif %} | |
<!-- External References Section --> | |
{% if aibom.components and aibom.components[0].externalReferences %} | |
<div class="aibom-section"> | |
<h4 class="collapsible" onclick="toggleCollapsible(this)">External References</h4> | |
<div class="collapsible-content"> | |
<ul> | |
{% for ref in aibom.components[0].externalReferences %} | |
<li> | |
<strong>{{ ref.type }}:</strong> | |
<a href="{{ ref.url }}" target="_blank">{{ ref.url }}</a> | |
{% if ref.comment %} | |
<br><em>{{ ref.comment }}</em> | |
{% endif %} | |
</li> | |
{% endfor %} | |
</ul> | |
</div> | |
</div> | |
{% endif %} | |
</div> | |
</div> | |
<div id="json-view" class="tab-content"> | |
<div class="json-view"> | |
<pre>{{ aibom | tojson(indent=2) }}</pre> | |
</div> | |
</div> | |
<div id="field-checklist" class="tab-content"> | |
<div class="content-section"> | |
<h3>Field Checklist & Mapping</h3> | |
<!-- Field Tier Legend --> | |
<div class="tier-legend"> | |
<div class="tier-legend-item"> | |
<span class="field-tier tier-critical"></span> | |
<span>Critical</span> | |
</div> | |
<div class="tier-legend-item"> | |
<span class="field-tier tier-important"></span> | |
<span>Important</span> | |
</div> | |
<div class="tier-legend-item"> | |
<span class="field-tier tier-supplementary"></span> | |
<span>Supplementary</span> | |
</div> | |
</div> | |
<p>This table shows how fields map to the CycloneDX specification and their status in your AI SBOM.</p> | |
<div class="field-mapping-container"> | |
<h4>Standard CycloneDX Fields</h4> | |
<p>These fields are part of the official CycloneDX specification and are used in all SBOMs:</p> | |
<table class="field-mapping-table"> | |
<thead> | |
<tr> | |
<th>Status</th> | |
<th>Field Name</th> | |
<th>CycloneDX JSON Path</th> | |
<th>Info</th> | |
<th>Importance</th> | |
</tr> | |
</thead> | |
<tbody> | |
{% for field_key, field_data in completeness_score.field_categorization.standard_cyclonedx_fields.items() %} | |
<tr class="{% if field_data.status == 'β' %}present-field{% else %}missing-field{% endif %}"> | |
<td class="status-cell"> | |
{% if field_data.status == "β" %} | |
<span class="check-mark">β</span> | |
{% else %} | |
<span class="x-mark">β</span> | |
{% endif %} | |
</td> | |
<td>{{ field_data.field_name }}</td> | |
<td>{{ field_data.json_path }}</td> | |
<td> | |
<span class="tooltip">(?) | |
<span class="tooltiptext">{{ field_data.field_name }} field information.</span> | |
</span> | |
</td> | |
<td> | |
<span class="field-tier tier-{{ field_data.importance|lower }}"></span> | |
{{ field_data.importance }} | |
</td> | |
</tr> | |
{% endfor %} | |
</tbody> | |
</table> | |
<h4>AI-Specific Extension Fields</h4> | |
<p>These fields extend the CycloneDX specification specifically for AI models:</p> | |
<table class="field-mapping-table"> | |
<thead> | |
<tr> | |
<th>Status</th> | |
<th>Field Name</th> | |
<th>CycloneDX JSON Path</th> | |
<th>Info</th> | |
<th>Importance</th> | |
</tr> | |
</thead> | |
<tbody> | |
{% for field_key, field_data in completeness_score.field_categorization.ai_specific_extension_fields.items() %} | |
<tr class="{% if field_data.status == 'β' %}present-field{% else %}missing-field{% endif %}"> | |
<td class="status-cell"> | |
{% if field_data.status == "β" %} | |
<span class="check-mark">β</span> | |
{% else %} | |
<span class="x-mark">β</span> | |
{% endif %} | |
</td> | |
<td>{{ field_data.field_name }}</td> | |
<td>{{ field_data.json_path }}</td> | |
<td> | |
<span class="tooltip">(?) | |
<span class="tooltiptext">{{ field_data.field_name }} field information.</span> | |
</span> | |
</td> | |
<td> | |
<span class="field-tier tier-{{ field_data.importance|lower }}"></span> | |
{{ field_data.importance }} | |
</td> | |
</tr> | |
{% endfor %} | |
</tbody> | |
</table> | |
</div> | |
<style> | |
.field-mapping-container { | |
margin-top: 20px; | |
max-width: 100%; | |
overflow-x: auto; | |
} | |
.field-mapping-table { | |
width: 100%; | |
border-collapse: collapse; | |
margin-bottom: 30px; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
border-radius: 8px; | |
overflow: hidden; | |
table-layout: fixed; | |
} | |
.field-mapping-table th { | |
background-color: #f5f5f5; | |
padding: 12px 15px; | |
text-align: left; | |
font-weight: 600; | |
color: #333; | |
border-bottom: 2px solid #ddd; | |
} | |
.field-mapping-table td { | |
padding: 10px 15px; | |
border-bottom: 1px solid #eee; | |
vertical-align: middle; | |
word-wrap: break-word; | |
word-break: break-word; | |
max-width: 250px; | |
} | |
.field-mapping-table tr:last-child td { | |
border-bottom: none; | |
} | |
.field-mapping-table tr:hover { | |
background-color: #f9f9f9; | |
} | |
.status-cell { | |
text-align: center; | |
width: 60px; | |
} | |
.present-field { | |
background-color: #f0f7f0; | |
} | |
.missing-field { | |
background-color: #fff7f7; | |
} | |
.check-mark { | |
color: #4caf50; | |
font-weight: bold; | |
font-size: 18px; | |
} | |
.x-mark { | |
color: #f44336; | |
font-weight: bold; | |
font-size: 18px; | |
} | |
</style> | |
</div> | |
</div> | |
<div id="score-view" class="tab-content"> | |
<div class="content-section"> | |
<h3>AI SBOM Completeness Score</h3> | |
<!-- Completeness Profile Section --> | |
{% if completeness_score.completeness_profile %} | |
<div class="completeness-profile"> | |
<h4>Completeness Profile: | |
<span class="profile-badge profile-{{ completeness_score.completeness_profile.name|lower }}"> | |
{{ completeness_score.completeness_profile.name }} | |
</span> | |
</h4> | |
<p>{{ completeness_score.completeness_profile.description }}</p> | |
{% if completeness_score.completeness_profile.next_level %} | |
<p><strong>Next level:</strong> {{ completeness_score.completeness_profile.next_level.name }} | |
({{ completeness_score.completeness_profile.next_level.missing_fields_count }} fields to add)</p> | |
{% endif %} | |
</div> | |
{% endif %} | |
<!-- Total Score with Progress Bar --> | |
<div class="total-score-container"> | |
<div class="total-score">{{ completeness_score.total_score|round(1) }}/100</div> | |
<div class="total-progress"> | |
<div class="progress-container"> | |
{% set score_percent = (completeness_score.total_score / 100) * 100 %} | |
{% set score_class = 'progress-poor' %} | |
{% set score_label = 'Poor' %} | |
{% 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' %} | |
{% endif %} | |
<div class="progress-bar {{ score_class }}" style="width: {{ score_percent }}%"> | |
{{ score_percent|int }}% {{ score_label }} | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Validation Penalty Explanation --> | |
{% if completeness_score.validation_penalty %} | |
<div class="validation-penalty-info"> | |
<h4>About the Validation Penalty</h4> | |
<p>Your score includes a penalty because the AIBOM has schema validation issues. These are structural problems that don't comply with the CycloneDX specification requirements.</p> | |
<p><strong>How to fix this:</strong> Look at the "Fix Validation Issues" section in the recommendations below. Fixing these issues will remove the penalty and improve your overall score.</p> | |
</div> | |
{% endif %} | |
<!-- Section Scores with Progress Bars and Tooltips --> | |
<table class="score-table"> | |
<thead> | |
<tr> | |
<th>Section</th> | |
<th>Score</th> | |
<th>Weight</th> | |
<th>Progress</th> | |
</tr> | |
</thead> | |
<tbody> | |
{% set weights = {'required_fields': 20, 'metadata': 20, 'component_basic': 20, 'component_model_card': 30, 'external_references': 10} %} | |
{% set tooltips = { | |
'required_fields': 'Basic SBOM fields required by the CycloneDX specification: bomFormat, specVersion, serialNumber, and version.', | |
'metadata': 'Information about the AI SBOM itself: timestamp, tools used to generate it, authors, and component metadata.', | |
'component_basic': 'Basic information about the AI model: type, name, bom-ref, PURL, description, and licenses.', | |
'component_model_card': 'Detailed information about the model: parameters, quantitative analysis, and ethical considerations.', | |
'external_references': 'Links to external resources like model cards, repositories, and datasets.' | |
} %} | |
{% set display_names = { | |
'required_fields': 'Required Fields', | |
'metadata': 'Metadata', | |
'component_basic': 'Component Basic', | |
'component_model_card': 'Model Card', | |
'external_references': 'External References' | |
} %} | |
{% for section, score in completeness_score.section_scores.items() %} | |
<tr> | |
<td> | |
{{ display_names[section] }} | |
<span class="tooltip">(?) | |
<span class="tooltiptext">{{ tooltips[section] }}</span> | |
</span> | |
</td> | |
<td>{{ score|round(1) }}/{{ completeness_score.max_scores[section] }}</td> | |
<td>{{ weights[section] }}%</td> | |
<td style="width: 50%;"> | |
<div class="progress-container"> | |
{% set percent = (score / completeness_score.max_scores[section]) * 100 %} | |
{% set class = 'progress-poor' %} | |
{% if percent >= 90 %} | |
{% set class = 'progress-excellent' %} | |
{% elif percent >= 70 %} | |
{% set class = 'progress-good' %} | |
{% elif percent >= 50 %} | |
{% set class = 'progress-fair' %} | |
{% endif %} | |
<div class="progress-bar {{ class }}" style="width: {{ percent }}%"> | |
{{ percent|int }}% | |
</div> | |
</div> | |
</td> | |
</tr> | |
{% endfor %} | |
</tbody> | |
</table> | |
<!-- How the Overall Score is Calculated Section --> | |
<div class="score-calculation"> | |
<h3>How the Overall Score is Calculated</h3> | |
<!-- Missing Fields Section --> | |
<div class="calculation-section missing-fields"> | |
<h4>Critical Missing Fields</h4> | |
<p>The following fields are missing or incomplete and have the biggest impact on your score:</p> | |
<ul> | |
{% set missing_critical = [] %} | |
{% for field, status in completeness_score.field_checklist.items() %} | |
{% if "β" in status %} | |
{% if completeness_score.field_tiers and field in completeness_score.field_tiers and completeness_score.field_tiers[field] == 'critical' %} | |
{% set _ = missing_critical.append(field) %} | |
<li> | |
<strong>{{ field }}</strong> | |
<span class="field-tier tier-critical"></span> | |
{% if field == "component.description" %} | |
- Add a detailed description of the model (at least 20 characters) | |
{% elif field == "component.purl" %} | |
- Add a valid PURL in the format pkg:huggingface/[owner]/[name]@[version] | |
{% elif field == "modelCard.modelParameters" %} | |
- Add model parameters section with architecture, size, and training details | |
{% elif field == "primaryPurpose" %} | |
- Add primary purpose information (what the model is designed for) | |
{% else %} | |
- This field is required for comprehensive documentation | |
{% endif %} | |
</li> | |
{% endif %} | |
{% endif %} | |
{% endfor %} | |
{% if missing_critical|length == 0 %} | |
<li>No critical fields are missing. Great job!</li> | |
{% endif %} | |
</ul> | |
</div> | |
<!-- Recommendations Section --> | |
<div class="calculation-section recommendations"> | |
<h4>Recommendations to Improve Your Score</h4> | |
<ol> | |
{% if completeness_score.section_scores.component_model_card < completeness_score.max_scores.component_model_card %} | |
<li> | |
<strong>Enhance Model Card</strong> (+{{ ((completeness_score.max_scores.component_model_card - completeness_score.section_scores.component_model_card) * 0.3)|round(1) }} points): | |
<ul> | |
{% if completeness_score.missing_fields.critical %} | |
{% for field in completeness_score.missing_fields.critical %} | |
{% if field == "modelCard.modelParameters" or field == "modelCard.considerations" %} | |
<li>Add {{ field }} information</li> | |
{% endif %} | |
{% endfor %} | |
{% endif %} | |
</ul> | |
</li> | |
{% endif %} | |
{% if completeness_score.section_scores.component_basic < completeness_score.max_scores.component_basic %} | |
<li> | |
<strong>Add Basic Component Information</strong> (+{{ ((completeness_score.max_scores.component_basic - completeness_score.section_scores.component_basic) * 0.2)|round(1) }} points): | |
<ul> | |
{% if completeness_score.missing_fields.critical %} | |
{% for field in completeness_score.missing_fields.critical %} | |
{% if field == "name" or field == "description" or field == "purl" %} | |
<li>Add {{ field }} information</li> | |
{% endif %} | |
{% endfor %} | |
{% endif %} | |
{% if completeness_score.missing_fields.important %} | |
{% for field in completeness_score.missing_fields.important %} | |
{% if field == "type" or field == "licenses" %} | |
<li>Add {{ field }} information</li> | |
{% endif %} | |
{% endfor %} | |
{% endif %} | |
</ul> | |
</li> | |
{% endif %} | |
{% if completeness_score.section_scores.metadata < completeness_score.max_scores.metadata %} | |
<li> | |
<strong>Add Metadata</strong> (+{{ ((completeness_score.max_scores.metadata - completeness_score.section_scores.metadata) * 0.2)|round(1) }} points): | |
<ul> | |
{% if completeness_score.missing_fields.critical %} | |
{% for field in completeness_score.missing_fields.critical %} | |
{% if field == "primaryPurpose" or field == "suppliedBy" %} | |
<li>Add {{ field }} information</li> | |
{% endif %} | |
{% endfor %} | |
{% endif %} | |
{% if completeness_score.missing_fields.supplementary %} | |
{% for field in completeness_score.missing_fields.supplementary %} | |
{% if field == "standardCompliance" or field == "domain" or field == "autonomyType" %} | |
<li>Add {{ field }} information</li> | |
{% endif %} | |
{% endfor %} | |
{% endif %} | |
</ul> | |
</li> | |
{% endif %} | |
{% if completeness_score.section_scores.external_references < completeness_score.max_scores.external_references %} | |
<li> | |
<strong>Add External References</strong> (+{{ ((completeness_score.max_scores.external_references - completeness_score.section_scores.external_references) * 0.1)|round(1) }} points): | |
<ul> | |
{% if completeness_score.missing_fields.critical %} | |
{% for field in completeness_score.missing_fields.critical %} | |
{% if field == "downloadLocation" %} | |
<li>Add download location reference</li> | |
{% endif %} | |
{% endfor %} | |
{% endif %} | |
<li>Add links to model card, repository, and dataset</li> | |
</ul> | |
</li> | |
{% endif %} | |
{% if completeness_score.validation and not completeness_score.validation.valid %} | |
<li> | |
<strong>Fix Validation Issues</strong> (remove validation penalty): | |
<ul> | |
{% for recommendation in completeness_score.validation.recommendations %} | |
<li>{{ recommendation }}</li> | |
{% endfor %} | |
</ul> | |
</li> | |
{% endif %} | |
</ol> | |
</div> | |
<!-- Scoring Rubric Section --> | |
<div class="calculation-section scoring-rubric"> | |
<h4>Scoring Rubric</h4> | |
<p>The overall score is calculated using a <strong>weighted normalization</strong> approach:</p> | |
<p><strong>Total Score = Sum of (Section Score Γ Section Weight)</strong></p> | |
<p>Where:</p> | |
<ul> | |
<li>Section Score = Points earned in that section</li> | |
<li>Section Weight = Section's maximum points Γ· Total possible points (100)</li> | |
</ul> | |
<div class="note-box"> | |
<p><strong>Example calculation:</strong> If your SBOM has these section scores:</p> | |
<ul> | |
<li>Required Fields: 20 points Γ 0.20 weight = 4.0 points</li> | |
<li>Metadata: 15 points Γ 0.20 weight = 3.0 points</li> | |
<li>Component Basic: 10 points Γ 0.20 weight = 2.0 points</li> | |
<li>Model Card: 10 points Γ 0.30 weight = 3.0 points</li> | |
<li>External References: 5 points Γ 0.10 weight = 0.5 points</li> | |
</ul> | |
<p>The total score would be 12.5 points, even though the raw section scores sum to 60 points.</p> | |
<p><strong>Note:</strong> The total score is <em>not</em> the sum of section scores. Each section contributes proportionally to its weight in the final score.</p> | |
</div> | |
<p>Fields are classified into three tiers based on importance:</p> | |
<ul> | |
<li><span class="field-tier tier-critical"></span> <strong>Critical fields</strong>: Highest weight (3-4 points each)</li> | |
<li><span class="field-tier tier-important"></span> <strong>Important fields</strong>: Medium weight (2-4 points each)</li> | |
<li><span class="field-tier tier-supplementary"></span> <strong>Supplementary fields</strong>: Lower weight (1-2 points each)</li> | |
</ul> | |
<p>Penalties are applied for missing critical fields:</p> | |
<ul> | |
<li>Missing >3 critical fields: 20% penalty (score Γ 0.8)</li> | |
<li>Missing 1-3 critical fields: 10% penalty (score Γ 0.9)</li> | |
<li>Missing >5 important fields: 5% penalty (score Γ 0.95)</li> | |
</ul> | |
{% if completeness_score.validation_penalty %} | |
<p>Additional penalties are applied based on validation results:</p> | |
<ul> | |
<li>Schema errors: Up to 50% reduction (10% per error)</li> | |
<li>Schema warnings: Up to 20% reduction (5% per warning)</li> | |
</ul> | |
{% endif %} | |
</div> | |
</div> | |
</div> | |
</div> | |
<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> |