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; | |
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> | |