|
|
<!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 { |
|
|
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 .header-content { |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
} |
|
|
.header h1 { |
|
|
margin: 0; |
|
|
font-size: 28px; |
|
|
color: #2c3e50; |
|
|
font-weight: 600; |
|
|
margin-bottom: 5px; |
|
|
} |
|
|
|
|
|
.header .sbom-count { |
|
|
font-size: 14px; |
|
|
color: #555; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
|
|
|
.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 { |
|
|
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 { |
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
.check-mark { color: #27ae60; } |
|
|
.x-mark { color: #e74c3c; } |
|
|
.field-name { color: #000; } |
|
|
.field-stars { color: #000; } |
|
|
|
|
|
.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; |
|
|
} |
|
|
|
|
|
|
|
|
.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-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; |
|
|
} |
|
|
.progress-good { |
|
|
background-color: #2196F3; |
|
|
} |
|
|
.progress-fair { |
|
|
background-color: #FF9800; |
|
|
} |
|
|
.progress-poor { |
|
|
background-color: #f44336; |
|
|
} |
|
|
.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; |
|
|
} |
|
|
.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; |
|
|
} |
|
|
|
|
|
|
|
|
.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; |
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
.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; |
|
|
} |
|
|
|
|
|
.profile-incomplete { |
|
|
background-color: #f44336; |
|
|
color: white; |
|
|
} |
|
|
.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; |
|
|
} |
|
|
|
|
|
.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; |
|
|
} |
|
|
|
|
|
|
|
|
.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 { |
|
|
text-align: center; |
|
|
padding: 20px; |
|
|
color: #7f8c8d; |
|
|
font-size: 14px; |
|
|
margin-top: 30px; |
|
|
} |
|
|
|
|
|
|
|
|
@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> |
|
|
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<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"> |
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
{% 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 %} |
|
|
|
|
|
|
|
|
{% 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> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
{% 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 %} |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
{% 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 %} |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<div class="score-calculation"> |
|
|
<h3>How the Overall Score is Calculated</h3> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<div class="footer"> |
|
|
<p>Β© 2025 AI SBOM Generator | Powered by Aetheris AI</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
function switchTab(tabId) { |
|
|
|
|
|
var tabContents = document.getElementsByClassName('tab-content'); |
|
|
for (var i = 0; i < tabContents.length; i++) { |
|
|
tabContents[i].classList.remove('active'); |
|
|
} |
|
|
|
|
|
|
|
|
var tabs = document.getElementsByClassName('aibom-tab'); |
|
|
for (var i = 0; i < tabs.length; i++) { |
|
|
tabs[i].classList.remove('active'); |
|
|
} |
|
|
|
|
|
|
|
|
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(); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
var collapsibles = document.getElementsByClassName('collapsible'); |
|
|
for (var i = 0; i < collapsibles.length; i++) { |
|
|
toggleCollapsible(collapsibles[i]); |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |