a1c00l commited on
Commit
91699da
·
verified ·
1 Parent(s): 2c929e0

Update templates/result.html

Browse files
Files changed (1) hide show
  1. templates/result.html +89 -344
templates/result.html CHANGED
@@ -8,8 +8,10 @@
8
  table { border-collapse: collapse; width: 60%; margin-top: 15px; }
9
  th, td { border: 1px solid #ddd; padding: 8px; }
10
  th { background-color: #f4f4f4; }
11
- .missing { color: red; }
12
- .present { color: green; }
 
 
13
  .improvement { color: #2c3e50; background-color: #ecf0f1; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
14
  .improvement-value { color: #27ae60; font-weight: bold; }
15
  .ai-badge { background-color: #3498db; color: white; padding: 3px 8px; border-radius: 3px; font-size: 0.8em; margin-left: 10px; }
@@ -462,45 +464,37 @@
462
  <div class="collapsible-content">
463
  <div class="aibom-property">
464
  <div class="property-name">Generated:</div>
465
- <div class="property-value">{{ aibom.metadata.timestamp if aibom.metadata.timestamp else 'Not specified' }}</div>
466
  </div>
467
 
468
- {% if aibom.metadata.authors %}
469
  <div class="aibom-property">
470
- <div class="property-name">Authors:</div>
471
  <div class="property-value">
472
- {% for author in aibom.metadata.authors %}
473
- <div>{{ author.name }} {% if author.email %}({{ author.email }}){% endif %}</div>
474
  {% endfor %}
475
  </div>
476
  </div>
477
  {% endif %}
478
 
479
- {% if aibom.metadata.tools %}
480
  <div class="aibom-property">
481
- <div class="property-name">Generated by:</div>
482
  <div class="property-value">
483
- {% for tool in aibom.metadata.tools %}
484
- <div>{{ tool.name }} v{{ tool.version }} ({{ tool.vendor }})</div>
485
  {% endfor %}
486
  </div>
487
  </div>
488
  {% endif %}
489
 
490
- {% if aibom.metadata.properties %}
491
  <div class="aibom-property">
492
  <div class="property-name">Properties:</div>
493
  <div class="property-value">
494
  {% for prop in aibom.metadata.properties %}
495
- {% if prop.name == 'tags' and prop.value %}
496
- <div><strong>Tags:</strong>
497
- <!-- Display tags without using split filter -->
498
- {% set tag_value = prop.value|replace('[', '')|replace(']', '')|replace('"', '') %}
499
- <span class="tag">{{ tag_value }}</span>
500
- </div>
501
- {% elif prop.name not in ['aibom:quality-score', 'aibom:quality-breakdown', 'aibom:max-scores', 'aibom:completeness-profile', 'aibom:completeness-description', 'aibom:ai-enhanced', 'aibom:ai-model', 'aibom:original-score', 'aibom:score-improvement'] %}
502
- <div><strong>{{ prop.name }}:</strong> {{ prop.value }}</div>
503
- {% endif %}
504
  {% endfor %}
505
  </div>
506
  </div>
@@ -515,68 +509,22 @@
515
  <div class="collapsible-content">
516
  {% for ref in aibom.components[0].externalReferences %}
517
  <div class="aibom-property">
518
- <div class="property-name">{{ ref.type|capitalize if ref.type else 'Reference' }}:</div>
519
- <div class="property-value"><a href="{{ ref.url }}" target="_blank">{{ ref.url }}</a></div>
520
- </div>
521
- {% endfor %}
522
- </div>
523
- </div>
524
- {% endif %}
525
-
526
- <!-- Dependencies Section -->
527
- {% if aibom.dependencies %}
528
- <div class="aibom-section">
529
- <h4 class="collapsible" onclick="toggleCollapsible(this)">Dependencies</h4>
530
- <div class="collapsible-content">
531
- {% for dep in aibom.dependencies %}
532
- <div class="aibom-property">
533
- <div class="property-name">{{ dep.ref }}:</div>
534
  <div class="property-value">
535
- {% if dep.dependsOn %}
536
- {% for dependency in dep.dependsOn %}
537
- <div>{{ dependency }}</div>
538
- {% endfor %}
539
- {% else %}
540
- No dependencies specified
541
- {% endif %}
542
  </div>
543
  </div>
544
  {% endfor %}
545
  </div>
546
  </div>
547
  {% endif %}
548
-
549
- <!-- SBOM Information Section -->
550
- <div class="aibom-section">
551
- <h4 class="collapsible" onclick="toggleCollapsible(this)">SBOM Information</h4>
552
- <div class="collapsible-content">
553
- <div class="aibom-property">
554
- <div class="property-name">Format:</div>
555
- <div class="property-value">{{ aibom.bomFormat }}</div>
556
- </div>
557
- <div class="aibom-property">
558
- <div class="property-name">Spec Version:</div>
559
- <div class="property-value">{{ aibom.specVersion }}</div>
560
- </div>
561
- <div class="aibom-property">
562
- <div class="property-name">Serial Number:</div>
563
- <div class="property-value">{{ aibom.serialNumber }}</div>
564
- </div>
565
- <div class="aibom-property">
566
- <div class="property-name">Version:</div>
567
- <div class="property-value">{{ aibom.version }}</div>
568
- </div>
569
- </div>
570
- </div>
571
  </div>
572
  </div>
573
 
574
  <div id="json-view" class="tab-content">
575
  <div class="json-view">
576
- <pre id="aibom-json">{{ aibom | tojson(indent=2) }}</pre>
577
- </div>
578
- <div style="margin-top: 15px;">
579
- <button onclick="downloadJSON()">Download JSON</button>
580
  </div>
581
  </div>
582
 
@@ -586,19 +534,22 @@
586
  <!-- Field Tier Legend -->
587
  <div class="tier-legend">
588
  <div class="tier-legend-item">
589
- <span class="field-tier tier-critical"></span> Critical Fields
 
590
  </div>
591
  <div class="tier-legend-item">
592
- <span class="field-tier tier-important"></span> Important Fields
 
593
  </div>
594
  <div class="tier-legend-item">
595
- <span class="field-tier tier-supplementary"></span> Supplementary Fields
 
596
  </div>
597
  </div>
598
 
599
  <ul>
600
  {% for field, status in completeness_score.field_checklist.items() %}
601
- {% if status == "✔" %}
602
  <li class="present">{{ status }} {{ field }}
603
  {% if completeness_score.field_tiers and field in completeness_score.field_tiers %}
604
  <span class="field-tier tier-{{ completeness_score.field_tiers[field] }}"></span>
@@ -854,297 +805,77 @@
854
  <li>
855
  <strong>Fix Validation Issues</strong> (removes validation penalty):
856
  <ul>
857
- {% for rec in completeness_score.validation.recommendations[:3] %}
858
- <li>{{ rec }}</li>
859
  {% endfor %}
860
  </ul>
861
  </li>
862
  {% endif %}
863
 
864
- {% if completeness_score.completeness_profile and completeness_score.completeness_profile.next_level %}
865
- {% set has_recommendations = true %}
866
- <li>
867
- <strong>Upgrade to {{ completeness_score.completeness_profile.next_level.name }} Profile</strong>:
868
- <ul>
869
- {% for field in completeness_score.completeness_profile.next_level.missing_fields[:5] %}
870
- <li>Add {{ field }}</li>
871
- {% endfor %}
872
- {% if completeness_score.completeness_profile.next_level.missing_fields|length > 5 %}
873
- <li>... and {{ completeness_score.completeness_profile.next_level.missing_fields|length - 5 }} more fields</li>
874
- {% endif %}
875
- </ul>
876
- </li>
877
- {% endif %}
878
-
879
  {% if not has_recommendations %}
880
  <li>Your AI SBOM is already well-documented. Great job!</li>
881
  {% endif %}
882
  </ol>
883
  </div>
884
 
885
- <!-- Scoring Rubric Details -->
886
  <div class="scoring-rubric">
887
- <h4 class="collapsible" onclick="toggleCollapsible(this)">Scoring Rubric Details</h4>
888
- <div class="collapsible-content">
889
- <p>The following criteria are used to calculate your score:</p>
890
-
891
- <h5>Required Fields (20% of total score)</h5>
892
- <table>
893
- <tr>
894
- <th>Field</th>
895
- <th>Points</th>
896
- <th>Criteria</th>
897
- </tr>
898
- <tr>
899
- <td>bomFormat</td>
900
- <td>5</td>
901
- <td>Must be "CycloneDX"</td>
902
- </tr>
903
- <tr>
904
- <td>specVersion</td>
905
- <td>5</td>
906
- <td>Must be "1.6" or later</td>
907
- </tr>
908
- <tr>
909
- <td>serialNumber</td>
910
- <td>5</td>
911
- <td>Must be a valid UUID</td>
912
- </tr>
913
- <tr>
914
- <td>version</td>
915
- <td>5</td>
916
- <td>Must be a positive integer</td>
917
- </tr>
918
- </table>
919
-
920
- <h5>Metadata (20% of total score)</h5>
921
- <table>
922
- <tr>
923
- <th>Field</th>
924
- <th>Points</th>
925
- <th>Criteria</th>
926
- </tr>
927
- <tr>
928
- <td>timestamp</td>
929
- <td>5</td>
930
- <td>Must be a valid ISO 8601 timestamp</td>
931
- </tr>
932
- <tr>
933
- <td>tools</td>
934
- <td>5</td>
935
- <td>Must include at least one tool with name, vendor, and version</td>
936
- </tr>
937
- <tr>
938
- <td>authors</td>
939
- <td>5</td>
940
- <td>Must include at least one author with name</td>
941
- </tr>
942
- <tr>
943
- <td>component</td>
944
- <td>5</td>
945
- <td>Must include component metadata</td>
946
- </tr>
947
- </table>
948
-
949
- <h5>Component Basic Info (20% of total score)</h5>
950
- <table>
951
- <tr>
952
- <th>Field</th>
953
- <th>Points</th>
954
- <th>Criteria</th>
955
- </tr>
956
- <tr>
957
- <td>type</td>
958
- <td>2</td>
959
- <td>Must be "machine-learning-model"</td>
960
- </tr>
961
- <tr>
962
- <td>name</td>
963
- <td>4</td>
964
- <td>Must be present</td>
965
- </tr>
966
- <tr>
967
- <td>bom-ref</td>
968
- <td>2</td>
969
- <td>Must be present</td>
970
- </tr>
971
- <tr>
972
- <td>purl</td>
973
- <td>4</td>
974
- <td>Must start with "pkg:huggingface/"</td>
975
- </tr>
976
- <tr>
977
- <td>description</td>
978
- <td>4</td>
979
- <td>Must be present and longer than 20 characters</td>
980
- </tr>
981
- <tr>
982
- <td>licenses</td>
983
- <td>4</td>
984
- <td>Must use valid SPDX license identifiers</td>
985
- </tr>
986
- </table>
987
-
988
- <h5>Model Card (30% of total score)</h5>
989
- <table>
990
- <tr>
991
- <th>Field</th>
992
- <th>Points</th>
993
- <th>Criteria</th>
994
- </tr>
995
- <tr>
996
- <td>modelParameters</td>
997
- <td>10</td>
998
- <td>Must include model parameters</td>
999
- </tr>
1000
- <tr>
1001
- <td>quantitativeAnalysis</td>
1002
- <td>10</td>
1003
- <td>Must include quantitative analysis</td>
1004
- </tr>
1005
- <tr>
1006
- <td>considerations</td>
1007
- <td>10</td>
1008
- <td>Must be present and longer than 50 characters</td>
1009
- </tr>
1010
- </table>
1011
-
1012
- <h5>External References (10% of total score)</h5>
1013
- <table>
1014
- <tr>
1015
- <th>Reference Type</th>
1016
- <th>Points</th>
1017
- <th>Maximum</th>
1018
- </tr>
1019
- <tr>
1020
- <td>Model Card URL</td>
1021
- <td>4 per reference</td>
1022
- <td rowspan="3">10</td>
1023
- </tr>
1024
- <tr>
1025
- <td>Hugging Face or GitHub URL</td>
1026
- <td>3 per reference</td>
1027
- </tr>
1028
- <tr>
1029
- <td>Dataset URL</td>
1030
- <td>3 per reference</td>
1031
- </tr>
1032
- </table>
1033
- </div>
1034
- </div>
1035
-
1036
- <!-- Validation Summary if available -->
1037
- {% if completeness_score.validation %}
1038
- <div class="aibom-section">
1039
- <h4>Validation Results</h4>
1040
- <div class="aibom-property">
1041
- <div class="property-name">Status:</div>
1042
- <div class="property-value">
1043
- {% if completeness_score.validation.valid %}
1044
- <span style="color: green;">✓ Valid</span>
1045
- {% else %}
1046
- <span style="color: red;">✗ Invalid</span>
1047
- {% endif %}
1048
- </div>
1049
- </div>
1050
-
1051
- <div class="aibom-property">
1052
- <div class="property-name">Issues:</div>
1053
- <div class="property-value">
1054
- <span style="color: red;">Errors: {{ completeness_score.validation.summary.error_count }}</span><br>
1055
- <span style="color: orange;">Warnings: {{ completeness_score.validation.summary.warning_count }}</span><br>
1056
- <span style="color: blue;">Info: {{ completeness_score.validation.summary.info_count }}</span>
1057
- </div>
1058
- </div>
1059
-
1060
- {% if not completeness_score.validation.valid and completeness_score.validation.issues %}
1061
- <div class="aibom-property">
1062
- <div class="property-name">Top Issues:</div>
1063
- <div class="property-value">
1064
- <ul>
1065
- {% for issue in completeness_score.validation.issues[:3] %}
1066
- <li>
1067
- {% if issue.severity == 'error' %}
1068
- <span style="color: red;">[ERROR]</span>
1069
- {% elif issue.severity == 'warning' %}
1070
- <span style="color: orange;">[WARNING]</span>
1071
- {% else %}
1072
- <span style="color: blue;">[INFO]</span>
1073
- {% endif %}
1074
- {{ issue.message }}
1075
- </li>
1076
- {% endfor %}
1077
- {% if completeness_score.validation.issues|length > 3 %}
1078
- <li>... and {{ completeness_score.validation.issues|length - 3 }} more issues</li>
1079
- {% endif %}
1080
- </ul>
1081
- </div>
1082
- </div>
1083
- {% endif %}
1084
-
1085
- {% if completeness_score.validation.recommendations %}
1086
- <div class="aibom-property">
1087
- <div class="property-name">Recommendations:</div>
1088
- <div class="property-value">
1089
- <ul>
1090
- {% for rec in completeness_score.validation.recommendations[:3] %}
1091
- <li>{{ rec }}</li>
1092
- {% endfor %}
1093
- {% if completeness_score.validation.recommendations|length > 3 %}
1094
- <li>... and {{ completeness_score.validation.recommendations|length - 3 }} more recommendations</li>
1095
- {% endif %}
1096
- </ul>
1097
- </div>
1098
- </div>
1099
- {% endif %}
1100
- </div>
1101
- {% endif %}
1102
-
1103
- <!-- Score Calculation Explanation -->
1104
- <div class="aibom-section">
1105
- <h4>Score Calculation</h4>
1106
- <p>The total score is calculated as a weighted sum of section scores:</p>
1107
- <ul>
1108
- <li>Required Fields: {{ completeness_score.section_scores.required_fields }}/{{ completeness_score.max_scores.required_fields }} × 20% = {{ (completeness_score.section_scores.required_fields / completeness_score.max_scores.required_fields * 20)|round(2) }}</li>
1109
- <li>Metadata: {{ completeness_score.section_scores.metadata }}/{{ completeness_score.max_scores.metadata }} × 20% = {{ (completeness_score.section_scores.metadata / completeness_score.max_scores.metadata * 20)|round(2) }}</li>
1110
- <li>Component Basic: {{ completeness_score.section_scores.component_basic }}/{{ completeness_score.max_scores.component_basic }} × 20% = {{ (completeness_score.section_scores.component_basic / completeness_score.max_scores.component_basic * 20)|round(2) }}</li>
1111
- <li>Model Card: {{ completeness_score.section_scores.component_model_card }}/{{ completeness_score.max_scores.component_model_card }} × 30% = {{ (completeness_score.section_scores.component_model_card / completeness_score.max_scores.component_model_card * 30)|round(2) }}</li>
1112
- <li>External References: {{ completeness_score.section_scores.external_references }}/{{ completeness_score.max_scores.external_references }} × 10% = {{ (completeness_score.section_scores.external_references / completeness_score.max_scores.external_references * 10)|round(2) }}</li>
1113
- </ul>
1114
-
1115
- {% if completeness_score.validation_penalty %}
1116
- <p>Validation penalty: {{ completeness_score.validation_penalty }}</p>
1117
- {% endif %}
1118
  </div>
1119
  </div>
1120
-
 
 
1121
  <script>
1122
- function downloadJSON() {
1123
- const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(document.getElementById('aibom-json').textContent);
1124
- const downloadAnchorNode = document.createElement('a');
1125
- downloadAnchorNode.setAttribute("href", dataStr);
1126
- downloadAnchorNode.setAttribute("download", "{{ model_id }}-aibom.json");
1127
- document.body.appendChild(downloadAnchorNode);
1128
- downloadAnchorNode.click();
1129
- downloadAnchorNode.remove();
1130
- }
1131
-
1132
  function switchTab(tabId) {
1133
  // Hide all tab contents
1134
- const tabContents = document.getElementsByClassName('tab-content');
1135
- for (let i = 0; i < tabContents.length; i++) {
1136
  tabContents[i].classList.remove('active');
1137
  }
1138
 
1139
  // Deactivate all tabs
1140
- const tabs = document.getElementsByClassName('aibom-tab');
1141
- for (let i = 0; i < tabs.length; i++) {
1142
  tabs[i].classList.remove('active');
1143
  }
1144
 
1145
  // Activate selected tab and content
1146
  document.getElementById(tabId).classList.add('active');
1147
- const selectedTab = document.querySelector(`.aibom-tab[onclick="switchTab('${tabId}')"]`);
1148
  if (selectedTab) {
1149
  selectedTab.classList.add('active');
1150
  }
@@ -1152,14 +883,28 @@
1152
 
1153
  function toggleCollapsible(element) {
1154
  element.classList.toggle('active');
1155
- const content = element.nextElementSibling;
1156
- content.classList.toggle('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1157
  }
1158
 
1159
  // Initialize collapsible sections
1160
  document.addEventListener('DOMContentLoaded', function() {
1161
- const collapsibles = document.getElementsByClassName('collapsible');
1162
- for (let i = 0; i < collapsibles.length; i++) {
1163
  collapsibles[i].nextElementSibling.classList.add('active');
1164
  collapsibles[i].classList.add('active');
1165
  }
 
8
  table { border-collapse: collapse; width: 60%; margin-top: 15px; }
9
  th, td { border: 1px solid #ddd; padding: 8px; }
10
  th { background-color: #f4f4f4; }
11
+ /* Fixed color styling for field checklist items */
12
+ .missing { color: #e74c3c; } /* Red color for missing fields */
13
+ .present { color: #27ae60; } /* Green color for present fields */
14
+ .unknown { color: #f39c12; } /* Orange color for unknown status */
15
  .improvement { color: #2c3e50; background-color: #ecf0f1; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
16
  .improvement-value { color: #27ae60; font-weight: bold; }
17
  .ai-badge { background-color: #3498db; color: white; padding: 3px 8px; border-radius: 3px; font-size: 0.8em; margin-left: 10px; }
 
464
  <div class="collapsible-content">
465
  <div class="aibom-property">
466
  <div class="property-name">Generated:</div>
467
+ <div class="property-value">{{ aibom.metadata.timestamp if aibom.metadata and aibom.metadata.timestamp else 'Not specified' }}</div>
468
  </div>
469
 
470
+ {% if aibom.metadata and aibom.metadata.tools %}
471
  <div class="aibom-property">
472
+ <div class="property-name">Generated By:</div>
473
  <div class="property-value">
474
+ {% for tool in aibom.metadata.tools %}
475
+ <div>{{ tool.name }} {{ tool.version }}</div>
476
  {% endfor %}
477
  </div>
478
  </div>
479
  {% endif %}
480
 
481
+ {% if aibom.metadata and aibom.metadata.authors %}
482
  <div class="aibom-property">
483
+ <div class="property-name">Authors:</div>
484
  <div class="property-value">
485
+ {% for author in aibom.metadata.authors %}
486
+ <div>{{ author.name }} {% if author.email %}({{ author.email }}){% endif %}</div>
487
  {% endfor %}
488
  </div>
489
  </div>
490
  {% endif %}
491
 
492
+ {% if aibom.metadata and aibom.metadata.properties %}
493
  <div class="aibom-property">
494
  <div class="property-name">Properties:</div>
495
  <div class="property-value">
496
  {% for prop in aibom.metadata.properties %}
497
+ <div><strong>{{ prop.name }}:</strong> {{ prop.value }}</div>
 
 
 
 
 
 
 
 
498
  {% endfor %}
499
  </div>
500
  </div>
 
509
  <div class="collapsible-content">
510
  {% for ref in aibom.components[0].externalReferences %}
511
  <div class="aibom-property">
512
+ <div class="property-name">{{ ref.type|title if ref.type else 'Reference' }}:</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  <div class="property-value">
514
+ <a href="{{ ref.url }}" target="_blank">{{ ref.url }}</a>
515
+ {% if ref.comment %}<div>{{ ref.comment }}</div>{% endif %}
 
 
 
 
 
516
  </div>
517
  </div>
518
  {% endfor %}
519
  </div>
520
  </div>
521
  {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
522
  </div>
523
  </div>
524
 
525
  <div id="json-view" class="tab-content">
526
  <div class="json-view">
527
+ <pre>{{ aibom | tojson(indent=2) }}</pre>
 
 
 
528
  </div>
529
  </div>
530
 
 
534
  <!-- Field Tier Legend -->
535
  <div class="tier-legend">
536
  <div class="tier-legend-item">
537
+ <span class="field-tier tier-critical"></span>
538
+ <span>Critical</span>
539
  </div>
540
  <div class="tier-legend-item">
541
+ <span class="field-tier tier-important"></span>
542
+ <span>Important</span>
543
  </div>
544
  <div class="tier-legend-item">
545
+ <span class="field-tier tier-supplementary"></span>
546
+ <span>Supplementary</span>
547
  </div>
548
  </div>
549
 
550
  <ul>
551
  {% for field, status in completeness_score.field_checklist.items() %}
552
+ {% if "✔" in status %}
553
  <li class="present">{{ status }} {{ field }}
554
  {% if completeness_score.field_tiers and field in completeness_score.field_tiers %}
555
  <span class="field-tier tier-{{ completeness_score.field_tiers[field] }}"></span>
 
805
  <li>
806
  <strong>Fix Validation Issues</strong> (removes validation penalty):
807
  <ul>
808
+ {% for issue in completeness_score.validation.issues %}
809
+ <li>{{ issue.message }}</li>
810
  {% endfor %}
811
  </ul>
812
  </li>
813
  {% endif %}
814
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815
  {% if not has_recommendations %}
816
  <li>Your AI SBOM is already well-documented. Great job!</li>
817
  {% endif %}
818
  </ol>
819
  </div>
820
 
821
+ <!-- Scoring Rubric -->
822
  <div class="scoring-rubric">
823
+ <h4>Scoring Rubric</h4>
824
+ <p>The completeness score is calculated based on the following categories:</p>
825
+ <table>
826
+ <tr>
827
+ <th>Category</th>
828
+ <th>Weight</th>
829
+ <th>Description</th>
830
+ </tr>
831
+ <tr>
832
+ <td>Required Fields</td>
833
+ <td>20%</td>
834
+ <td>Basic SBOM fields required by the CycloneDX specification</td>
835
+ </tr>
836
+ <tr>
837
+ <td>Metadata</td>
838
+ <td>20%</td>
839
+ <td>Information about the AI SBOM itself</td>
840
+ </tr>
841
+ <tr>
842
+ <td>Component Basic</td>
843
+ <td>20%</td>
844
+ <td>Basic information about the AI model</td>
845
+ </tr>
846
+ <tr>
847
+ <td>Model Card</td>
848
+ <td>30%</td>
849
+ <td>Detailed information about the model</td>
850
+ </tr>
851
+ <tr>
852
+ <td>External References</td>
853
+ <td>10%</td>
854
+ <td>Links to external resources</td>
855
+ </tr>
856
+ </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
857
  </div>
858
  </div>
859
+
860
+ <pre id="aibom-json" style="display: none;">{{ aibom | tojson(indent=2) }}</pre>
861
+
862
  <script>
 
 
 
 
 
 
 
 
 
 
863
  function switchTab(tabId) {
864
  // Hide all tab contents
865
+ var tabContents = document.getElementsByClassName('tab-content');
866
+ for (var i = 0; i < tabContents.length; i++) {
867
  tabContents[i].classList.remove('active');
868
  }
869
 
870
  // Deactivate all tabs
871
+ var tabs = document.getElementsByClassName('aibom-tab');
872
+ for (var i = 0; i < tabs.length; i++) {
873
  tabs[i].classList.remove('active');
874
  }
875
 
876
  // Activate selected tab and content
877
  document.getElementById(tabId).classList.add('active');
878
+ var selectedTab = document.querySelector('.aibom-tab[onclick="switchTab(\'' + tabId + '\')"]');
879
  if (selectedTab) {
880
  selectedTab.classList.add('active');
881
  }
 
883
 
884
  function toggleCollapsible(element) {
885
  element.classList.toggle('active');
886
+ var content = element.nextElementSibling;
887
+ if (content.classList.contains('active')) {
888
+ content.classList.remove('active');
889
+ } else {
890
+ content.classList.add('active');
891
+ }
892
+ }
893
+
894
+ function downloadJSON() {
895
+ const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(document.getElementById('aibom-json').textContent);
896
+ const downloadAnchorNode = document.createElement('a');
897
+ downloadAnchorNode.setAttribute("href", dataStr);
898
+ downloadAnchorNode.setAttribute("download", "{{ model_id }}-aibom.json");
899
+ document.body.appendChild(downloadAnchorNode);
900
+ downloadAnchorNode.click();
901
+ downloadAnchorNode.remove();
902
  }
903
 
904
  // Initialize collapsible sections
905
  document.addEventListener('DOMContentLoaded', function() {
906
+ var collapsibles = document.getElementsByClassName('collapsible');
907
+ for (var i = 0; i < collapsibles.length; i++) {
908
  collapsibles[i].nextElementSibling.classList.add('active');
909
  collapsibles[i].classList.add('active');
910
  }