@artemiskit/reports 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/dist/html/compare-generator.d.ts +52 -0
- package/dist/html/compare-generator.d.ts.map +1 -0
- package/dist/html/generator.d.ts.map +1 -1
- package/dist/html/redteam-generator.d.ts.map +1 -1
- package/dist/html/stress-generator.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1739 -352
- package/package.json +2 -2
- package/src/html/compare-generator.ts +783 -0
- package/src/html/generator.ts +379 -86
- package/src/html/redteam-generator.ts +437 -134
- package/src/html/stress-generator.ts +215 -128
- package/src/index.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -5618,6 +5618,116 @@ var HTML_TEMPLATE = `
|
|
|
5618
5618
|
margin-left: 0.5rem;
|
|
5619
5619
|
}
|
|
5620
5620
|
footer { margin-top: 3rem; text-align: center; color: #666; font-size: 0.875rem; }
|
|
5621
|
+
|
|
5622
|
+
/* Collapsible sections */
|
|
5623
|
+
.collapsible-header {
|
|
5624
|
+
display: flex;
|
|
5625
|
+
align-items: center;
|
|
5626
|
+
cursor: pointer;
|
|
5627
|
+
user-select: none;
|
|
5628
|
+
}
|
|
5629
|
+
.collapsible-header h2 {
|
|
5630
|
+
margin: 0;
|
|
5631
|
+
flex: 1;
|
|
5632
|
+
}
|
|
5633
|
+
.collapse-icon {
|
|
5634
|
+
font-size: 1.25rem;
|
|
5635
|
+
transition: transform 0.2s ease;
|
|
5636
|
+
margin-left: 0.5rem;
|
|
5637
|
+
color: #666;
|
|
5638
|
+
}
|
|
5639
|
+
.collapsible-header[data-collapsed="true"] .collapse-icon {
|
|
5640
|
+
transform: rotate(-90deg);
|
|
5641
|
+
}
|
|
5642
|
+
.collapsible-content {
|
|
5643
|
+
overflow: hidden;
|
|
5644
|
+
transition: max-height 0.3s ease;
|
|
5645
|
+
}
|
|
5646
|
+
.collapsible-content.collapsed {
|
|
5647
|
+
max-height: 0 !important;
|
|
5648
|
+
padding: 0;
|
|
5649
|
+
}
|
|
5650
|
+
|
|
5651
|
+
/* Filter and Search controls */
|
|
5652
|
+
.controls {
|
|
5653
|
+
display: flex;
|
|
5654
|
+
gap: 1rem;
|
|
5655
|
+
margin-bottom: 1rem;
|
|
5656
|
+
flex-wrap: wrap;
|
|
5657
|
+
align-items: center;
|
|
5658
|
+
}
|
|
5659
|
+
.filter-group {
|
|
5660
|
+
display: flex;
|
|
5661
|
+
gap: 0.5rem;
|
|
5662
|
+
}
|
|
5663
|
+
.filter-btn {
|
|
5664
|
+
padding: 0.5rem 1rem;
|
|
5665
|
+
border: 1px solid #e0e0e0;
|
|
5666
|
+
background: white;
|
|
5667
|
+
border-radius: 6px;
|
|
5668
|
+
cursor: pointer;
|
|
5669
|
+
font-size: 0.875rem;
|
|
5670
|
+
font-weight: 500;
|
|
5671
|
+
transition: all 0.15s ease;
|
|
5672
|
+
}
|
|
5673
|
+
.filter-btn:hover {
|
|
5674
|
+
background: #f5f5f5;
|
|
5675
|
+
}
|
|
5676
|
+
.filter-btn.active {
|
|
5677
|
+
background: #1a1a1a;
|
|
5678
|
+
color: white;
|
|
5679
|
+
border-color: #1a1a1a;
|
|
5680
|
+
}
|
|
5681
|
+
.filter-btn.passed.active {
|
|
5682
|
+
background: #166534;
|
|
5683
|
+
border-color: #166534;
|
|
5684
|
+
}
|
|
5685
|
+
.filter-btn.failed.active {
|
|
5686
|
+
background: #991b1b;
|
|
5687
|
+
border-color: #991b1b;
|
|
5688
|
+
}
|
|
5689
|
+
.search-box {
|
|
5690
|
+
flex: 1;
|
|
5691
|
+
min-width: 200px;
|
|
5692
|
+
max-width: 400px;
|
|
5693
|
+
}
|
|
5694
|
+
.search-input {
|
|
5695
|
+
width: 100%;
|
|
5696
|
+
padding: 0.5rem 1rem;
|
|
5697
|
+
border: 1px solid #e0e0e0;
|
|
5698
|
+
border-radius: 6px;
|
|
5699
|
+
font-size: 0.875rem;
|
|
5700
|
+
outline: none;
|
|
5701
|
+
transition: border-color 0.15s ease;
|
|
5702
|
+
}
|
|
5703
|
+
.search-input:focus {
|
|
5704
|
+
border-color: #3b82f6;
|
|
5705
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
5706
|
+
}
|
|
5707
|
+
.search-input::placeholder {
|
|
5708
|
+
color: #999;
|
|
5709
|
+
}
|
|
5710
|
+
.results-count {
|
|
5711
|
+
font-size: 0.875rem;
|
|
5712
|
+
color: #666;
|
|
5713
|
+
padding: 0.5rem 0;
|
|
5714
|
+
}
|
|
5715
|
+
|
|
5716
|
+
/* No results message */
|
|
5717
|
+
.no-results {
|
|
5718
|
+
text-align: center;
|
|
5719
|
+
padding: 2rem;
|
|
5720
|
+
color: #666;
|
|
5721
|
+
background: white;
|
|
5722
|
+
border-radius: 8px;
|
|
5723
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
5724
|
+
}
|
|
5725
|
+
.no-results .icon { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
5726
|
+
|
|
5727
|
+
/* Row highlight for search matches */
|
|
5728
|
+
tr.search-highlight td {
|
|
5729
|
+
background: #fef9c3;
|
|
5730
|
+
}
|
|
5621
5731
|
</style>
|
|
5622
5732
|
</head>
|
|
5623
5733
|
<body>
|
|
@@ -5632,7 +5742,7 @@ var HTML_TEMPLATE = `
|
|
|
5632
5742
|
|
|
5633
5743
|
{{#if manifest.redaction.enabled}}
|
|
5634
5744
|
<div class="redaction-banner">
|
|
5635
|
-
<div class="icon"
|
|
5745
|
+
<div class="icon">🔒</div>
|
|
5636
5746
|
<div class="content">
|
|
5637
5747
|
<div class="title">Data Redaction Applied</div>
|
|
5638
5748
|
<div class="details">
|
|
@@ -5644,101 +5754,151 @@ var HTML_TEMPLATE = `
|
|
|
5644
5754
|
</div>
|
|
5645
5755
|
{{/if}}
|
|
5646
5756
|
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
<
|
|
5651
|
-
|
|
5652
|
-
</div>
|
|
5757
|
+
<!-- Summary Section (Collapsible) -->
|
|
5758
|
+
<div class="collapsible-section" data-section="summary">
|
|
5759
|
+
<div class="collapsible-header" onclick="toggleSection('summary')">
|
|
5760
|
+
<h2>Summary</h2>
|
|
5761
|
+
<span class="collapse-icon">▼</span>
|
|
5653
5762
|
</div>
|
|
5654
|
-
<div class="
|
|
5655
|
-
<
|
|
5656
|
-
|
|
5763
|
+
<div class="collapsible-content" id="section-summary">
|
|
5764
|
+
<div class="summary">
|
|
5765
|
+
<div class="card">
|
|
5766
|
+
<h3>Success Rate</h3>
|
|
5767
|
+
<div class="value {{successRateClass manifest.metrics.success_rate}}">
|
|
5768
|
+
{{formatPercent manifest.metrics.success_rate}}
|
|
5769
|
+
</div>
|
|
5770
|
+
</div>
|
|
5771
|
+
<div class="card">
|
|
5772
|
+
<h3>Passed / Total</h3>
|
|
5773
|
+
<div class="value">{{manifest.metrics.passed_cases}} / {{manifest.metrics.total_cases}}</div>
|
|
5774
|
+
</div>
|
|
5775
|
+
<div class="card">
|
|
5776
|
+
<h3>Median Latency</h3>
|
|
5777
|
+
<div class="value">{{manifest.metrics.median_latency_ms}}ms</div>
|
|
5778
|
+
</div>
|
|
5779
|
+
<div class="card">
|
|
5780
|
+
<h3>Total Tokens</h3>
|
|
5781
|
+
<div class="value">{{formatNumber manifest.metrics.total_tokens}}</div>
|
|
5782
|
+
</div>
|
|
5783
|
+
</div>
|
|
5657
5784
|
</div>
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5785
|
+
</div>
|
|
5786
|
+
|
|
5787
|
+
<!-- Test Cases Section (Collapsible with Filter & Search) -->
|
|
5788
|
+
<div class="collapsible-section" data-section="cases">
|
|
5789
|
+
<div class="collapsible-header" onclick="toggleSection('cases')">
|
|
5790
|
+
<h2>Test Cases</h2>
|
|
5791
|
+
<span class="collapse-icon">▼</span>
|
|
5661
5792
|
</div>
|
|
5662
|
-
<div class="
|
|
5663
|
-
|
|
5664
|
-
<div class="
|
|
5793
|
+
<div class="collapsible-content" id="section-cases">
|
|
5794
|
+
<!-- Filter and Search Controls -->
|
|
5795
|
+
<div class="controls">
|
|
5796
|
+
<div class="filter-group">
|
|
5797
|
+
<button class="filter-btn active" data-filter="all" onclick="filterCases('all')">All ({{manifest.metrics.total_cases}})</button>
|
|
5798
|
+
<button class="filter-btn passed" data-filter="passed" onclick="filterCases('passed')">Passed ({{manifest.metrics.passed_cases}})</button>
|
|
5799
|
+
<button class="filter-btn failed" data-filter="failed" onclick="filterCases('failed')">Failed ({{failedCount manifest}})</button>
|
|
5800
|
+
</div>
|
|
5801
|
+
<div class="search-box">
|
|
5802
|
+
<input type="text" class="search-input" id="search-input" placeholder="Search by ID, name, response..." oninput="searchCases(this.value)">
|
|
5803
|
+
</div>
|
|
5804
|
+
</div>
|
|
5805
|
+
<div class="results-count" id="results-count">Showing all {{manifest.metrics.total_cases}} test cases</div>
|
|
5806
|
+
|
|
5807
|
+
<table id="cases-table">
|
|
5808
|
+
<thead>
|
|
5809
|
+
<tr>
|
|
5810
|
+
<th>ID</th>
|
|
5811
|
+
<th>Status</th>
|
|
5812
|
+
<th>Score</th>
|
|
5813
|
+
<th>Matcher</th>
|
|
5814
|
+
<th>Latency</th>
|
|
5815
|
+
<th>Tokens</th>
|
|
5816
|
+
</tr>
|
|
5817
|
+
</thead>
|
|
5818
|
+
<tbody>
|
|
5819
|
+
{{#each manifest.cases}}
|
|
5820
|
+
<tr class="expandable case-row" data-status="{{#if ok}}passed{{else}}failed{{/if}}" data-id="{{id}}" data-name="{{name}}" data-response="{{response}}" data-reason="{{reason}}" onclick="toggleDetails('{{id}}')">
|
|
5821
|
+
<td><strong>{{id}}</strong>{{#if name}}<br><small>{{name}}</small>{{/if}}{{#if redaction.redacted}}<span class="redacted-badge">redacted</span>{{/if}}</td>
|
|
5822
|
+
<td><span class="status {{#if ok}}passed{{else}}failed{{/if}}">{{#if ok}}PASSED{{else}}FAILED{{/if}}</span></td>
|
|
5823
|
+
<td class="score">{{formatPercent score}}</td>
|
|
5824
|
+
<td>{{matcherType}}</td>
|
|
5825
|
+
<td>{{latencyMs}}ms</td>
|
|
5826
|
+
<td>{{tokens.total}}</td>
|
|
5827
|
+
</tr>
|
|
5828
|
+
<tr id="details-{{id}}" class="hidden details-row" data-parent="{{id}}">
|
|
5829
|
+
<td colspan="6">
|
|
5830
|
+
<div class="details">
|
|
5831
|
+
<p><strong>Reason:</strong> {{reason}}</p>
|
|
5832
|
+
<p><strong>Prompt:</strong>{{#if redaction.promptRedacted}} <span class="redacted-badge">redacted</span>{{/if}}</p>
|
|
5833
|
+
<pre>{{formatPrompt prompt}}</pre>
|
|
5834
|
+
<p><strong>Response:</strong>{{#if redaction.responseRedacted}} <span class="redacted-badge">redacted</span>{{/if}}</p>
|
|
5835
|
+
<pre>{{response}}</pre>
|
|
5836
|
+
</div>
|
|
5837
|
+
</td>
|
|
5838
|
+
</tr>
|
|
5839
|
+
{{/each}}
|
|
5840
|
+
</tbody>
|
|
5841
|
+
</table>
|
|
5842
|
+
<div class="no-results hidden" id="no-results">
|
|
5843
|
+
<div class="icon">🔍</div>
|
|
5844
|
+
<p>No test cases match your filter or search criteria.</p>
|
|
5845
|
+
</div>
|
|
5665
5846
|
</div>
|
|
5666
5847
|
</div>
|
|
5667
5848
|
|
|
5668
|
-
<h2>Test Cases</h2>
|
|
5669
|
-
<table>
|
|
5670
|
-
<thead>
|
|
5671
|
-
<tr>
|
|
5672
|
-
<th>ID</th>
|
|
5673
|
-
<th>Status</th>
|
|
5674
|
-
<th>Score</th>
|
|
5675
|
-
<th>Matcher</th>
|
|
5676
|
-
<th>Latency</th>
|
|
5677
|
-
<th>Tokens</th>
|
|
5678
|
-
</tr>
|
|
5679
|
-
</thead>
|
|
5680
|
-
<tbody>
|
|
5681
|
-
{{#each manifest.cases}}
|
|
5682
|
-
<tr class="expandable" onclick="toggleDetails('{{id}}')">
|
|
5683
|
-
<td><strong>{{id}}</strong>{{#if name}}<br><small>{{name}}</small>{{/if}}{{#if redaction.redacted}}<span class="redacted-badge">redacted</span>{{/if}}</td>
|
|
5684
|
-
<td><span class="status {{#if ok}}passed{{else}}failed{{/if}}">{{#if ok}}PASSED{{else}}FAILED{{/if}}</span></td>
|
|
5685
|
-
<td class="score">{{formatPercent score}}</td>
|
|
5686
|
-
<td>{{matcherType}}</td>
|
|
5687
|
-
<td>{{latencyMs}}ms</td>
|
|
5688
|
-
<td>{{tokens.total}}</td>
|
|
5689
|
-
</tr>
|
|
5690
|
-
<tr id="details-{{id}}" class="hidden">
|
|
5691
|
-
<td colspan="6">
|
|
5692
|
-
<div class="details">
|
|
5693
|
-
<p><strong>Reason:</strong> {{reason}}</p>
|
|
5694
|
-
<p><strong>Prompt:</strong>{{#if redaction.promptRedacted}} <span class="redacted-badge">redacted</span>{{/if}}</p>
|
|
5695
|
-
<pre>{{formatPrompt prompt}}</pre>
|
|
5696
|
-
<p><strong>Response:</strong>{{#if redaction.responseRedacted}} <span class="redacted-badge">redacted</span>{{/if}}</p>
|
|
5697
|
-
<pre>{{response}}</pre>
|
|
5698
|
-
</div>
|
|
5699
|
-
</td>
|
|
5700
|
-
</tr>
|
|
5701
|
-
{{/each}}
|
|
5702
|
-
</tbody>
|
|
5703
|
-
</table>
|
|
5704
|
-
|
|
5705
5849
|
{{#if manifest.resolved_config}}
|
|
5706
|
-
|
|
5707
|
-
<div class="
|
|
5708
|
-
<
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5850
|
+
<!-- Resolved Configuration Section (Collapsible) -->
|
|
5851
|
+
<div class="collapsible-section" data-section="config">
|
|
5852
|
+
<div class="collapsible-header" onclick="toggleSection('config')">
|
|
5853
|
+
<h2>Resolved Configuration</h2>
|
|
5854
|
+
<span class="collapse-icon">▼</span>
|
|
5855
|
+
</div>
|
|
5856
|
+
<div class="collapsible-content" id="section-config">
|
|
5857
|
+
<div class="card">
|
|
5858
|
+
<p><strong>Provider:</strong> {{manifest.resolved_config.provider}} <span class="source-badge">{{manifest.resolved_config.source.provider}}</span></p>
|
|
5859
|
+
{{#if manifest.resolved_config.model}}
|
|
5860
|
+
<p><strong>Model:</strong> {{manifest.resolved_config.model}} <span class="source-badge">{{manifest.resolved_config.source.model}}</span></p>
|
|
5861
|
+
{{/if}}
|
|
5862
|
+
{{#if manifest.resolved_config.deployment_name}}
|
|
5863
|
+
<p><strong>Deployment:</strong> {{manifest.resolved_config.deployment_name}} <span class="source-badge">{{manifest.resolved_config.source.deployment_name}}</span></p>
|
|
5864
|
+
{{/if}}
|
|
5865
|
+
{{#if manifest.resolved_config.resource_name}}
|
|
5866
|
+
<p><strong>Resource:</strong> {{manifest.resolved_config.resource_name}} <span class="source-badge">{{manifest.resolved_config.source.resource_name}}</span></p>
|
|
5867
|
+
{{/if}}
|
|
5868
|
+
{{#if manifest.resolved_config.api_version}}
|
|
5869
|
+
<p><strong>API Version:</strong> {{manifest.resolved_config.api_version}} <span class="source-badge">{{manifest.resolved_config.source.api_version}}</span></p>
|
|
5870
|
+
{{/if}}
|
|
5871
|
+
{{#if manifest.resolved_config.base_url}}
|
|
5872
|
+
<p><strong>Base URL:</strong> {{manifest.resolved_config.base_url}} <span class="source-badge">{{manifest.resolved_config.source.base_url}}</span></p>
|
|
5873
|
+
{{/if}}
|
|
5874
|
+
{{#if manifest.resolved_config.underlying_provider}}
|
|
5875
|
+
<p><strong>Underlying Provider:</strong> {{manifest.resolved_config.underlying_provider}} <span class="source-badge">{{manifest.resolved_config.source.underlying_provider}}</span></p>
|
|
5876
|
+
{{/if}}
|
|
5877
|
+
{{#if manifest.resolved_config.temperature}}
|
|
5878
|
+
<p><strong>Temperature:</strong> {{manifest.resolved_config.temperature}} <span class="source-badge">{{manifest.resolved_config.source.temperature}}</span></p>
|
|
5879
|
+
{{/if}}
|
|
5880
|
+
{{#if manifest.resolved_config.max_tokens}}
|
|
5881
|
+
<p><strong>Max Tokens:</strong> {{manifest.resolved_config.max_tokens}} <span class="source-badge">{{manifest.resolved_config.source.max_tokens}}</span></p>
|
|
5882
|
+
{{/if}}
|
|
5883
|
+
</div>
|
|
5884
|
+
</div>
|
|
5733
5885
|
</div>
|
|
5734
5886
|
{{/if}}
|
|
5735
5887
|
|
|
5736
|
-
|
|
5737
|
-
<div class="
|
|
5738
|
-
<
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5888
|
+
<!-- Provenance Section (Collapsible) -->
|
|
5889
|
+
<div class="collapsible-section" data-section="provenance">
|
|
5890
|
+
<div class="collapsible-header" onclick="toggleSection('provenance')">
|
|
5891
|
+
<h2>Provenance</h2>
|
|
5892
|
+
<span class="collapse-icon">▼</span>
|
|
5893
|
+
</div>
|
|
5894
|
+
<div class="collapsible-content" id="section-provenance">
|
|
5895
|
+
<div class="card">
|
|
5896
|
+
<p><strong>Git Commit:</strong> {{manifest.git.commit}}</p>
|
|
5897
|
+
<p><strong>Git Branch:</strong> {{manifest.git.branch}}</p>
|
|
5898
|
+
<p><strong>Run By:</strong> {{manifest.provenance.run_by}}</p>
|
|
5899
|
+
<p><strong>Duration:</strong> {{manifest.duration_ms}}ms</p>
|
|
5900
|
+
</div>
|
|
5901
|
+
</div>
|
|
5742
5902
|
</div>
|
|
5743
5903
|
|
|
5744
5904
|
<footer>
|
|
@@ -5747,10 +5907,139 @@ var HTML_TEMPLATE = `
|
|
|
5747
5907
|
</div>
|
|
5748
5908
|
|
|
5749
5909
|
<script>
|
|
5910
|
+
// State
|
|
5911
|
+
let currentFilter = 'all';
|
|
5912
|
+
let currentSearch = '';
|
|
5913
|
+
|
|
5914
|
+
// Toggle collapsible sections
|
|
5915
|
+
function toggleSection(sectionId) {
|
|
5916
|
+
const header = document.querySelector('[data-section="' + sectionId + '"] .collapsible-header');
|
|
5917
|
+
const content = document.getElementById('section-' + sectionId);
|
|
5918
|
+
const isCollapsed = header.getAttribute('data-collapsed') === 'true';
|
|
5919
|
+
|
|
5920
|
+
if (isCollapsed) {
|
|
5921
|
+
header.setAttribute('data-collapsed', 'false');
|
|
5922
|
+
content.classList.remove('collapsed');
|
|
5923
|
+
} else {
|
|
5924
|
+
header.setAttribute('data-collapsed', 'true');
|
|
5925
|
+
content.classList.add('collapsed');
|
|
5926
|
+
}
|
|
5927
|
+
}
|
|
5928
|
+
|
|
5929
|
+
// Toggle case details
|
|
5750
5930
|
function toggleDetails(id) {
|
|
5751
5931
|
const details = document.getElementById('details-' + id);
|
|
5752
5932
|
details.classList.toggle('hidden');
|
|
5753
5933
|
}
|
|
5934
|
+
|
|
5935
|
+
// Filter cases by status
|
|
5936
|
+
function filterCases(status) {
|
|
5937
|
+
currentFilter = status;
|
|
5938
|
+
|
|
5939
|
+
// Update active button
|
|
5940
|
+
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
5941
|
+
btn.classList.remove('active');
|
|
5942
|
+
if (btn.getAttribute('data-filter') === status) {
|
|
5943
|
+
btn.classList.add('active');
|
|
5944
|
+
}
|
|
5945
|
+
});
|
|
5946
|
+
|
|
5947
|
+
applyFilters();
|
|
5948
|
+
}
|
|
5949
|
+
|
|
5950
|
+
// Search cases
|
|
5951
|
+
function searchCases(query) {
|
|
5952
|
+
currentSearch = query.toLowerCase().trim();
|
|
5953
|
+
applyFilters();
|
|
5954
|
+
}
|
|
5955
|
+
|
|
5956
|
+
// Apply both filter and search
|
|
5957
|
+
function applyFilters() {
|
|
5958
|
+
const rows = document.querySelectorAll('.case-row');
|
|
5959
|
+
const table = document.getElementById('cases-table');
|
|
5960
|
+
const noResults = document.getElementById('no-results');
|
|
5961
|
+
let visibleCount = 0;
|
|
5962
|
+
|
|
5963
|
+
rows.forEach(row => {
|
|
5964
|
+
const status = row.getAttribute('data-status');
|
|
5965
|
+
const id = (row.getAttribute('data-id') || '').toLowerCase();
|
|
5966
|
+
const name = (row.getAttribute('data-name') || '').toLowerCase();
|
|
5967
|
+
const response = (row.getAttribute('data-response') || '').toLowerCase();
|
|
5968
|
+
const reason = (row.getAttribute('data-reason') || '').toLowerCase();
|
|
5969
|
+
const detailsRow = document.getElementById('details-' + row.getAttribute('data-id'));
|
|
5970
|
+
|
|
5971
|
+
// Check filter
|
|
5972
|
+
const passesFilter = currentFilter === 'all' || status === currentFilter;
|
|
5973
|
+
|
|
5974
|
+
// Check search
|
|
5975
|
+
let passesSearch = true;
|
|
5976
|
+
if (currentSearch) {
|
|
5977
|
+
passesSearch = id.includes(currentSearch) ||
|
|
5978
|
+
name.includes(currentSearch) ||
|
|
5979
|
+
response.includes(currentSearch) ||
|
|
5980
|
+
reason.includes(currentSearch);
|
|
5981
|
+
}
|
|
5982
|
+
|
|
5983
|
+
// Show/hide row
|
|
5984
|
+
const shouldShow = passesFilter && passesSearch;
|
|
5985
|
+
row.classList.toggle('hidden', !shouldShow);
|
|
5986
|
+
|
|
5987
|
+
// Handle search highlighting
|
|
5988
|
+
if (shouldShow && currentSearch) {
|
|
5989
|
+
row.classList.add('search-highlight');
|
|
5990
|
+
} else {
|
|
5991
|
+
row.classList.remove('search-highlight');
|
|
5992
|
+
}
|
|
5993
|
+
|
|
5994
|
+
// Keep details row hidden state in sync but don't force it closed
|
|
5995
|
+
if (!shouldShow && detailsRow) {
|
|
5996
|
+
detailsRow.classList.add('hidden');
|
|
5997
|
+
}
|
|
5998
|
+
|
|
5999
|
+
if (shouldShow) visibleCount++;
|
|
6000
|
+
});
|
|
6001
|
+
|
|
6002
|
+
// Update results count
|
|
6003
|
+
const totalCases = rows.length;
|
|
6004
|
+
const resultsText = document.getElementById('results-count');
|
|
6005
|
+
if (currentFilter === 'all' && !currentSearch) {
|
|
6006
|
+
resultsText.textContent = 'Showing all ' + totalCases + ' test cases';
|
|
6007
|
+
} else {
|
|
6008
|
+
resultsText.textContent = 'Showing ' + visibleCount + ' of ' + totalCases + ' test cases';
|
|
6009
|
+
}
|
|
6010
|
+
|
|
6011
|
+
// Show/hide no results message
|
|
6012
|
+
if (visibleCount === 0) {
|
|
6013
|
+
table.classList.add('hidden');
|
|
6014
|
+
noResults.classList.remove('hidden');
|
|
6015
|
+
} else {
|
|
6016
|
+
table.classList.remove('hidden');
|
|
6017
|
+
noResults.classList.add('hidden');
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
|
|
6021
|
+
// Keyboard shortcut for search (Ctrl/Cmd + F focuses search)
|
|
6022
|
+
document.addEventListener('keydown', function(e) {
|
|
6023
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
|
|
6024
|
+
const searchInput = document.getElementById('search-input');
|
|
6025
|
+
// Only if we're on this page (not another browser search)
|
|
6026
|
+
if (searchInput) {
|
|
6027
|
+
e.preventDefault();
|
|
6028
|
+
searchInput.focus();
|
|
6029
|
+
searchInput.select();
|
|
6030
|
+
}
|
|
6031
|
+
}
|
|
6032
|
+
|
|
6033
|
+
// Escape clears search
|
|
6034
|
+
if (e.key === 'Escape') {
|
|
6035
|
+
const searchInput = document.getElementById('search-input');
|
|
6036
|
+
if (searchInput && document.activeElement === searchInput) {
|
|
6037
|
+
searchInput.value = '';
|
|
6038
|
+
searchCases('');
|
|
6039
|
+
searchInput.blur();
|
|
6040
|
+
}
|
|
6041
|
+
}
|
|
6042
|
+
});
|
|
5754
6043
|
</script>
|
|
5755
6044
|
</body>
|
|
5756
6045
|
</html>
|
|
@@ -5777,6 +6066,9 @@ function generateHTMLReport(manifest) {
|
|
|
5777
6066
|
return prompt;
|
|
5778
6067
|
return JSON.stringify(prompt, null, 2);
|
|
5779
6068
|
});
|
|
6069
|
+
import_handlebars.default.registerHelper("failedCount", (manifest2) => {
|
|
6070
|
+
return manifest2.metrics.total_cases - manifest2.metrics.passed_cases;
|
|
6071
|
+
});
|
|
5780
6072
|
const template = import_handlebars.default.compile(HTML_TEMPLATE);
|
|
5781
6073
|
return template({ manifest });
|
|
5782
6074
|
}
|
|
@@ -5928,6 +6220,125 @@ var HTML_TEMPLATE2 = `
|
|
|
5928
6220
|
font-size: 0.875rem;
|
|
5929
6221
|
}
|
|
5930
6222
|
footer { margin-top: 3rem; text-align: center; color: #666; font-size: 0.875rem; }
|
|
6223
|
+
|
|
6224
|
+
/* Collapsible sections */
|
|
6225
|
+
.collapsible-header {
|
|
6226
|
+
display: flex;
|
|
6227
|
+
align-items: center;
|
|
6228
|
+
cursor: pointer;
|
|
6229
|
+
user-select: none;
|
|
6230
|
+
}
|
|
6231
|
+
.collapsible-header h2 {
|
|
6232
|
+
margin: 0;
|
|
6233
|
+
flex: 1;
|
|
6234
|
+
}
|
|
6235
|
+
.collapse-icon {
|
|
6236
|
+
font-size: 1.25rem;
|
|
6237
|
+
transition: transform 0.2s ease;
|
|
6238
|
+
margin-left: 0.5rem;
|
|
6239
|
+
color: #666;
|
|
6240
|
+
}
|
|
6241
|
+
.collapsible-header[data-collapsed="true"] .collapse-icon {
|
|
6242
|
+
transform: rotate(-90deg);
|
|
6243
|
+
}
|
|
6244
|
+
.collapsible-content {
|
|
6245
|
+
overflow: hidden;
|
|
6246
|
+
transition: max-height 0.3s ease;
|
|
6247
|
+
}
|
|
6248
|
+
.collapsible-content.collapsed {
|
|
6249
|
+
max-height: 0 !important;
|
|
6250
|
+
padding: 0;
|
|
6251
|
+
}
|
|
6252
|
+
|
|
6253
|
+
/* Filter and Search controls */
|
|
6254
|
+
.controls {
|
|
6255
|
+
display: flex;
|
|
6256
|
+
gap: 1rem;
|
|
6257
|
+
margin-bottom: 1rem;
|
|
6258
|
+
flex-wrap: wrap;
|
|
6259
|
+
align-items: center;
|
|
6260
|
+
}
|
|
6261
|
+
.filter-group {
|
|
6262
|
+
display: flex;
|
|
6263
|
+
gap: 0.5rem;
|
|
6264
|
+
flex-wrap: wrap;
|
|
6265
|
+
}
|
|
6266
|
+
.filter-btn {
|
|
6267
|
+
padding: 0.5rem 1rem;
|
|
6268
|
+
border: 1px solid #e0e0e0;
|
|
6269
|
+
background: white;
|
|
6270
|
+
border-radius: 6px;
|
|
6271
|
+
cursor: pointer;
|
|
6272
|
+
font-size: 0.875rem;
|
|
6273
|
+
font-weight: 500;
|
|
6274
|
+
transition: all 0.15s ease;
|
|
6275
|
+
}
|
|
6276
|
+
.filter-btn:hover {
|
|
6277
|
+
background: #f5f5f5;
|
|
6278
|
+
}
|
|
6279
|
+
.filter-btn.active {
|
|
6280
|
+
background: #1a1a1a;
|
|
6281
|
+
color: white;
|
|
6282
|
+
border-color: #1a1a1a;
|
|
6283
|
+
}
|
|
6284
|
+
.filter-btn.safe.active {
|
|
6285
|
+
background: #166534;
|
|
6286
|
+
border-color: #166534;
|
|
6287
|
+
}
|
|
6288
|
+
.filter-btn.blocked.active {
|
|
6289
|
+
background: #1e40af;
|
|
6290
|
+
border-color: #1e40af;
|
|
6291
|
+
}
|
|
6292
|
+
.filter-btn.unsafe.active {
|
|
6293
|
+
background: #991b1b;
|
|
6294
|
+
border-color: #991b1b;
|
|
6295
|
+
}
|
|
6296
|
+
.filter-btn.error.active {
|
|
6297
|
+
background: #92400e;
|
|
6298
|
+
border-color: #92400e;
|
|
6299
|
+
}
|
|
6300
|
+
.search-box {
|
|
6301
|
+
flex: 1;
|
|
6302
|
+
min-width: 200px;
|
|
6303
|
+
max-width: 400px;
|
|
6304
|
+
}
|
|
6305
|
+
.search-input {
|
|
6306
|
+
width: 100%;
|
|
6307
|
+
padding: 0.5rem 1rem;
|
|
6308
|
+
border: 1px solid #e0e0e0;
|
|
6309
|
+
border-radius: 6px;
|
|
6310
|
+
font-size: 0.875rem;
|
|
6311
|
+
outline: none;
|
|
6312
|
+
transition: border-color 0.15s ease;
|
|
6313
|
+
}
|
|
6314
|
+
.search-input:focus {
|
|
6315
|
+
border-color: #3b82f6;
|
|
6316
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
6317
|
+
}
|
|
6318
|
+
.search-input::placeholder {
|
|
6319
|
+
color: #999;
|
|
6320
|
+
}
|
|
6321
|
+
.results-count {
|
|
6322
|
+
font-size: 0.875rem;
|
|
6323
|
+
color: #666;
|
|
6324
|
+
padding: 0.5rem 0;
|
|
6325
|
+
}
|
|
6326
|
+
|
|
6327
|
+
/* No results message */
|
|
6328
|
+
.no-results {
|
|
6329
|
+
text-align: center;
|
|
6330
|
+
padding: 2rem;
|
|
6331
|
+
color: #666;
|
|
6332
|
+
background: white;
|
|
6333
|
+
border-radius: 8px;
|
|
6334
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
6335
|
+
}
|
|
6336
|
+
.no-results .icon { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
6337
|
+
|
|
6338
|
+
/* Row highlight for search matches */
|
|
6339
|
+
tr.search-highlight td {
|
|
6340
|
+
background: #fef9c3;
|
|
6341
|
+
}
|
|
5931
6342
|
</style>
|
|
5932
6343
|
</head>
|
|
5933
6344
|
<body>
|
|
@@ -5945,7 +6356,7 @@ var HTML_TEMPLATE2 = `
|
|
|
5945
6356
|
|
|
5946
6357
|
{{#if manifest.redaction.enabled}}
|
|
5947
6358
|
<div class="redaction-banner">
|
|
5948
|
-
<div class="icon"
|
|
6359
|
+
<div class="icon">🔒</div>
|
|
5949
6360
|
<div class="content">
|
|
5950
6361
|
<div class="title">Data Redaction Applied</div>
|
|
5951
6362
|
<div class="details">
|
|
@@ -5957,166 +6368,346 @@ var HTML_TEMPLATE2 = `
|
|
|
5957
6368
|
</div>
|
|
5958
6369
|
{{/if}}
|
|
5959
6370
|
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
<
|
|
5964
|
-
|
|
6371
|
+
<!-- Summary Section (Collapsible) -->
|
|
6372
|
+
<div class="collapsible-section" data-section="summary">
|
|
6373
|
+
<div class="collapsible-header" onclick="toggleSection('summary')">
|
|
6374
|
+
<h2>Summary</h2>
|
|
6375
|
+
<span class="collapse-icon">▼</span>
|
|
6376
|
+
</div>
|
|
6377
|
+
<div class="collapsible-content" id="section-summary">
|
|
6378
|
+
<div class="summary">
|
|
6379
|
+
<div class="card">
|
|
6380
|
+
<h3>Defense Rate</h3>
|
|
6381
|
+
<div class="value {{defenseRateClass manifest.metrics.defense_rate}}">
|
|
6382
|
+
{{formatPercent manifest.metrics.defense_rate}}
|
|
6383
|
+
</div>
|
|
6384
|
+
<div class="defense-meter">
|
|
6385
|
+
<div class="defense-meter-fill" style="width: {{formatPercentRaw manifest.metrics.defense_rate}}">
|
|
6386
|
+
{{manifest.metrics.defended}}/{{subtract manifest.metrics.total_tests manifest.metrics.error_responses}}
|
|
6387
|
+
</div>
|
|
6388
|
+
</div>
|
|
6389
|
+
</div>
|
|
6390
|
+
<div class="card">
|
|
6391
|
+
<h3>Total Tests</h3>
|
|
6392
|
+
<div class="value">{{manifest.metrics.total_tests}}</div>
|
|
6393
|
+
</div>
|
|
6394
|
+
<div class="card">
|
|
6395
|
+
<h3>Safe Responses</h3>
|
|
6396
|
+
<div class="value success">{{manifest.metrics.safe_responses}}</div>
|
|
6397
|
+
</div>
|
|
6398
|
+
<div class="card">
|
|
6399
|
+
<h3>Blocked by Provider</h3>
|
|
6400
|
+
<div class="value info">{{manifest.metrics.blocked_responses}}</div>
|
|
6401
|
+
</div>
|
|
6402
|
+
<div class="card">
|
|
6403
|
+
<h3>Unsafe Responses</h3>
|
|
6404
|
+
<div class="value {{#if manifest.metrics.unsafe_responses}}error{{/if}}">{{manifest.metrics.unsafe_responses}}</div>
|
|
6405
|
+
</div>
|
|
6406
|
+
<div class="card">
|
|
6407
|
+
<h3>Errors</h3>
|
|
6408
|
+
<div class="value {{#if manifest.metrics.error_responses}}warning{{/if}}">{{manifest.metrics.error_responses}}</div>
|
|
6409
|
+
</div>
|
|
5965
6410
|
</div>
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
6411
|
+
|
|
6412
|
+
{{#if manifest.metrics.unsafe_responses}}
|
|
6413
|
+
<div class="card" style="margin-bottom: 2rem; border-left: 4px solid #ef4444;">
|
|
6414
|
+
<h3>Severity Breakdown</h3>
|
|
6415
|
+
<div class="severity-breakdown">
|
|
6416
|
+
{{#if manifest.metrics.by_severity.critical}}
|
|
6417
|
+
<div class="severity-item">
|
|
6418
|
+
<span class="severity-dot critical"></span>
|
|
6419
|
+
<span>Critical: {{manifest.metrics.by_severity.critical}}</span>
|
|
6420
|
+
</div>
|
|
6421
|
+
{{/if}}
|
|
6422
|
+
{{#if manifest.metrics.by_severity.high}}
|
|
6423
|
+
<div class="severity-item">
|
|
6424
|
+
<span class="severity-dot high"></span>
|
|
6425
|
+
<span>High: {{manifest.metrics.by_severity.high}}</span>
|
|
6426
|
+
</div>
|
|
6427
|
+
{{/if}}
|
|
6428
|
+
{{#if manifest.metrics.by_severity.medium}}
|
|
6429
|
+
<div class="severity-item">
|
|
6430
|
+
<span class="severity-dot medium"></span>
|
|
6431
|
+
<span>Medium: {{manifest.metrics.by_severity.medium}}</span>
|
|
6432
|
+
</div>
|
|
6433
|
+
{{/if}}
|
|
6434
|
+
{{#if manifest.metrics.by_severity.low}}
|
|
6435
|
+
<div class="severity-item">
|
|
6436
|
+
<span class="severity-dot low"></span>
|
|
6437
|
+
<span>Low: {{manifest.metrics.by_severity.low}}</span>
|
|
6438
|
+
</div>
|
|
6439
|
+
{{/if}}
|
|
5969
6440
|
</div>
|
|
5970
6441
|
</div>
|
|
5971
|
-
|
|
5972
|
-
<div class="card">
|
|
5973
|
-
<h3>Total Tests</h3>
|
|
5974
|
-
<div class="value">{{manifest.metrics.total_tests}}</div>
|
|
5975
|
-
</div>
|
|
5976
|
-
<div class="card">
|
|
5977
|
-
<h3>Safe Responses</h3>
|
|
5978
|
-
<div class="value success">{{manifest.metrics.safe_responses}}</div>
|
|
5979
|
-
</div>
|
|
5980
|
-
<div class="card">
|
|
5981
|
-
<h3>Blocked by Provider</h3>
|
|
5982
|
-
<div class="value info">{{manifest.metrics.blocked_responses}}</div>
|
|
5983
|
-
</div>
|
|
5984
|
-
<div class="card">
|
|
5985
|
-
<h3>Unsafe Responses</h3>
|
|
5986
|
-
<div class="value {{#if manifest.metrics.unsafe_responses}}error{{/if}}">{{manifest.metrics.unsafe_responses}}</div>
|
|
5987
|
-
</div>
|
|
5988
|
-
<div class="card">
|
|
5989
|
-
<h3>Errors</h3>
|
|
5990
|
-
<div class="value {{#if manifest.metrics.error_responses}}warning{{/if}}">{{manifest.metrics.error_responses}}</div>
|
|
6442
|
+
{{/if}}
|
|
5991
6443
|
</div>
|
|
5992
6444
|
</div>
|
|
5993
6445
|
|
|
5994
|
-
|
|
5995
|
-
<div class="
|
|
5996
|
-
<
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6446
|
+
<!-- Configuration Section (Collapsible) -->
|
|
6447
|
+
<div class="collapsible-section" data-section="config">
|
|
6448
|
+
<div class="collapsible-header" onclick="toggleSection('config')">
|
|
6449
|
+
<h2>Configuration</h2>
|
|
6450
|
+
<span class="collapse-icon">▼</span>
|
|
6451
|
+
</div>
|
|
6452
|
+
<div class="collapsible-content" id="section-config">
|
|
6453
|
+
<div class="card">
|
|
6454
|
+
<p><strong>Mutations:</strong> {{join manifest.config.mutations ', '}}</p>
|
|
6455
|
+
<p><strong>Count per case:</strong> {{manifest.config.count_per_case}}</p>
|
|
6002
6456
|
</div>
|
|
6003
|
-
|
|
6004
|
-
{{#if manifest.
|
|
6005
|
-
<div class="
|
|
6006
|
-
<
|
|
6007
|
-
<
|
|
6457
|
+
|
|
6458
|
+
{{#if manifest.resolved_config}}
|
|
6459
|
+
<div class="card" style="margin-top: 1rem;">
|
|
6460
|
+
<h3 style="margin-bottom: 1rem;">Resolved Provider Configuration</h3>
|
|
6461
|
+
<p><strong>Provider:</strong> {{manifest.resolved_config.provider}} <span class="source-badge">{{manifest.resolved_config.source.provider}}</span></p>
|
|
6462
|
+
{{#if manifest.resolved_config.model}}
|
|
6463
|
+
<p><strong>Model:</strong> {{manifest.resolved_config.model}} <span class="source-badge">{{manifest.resolved_config.source.model}}</span></p>
|
|
6464
|
+
{{/if}}
|
|
6465
|
+
{{#if manifest.resolved_config.deployment_name}}
|
|
6466
|
+
<p><strong>Deployment:</strong> {{manifest.resolved_config.deployment_name}} <span class="source-badge">{{manifest.resolved_config.source.deployment_name}}</span></p>
|
|
6467
|
+
{{/if}}
|
|
6468
|
+
{{#if manifest.resolved_config.resource_name}}
|
|
6469
|
+
<p><strong>Resource:</strong> {{manifest.resolved_config.resource_name}} <span class="source-badge">{{manifest.resolved_config.source.resource_name}}</span></p>
|
|
6470
|
+
{{/if}}
|
|
6471
|
+
{{#if manifest.resolved_config.api_version}}
|
|
6472
|
+
<p><strong>API Version:</strong> {{manifest.resolved_config.api_version}} <span class="source-badge">{{manifest.resolved_config.source.api_version}}</span></p>
|
|
6473
|
+
{{/if}}
|
|
6474
|
+
{{#if manifest.resolved_config.base_url}}
|
|
6475
|
+
<p><strong>Base URL:</strong> {{manifest.resolved_config.base_url}} <span class="source-badge">{{manifest.resolved_config.source.base_url}}</span></p>
|
|
6476
|
+
{{/if}}
|
|
6477
|
+
{{#if manifest.resolved_config.temperature}}
|
|
6478
|
+
<p><strong>Temperature:</strong> {{manifest.resolved_config.temperature}} <span class="source-badge">{{manifest.resolved_config.source.temperature}}</span></p>
|
|
6479
|
+
{{/if}}
|
|
6008
6480
|
</div>
|
|
6009
6481
|
{{/if}}
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6482
|
+
</div>
|
|
6483
|
+
</div>
|
|
6484
|
+
|
|
6485
|
+
<!-- Test Results Section (Collapsible with Filter & Search) -->
|
|
6486
|
+
<div class="collapsible-section" data-section="results">
|
|
6487
|
+
<div class="collapsible-header" onclick="toggleSection('results')">
|
|
6488
|
+
<h2>Test Results</h2>
|
|
6489
|
+
<span class="collapse-icon">▼</span>
|
|
6490
|
+
</div>
|
|
6491
|
+
<div class="collapsible-content" id="section-results">
|
|
6492
|
+
<!-- Filter and Search Controls -->
|
|
6493
|
+
<div class="controls">
|
|
6494
|
+
<div class="filter-group">
|
|
6495
|
+
<button class="filter-btn active" data-filter="all" onclick="filterResults('all')">All ({{manifest.metrics.total_tests}})</button>
|
|
6496
|
+
<button class="filter-btn safe" data-filter="safe" onclick="filterResults('safe')">Safe ({{manifest.metrics.safe_responses}})</button>
|
|
6497
|
+
<button class="filter-btn blocked" data-filter="blocked" onclick="filterResults('blocked')">Blocked ({{manifest.metrics.blocked_responses}})</button>
|
|
6498
|
+
<button class="filter-btn unsafe" data-filter="unsafe" onclick="filterResults('unsafe')">Unsafe ({{manifest.metrics.unsafe_responses}})</button>
|
|
6499
|
+
<button class="filter-btn error" data-filter="error" onclick="filterResults('error')">Error ({{manifest.metrics.error_responses}})</button>
|
|
6500
|
+
</div>
|
|
6501
|
+
<div class="search-box">
|
|
6502
|
+
<input type="text" class="search-input" id="search-input" placeholder="Search by case ID, mutation, response..." oninput="searchResults(this.value)">
|
|
6503
|
+
</div>
|
|
6014
6504
|
</div>
|
|
6015
|
-
{{
|
|
6016
|
-
|
|
6017
|
-
<
|
|
6018
|
-
<
|
|
6019
|
-
|
|
6505
|
+
<div class="results-count" id="results-count">Showing all {{manifest.metrics.total_tests}} test results</div>
|
|
6506
|
+
|
|
6507
|
+
<table id="results-table">
|
|
6508
|
+
<thead>
|
|
6509
|
+
<tr>
|
|
6510
|
+
<th>Case ID</th>
|
|
6511
|
+
<th>Mutation</th>
|
|
6512
|
+
<th>Status</th>
|
|
6513
|
+
<th>Details</th>
|
|
6514
|
+
</tr>
|
|
6515
|
+
</thead>
|
|
6516
|
+
<tbody>
|
|
6517
|
+
{{#each manifest.results}}
|
|
6518
|
+
<tr class="expandable result-row" data-status="{{status}}" data-caseid="{{caseId}}" data-mutation="{{mutation}}" data-response="{{response}}" data-reasons="{{joinReasons reasons}}" onclick="toggleDetails('{{@index}}')">
|
|
6519
|
+
<td><strong>{{caseId}}</strong>{{#if redaction.redacted}}<span class="redacted-badge">redacted</span>{{/if}}</td>
|
|
6520
|
+
<td><span class="mutation-tag">{{mutation}}</span></td>
|
|
6521
|
+
<td>
|
|
6522
|
+
<span class="status {{status}}">{{upperCase status}}</span>
|
|
6523
|
+
{{#if (eq status 'unsafe')}}<span class="severity {{severity}}">{{severity}}</span>{{/if}}
|
|
6524
|
+
</td>
|
|
6525
|
+
<td>{{#if reasons}}{{first reasons}}{{else}}-{{/if}}</td>
|
|
6526
|
+
</tr>
|
|
6527
|
+
<tr id="details-{{@index}}" class="hidden details-row" data-parent="{{@index}}">
|
|
6528
|
+
<td colspan="4">
|
|
6529
|
+
<div class="details">
|
|
6530
|
+
<p><strong>Mutated Prompt:</strong>{{#if redaction.promptRedacted}} <span class="redacted-badge">redacted</span>{{/if}}</p>
|
|
6531
|
+
<pre>{{prompt}}</pre>
|
|
6532
|
+
{{#if response}}
|
|
6533
|
+
<p style="margin-top: 1rem;"><strong>Response:</strong>{{#if redaction.responseRedacted}} <span class="redacted-badge">redacted</span>{{/if}}</p>
|
|
6534
|
+
<pre>{{response}}</pre>
|
|
6535
|
+
{{/if}}
|
|
6536
|
+
{{#if reasons.length}}
|
|
6537
|
+
<p style="margin-top: 1rem;"><strong>Reasons:</strong></p>
|
|
6538
|
+
<ul>
|
|
6539
|
+
{{#each reasons}}
|
|
6540
|
+
<li>{{this}}</li>
|
|
6541
|
+
{{/each}}
|
|
6542
|
+
</ul>
|
|
6543
|
+
{{/if}}
|
|
6544
|
+
</div>
|
|
6545
|
+
</td>
|
|
6546
|
+
</tr>
|
|
6547
|
+
{{/each}}
|
|
6548
|
+
</tbody>
|
|
6549
|
+
</table>
|
|
6550
|
+
<div class="no-results hidden" id="no-results">
|
|
6551
|
+
<div class="icon">🔍</div>
|
|
6552
|
+
<p>No test results match your filter or search criteria.</p>
|
|
6020
6553
|
</div>
|
|
6021
|
-
{{/if}}
|
|
6022
6554
|
</div>
|
|
6023
6555
|
</div>
|
|
6024
|
-
{{/if}}
|
|
6025
6556
|
|
|
6026
|
-
|
|
6027
|
-
<div class="
|
|
6028
|
-
<
|
|
6029
|
-
|
|
6557
|
+
<!-- Provenance Section (Collapsible) -->
|
|
6558
|
+
<div class="collapsible-section" data-section="provenance">
|
|
6559
|
+
<div class="collapsible-header" onclick="toggleSection('provenance')">
|
|
6560
|
+
<h2>Provenance</h2>
|
|
6561
|
+
<span class="collapse-icon">▼</span>
|
|
6562
|
+
</div>
|
|
6563
|
+
<div class="collapsible-content" id="section-provenance">
|
|
6564
|
+
<div class="card">
|
|
6565
|
+
<p><strong>Git Commit:</strong> {{manifest.git.commit}}</p>
|
|
6566
|
+
<p><strong>Git Branch:</strong> {{manifest.git.branch}}</p>
|
|
6567
|
+
<p><strong>Run By:</strong> {{manifest.provenance.run_by}}</p>
|
|
6568
|
+
<p><strong>Duration:</strong> {{manifest.duration_ms}}ms</p>
|
|
6569
|
+
</div>
|
|
6570
|
+
</div>
|
|
6030
6571
|
</div>
|
|
6031
6572
|
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
{{#if manifest.resolved_config.model}}
|
|
6037
|
-
<p><strong>Model:</strong> {{manifest.resolved_config.model}} <span class="source-badge">{{manifest.resolved_config.source.model}}</span></p>
|
|
6038
|
-
{{/if}}
|
|
6039
|
-
{{#if manifest.resolved_config.deployment_name}}
|
|
6040
|
-
<p><strong>Deployment:</strong> {{manifest.resolved_config.deployment_name}} <span class="source-badge">{{manifest.resolved_config.source.deployment_name}}</span></p>
|
|
6041
|
-
{{/if}}
|
|
6042
|
-
{{#if manifest.resolved_config.resource_name}}
|
|
6043
|
-
<p><strong>Resource:</strong> {{manifest.resolved_config.resource_name}} <span class="source-badge">{{manifest.resolved_config.source.resource_name}}</span></p>
|
|
6044
|
-
{{/if}}
|
|
6045
|
-
{{#if manifest.resolved_config.api_version}}
|
|
6046
|
-
<p><strong>API Version:</strong> {{manifest.resolved_config.api_version}} <span class="source-badge">{{manifest.resolved_config.source.api_version}}</span></p>
|
|
6047
|
-
{{/if}}
|
|
6048
|
-
{{#if manifest.resolved_config.base_url}}
|
|
6049
|
-
<p><strong>Base URL:</strong> {{manifest.resolved_config.base_url}} <span class="source-badge">{{manifest.resolved_config.source.base_url}}</span></p>
|
|
6050
|
-
{{/if}}
|
|
6051
|
-
{{#if manifest.resolved_config.temperature}}
|
|
6052
|
-
<p><strong>Temperature:</strong> {{manifest.resolved_config.temperature}} <span class="source-badge">{{manifest.resolved_config.source.temperature}}</span></p>
|
|
6053
|
-
{{/if}}
|
|
6054
|
-
</div>
|
|
6055
|
-
{{/if}}
|
|
6573
|
+
<footer>
|
|
6574
|
+
Generated by Artemis Agent Reliability Toolkit - Red Team Module
|
|
6575
|
+
</footer>
|
|
6576
|
+
</div>
|
|
6056
6577
|
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
<th>Case ID</th>
|
|
6062
|
-
<th>Mutation</th>
|
|
6063
|
-
<th>Status</th>
|
|
6064
|
-
<th>Details</th>
|
|
6065
|
-
</tr>
|
|
6066
|
-
</thead>
|
|
6067
|
-
<tbody>
|
|
6068
|
-
{{#each manifest.results}}
|
|
6069
|
-
<tr class="expandable" onclick="toggleDetails('{{@index}}')">
|
|
6070
|
-
<td><strong>{{caseId}}</strong>{{#if redaction.redacted}}<span class="redacted-badge">redacted</span>{{/if}}</td>
|
|
6071
|
-
<td><span class="mutation-tag">{{mutation}}</span></td>
|
|
6072
|
-
<td>
|
|
6073
|
-
<span class="status {{status}}">{{upperCase status}}</span>
|
|
6074
|
-
{{#if (eq status 'unsafe')}}<span class="severity {{severity}}">{{severity}}</span>{{/if}}
|
|
6075
|
-
</td>
|
|
6076
|
-
<td>{{#if reasons}}{{first reasons}}{{else}}-{{/if}}</td>
|
|
6077
|
-
</tr>
|
|
6078
|
-
<tr id="details-{{@index}}" class="hidden">
|
|
6079
|
-
<td colspan="4">
|
|
6080
|
-
<div class="details">
|
|
6081
|
-
<p><strong>Mutated Prompt:</strong>{{#if redaction.promptRedacted}} <span class="redacted-badge">redacted</span>{{/if}}</p>
|
|
6082
|
-
<pre>{{prompt}}</pre>
|
|
6083
|
-
{{#if response}}
|
|
6084
|
-
<p style="margin-top: 1rem;"><strong>Response:</strong>{{#if redaction.responseRedacted}} <span class="redacted-badge">redacted</span>{{/if}}</p>
|
|
6085
|
-
<pre>{{response}}</pre>
|
|
6086
|
-
{{/if}}
|
|
6087
|
-
{{#if reasons.length}}
|
|
6088
|
-
<p style="margin-top: 1rem;"><strong>Reasons:</strong></p>
|
|
6089
|
-
<ul>
|
|
6090
|
-
{{#each reasons}}
|
|
6091
|
-
<li>{{this}}</li>
|
|
6092
|
-
{{/each}}
|
|
6093
|
-
</ul>
|
|
6094
|
-
{{/if}}
|
|
6095
|
-
</div>
|
|
6096
|
-
</td>
|
|
6097
|
-
</tr>
|
|
6098
|
-
{{/each}}
|
|
6099
|
-
</tbody>
|
|
6100
|
-
</table>
|
|
6578
|
+
<script>
|
|
6579
|
+
// State
|
|
6580
|
+
let currentFilter = 'all';
|
|
6581
|
+
let currentSearch = '';
|
|
6101
6582
|
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
<p><strong>Duration:</strong> {{manifest.duration_ms}}ms</p>
|
|
6108
|
-
</div>
|
|
6583
|
+
// Toggle collapsible sections
|
|
6584
|
+
function toggleSection(sectionId) {
|
|
6585
|
+
const header = document.querySelector('[data-section="' + sectionId + '"] .collapsible-header');
|
|
6586
|
+
const content = document.getElementById('section-' + sectionId);
|
|
6587
|
+
const isCollapsed = header.getAttribute('data-collapsed') === 'true';
|
|
6109
6588
|
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6589
|
+
if (isCollapsed) {
|
|
6590
|
+
header.setAttribute('data-collapsed', 'false');
|
|
6591
|
+
content.classList.remove('collapsed');
|
|
6592
|
+
} else {
|
|
6593
|
+
header.setAttribute('data-collapsed', 'true');
|
|
6594
|
+
content.classList.add('collapsed');
|
|
6595
|
+
}
|
|
6596
|
+
}
|
|
6114
6597
|
|
|
6115
|
-
|
|
6598
|
+
// Toggle result details
|
|
6116
6599
|
function toggleDetails(id) {
|
|
6117
6600
|
const details = document.getElementById('details-' + id);
|
|
6118
6601
|
details.classList.toggle('hidden');
|
|
6119
6602
|
}
|
|
6603
|
+
|
|
6604
|
+
// Filter results by status
|
|
6605
|
+
function filterResults(status) {
|
|
6606
|
+
currentFilter = status;
|
|
6607
|
+
|
|
6608
|
+
// Update active button
|
|
6609
|
+
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
6610
|
+
btn.classList.remove('active');
|
|
6611
|
+
if (btn.getAttribute('data-filter') === status) {
|
|
6612
|
+
btn.classList.add('active');
|
|
6613
|
+
}
|
|
6614
|
+
});
|
|
6615
|
+
|
|
6616
|
+
applyFilters();
|
|
6617
|
+
}
|
|
6618
|
+
|
|
6619
|
+
// Search results
|
|
6620
|
+
function searchResults(query) {
|
|
6621
|
+
currentSearch = query.toLowerCase().trim();
|
|
6622
|
+
applyFilters();
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6625
|
+
// Apply both filter and search
|
|
6626
|
+
function applyFilters() {
|
|
6627
|
+
const rows = document.querySelectorAll('.result-row');
|
|
6628
|
+
const table = document.getElementById('results-table');
|
|
6629
|
+
const noResults = document.getElementById('no-results');
|
|
6630
|
+
let visibleCount = 0;
|
|
6631
|
+
|
|
6632
|
+
rows.forEach(row => {
|
|
6633
|
+
const status = row.getAttribute('data-status');
|
|
6634
|
+
const caseId = (row.getAttribute('data-caseid') || '').toLowerCase();
|
|
6635
|
+
const mutation = (row.getAttribute('data-mutation') || '').toLowerCase();
|
|
6636
|
+
const response = (row.getAttribute('data-response') || '').toLowerCase();
|
|
6637
|
+
const reasons = (row.getAttribute('data-reasons') || '').toLowerCase();
|
|
6638
|
+
const index = row.querySelector('td strong').textContent;
|
|
6639
|
+
const detailsRow = document.getElementById('details-' + Array.from(document.querySelectorAll('.result-row')).indexOf(row));
|
|
6640
|
+
|
|
6641
|
+
// Check filter
|
|
6642
|
+
const passesFilter = currentFilter === 'all' || status === currentFilter;
|
|
6643
|
+
|
|
6644
|
+
// Check search
|
|
6645
|
+
let passesSearch = true;
|
|
6646
|
+
if (currentSearch) {
|
|
6647
|
+
passesSearch = caseId.includes(currentSearch) ||
|
|
6648
|
+
mutation.includes(currentSearch) ||
|
|
6649
|
+
response.includes(currentSearch) ||
|
|
6650
|
+
reasons.includes(currentSearch);
|
|
6651
|
+
}
|
|
6652
|
+
|
|
6653
|
+
// Show/hide row
|
|
6654
|
+
const shouldShow = passesFilter && passesSearch;
|
|
6655
|
+
row.classList.toggle('hidden', !shouldShow);
|
|
6656
|
+
|
|
6657
|
+
// Handle search highlighting
|
|
6658
|
+
if (shouldShow && currentSearch) {
|
|
6659
|
+
row.classList.add('search-highlight');
|
|
6660
|
+
} else {
|
|
6661
|
+
row.classList.remove('search-highlight');
|
|
6662
|
+
}
|
|
6663
|
+
|
|
6664
|
+
// Keep details row hidden if parent is hidden
|
|
6665
|
+
if (!shouldShow && detailsRow) {
|
|
6666
|
+
detailsRow.classList.add('hidden');
|
|
6667
|
+
}
|
|
6668
|
+
|
|
6669
|
+
if (shouldShow) visibleCount++;
|
|
6670
|
+
});
|
|
6671
|
+
|
|
6672
|
+
// Update results count
|
|
6673
|
+
const totalResults = rows.length;
|
|
6674
|
+
const resultsText = document.getElementById('results-count');
|
|
6675
|
+
if (currentFilter === 'all' && !currentSearch) {
|
|
6676
|
+
resultsText.textContent = 'Showing all ' + totalResults + ' test results';
|
|
6677
|
+
} else {
|
|
6678
|
+
resultsText.textContent = 'Showing ' + visibleCount + ' of ' + totalResults + ' test results';
|
|
6679
|
+
}
|
|
6680
|
+
|
|
6681
|
+
// Show/hide no results message
|
|
6682
|
+
if (visibleCount === 0) {
|
|
6683
|
+
table.classList.add('hidden');
|
|
6684
|
+
noResults.classList.remove('hidden');
|
|
6685
|
+
} else {
|
|
6686
|
+
table.classList.remove('hidden');
|
|
6687
|
+
noResults.classList.add('hidden');
|
|
6688
|
+
}
|
|
6689
|
+
}
|
|
6690
|
+
|
|
6691
|
+
// Keyboard shortcuts
|
|
6692
|
+
document.addEventListener('keydown', function(e) {
|
|
6693
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
|
|
6694
|
+
const searchInput = document.getElementById('search-input');
|
|
6695
|
+
if (searchInput) {
|
|
6696
|
+
e.preventDefault();
|
|
6697
|
+
searchInput.focus();
|
|
6698
|
+
searchInput.select();
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
|
|
6702
|
+
if (e.key === 'Escape') {
|
|
6703
|
+
const searchInput = document.getElementById('search-input');
|
|
6704
|
+
if (searchInput && document.activeElement === searchInput) {
|
|
6705
|
+
searchInput.value = '';
|
|
6706
|
+
searchResults('');
|
|
6707
|
+
searchInput.blur();
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
});
|
|
6120
6711
|
</script>
|
|
6121
6712
|
</body>
|
|
6122
6713
|
</html>
|
|
@@ -6144,6 +6735,9 @@ function generateRedTeamHTMLReport(manifest) {
|
|
|
6144
6735
|
import_handlebars2.default.registerHelper("join", (arr, separator) => {
|
|
6145
6736
|
return arr.join(separator);
|
|
6146
6737
|
});
|
|
6738
|
+
import_handlebars2.default.registerHelper("joinReasons", (arr) => {
|
|
6739
|
+
return arr ? arr.join(" | ") : "";
|
|
6740
|
+
});
|
|
6147
6741
|
import_handlebars2.default.registerHelper("first", (arr) => {
|
|
6148
6742
|
return arr[0] || "";
|
|
6149
6743
|
});
|
|
@@ -6306,6 +6900,35 @@ var HTML_TEMPLATE3 = `
|
|
|
6306
6900
|
.redaction-banner .title { font-weight: 600; color: #92400e; }
|
|
6307
6901
|
.redaction-banner .details { font-size: 0.875rem; color: #a16207; margin-top: 0.25rem; }
|
|
6308
6902
|
footer { margin-top: 3rem; text-align: center; color: #666; font-size: 0.875rem; }
|
|
6903
|
+
|
|
6904
|
+
/* Collapsible sections */
|
|
6905
|
+
.collapsible-header {
|
|
6906
|
+
display: flex;
|
|
6907
|
+
align-items: center;
|
|
6908
|
+
cursor: pointer;
|
|
6909
|
+
user-select: none;
|
|
6910
|
+
}
|
|
6911
|
+
.collapsible-header h2 {
|
|
6912
|
+
margin: 0;
|
|
6913
|
+
flex: 1;
|
|
6914
|
+
}
|
|
6915
|
+
.collapse-icon {
|
|
6916
|
+
font-size: 1.25rem;
|
|
6917
|
+
transition: transform 0.2s ease;
|
|
6918
|
+
margin-left: 0.5rem;
|
|
6919
|
+
color: #666;
|
|
6920
|
+
}
|
|
6921
|
+
.collapsible-header[data-collapsed="true"] .collapse-icon {
|
|
6922
|
+
transform: rotate(-90deg);
|
|
6923
|
+
}
|
|
6924
|
+
.collapsible-content {
|
|
6925
|
+
overflow: hidden;
|
|
6926
|
+
transition: max-height 0.3s ease;
|
|
6927
|
+
}
|
|
6928
|
+
.collapsible-content.collapsed {
|
|
6929
|
+
max-height: 0 !important;
|
|
6930
|
+
padding: 0;
|
|
6931
|
+
}
|
|
6309
6932
|
</style>
|
|
6310
6933
|
</head>
|
|
6311
6934
|
<body>
|
|
@@ -6323,7 +6946,7 @@ var HTML_TEMPLATE3 = `
|
|
|
6323
6946
|
|
|
6324
6947
|
{{#if manifest.redaction.enabled}}
|
|
6325
6948
|
<div class="redaction-banner">
|
|
6326
|
-
<div class="icon"
|
|
6949
|
+
<div class="icon">🔒</div>
|
|
6327
6950
|
<div class="content">
|
|
6328
6951
|
<div class="title">Data Redaction Configured</div>
|
|
6329
6952
|
<div class="details">
|
|
@@ -6334,153 +6957,211 @@ var HTML_TEMPLATE3 = `
|
|
|
6334
6957
|
</div>
|
|
6335
6958
|
{{/if}}
|
|
6336
6959
|
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
<
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6960
|
+
<!-- Summary Section (Collapsible) -->
|
|
6961
|
+
<div class="collapsible-section" data-section="summary">
|
|
6962
|
+
<div class="collapsible-header" onclick="toggleSection('summary')">
|
|
6963
|
+
<h2>Summary</h2>
|
|
6964
|
+
<span class="collapse-icon">▼</span>
|
|
6965
|
+
</div>
|
|
6966
|
+
<div class="collapsible-content" id="section-summary">
|
|
6967
|
+
<div class="summary">
|
|
6968
|
+
<div class="card">
|
|
6969
|
+
<h3>Success Rate</h3>
|
|
6970
|
+
<div class="value {{successRateClass manifest.metrics.success_rate}}">
|
|
6971
|
+
{{formatPercent manifest.metrics.success_rate}}
|
|
6972
|
+
</div>
|
|
6973
|
+
<div class="success-meter">
|
|
6974
|
+
<div class="success-meter-fill {{successRateClass manifest.metrics.success_rate}}" style="width: {{formatPercentRaw manifest.metrics.success_rate}}">
|
|
6975
|
+
{{manifest.metrics.successful_requests}}/{{manifest.metrics.total_requests}}
|
|
6976
|
+
</div>
|
|
6977
|
+
</div>
|
|
6978
|
+
</div>
|
|
6979
|
+
<div class="card">
|
|
6980
|
+
<h3>Requests/Second</h3>
|
|
6981
|
+
<div class="value info">{{formatDecimal manifest.metrics.requests_per_second}}</div>
|
|
6982
|
+
</div>
|
|
6983
|
+
<div class="card">
|
|
6984
|
+
<h3>Total Requests</h3>
|
|
6985
|
+
<div class="value">{{formatNumber manifest.metrics.total_requests}}</div>
|
|
6986
|
+
</div>
|
|
6987
|
+
<div class="card">
|
|
6988
|
+
<h3>Duration</h3>
|
|
6989
|
+
<div class="value">{{formatDuration manifest.duration_ms}}<span class="unit">s</span></div>
|
|
6346
6990
|
</div>
|
|
6347
6991
|
</div>
|
|
6348
6992
|
</div>
|
|
6349
|
-
<div class="card">
|
|
6350
|
-
<h3>Requests/Second</h3>
|
|
6351
|
-
<div class="value info">{{formatDecimal manifest.metrics.requests_per_second}}</div>
|
|
6352
|
-
</div>
|
|
6353
|
-
<div class="card">
|
|
6354
|
-
<h3>Total Requests</h3>
|
|
6355
|
-
<div class="value">{{formatNumber manifest.metrics.total_requests}}</div>
|
|
6356
|
-
</div>
|
|
6357
|
-
<div class="card">
|
|
6358
|
-
<h3>Duration</h3>
|
|
6359
|
-
<div class="value">{{formatDuration manifest.duration_ms}}<span class="unit">s</span></div>
|
|
6360
|
-
</div>
|
|
6361
6993
|
</div>
|
|
6362
6994
|
|
|
6363
|
-
|
|
6364
|
-
<div class="
|
|
6365
|
-
<div class="
|
|
6366
|
-
<
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
<div class="
|
|
6371
|
-
<div class="
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6995
|
+
<!-- Latency Percentiles Section (Collapsible) -->
|
|
6996
|
+
<div class="collapsible-section" data-section="latency">
|
|
6997
|
+
<div class="collapsible-header" onclick="toggleSection('latency')">
|
|
6998
|
+
<h2>Latency Percentiles</h2>
|
|
6999
|
+
<span class="collapse-icon">▼</span>
|
|
7000
|
+
</div>
|
|
7001
|
+
<div class="collapsible-content" id="section-latency">
|
|
7002
|
+
<div class="card">
|
|
7003
|
+
<div class="latency-grid">
|
|
7004
|
+
<div class="latency-item">
|
|
7005
|
+
<div class="label">Min</div>
|
|
7006
|
+
<div class="value">{{manifest.metrics.min_latency_ms}}ms</div>
|
|
7007
|
+
</div>
|
|
7008
|
+
<div class="latency-item">
|
|
7009
|
+
<div class="label">P50</div>
|
|
7010
|
+
<div class="value">{{manifest.metrics.p50_latency_ms}}ms</div>
|
|
7011
|
+
</div>
|
|
7012
|
+
<div class="latency-item">
|
|
7013
|
+
<div class="label">P90</div>
|
|
7014
|
+
<div class="value">{{manifest.metrics.p90_latency_ms}}ms</div>
|
|
7015
|
+
</div>
|
|
7016
|
+
<div class="latency-item">
|
|
7017
|
+
<div class="label">P95</div>
|
|
7018
|
+
<div class="value">{{manifest.metrics.p95_latency_ms}}ms</div>
|
|
7019
|
+
</div>
|
|
7020
|
+
<div class="latency-item">
|
|
7021
|
+
<div class="label">P99</div>
|
|
7022
|
+
<div class="value">{{manifest.metrics.p99_latency_ms}}ms</div>
|
|
7023
|
+
</div>
|
|
7024
|
+
<div class="latency-item">
|
|
7025
|
+
<div class="label">Max</div>
|
|
7026
|
+
<div class="value">{{manifest.metrics.max_latency_ms}}ms</div>
|
|
7027
|
+
</div>
|
|
7028
|
+
</div>
|
|
6389
7029
|
</div>
|
|
6390
7030
|
</div>
|
|
6391
7031
|
</div>
|
|
6392
7032
|
|
|
6393
|
-
|
|
6394
|
-
<div class="
|
|
6395
|
-
<
|
|
6396
|
-
<
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
<
|
|
6401
|
-
<
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
7033
|
+
<!-- Configuration Section (Collapsible) -->
|
|
7034
|
+
<div class="collapsible-section" data-section="config">
|
|
7035
|
+
<div class="collapsible-header" onclick="toggleSection('config')">
|
|
7036
|
+
<h2>Configuration</h2>
|
|
7037
|
+
<span class="collapse-icon">▼</span>
|
|
7038
|
+
</div>
|
|
7039
|
+
<div class="collapsible-content" id="section-config">
|
|
7040
|
+
<div class="card">
|
|
7041
|
+
<table>
|
|
7042
|
+
<tr>
|
|
7043
|
+
<th>Parameter</th>
|
|
7044
|
+
<th>Value</th>
|
|
7045
|
+
</tr>
|
|
7046
|
+
<tr>
|
|
7047
|
+
<td>Concurrency</td>
|
|
7048
|
+
<td>{{manifest.config.concurrency}}</td>
|
|
7049
|
+
</tr>
|
|
7050
|
+
<tr>
|
|
7051
|
+
<td>Duration</td>
|
|
7052
|
+
<td>{{manifest.config.duration_seconds}} seconds</td>
|
|
7053
|
+
</tr>
|
|
7054
|
+
<tr>
|
|
7055
|
+
<td>Ramp-up Time</td>
|
|
7056
|
+
<td>{{manifest.config.ramp_up_seconds}} seconds</td>
|
|
7057
|
+
</tr>
|
|
7058
|
+
{{#if manifest.config.max_requests}}
|
|
7059
|
+
<tr>
|
|
7060
|
+
<td>Max Requests</td>
|
|
7061
|
+
<td>{{manifest.config.max_requests}}</td>
|
|
7062
|
+
</tr>
|
|
7063
|
+
{{/if}}
|
|
7064
|
+
</table>
|
|
7065
|
+
</div>
|
|
6420
7066
|
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
<
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
<
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
<
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
<
|
|
6442
|
-
|
|
6443
|
-
</
|
|
6444
|
-
|
|
7067
|
+
{{#if manifest.resolved_config}}
|
|
7068
|
+
<div class="card" style="margin-top: 1rem;">
|
|
7069
|
+
<h3 style="margin-bottom: 1rem;">Resolved Provider Configuration</h3>
|
|
7070
|
+
<p><strong>Provider:</strong> {{manifest.resolved_config.provider}} <span class="source-badge">{{manifest.resolved_config.source.provider}}</span></p>
|
|
7071
|
+
{{#if manifest.resolved_config.model}}
|
|
7072
|
+
<p><strong>Model:</strong> {{manifest.resolved_config.model}} <span class="source-badge">{{manifest.resolved_config.source.model}}</span></p>
|
|
7073
|
+
{{/if}}
|
|
7074
|
+
{{#if manifest.resolved_config.deployment_name}}
|
|
7075
|
+
<p><strong>Deployment:</strong> {{manifest.resolved_config.deployment_name}} <span class="source-badge">{{manifest.resolved_config.source.deployment_name}}</span></p>
|
|
7076
|
+
{{/if}}
|
|
7077
|
+
{{#if manifest.resolved_config.resource_name}}
|
|
7078
|
+
<p><strong>Resource:</strong> {{manifest.resolved_config.resource_name}} <span class="source-badge">{{manifest.resolved_config.source.resource_name}}</span></p>
|
|
7079
|
+
{{/if}}
|
|
7080
|
+
{{#if manifest.resolved_config.api_version}}
|
|
7081
|
+
<p><strong>API Version:</strong> {{manifest.resolved_config.api_version}} <span class="source-badge">{{manifest.resolved_config.source.api_version}}</span></p>
|
|
7082
|
+
{{/if}}
|
|
7083
|
+
{{#if manifest.resolved_config.base_url}}
|
|
7084
|
+
<p><strong>Base URL:</strong> {{manifest.resolved_config.base_url}} <span class="source-badge">{{manifest.resolved_config.source.base_url}}</span></p>
|
|
7085
|
+
{{/if}}
|
|
7086
|
+
{{#if manifest.resolved_config.temperature}}
|
|
7087
|
+
<p><strong>Temperature:</strong> {{manifest.resolved_config.temperature}} <span class="source-badge">{{manifest.resolved_config.source.temperature}}</span></p>
|
|
7088
|
+
{{/if}}
|
|
7089
|
+
</div>
|
|
7090
|
+
{{/if}}
|
|
7091
|
+
</div>
|
|
6445
7092
|
</div>
|
|
6446
7093
|
|
|
6447
|
-
|
|
6448
|
-
<
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
7094
|
+
<!-- Results Summary Section (Collapsible) -->
|
|
7095
|
+
<div class="collapsible-section" data-section="results">
|
|
7096
|
+
<div class="collapsible-header" onclick="toggleSection('results')">
|
|
7097
|
+
<h2>Results Summary</h2>
|
|
7098
|
+
<span class="collapse-icon">▼</span>
|
|
7099
|
+
</div>
|
|
7100
|
+
<div class="collapsible-content" id="section-results">
|
|
7101
|
+
<div class="card">
|
|
7102
|
+
<table>
|
|
7103
|
+
<tr>
|
|
7104
|
+
<th>Metric</th>
|
|
7105
|
+
<th>Value</th>
|
|
7106
|
+
</tr>
|
|
7107
|
+
<tr>
|
|
7108
|
+
<td>Total Requests</td>
|
|
7109
|
+
<td>{{formatNumber manifest.metrics.total_requests}}</td>
|
|
7110
|
+
</tr>
|
|
7111
|
+
<tr>
|
|
7112
|
+
<td>Successful</td>
|
|
7113
|
+
<td style="color: #22c55e;">{{formatNumber manifest.metrics.successful_requests}}</td>
|
|
7114
|
+
</tr>
|
|
7115
|
+
<tr>
|
|
7116
|
+
<td>Failed</td>
|
|
7117
|
+
<td style="color: {{#if manifest.metrics.failed_requests}}#ef4444{{else}}inherit{{/if}};">{{formatNumber manifest.metrics.failed_requests}}</td>
|
|
7118
|
+
</tr>
|
|
7119
|
+
<tr>
|
|
7120
|
+
<td>Average Latency</td>
|
|
7121
|
+
<td>{{formatDecimal manifest.metrics.avg_latency_ms}}ms</td>
|
|
7122
|
+
</tr>
|
|
7123
|
+
</table>
|
|
7124
|
+
</div>
|
|
7125
|
+
</div>
|
|
6469
7126
|
</div>
|
|
6470
|
-
{{/if}}
|
|
6471
7127
|
|
|
6472
|
-
|
|
6473
|
-
<div class="
|
|
6474
|
-
<
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
7128
|
+
<!-- Provenance Section (Collapsible) -->
|
|
7129
|
+
<div class="collapsible-section" data-section="provenance">
|
|
7130
|
+
<div class="collapsible-header" onclick="toggleSection('provenance')">
|
|
7131
|
+
<h2>Provenance</h2>
|
|
7132
|
+
<span class="collapse-icon">▼</span>
|
|
7133
|
+
</div>
|
|
7134
|
+
<div class="collapsible-content" id="section-provenance">
|
|
7135
|
+
<div class="card">
|
|
7136
|
+
<p><strong>Git Commit:</strong> {{manifest.git.commit}}</p>
|
|
7137
|
+
<p><strong>Git Branch:</strong> {{manifest.git.branch}}</p>
|
|
7138
|
+
<p><strong>Run By:</strong> {{manifest.provenance.run_by}}</p>
|
|
7139
|
+
<p><strong>Platform:</strong> {{manifest.environment.platform}} ({{manifest.environment.arch}})</p>
|
|
7140
|
+
</div>
|
|
7141
|
+
</div>
|
|
6478
7142
|
</div>
|
|
6479
7143
|
|
|
6480
7144
|
<footer>
|
|
6481
7145
|
Generated by Artemis Agent Reliability Toolkit - Stress Test Module
|
|
6482
7146
|
</footer>
|
|
6483
7147
|
</div>
|
|
7148
|
+
|
|
7149
|
+
<script>
|
|
7150
|
+
// Toggle collapsible sections
|
|
7151
|
+
function toggleSection(sectionId) {
|
|
7152
|
+
const header = document.querySelector('[data-section="' + sectionId + '"] .collapsible-header');
|
|
7153
|
+
const content = document.getElementById('section-' + sectionId);
|
|
7154
|
+
const isCollapsed = header.getAttribute('data-collapsed') === 'true';
|
|
7155
|
+
|
|
7156
|
+
if (isCollapsed) {
|
|
7157
|
+
header.setAttribute('data-collapsed', 'false');
|
|
7158
|
+
content.classList.remove('collapsed');
|
|
7159
|
+
} else {
|
|
7160
|
+
header.setAttribute('data-collapsed', 'true');
|
|
7161
|
+
content.classList.add('collapsed');
|
|
7162
|
+
}
|
|
7163
|
+
}
|
|
7164
|
+
</script>
|
|
6484
7165
|
</body>
|
|
6485
7166
|
</html>
|
|
6486
7167
|
`;
|
|
@@ -6516,9 +7197,715 @@ function generateStressHTMLReport(manifest) {
|
|
|
6516
7197
|
const template = import_handlebars3.default.compile(HTML_TEMPLATE3);
|
|
6517
7198
|
return template({ manifest });
|
|
6518
7199
|
}
|
|
7200
|
+
// src/html/compare-generator.ts
|
|
7201
|
+
var import_handlebars4 = __toESM(require_lib(), 1);
|
|
7202
|
+
var COMPARE_HTML_TEMPLATE = `
|
|
7203
|
+
<!DOCTYPE html>
|
|
7204
|
+
<html lang="en">
|
|
7205
|
+
<head>
|
|
7206
|
+
<meta charset="UTF-8">
|
|
7207
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7208
|
+
<title>Artemis Comparison - {{data.baseline.config.scenario}}</title>
|
|
7209
|
+
<style>
|
|
7210
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
7211
|
+
body {
|
|
7212
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
7213
|
+
line-height: 1.6;
|
|
7214
|
+
color: #333;
|
|
7215
|
+
background: #f5f5f5;
|
|
7216
|
+
padding: 2rem;
|
|
7217
|
+
}
|
|
7218
|
+
.container { max-width: 1400px; margin: 0 auto; }
|
|
7219
|
+
h1 { margin-bottom: 0.5rem; color: #1a1a1a; }
|
|
7220
|
+
h2 { margin: 2rem 0 1rem; color: #333; border-bottom: 2px solid #e0e0e0; padding-bottom: 0.5rem; }
|
|
7221
|
+
h3 { margin: 1rem 0 0.5rem; color: #555; font-size: 1rem; }
|
|
7222
|
+
.meta { color: #666; margin-bottom: 2rem; }
|
|
7223
|
+
|
|
7224
|
+
/* Summary Cards */
|
|
7225
|
+
.summary-grid {
|
|
7226
|
+
display: grid;
|
|
7227
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
7228
|
+
gap: 1rem;
|
|
7229
|
+
margin-bottom: 2rem;
|
|
7230
|
+
}
|
|
7231
|
+
.card {
|
|
7232
|
+
background: white;
|
|
7233
|
+
padding: 1.5rem;
|
|
7234
|
+
border-radius: 8px;
|
|
7235
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
7236
|
+
}
|
|
7237
|
+
.card h3 { font-size: 0.875rem; color: #666; margin-bottom: 0.75rem; }
|
|
7238
|
+
.card .compare-row {
|
|
7239
|
+
display: flex;
|
|
7240
|
+
justify-content: space-between;
|
|
7241
|
+
align-items: center;
|
|
7242
|
+
margin-bottom: 0.5rem;
|
|
7243
|
+
}
|
|
7244
|
+
.card .label { color: #888; font-size: 0.875rem; }
|
|
7245
|
+
.card .value { font-size: 1.25rem; font-weight: 600; }
|
|
7246
|
+
.card .delta {
|
|
7247
|
+
padding: 0.25rem 0.5rem;
|
|
7248
|
+
border-radius: 4px;
|
|
7249
|
+
font-size: 0.875rem;
|
|
7250
|
+
font-weight: 600;
|
|
7251
|
+
}
|
|
7252
|
+
.delta.positive { background: #dcfce7; color: #166534; }
|
|
7253
|
+
.delta.negative { background: #fee2e2; color: #991b1b; }
|
|
7254
|
+
.delta.neutral { background: #f3f4f6; color: #6b7280; }
|
|
7255
|
+
|
|
7256
|
+
/* Comparison Stats */
|
|
7257
|
+
.stats-row {
|
|
7258
|
+
display: flex;
|
|
7259
|
+
gap: 1rem;
|
|
7260
|
+
flex-wrap: wrap;
|
|
7261
|
+
margin-bottom: 1.5rem;
|
|
7262
|
+
}
|
|
7263
|
+
.stat-badge {
|
|
7264
|
+
padding: 0.5rem 1rem;
|
|
7265
|
+
border-radius: 6px;
|
|
7266
|
+
font-weight: 600;
|
|
7267
|
+
font-size: 0.875rem;
|
|
7268
|
+
}
|
|
7269
|
+
.stat-badge.regressions { background: #fee2e2; color: #991b1b; }
|
|
7270
|
+
.stat-badge.improvements { background: #dcfce7; color: #166534; }
|
|
7271
|
+
.stat-badge.unchanged { background: #f3f4f6; color: #6b7280; }
|
|
7272
|
+
.stat-badge.added { background: #dbeafe; color: #1e40af; }
|
|
7273
|
+
.stat-badge.removed { background: #fef3c7; color: #92400e; }
|
|
7274
|
+
|
|
7275
|
+
/* Table Styles */
|
|
7276
|
+
table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
7277
|
+
th, td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid #e0e0e0; }
|
|
7278
|
+
th { background: #f9fafb; font-weight: 600; font-size: 0.875rem; }
|
|
7279
|
+
tr:last-child td { border-bottom: none; }
|
|
7280
|
+
|
|
7281
|
+
/* Status indicators */
|
|
7282
|
+
.status { display: inline-block; padding: 0.125rem 0.5rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 500; }
|
|
7283
|
+
.status.passed { background: #dcfce7; color: #166534; }
|
|
7284
|
+
.status.failed { background: #fee2e2; color: #991b1b; }
|
|
7285
|
+
.status.na { background: #f3f4f6; color: #9ca3af; }
|
|
7286
|
+
|
|
7287
|
+
/* Change type badges */
|
|
7288
|
+
.change-type {
|
|
7289
|
+
display: inline-flex;
|
|
7290
|
+
align-items: center;
|
|
7291
|
+
gap: 0.25rem;
|
|
7292
|
+
padding: 0.25rem 0.5rem;
|
|
7293
|
+
border-radius: 4px;
|
|
7294
|
+
font-size: 0.75rem;
|
|
7295
|
+
font-weight: 600;
|
|
7296
|
+
}
|
|
7297
|
+
.change-type.regressed { background: #fee2e2; color: #991b1b; }
|
|
7298
|
+
.change-type.improved { background: #dcfce7; color: #166534; }
|
|
7299
|
+
.change-type.unchanged { background: #f3f4f6; color: #6b7280; }
|
|
7300
|
+
.change-type.new { background: #dbeafe; color: #1e40af; }
|
|
7301
|
+
.change-type.removed { background: #fef3c7; color: #92400e; }
|
|
7302
|
+
|
|
7303
|
+
/* Arrow indicators */
|
|
7304
|
+
.arrow { font-size: 1rem; }
|
|
7305
|
+
.arrow.up { color: #22c55e; }
|
|
7306
|
+
.arrow.down { color: #ef4444; }
|
|
7307
|
+
.arrow.same { color: #9ca3af; }
|
|
7308
|
+
|
|
7309
|
+
/* Score comparison */
|
|
7310
|
+
.score-compare {
|
|
7311
|
+
font-family: monospace;
|
|
7312
|
+
font-size: 0.875rem;
|
|
7313
|
+
}
|
|
7314
|
+
.score-compare .old { color: #9ca3af; text-decoration: line-through; margin-right: 0.25rem; }
|
|
7315
|
+
.score-compare .new { font-weight: 600; }
|
|
7316
|
+
|
|
7317
|
+
/* Details section */
|
|
7318
|
+
.details { margin-top: 0.5rem; padding: 1rem; background: #f9fafb; border-radius: 4px; font-size: 0.875rem; }
|
|
7319
|
+
.details pre { white-space: pre-wrap; word-break: break-word; margin: 0.5rem 0; }
|
|
7320
|
+
.details-grid {
|
|
7321
|
+
display: grid;
|
|
7322
|
+
grid-template-columns: 1fr 1fr;
|
|
7323
|
+
gap: 1rem;
|
|
7324
|
+
}
|
|
7325
|
+
.details-col { }
|
|
7326
|
+
.details-col h4 { font-size: 0.75rem; color: #666; margin-bottom: 0.5rem; text-transform: uppercase; }
|
|
7327
|
+
|
|
7328
|
+
.expandable { cursor: pointer; }
|
|
7329
|
+
.expandable:hover { background: #f0f0f0; }
|
|
7330
|
+
.hidden { display: none; }
|
|
7331
|
+
|
|
7332
|
+
/* Filter controls */
|
|
7333
|
+
.controls {
|
|
7334
|
+
display: flex;
|
|
7335
|
+
gap: 1rem;
|
|
7336
|
+
margin-bottom: 1rem;
|
|
7337
|
+
flex-wrap: wrap;
|
|
7338
|
+
align-items: center;
|
|
7339
|
+
}
|
|
7340
|
+
.filter-group { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
|
7341
|
+
.filter-btn {
|
|
7342
|
+
padding: 0.5rem 1rem;
|
|
7343
|
+
border: 1px solid #e0e0e0;
|
|
7344
|
+
background: white;
|
|
7345
|
+
border-radius: 6px;
|
|
7346
|
+
cursor: pointer;
|
|
7347
|
+
font-size: 0.875rem;
|
|
7348
|
+
font-weight: 500;
|
|
7349
|
+
transition: all 0.15s ease;
|
|
7350
|
+
}
|
|
7351
|
+
.filter-btn:hover { background: #f5f5f5; }
|
|
7352
|
+
.filter-btn.active { background: #1a1a1a; color: white; border-color: #1a1a1a; }
|
|
7353
|
+
.filter-btn.regressed.active { background: #991b1b; border-color: #991b1b; }
|
|
7354
|
+
.filter-btn.improved.active { background: #166534; border-color: #166534; }
|
|
7355
|
+
|
|
7356
|
+
.search-box { flex: 1; min-width: 200px; max-width: 400px; }
|
|
7357
|
+
.search-input {
|
|
7358
|
+
width: 100%;
|
|
7359
|
+
padding: 0.5rem 1rem;
|
|
7360
|
+
border: 1px solid #e0e0e0;
|
|
7361
|
+
border-radius: 6px;
|
|
7362
|
+
font-size: 0.875rem;
|
|
7363
|
+
outline: none;
|
|
7364
|
+
}
|
|
7365
|
+
.search-input:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); }
|
|
7366
|
+
|
|
7367
|
+
.results-count { font-size: 0.875rem; color: #666; padding: 0.5rem 0; }
|
|
7368
|
+
|
|
7369
|
+
/* No results message */
|
|
7370
|
+
.no-results {
|
|
7371
|
+
text-align: center;
|
|
7372
|
+
padding: 2rem;
|
|
7373
|
+
color: #666;
|
|
7374
|
+
background: white;
|
|
7375
|
+
border-radius: 8px;
|
|
7376
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
7377
|
+
}
|
|
7378
|
+
.no-results .icon { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
7379
|
+
|
|
7380
|
+
/* Run info */
|
|
7381
|
+
.run-info {
|
|
7382
|
+
display: grid;
|
|
7383
|
+
grid-template-columns: 1fr 1fr;
|
|
7384
|
+
gap: 2rem;
|
|
7385
|
+
margin-bottom: 2rem;
|
|
7386
|
+
}
|
|
7387
|
+
.run-box {
|
|
7388
|
+
background: white;
|
|
7389
|
+
padding: 1rem 1.5rem;
|
|
7390
|
+
border-radius: 8px;
|
|
7391
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
7392
|
+
}
|
|
7393
|
+
.run-box h3 {
|
|
7394
|
+
font-size: 0.75rem;
|
|
7395
|
+
text-transform: uppercase;
|
|
7396
|
+
color: #666;
|
|
7397
|
+
margin-bottom: 0.5rem;
|
|
7398
|
+
}
|
|
7399
|
+
.run-box .run-id { font-family: monospace; font-size: 0.875rem; color: #333; }
|
|
7400
|
+
.run-box .run-meta { font-size: 0.875rem; color: #666; margin-top: 0.25rem; }
|
|
7401
|
+
|
|
7402
|
+
footer { margin-top: 3rem; text-align: center; color: #666; font-size: 0.875rem; }
|
|
7403
|
+
|
|
7404
|
+
@media (max-width: 768px) {
|
|
7405
|
+
.run-info, .details-grid { grid-template-columns: 1fr; }
|
|
7406
|
+
}
|
|
7407
|
+
</style>
|
|
7408
|
+
</head>
|
|
7409
|
+
<body>
|
|
7410
|
+
<div class="container">
|
|
7411
|
+
<h1>Run Comparison</h1>
|
|
7412
|
+
<p class="meta">{{data.baseline.config.scenario}} | Comparing two evaluation runs</p>
|
|
7413
|
+
|
|
7414
|
+
<!-- Run Info -->
|
|
7415
|
+
<div class="run-info">
|
|
7416
|
+
<div class="run-box">
|
|
7417
|
+
<h3>📄 Baseline Run</h3>
|
|
7418
|
+
<div class="run-id">{{data.baseline.run_id}}</div>
|
|
7419
|
+
<div class="run-meta">{{formatDate data.baseline.start_time}}</div>
|
|
7420
|
+
</div>
|
|
7421
|
+
<div class="run-box">
|
|
7422
|
+
<h3>📋 Current Run</h3>
|
|
7423
|
+
<div class="run-id">{{data.current.run_id}}</div>
|
|
7424
|
+
<div class="run-meta">{{formatDate data.current.start_time}}</div>
|
|
7425
|
+
</div>
|
|
7426
|
+
</div>
|
|
7427
|
+
|
|
7428
|
+
<!-- Metrics Comparison -->
|
|
7429
|
+
<h2>Metrics Overview</h2>
|
|
7430
|
+
<div class="summary-grid">
|
|
7431
|
+
<div class="card">
|
|
7432
|
+
<h3>Success Rate</h3>
|
|
7433
|
+
<div class="compare-row">
|
|
7434
|
+
<span class="label">Baseline</span>
|
|
7435
|
+
<span class="value">{{formatPercent data.baseline.metrics.success_rate}}</span>
|
|
7436
|
+
</div>
|
|
7437
|
+
<div class="compare-row">
|
|
7438
|
+
<span class="label">Current</span>
|
|
7439
|
+
<span class="value">{{formatPercent data.current.metrics.success_rate}}</span>
|
|
7440
|
+
</div>
|
|
7441
|
+
<div class="compare-row">
|
|
7442
|
+
<span class="label">Delta</span>
|
|
7443
|
+
<span class="delta {{deltaClass data.metrics.successRateDelta}}">
|
|
7444
|
+
{{formatDelta data.metrics.successRateDelta}}
|
|
7445
|
+
</span>
|
|
7446
|
+
</div>
|
|
7447
|
+
</div>
|
|
7448
|
+
|
|
7449
|
+
<div class="card">
|
|
7450
|
+
<h3>Median Latency</h3>
|
|
7451
|
+
<div class="compare-row">
|
|
7452
|
+
<span class="label">Baseline</span>
|
|
7453
|
+
<span class="value">{{data.baseline.metrics.median_latency_ms}}ms</span>
|
|
7454
|
+
</div>
|
|
7455
|
+
<div class="compare-row">
|
|
7456
|
+
<span class="label">Current</span>
|
|
7457
|
+
<span class="value">{{data.current.metrics.median_latency_ms}}ms</span>
|
|
7458
|
+
</div>
|
|
7459
|
+
<div class="compare-row">
|
|
7460
|
+
<span class="label">Delta</span>
|
|
7461
|
+
<span class="delta {{latencyDeltaClass data.metrics.medianLatencyDelta}}">
|
|
7462
|
+
{{formatLatencyDelta data.metrics.medianLatencyDelta}}
|
|
7463
|
+
</span>
|
|
7464
|
+
</div>
|
|
7465
|
+
</div>
|
|
7466
|
+
|
|
7467
|
+
<div class="card">
|
|
7468
|
+
<h3>Total Tokens</h3>
|
|
7469
|
+
<div class="compare-row">
|
|
7470
|
+
<span class="label">Baseline</span>
|
|
7471
|
+
<span class="value">{{formatNumber data.baseline.metrics.total_tokens}}</span>
|
|
7472
|
+
</div>
|
|
7473
|
+
<div class="compare-row">
|
|
7474
|
+
<span class="label">Current</span>
|
|
7475
|
+
<span class="value">{{formatNumber data.current.metrics.total_tokens}}</span>
|
|
7476
|
+
</div>
|
|
7477
|
+
<div class="compare-row">
|
|
7478
|
+
<span class="label">Delta</span>
|
|
7479
|
+
<span class="delta {{tokenDeltaClass data.metrics.totalTokensDelta}}">
|
|
7480
|
+
{{formatTokenDelta data.metrics.totalTokensDelta}}
|
|
7481
|
+
</span>
|
|
7482
|
+
</div>
|
|
7483
|
+
</div>
|
|
7484
|
+
|
|
7485
|
+
<div class="card">
|
|
7486
|
+
<h3>Test Cases</h3>
|
|
7487
|
+
<div class="compare-row">
|
|
7488
|
+
<span class="label">Baseline</span>
|
|
7489
|
+
<span class="value">{{data.baseline.metrics.passed_cases}}/{{data.baseline.metrics.total_cases}}</span>
|
|
7490
|
+
</div>
|
|
7491
|
+
<div class="compare-row">
|
|
7492
|
+
<span class="label">Current</span>
|
|
7493
|
+
<span class="value">{{data.current.metrics.passed_cases}}/{{data.current.metrics.total_cases}}</span>
|
|
7494
|
+
</div>
|
|
7495
|
+
<div class="compare-row">
|
|
7496
|
+
<span class="label">Pass Delta</span>
|
|
7497
|
+
<span class="delta {{passedDeltaClass data.baseline.metrics.passed_cases data.current.metrics.passed_cases}}">
|
|
7498
|
+
{{formatPassedDelta data.baseline.metrics.passed_cases data.current.metrics.passed_cases}}
|
|
7499
|
+
</span>
|
|
7500
|
+
</div>
|
|
7501
|
+
</div>
|
|
7502
|
+
</div>
|
|
7503
|
+
|
|
7504
|
+
<!-- Change Summary -->
|
|
7505
|
+
<h2>Change Summary</h2>
|
|
7506
|
+
<div class="stats-row">
|
|
7507
|
+
{{#if data.summary.totalRegressions}}
|
|
7508
|
+
<span class="stat-badge regressions">⚠ {{data.summary.totalRegressions}} Regression{{#if (gt data.summary.totalRegressions 1)}}s{{/if}}</span>
|
|
7509
|
+
{{/if}}
|
|
7510
|
+
{{#if data.summary.totalImprovements}}
|
|
7511
|
+
<span class="stat-badge improvements">✅ {{data.summary.totalImprovements}} Improvement{{#if (gt data.summary.totalImprovements 1)}}s{{/if}}</span>
|
|
7512
|
+
{{/if}}
|
|
7513
|
+
{{#if data.summary.totalUnchanged}}
|
|
7514
|
+
<span class="stat-badge unchanged">➖ {{data.summary.totalUnchanged}} Unchanged</span>
|
|
7515
|
+
{{/if}}
|
|
7516
|
+
{{#if data.summary.casesAdded}}
|
|
7517
|
+
<span class="stat-badge added">➕ {{data.summary.casesAdded}} New</span>
|
|
7518
|
+
{{/if}}
|
|
7519
|
+
{{#if data.summary.casesRemoved}}
|
|
7520
|
+
<span class="stat-badge removed">➖ {{data.summary.casesRemoved}} Removed</span>
|
|
7521
|
+
{{/if}}
|
|
7522
|
+
</div>
|
|
7523
|
+
|
|
7524
|
+
<!-- Case Comparison Table -->
|
|
7525
|
+
<h2>Case Comparison</h2>
|
|
7526
|
+
|
|
7527
|
+
<div class="controls">
|
|
7528
|
+
<div class="filter-group">
|
|
7529
|
+
<button class="filter-btn active" data-filter="all" onclick="filterCases('all')">All ({{data.caseComparisons.length}})</button>
|
|
7530
|
+
<button class="filter-btn regressed" data-filter="regressed" onclick="filterCases('regressed')">Regressed ({{data.summary.totalRegressions}})</button>
|
|
7531
|
+
<button class="filter-btn improved" data-filter="improved" onclick="filterCases('improved')">Improved ({{data.summary.totalImprovements}})</button>
|
|
7532
|
+
<button class="filter-btn" data-filter="unchanged" onclick="filterCases('unchanged')">Unchanged ({{data.summary.totalUnchanged}})</button>
|
|
7533
|
+
</div>
|
|
7534
|
+
<div class="search-box">
|
|
7535
|
+
<input type="text" class="search-input" id="search-input" placeholder="Search by ID or name..." oninput="searchCases(this.value)">
|
|
7536
|
+
</div>
|
|
7537
|
+
</div>
|
|
7538
|
+
<div class="results-count" id="results-count">Showing all {{data.caseComparisons.length}} cases</div>
|
|
7539
|
+
|
|
7540
|
+
<table id="cases-table">
|
|
7541
|
+
<thead>
|
|
7542
|
+
<tr>
|
|
7543
|
+
<th>Case</th>
|
|
7544
|
+
<th>Baseline</th>
|
|
7545
|
+
<th>Current</th>
|
|
7546
|
+
<th>Change</th>
|
|
7547
|
+
<th>Score</th>
|
|
7548
|
+
<th>Latency</th>
|
|
7549
|
+
</tr>
|
|
7550
|
+
</thead>
|
|
7551
|
+
<tbody>
|
|
7552
|
+
{{#each data.caseComparisons}}
|
|
7553
|
+
<tr class="expandable case-row" data-change="{{changeType}}" data-id="{{caseId}}" data-name="{{name}}" onclick="toggleDetails('{{caseId}}')">
|
|
7554
|
+
<td>
|
|
7555
|
+
<strong>{{caseId}}</strong>
|
|
7556
|
+
{{#if name}}<br><small>{{name}}</small>{{/if}}
|
|
7557
|
+
</td>
|
|
7558
|
+
<td>
|
|
7559
|
+
{{#if baselineStatus}}
|
|
7560
|
+
<span class="status {{baselineStatus}}">{{uppercase baselineStatus}}</span>
|
|
7561
|
+
{{else}}
|
|
7562
|
+
<span class="status na">N/A</span>
|
|
7563
|
+
{{/if}}
|
|
7564
|
+
</td>
|
|
7565
|
+
<td>
|
|
7566
|
+
{{#if currentStatus}}
|
|
7567
|
+
<span class="status {{currentStatus}}">{{uppercase currentStatus}}</span>
|
|
7568
|
+
{{else}}
|
|
7569
|
+
<span class="status na">N/A</span>
|
|
7570
|
+
{{/if}}
|
|
7571
|
+
</td>
|
|
7572
|
+
<td>
|
|
7573
|
+
<span class="change-type {{changeType}}">
|
|
7574
|
+
{{changeTypeIcon changeType}} {{changeTypeLabel changeType}}
|
|
7575
|
+
</span>
|
|
7576
|
+
</td>
|
|
7577
|
+
<td>
|
|
7578
|
+
<span class="score-compare">
|
|
7579
|
+
{{#if baselineScore}}<span class="old">{{formatPercent baselineScore}}</span>{{/if}}
|
|
7580
|
+
{{#if currentScore}}<span class="new">{{formatPercent currentScore}}</span>{{/if}}
|
|
7581
|
+
{{#unless currentScore}}{{#unless baselineScore}}<span class="new">-</span>{{/unless}}{{/unless}}
|
|
7582
|
+
</span>
|
|
7583
|
+
</td>
|
|
7584
|
+
<td>
|
|
7585
|
+
{{#if latencyDelta}}
|
|
7586
|
+
<span class="arrow {{latencyArrow latencyDelta}}">{{latencyArrowIcon latencyDelta}}</span>
|
|
7587
|
+
{{formatLatencyDelta latencyDelta}}
|
|
7588
|
+
{{else}}
|
|
7589
|
+
-
|
|
7590
|
+
{{/if}}
|
|
7591
|
+
</td>
|
|
7592
|
+
</tr>
|
|
7593
|
+
<tr id="details-{{caseId}}" class="hidden details-row" data-parent="{{caseId}}">
|
|
7594
|
+
<td colspan="6">
|
|
7595
|
+
<div class="details">
|
|
7596
|
+
<div class="details-grid">
|
|
7597
|
+
<div class="details-col">
|
|
7598
|
+
<h4>Baseline Response</h4>
|
|
7599
|
+
{{#if baselineCase}}
|
|
7600
|
+
<pre>{{baselineCase.response}}</pre>
|
|
7601
|
+
<p><strong>Reason:</strong> {{baselineCase.reason}}</p>
|
|
7602
|
+
{{else}}
|
|
7603
|
+
<p><em>No baseline data</em></p>
|
|
7604
|
+
{{/if}}
|
|
7605
|
+
</div>
|
|
7606
|
+
<div class="details-col">
|
|
7607
|
+
<h4>Current Response</h4>
|
|
7608
|
+
{{#if currentCase}}
|
|
7609
|
+
<pre>{{currentCase.response}}</pre>
|
|
7610
|
+
<p><strong>Reason:</strong> {{currentCase.reason}}</p>
|
|
7611
|
+
{{else}}
|
|
7612
|
+
<p><em>No current data</em></p>
|
|
7613
|
+
{{/if}}
|
|
7614
|
+
</div>
|
|
7615
|
+
</div>
|
|
7616
|
+
</div>
|
|
7617
|
+
</td>
|
|
7618
|
+
</tr>
|
|
7619
|
+
{{/each}}
|
|
7620
|
+
</tbody>
|
|
7621
|
+
</table>
|
|
7622
|
+
|
|
7623
|
+
<div class="no-results hidden" id="no-results">
|
|
7624
|
+
<div class="icon">🔍</div>
|
|
7625
|
+
<p>No cases match your filter or search criteria.</p>
|
|
7626
|
+
</div>
|
|
7627
|
+
|
|
7628
|
+
<footer>
|
|
7629
|
+
Generated by Artemis Agent Reliability Toolkit
|
|
7630
|
+
</footer>
|
|
7631
|
+
</div>
|
|
7632
|
+
|
|
7633
|
+
<script>
|
|
7634
|
+
let currentFilter = 'all';
|
|
7635
|
+
let currentSearch = '';
|
|
7636
|
+
|
|
7637
|
+
function toggleDetails(id) {
|
|
7638
|
+
const details = document.getElementById('details-' + id);
|
|
7639
|
+
details.classList.toggle('hidden');
|
|
7640
|
+
}
|
|
7641
|
+
|
|
7642
|
+
function filterCases(filter) {
|
|
7643
|
+
currentFilter = filter;
|
|
7644
|
+
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
7645
|
+
btn.classList.remove('active');
|
|
7646
|
+
if (btn.getAttribute('data-filter') === filter) {
|
|
7647
|
+
btn.classList.add('active');
|
|
7648
|
+
}
|
|
7649
|
+
});
|
|
7650
|
+
applyFilters();
|
|
7651
|
+
}
|
|
7652
|
+
|
|
7653
|
+
function searchCases(query) {
|
|
7654
|
+
currentSearch = query.toLowerCase().trim();
|
|
7655
|
+
applyFilters();
|
|
7656
|
+
}
|
|
7657
|
+
|
|
7658
|
+
function applyFilters() {
|
|
7659
|
+
const rows = document.querySelectorAll('.case-row');
|
|
7660
|
+
const table = document.getElementById('cases-table');
|
|
7661
|
+
const noResults = document.getElementById('no-results');
|
|
7662
|
+
let visibleCount = 0;
|
|
7663
|
+
|
|
7664
|
+
rows.forEach(row => {
|
|
7665
|
+
const change = row.getAttribute('data-change');
|
|
7666
|
+
const id = (row.getAttribute('data-id') || '').toLowerCase();
|
|
7667
|
+
const name = (row.getAttribute('data-name') || '').toLowerCase();
|
|
7668
|
+
const detailsRow = document.getElementById('details-' + row.getAttribute('data-id'));
|
|
7669
|
+
|
|
7670
|
+
const passesFilter = currentFilter === 'all' || change === currentFilter;
|
|
7671
|
+
const passesSearch = !currentSearch || id.includes(currentSearch) || name.includes(currentSearch);
|
|
7672
|
+
|
|
7673
|
+
const shouldShow = passesFilter && passesSearch;
|
|
7674
|
+
row.classList.toggle('hidden', !shouldShow);
|
|
7675
|
+
|
|
7676
|
+
if (!shouldShow && detailsRow) {
|
|
7677
|
+
detailsRow.classList.add('hidden');
|
|
7678
|
+
}
|
|
7679
|
+
|
|
7680
|
+
if (shouldShow) visibleCount++;
|
|
7681
|
+
});
|
|
7682
|
+
|
|
7683
|
+
const totalCases = rows.length;
|
|
7684
|
+
const resultsText = document.getElementById('results-count');
|
|
7685
|
+
if (currentFilter === 'all' && !currentSearch) {
|
|
7686
|
+
resultsText.textContent = 'Showing all ' + totalCases + ' cases';
|
|
7687
|
+
} else {
|
|
7688
|
+
resultsText.textContent = 'Showing ' + visibleCount + ' of ' + totalCases + ' cases';
|
|
7689
|
+
}
|
|
7690
|
+
|
|
7691
|
+
if (visibleCount === 0) {
|
|
7692
|
+
table.classList.add('hidden');
|
|
7693
|
+
noResults.classList.remove('hidden');
|
|
7694
|
+
} else {
|
|
7695
|
+
table.classList.remove('hidden');
|
|
7696
|
+
noResults.classList.add('hidden');
|
|
7697
|
+
}
|
|
7698
|
+
}
|
|
7699
|
+
</script>
|
|
7700
|
+
</body>
|
|
7701
|
+
</html>
|
|
7702
|
+
`;
|
|
7703
|
+
function buildComparisonData(baseline, current) {
|
|
7704
|
+
const baselineCases = new Map;
|
|
7705
|
+
const currentCases = new Map;
|
|
7706
|
+
for (const c of baseline.cases) {
|
|
7707
|
+
baselineCases.set(c.id, c);
|
|
7708
|
+
}
|
|
7709
|
+
for (const c of current.cases) {
|
|
7710
|
+
currentCases.set(c.id, c);
|
|
7711
|
+
}
|
|
7712
|
+
const allCaseIds = new Set([...baselineCases.keys(), ...currentCases.keys()]);
|
|
7713
|
+
const caseComparisons = [];
|
|
7714
|
+
let totalRegressions = 0;
|
|
7715
|
+
let totalImprovements = 0;
|
|
7716
|
+
let totalUnchanged = 0;
|
|
7717
|
+
let casesRemoved = 0;
|
|
7718
|
+
let casesAdded = 0;
|
|
7719
|
+
for (const caseId of allCaseIds) {
|
|
7720
|
+
const baselineCase = baselineCases.get(caseId);
|
|
7721
|
+
const currentCase = currentCases.get(caseId);
|
|
7722
|
+
const baselineStatus = baselineCase ? baselineCase.ok ? "passed" : "failed" : null;
|
|
7723
|
+
const currentStatus = currentCase ? currentCase.ok ? "passed" : "failed" : null;
|
|
7724
|
+
const baselineScore = baselineCase?.score ?? null;
|
|
7725
|
+
const currentScore = currentCase?.score ?? null;
|
|
7726
|
+
const baselineLatency = baselineCase?.latencyMs ?? null;
|
|
7727
|
+
const currentLatency = currentCase?.latencyMs ?? null;
|
|
7728
|
+
const scoreDelta = baselineScore !== null && currentScore !== null ? currentScore - baselineScore : 0;
|
|
7729
|
+
const latencyDelta = baselineLatency !== null && currentLatency !== null ? currentLatency - baselineLatency : 0;
|
|
7730
|
+
let changeType;
|
|
7731
|
+
if (!baselineCase) {
|
|
7732
|
+
changeType = "new";
|
|
7733
|
+
casesAdded++;
|
|
7734
|
+
} else if (!currentCase) {
|
|
7735
|
+
changeType = "removed";
|
|
7736
|
+
casesRemoved++;
|
|
7737
|
+
} else if (baselineStatus === "passed" && currentStatus === "failed") {
|
|
7738
|
+
changeType = "regressed";
|
|
7739
|
+
totalRegressions++;
|
|
7740
|
+
} else if (baselineStatus === "failed" && currentStatus === "passed") {
|
|
7741
|
+
changeType = "improved";
|
|
7742
|
+
totalImprovements++;
|
|
7743
|
+
} else {
|
|
7744
|
+
changeType = "unchanged";
|
|
7745
|
+
totalUnchanged++;
|
|
7746
|
+
}
|
|
7747
|
+
caseComparisons.push({
|
|
7748
|
+
caseId,
|
|
7749
|
+
name: currentCase?.name || baselineCase?.name,
|
|
7750
|
+
baselineStatus,
|
|
7751
|
+
currentStatus,
|
|
7752
|
+
baselineScore,
|
|
7753
|
+
currentScore,
|
|
7754
|
+
scoreDelta,
|
|
7755
|
+
baselineLatency,
|
|
7756
|
+
currentLatency,
|
|
7757
|
+
latencyDelta,
|
|
7758
|
+
changeType,
|
|
7759
|
+
baselineCase,
|
|
7760
|
+
currentCase
|
|
7761
|
+
});
|
|
7762
|
+
}
|
|
7763
|
+
const changeOrder = { regressed: 0, improved: 1, new: 2, removed: 3, unchanged: 4 };
|
|
7764
|
+
caseComparisons.sort((a, b) => changeOrder[a.changeType] - changeOrder[b.changeType]);
|
|
7765
|
+
const successRateDelta = current.metrics.success_rate - baseline.metrics.success_rate;
|
|
7766
|
+
const medianLatencyDelta = current.metrics.median_latency_ms - baseline.metrics.median_latency_ms;
|
|
7767
|
+
const totalTokensDelta = current.metrics.total_tokens - baseline.metrics.total_tokens;
|
|
7768
|
+
return {
|
|
7769
|
+
baseline,
|
|
7770
|
+
current,
|
|
7771
|
+
metrics: {
|
|
7772
|
+
successRateDelta,
|
|
7773
|
+
medianLatencyDelta,
|
|
7774
|
+
totalTokensDelta
|
|
7775
|
+
},
|
|
7776
|
+
caseComparisons,
|
|
7777
|
+
summary: {
|
|
7778
|
+
totalRegressions,
|
|
7779
|
+
totalImprovements,
|
|
7780
|
+
totalUnchanged,
|
|
7781
|
+
casesRemoved,
|
|
7782
|
+
casesAdded
|
|
7783
|
+
}
|
|
7784
|
+
};
|
|
7785
|
+
}
|
|
7786
|
+
function generateCompareHTMLReport(baseline, current) {
|
|
7787
|
+
const data = buildComparisonData(baseline, current);
|
|
7788
|
+
import_handlebars4.default.registerHelper("formatPercent", (value) => {
|
|
7789
|
+
if (value === null || value === undefined)
|
|
7790
|
+
return "-";
|
|
7791
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
7792
|
+
});
|
|
7793
|
+
import_handlebars4.default.registerHelper("formatNumber", (value) => {
|
|
7794
|
+
return value?.toLocaleString() ?? "-";
|
|
7795
|
+
});
|
|
7796
|
+
import_handlebars4.default.registerHelper("formatDate", (value) => {
|
|
7797
|
+
return new Date(value).toLocaleString();
|
|
7798
|
+
});
|
|
7799
|
+
import_handlebars4.default.registerHelper("formatDelta", (value) => {
|
|
7800
|
+
const sign = value >= 0 ? "+" : "";
|
|
7801
|
+
return `${sign}${(value * 100).toFixed(1)}%`;
|
|
7802
|
+
});
|
|
7803
|
+
import_handlebars4.default.registerHelper("formatLatencyDelta", (value) => {
|
|
7804
|
+
if (value === 0)
|
|
7805
|
+
return "0ms";
|
|
7806
|
+
const sign = value > 0 ? "+" : "";
|
|
7807
|
+
return `${sign}${Math.round(value)}ms`;
|
|
7808
|
+
});
|
|
7809
|
+
import_handlebars4.default.registerHelper("formatTokenDelta", (value) => {
|
|
7810
|
+
if (value === 0)
|
|
7811
|
+
return "0";
|
|
7812
|
+
const sign = value > 0 ? "+" : "";
|
|
7813
|
+
return `${sign}${value.toLocaleString()}`;
|
|
7814
|
+
});
|
|
7815
|
+
import_handlebars4.default.registerHelper("formatPassedDelta", (baseline2, current2) => {
|
|
7816
|
+
const delta = current2 - baseline2;
|
|
7817
|
+
if (delta === 0)
|
|
7818
|
+
return "0";
|
|
7819
|
+
const sign = delta > 0 ? "+" : "";
|
|
7820
|
+
return `${sign}${delta}`;
|
|
7821
|
+
});
|
|
7822
|
+
import_handlebars4.default.registerHelper("deltaClass", (value) => {
|
|
7823
|
+
if (value > 0.001)
|
|
7824
|
+
return "positive";
|
|
7825
|
+
if (value < -0.001)
|
|
7826
|
+
return "negative";
|
|
7827
|
+
return "neutral";
|
|
7828
|
+
});
|
|
7829
|
+
import_handlebars4.default.registerHelper("latencyDeltaClass", (value) => {
|
|
7830
|
+
if (value < -5)
|
|
7831
|
+
return "positive";
|
|
7832
|
+
if (value > 5)
|
|
7833
|
+
return "negative";
|
|
7834
|
+
return "neutral";
|
|
7835
|
+
});
|
|
7836
|
+
import_handlebars4.default.registerHelper("tokenDeltaClass", (value) => {
|
|
7837
|
+
if (value < -10)
|
|
7838
|
+
return "positive";
|
|
7839
|
+
if (value > 10)
|
|
7840
|
+
return "negative";
|
|
7841
|
+
return "neutral";
|
|
7842
|
+
});
|
|
7843
|
+
import_handlebars4.default.registerHelper("passedDeltaClass", (baseline2, current2) => {
|
|
7844
|
+
const delta = current2 - baseline2;
|
|
7845
|
+
if (delta > 0)
|
|
7846
|
+
return "positive";
|
|
7847
|
+
if (delta < 0)
|
|
7848
|
+
return "negative";
|
|
7849
|
+
return "neutral";
|
|
7850
|
+
});
|
|
7851
|
+
import_handlebars4.default.registerHelper("uppercase", (value) => {
|
|
7852
|
+
return value?.toUpperCase() ?? "";
|
|
7853
|
+
});
|
|
7854
|
+
import_handlebars4.default.registerHelper("changeTypeIcon", (changeType) => {
|
|
7855
|
+
switch (changeType) {
|
|
7856
|
+
case "regressed":
|
|
7857
|
+
return "\u2193";
|
|
7858
|
+
case "improved":
|
|
7859
|
+
return "\u2191";
|
|
7860
|
+
case "unchanged":
|
|
7861
|
+
return "\u2192";
|
|
7862
|
+
case "new":
|
|
7863
|
+
return "+";
|
|
7864
|
+
case "removed":
|
|
7865
|
+
return "\u2212";
|
|
7866
|
+
default:
|
|
7867
|
+
return "";
|
|
7868
|
+
}
|
|
7869
|
+
});
|
|
7870
|
+
import_handlebars4.default.registerHelper("changeTypeLabel", (changeType) => {
|
|
7871
|
+
switch (changeType) {
|
|
7872
|
+
case "regressed":
|
|
7873
|
+
return "Regressed";
|
|
7874
|
+
case "improved":
|
|
7875
|
+
return "Improved";
|
|
7876
|
+
case "unchanged":
|
|
7877
|
+
return "Unchanged";
|
|
7878
|
+
case "new":
|
|
7879
|
+
return "New";
|
|
7880
|
+
case "removed":
|
|
7881
|
+
return "Removed";
|
|
7882
|
+
default:
|
|
7883
|
+
return changeType;
|
|
7884
|
+
}
|
|
7885
|
+
});
|
|
7886
|
+
import_handlebars4.default.registerHelper("latencyArrow", (value) => {
|
|
7887
|
+
if (value < -5)
|
|
7888
|
+
return "up";
|
|
7889
|
+
if (value > 5)
|
|
7890
|
+
return "down";
|
|
7891
|
+
return "same";
|
|
7892
|
+
});
|
|
7893
|
+
import_handlebars4.default.registerHelper("latencyArrowIcon", (value) => {
|
|
7894
|
+
if (value < -5)
|
|
7895
|
+
return "\u2191";
|
|
7896
|
+
if (value > 5)
|
|
7897
|
+
return "\u2193";
|
|
7898
|
+
return "\u2192";
|
|
7899
|
+
});
|
|
7900
|
+
import_handlebars4.default.registerHelper("gt", (a, b) => a > b);
|
|
7901
|
+
const template = import_handlebars4.default.compile(COMPARE_HTML_TEMPLATE);
|
|
7902
|
+
return template({ data });
|
|
7903
|
+
}
|
|
6519
7904
|
export {
|
|
6520
7905
|
generateStressHTMLReport,
|
|
6521
7906
|
generateRedTeamHTMLReport,
|
|
6522
7907
|
generateJSONReport,
|
|
6523
|
-
generateHTMLReport
|
|
7908
|
+
generateHTMLReport,
|
|
7909
|
+
generateCompareHTMLReport,
|
|
7910
|
+
buildComparisonData
|
|
6524
7911
|
};
|